构造函数 构造函数也是函数,与普通函数唯一区别就是调用方式不同。任何函数只要使用new操作符调用就是构造函数,而不适用new操作符调用的函数就是普通函数。
理解原型 只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象),默认情况下,所有原型对象会自动获得一个名为constructor指向Person
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 function Person ( ) {} console .log(typeof Person.prototype); console .log(Person.prototype); console .log(Person.prototype.__proto__===Object .prototype); console .log(Person.prototype.__proto__.constructor===Object ); console .log(Person.prototype.__proto__.__proto__===null ); console .log(Person.prototype.__proto__); let person1=new Person(),person2=new Person(); console .log(person1!==Person); console .log(person1!==Person.prototype); console .log(Person.prototype!==Person); console .log(person1.__proto__===Person.prototype); console .log(person1.__proto__.constructor===Person); console .log(person1.__proto__===person2.__proto__); console .log(person1 instanceof Person); console .log(person1 instanceof Object ); console .log(Person.prototype instanceof Object );
Person构造函数,Person原型对象和Person现有实例的关系如上:Person.prototype指向原型对象,因此Person.prototye.constructor指回Person构造函数。原型对象包含constructor属性和其他后来添加的属性。Person的两个实例person1,person2有一个内部属性指回Person.prototype,而且两者和构造函数没有直接联系。person1.sayName()可以正常调用,这是由于对象属性查找机制的原因
虽然不是所有实现都对外暴露[[Prototype]],但可以使用isPrototypeOf()方法确定两个对象之间的关系,本质上isPrototypeOf()会在传入参数的[[Prototype]]指向调用它的对象时返回true
1 2 console .log(Person.prototype.isprototypeOf(person1));console .log(Person.prototype.isprototypeOf(person2));
Object类型有一个方法叫Object.getPrototypeOf(),返回参数的内部特性[[Prototype]]的值
1 2 console .log(Object .getPrototypeOf(person1)==Person.prototype);console .log(Object .getPrototypeOf(person1).name);
使用Object.getPrototypeOf()可以取得一个对象的原型
Object类型还有一个setPrototypeOf()方法,可以向实例的私有特性[[Prototype]]写入一个新值,这样就可以重写一个对象的原型继承关系
1 2 3 4 5 6 7 8 9 10 let biped={ numLegs :2 }; let person={ name :'Matt' }; Object .setPrototypeOf(person,biped); console .log(person.name); console .log(person.numLegs); console .log(Object .getPrototypeOf(person)===biped);
Object.setPrototypeOf()可能会严重影响代码性能,会涉及所有访问了哪些修改过[[Prototype]]的对象的代码
可以通过Object.create()创建一个新对象,同时为其指定原型
1 2 3 4 5 6 7 8 let biped={ numLegs :2 }; let person=Object .create(biped);person.name='Matt' ; console .log(person.name);console .log(person.numLegs);console .log(Object .getPrototypeOf(person)===biped);
原型层级 通过对象访问属性时会按照这个属性名称开始搜索,搜索开始于对象实例本身,如果在实例上发现给定的名称,则返回该名称对应值,如果没有找到这个属性,则搜索会沿着指针进入原型对象,在原型对象找到属性后返回对应值。
比如foo不直接存在于myObject中而是存在于原型链上层时,myObject.foo=”bar”会出现三种情况:
如果在[[Prototype]]链上层存在名为foo的普通数据访问属性,并且writable:true,那就会直接在myObject中添加一个名为foo的新属性,它是屏蔽属性
如果在[[Prototype]]链上层存在foo,但是它是被标记为只读(writable:false),那么无法修改已有属性或者在myObject上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。不会发生屏蔽
如果在[[Prototype]]链上层存在foo并且它是一个setter,那就一定会调用这个setter,foo不会添加到myObject,也不会重新定义foo这个setter。
当属性被屏蔽时,可以使用delete删除实例上的这个属性。
1 2 3 4 5 6 7 8 9 10 11 function Person ( ) {}Person.prototype.name='Nicholas' ; Person.prototype.age=29 ; let person1=new Person();let person2=new Person();person1.name="Greg" ; console .log(person1.name);console .log(person2.name);delete person1.name;console .log(person1.name);
hasOwnProperty() hasOwnProperty()方法用于确定某个属性是在实例上还是原型对象上。这个方法继承自Object,会在属性存在于调用它的对象实例善时返回true
in操作符 in操作符会在可以通过对象访问指定属性时返回true,无论该属性是在实例上还是在原型上
因此如果要确定某个属性是否在原型上可以同时使用hasOwnProperty()和in操作符
1 2 3 function hasPrototypeProperty (object,name ) { return !Object .hasOwnProperty(name)&&(name in object) }
原型的问题 弱化了向构造函数传递初始化参数的能力,会导致所有实例默认取得属性相同的值,以及它的共享特性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function Person ( ) { Person.prototype={ constructor :Person, name :'nic' , age :29 , job :"Software Engineer" , friends :['Coloey' ,'Amy' ], sayName ( ) { console .log(this .name) } } } let person1=new Person(); let person2=new Person(); person1.friends.push('Van' ); console .log(person1.friends===person2.friends);
不同实例应有不同的副本 。
原型链 原型链是ECMAScript的主要继承方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 function SuperType ( ) { this .property=true ; } SuperType.prototype.getSuperValue=function ( ) { return this .property; } function SubType ( ) { this .subproperty=false ; } SubType.prototype=new SuperType(); SubType.prototype.getSubValue=function ( ) { return this .subproperty; } let instance=new SubType(); console .log(instance.getSuperValue()); console .log(instance.property)
SubType.prototype是SuperType的实例,因此SubType.prototype指向SuperType.prototype,注意,getSuperValue()方法还在SuperType.prototype对象上,而property是一个实例属性,SubType.prototype是SuperType的实例,因此property存在它上面,由于SubType.prototype的constructor属性被重写指向SuperType,所以instance.constructor也指向SuperType.
原型与继承关系 使用instanceOf操作符,如果一个实例的原型链出现过相应构造函数则instanceOf返回true
1 2 console .log(instance instanceof Object );console .loh(instance instanceof SubType);
使用**isPrototypeOf()**方法,原型链上的每个原型都可以调用这个方法,只要原型链上包含这个原型就返回true
1 2 3 console .log(Object .prototype.isPrototypeOf(instance)); console .log(SuperType.prototype.isPrototypeOf(instance)); console .log(SubType.prototype.isPrototypeOf(instance));
以对象字面量的方式创建原型方法会破坏之前的原型链:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 function SuperType(){ this.property=true; } SuperType.prototype.getSuperValue=function(){ return this.property; } function SubType(){ this.subproperty=false; } //继承SuperType SubType.prototype=new SuperType(); //通过对象字面量添加新方法,会导致上一行无效 SubType.prototype={ getSubValue(){ return this.subproperty; }, someOtherMethod(){ return false; } } let instance=new SubType(); console.log(instance.getSuperValue());//出错
原型链的问题就是会在原型中包含的引用值会在实例间共享 。
1 2 3 4 5 6 7 8 9 10 11 function SuperType ( ) { this .color=["red" ,"blue" ,"green" ] } function SubType ( ) { } SubType.prototype=new SuperType(); let intance1=new SubType(); intance1.color.push("black" ); console .log(intance1.color); let instance2=new SubType(); console .log(instance2.color);
盗用构造函数 1 2 3 4 5 6 7 8 9 10 11 12 13 function SuperType ( ) { this .color=["red" ,"blue" ,"green" ] } function SubType ( ) { SuperType.call(this ); } let intance1=new SubType(); intance1.color.push("black" ); console .log(intance1.color); let instance2=new SubType(); console .log(instance2.color);
使用call(),SuperType构造函数在为SubType的实例创建的新对象的上下文中执行了,相当于新的SubType对象上运行了SuperType()函数的所有初始化代码
传递参数: 1 2 3 4 5 6 7 8 9 10 11 12 function SuperType (arr ) { this .color=["red" ,"blue" ,"green" ] arr.forEach(item =>this .color.push(item)); } function SubType ( ) { SuperType.call(this ,['black' ]); } let intance1=new SubType(); console .log(intance1.color); let instance2=new SubType(); console .log(instance2.color);
问题: 也是使用构造函数模式自定义类型的问题:必须在构造函数中定义方法,因此函数不能重用,此外,子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式。