构造函数
所谓构造函数是一种特定用途的函数的称呼,这种函数专门用来创造拥有某些属性和方法的同一类对象。
编程建议:构造函数为了方便识别,会把首字母大写(是一种约定的写法,不具有任何额外执行效果)。
代码如下:
function CatConstructor(name,age){
this.name = name
this.age = age
this.miao = function(){
console.log('喵喵叫')
}
}
var cat1 = new CatConstructor('eleven',4)
console.log(cat1)
构造函数使用 一般都要配合关键字 new。任何函数 (除了箭头函数)只要在调用的时候前面加了new数,js引擎(也就是js代码编译器)就会按照下面的过程来执行函数内的代码:
- 在内存中创建一个新对象
- 构造函数内部的this被赋值为这个新对象 即this指向新对象
- 执行构造函数内部的代码(给新对象添加属性和方法)
- 如果构造函数返回非空对象,则返回该对象,否则,默认返回刚创建的对象,也就是返回this。
也就是说,如果在调用构造函数时前面加了new,你就算不在最后写return this的代码,它也会把this返回。
编程建议:如果不用new来直接调用执行构造函数,this会指向window对象,可能会对window对象中的属性和方法产生污染。所以构造函数 一定要配合new来使用
构造函数与原型
关系
- 每个函数都有prototype属性(函数定义执行之后就产生的),该属性指向一个对象,这个属性指向的对象有一个constructor(与class中的constructor没有一点关系)属性,指向的是函数本身。
- 用new 关键字配合构造函数产生的实例,它的[[ Prototype ]]属性会指向该构造函数上prototype属性指向的对象。所有对象实例[[ Prototype ]]属性默认指向的都是一个对象。
- 还有__proto__属性,这个属性是非标准属性,是对象实例就有的,和内部属性[[Prototype]]指向的是同一个对象,不建议使用。
原型机制
由于构造函数创建的实例的[[Prototype]]属性指向的对象,和该构造函数的prototype属性指向的对象是同一个。我们就可以通过这个对象,来让所有该构造函数的实例都能共享一些属性和方法,可以直接通过实例调用或者访问。
注意:所有同一个构造函数创建的实例中的[[Prototype]]内部属性都指向这一个对象。
类
可以把类理解为特殊的一种构造函数写法。
这部分内容不是重点,简单讲讲。
类的基本写法
固定的constructor方法,会被作用构造函数调用。内部逻辑和构造函数配合new使用时是一样的,在constructor方法中this设定的属性和方法都会反应到最终的实例上。在创建实例时 传入的参数 都会被constructor方法接收。
class Dog {
constructor(name,age,fn){
this.name = name
this.age = age
this.bark = fn
}
}
const dog = new Dog('可乐',5,()=>{console.log('wang')})
可直接用方法简写的方式 可以快速设置类型原型对象上的方法。
class Dog {
constructor(name,age,fn){
this.name = name
this.age = age
this.bark = fn
}
swim(){} // 这个方法被设置在了类的原型对象上
}
类的表达式写法
const Dog = class {
// 其他写法一样
}
类的继承
代码如下:
class Cat {
constructor(name,age,fn){
this.name = name
this.age = age
this.action = fn
}
miao(){
console.log('swim')
}
}
class ChinaCat extends Cat{
}
- 子类如果不写constructor方法,它会偷偷会在子类中调用了父类的constructor方法,父类中constrcutor设置的属性或者方法都会出现在子类创造的实例中。在使用子类new时,可以通过给子类传相应的参数,来给子类实例相应属性设置数据。
- 一旦子类中写了constructor 这个方法,那么一定要第一时间写super( )。子类的constructor方法在执行时就是是通过这个super()来显式地调用父类的constructor方法。
class ChinaCat extends Cat{ constructor(name,age,fn){ super(name,age,fn) } } const chinaCat = new ChinaCat('a',22,function(){console.log(666)})
- 如果你希望你的子类的实例上能有一些和父类不同的属性或者方法你也可以额外设置。
class ChinaCat extends Cat{ constructor(name,age,fn,type){ super(name,age,fn) this.type = type } } const chinaCat = new ChinaCat('a',22,function(){console.log(666)},'abc')
- 子类也可以设置自己的原型方法。
class ChinaCat extends Cat{ constructor(name,age,fn,type){ super(name,age,fn,type) this.type = type } t(){ console.log('t') } }
- 可以在子类的原型方法中,利用super来访问父类的原型方法。
class ChinaCat extends Cat{ constructor(name,age,fn,type){ super(name,age,fn,type) this.type = type } t(){ console.log('t') super.swim() } }
原型到原型链
实验
class Cat {}
class ChinaCat extends Cat{}
const chinacat1 = new ChinaCat()
代码对应原型链如下:
- 任何一个自定义类型的实例,其构造函数或者 class 类的 prototype 属性默认指向的一定是父类型的是实例(这个实例是类型定义执行时 后台自行创建的)。
- Object是最原始的类型,任何自定义的类型(非派生类)的父类 一定是Object类型。
- 对象构造函数Object的Prototype指向的对象 是一个 [[Prototype]]为null的普通对象的实例 是所有对象类型的原型链的顶端。
- 原型链的尽头就是null。
- 任何对象调用方法或者访问属性都会沿着原型链往上找,找到为止。找到头都找不到就报错(所以子类的实例调用方法或者访问属性 是会一直沿着原型链往上找的)。
- 原型链是单线的,而且只认[[ Prototype]],不会去其他属性的对象里找。所以 根本就不会跑到任何一个构造函数或者类上去找。所以实例没办法调用构造函数或者类上的静态的方法和属性。
原型链的本质,就是通过对象[[ Prototype]属性,来实现可逐级访问的一个机制,它是js实现继承的核心。
数组、函数的实力都是对象,而数组、函数这两个类型都是基于Object的派生类,也就是说数组、函数的实例也能调用到Object类型上的原型对象上的方法和属性。任何自定义的类型(非派生类) 都是基于Object的,也就是他们的实例都可以调用到Object上的原型对象上的属性和方法。
例子
特殊的包装类
数字、字符串、布尔是三种原始值类型的数据,他们本身不是对象。
String、Number、Boolean 是特殊的三个引用类型(也是构造函数),这三个类型叫做包装类。
console.log('你好'.length)j
每当用到某个原始值的方法或属性时,后台都会创建一个相应原始包装类型的对象实例,从而暴露出操作原始值的各种方法,操作完得到结果之后成马上销毁该实例(该语句结束后就销毁,后面的语句如果还有操作原始值,那么重新创建)
三个包装类型的函数,有数据类型的转换作用。
console.log(String(1),1)
console.log(Boolean({}),1)
console.log(Number('12312'),1)
通过包装类,这三种原始值不仅能像对象一样调用各自引用类型构造函数上的原型对象上的方法,还能通过原型链调用到Object构造函数上原型对象上的方法