0%

原型模式

构造函数

构造函数也是函数,与普通函数唯一区别就是调用方式不同。任何函数只要使用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);
//正常原型链都会终止于Object的原型对象
//Object原型的原型是null
console.log(Person.prototype.__proto__===Object.prototype);//true
console.log(Person.prototype.__proto__.constructor===Object);//true
console.log(Person.prototype.__proto__.__proto__===null);//true
console.log(Person.prototype.__proto__);//true
let person1=new Person(),person2=new Person();
//构造函数,原型对象和实例是3个完全不同的对象
console.log(person1!==Person);//true
console.log(person1!==Person.prototype);//true
console.log(Person.prototype!==Person);//true
//实例通过__proto__链接到原型对象,它实际上指向隐藏特性[[Prototype]]
//实例与构造函数没有联系,与原型对象有直接联系
console.log(person1.__proto__===Person.prototype);
console.log(person1.__proto__.constructor===Person);
//同一个构造函数创建的两个实例共享同一个原型对象
console.log(person1.__proto__===person2.__proto__);
//instanceof检查实例的原型链中是否包含指定构造函数的原型
console.log(person1 instanceof Person);//true
console.log(person1 instanceof Object);//true
console.log(Person.prototype instanceof Object);//true

原型

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));//true
console.log(Person.prototype.isprototypeOf(person2));//true

Object类型有一个方法叫Object.getPrototypeOf(),返回参数的内部特性[[Prototype]]的值

1
2
console.log(Object.getPrototypeOf(person1)==Person.prototype);//true
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);//true

原型层级

通过对象访问属性时会按照这个属性名称开始搜索,搜索开始于对象实例本身,如果在实例上发现给定的名称,则返回该名称对应值,如果没有找到这个属性,则搜索会沿着指针进入原型对象,在原型对象找到属性后返回对应值。

比如foo不直接存在于myObject中而是存在于原型链上层时,myObject.foo=”bar”会出现三种情况:

  1. 如果在[[Prototype]]链上层存在名为foo的普通数据访问属性,并且writable:true,那就会直接在myObject中添加一个名为foo的新属性,它是屏蔽属性

  2. 如果在[[Prototype]]链上层存在foo,但是它是被标记为只读(writable:false),那么无法修改已有属性或者在myObject上创建屏蔽属性。如果运行在严格模式下,代码会抛出一个错误。否则,这条赋值语句会被忽略。不会发生屏蔽

  3. 如果在[[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);//"Greg",来自实例
console.log(person2.name);//Nicholas,来自原型
delete person1.name;
console.log(person1.name);//Nicholas,来自原型

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);//true

不同实例应有不同的副本

原型链

原型链是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;
}
//继承SuperType
SubType.prototype=new SuperType();
SubType.prototype.getSubValue=function(){
return this.subproperty;
}
let instance=new SubType();
console.log(instance.getSuperValue());//true
console.log(instance.property)//true

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));//true
console.log(SuperType.prototype.isPrototypeOf(instance));//true
console.log(SubType.prototype.isPrototypeOf(instance));//true

以对象字面量的方式创建原型方法会破坏之前的原型链:

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
SuperType.call(this);
}
//SubType.prototype=new SuperType();
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
SuperType.call(this,['black']);
}
let intance1=new SubType();
console.log(intance1.color);//['red', 'blue', 'green', 'black']
let instance2=new SubType();
console.log(instance2.color);//['red', 'blue', 'green', 'black']

问题:

也是使用构造函数模式自定义类型的问题:必须在构造函数中定义方法,因此函数不能重用,此外,子类也不能访问父类原型上定义的方法,因此所有类型只能使用构造函数模式。