每个函数都会创建一个 prototype 属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法。实际上,这个对象就是通过调用构造函数创建的对象的原型。使用原型对象的好处是,在它上面定义大的属性和方法可以被对象实例共享。1.原来在构造函数中直接赋给对象实例的值,可以直接赋值给它们的原型,如下所示:
function Person(){}
Person.prototype.name="张三";
Person.prototype.age=29;
Person.prototype.job="Web前端开发";
Person.prototype.sayName=function(){
console.log(this.name);
}
let person1=new Person();
person1.sayName();// 张三
let person2=new Person();
person2.sayName();// 张三
console.log(person1.sayName===person2.sayName);// true
使用函数表达式也可以:
let Person=function(){};
Person.prototype.name="李四";
Person.prototype.age=20;
Person.prototype.job="IOS开发";
Person.prototype.sayName=function(){
console.log(this.name);
}
let person1=new Person();
person1.sayName(); //李四
let person2=new Person();
person2.sayName(); //李四
console.log(person1.sayName===person2.sayName);// true
这里,所有属性和sayName()方法都直接添加到了 Person 的prototype属性上,构造函数体中什么都没有。但这样定义之后,调用构造函数创建的新对象仍然拥有相应的属性和方法。与构造函数模式不同,使用这种原型模式定义的属性和方法是由所有实例共享的。因此 person1和person2访问的都是相同的属性和相同的 sayName()函数。要理解这个过程,就必须理解 ECMAScript中原型的本质。
2.理解原型
无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个 prototype属性(指向原型对象)。默认情况下,所有原型对象自动获得一个名为 constructor 的属性,指回与之关联的构造函数。对前面的例子而言,Person.prototype.constructor 指向Person。然后,因构造函数而异,可能会给原型对象添加其他属性和方法。
在自定义构造函数时,原型对象默认只会获得 constructor 属性,其他的所有方法都继承自 Object。每次调用构造函数创建一个新实例,这个实例的内部 [[Prototype]]指针就会被赋值为构造函数的原型对象。脚本中没有访问这个[[Prototype]]特性的标准方式,但 Firefox\Safari和Chrome会在每个对象上暴漏 proto 属性,通过这个属性可以访问对象的原型。在其他实现中,这个特性完全被隐藏了。关键在于理解这一点:实例与构造函数原型之间由直接的联系,但实例与构造函数之间没有。
3.这种关系不好可视化,但可以通过下面的代码来理解原型的行为:
构造函数可以是函数表达式
也可以是函数声明,因此以下两种形式都可以:
/*
function Person(){}
let Person=function(){}
*/
function Person(){}
/*
声明之后,构造函数就有了一个与之关联的原型对象:
*/
/*
如前所述,构造函数有一个 prototype 属性
引用其原型对象,而这个原型对象也有一个
constructor 属性,引用这个构造函数
换句话说,两者循环引用:
console.log(Person.prototype.constrctor===Person); // true
*/
4.正常的原型链都会终止于 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
5.构造函数\原型对象\实例是三个完全不相同的对象:
let person1=new Person(),
person2=new Person();
console.log(person1 !=Person); // true
console.log(person1 !=Person.ptototype); // true
console.log(Person.prototype !=Person); // true
1.实例通过 __proto__链接到原型对象,它实际上指向隐藏特性[[Prototype]]
2.构造函数通过 prototype 属性链接到原型对象
3.实例与构造函数没有直接联系,与原型对象有直接联系
console.log(person1.__proto__===Person.prototype); // true
console.log(person1.__prototype__.constructor===Person); // true
6.同一个构造函数创建的联赛哥哥实例,共享同一个原型对象。
console.log(person1.__proto__===person2.__proto__); // true
7.使用 instanceof 检查实例的原型链中是否包含指定构造函数的原型:
console.log(person1 instanceof Person); // true
console.log(person1 instanceof Object); // true
console.log(Person.prototype instanceof Object); //true
8.对于前面例子中的 Person 构造函数和 Person.prototype,可以通过图 8-1 看出各个对象之间的关系。
图8-1展示了Person构造函数\Person的原型对象和Person现有两个实例之间的关系。注意,Person.prototype指向原型对象,而Person.prototype.constrctor指回Person构造函数。原型对象包含 constructor 属性和其他后来添加的属性。Person的两个实例 person1和 person2都只有一个内部属性指会 Person.prototype,而且两者都与构造函数没有直接联系。另外要注意,虽然这两个实例都没有属性和方法,但 person1.sayName()可以正常调用。这是由于对象属性查找机制的原因。
9.虽然不是所有实现都对外暴露了 [[Prototype]],但可以使用 isPrototypeof()方法确定两个对象之间的这种关系。本质上,isPrototypeof()会在传入参数的 [[Prototype]]指向调用它的对象时返回 true,所下所示:
console.log(Person.prototype.isPrototype(person1)); // true
console.log(Proson.prototype.isPrototype(person2)); // true
这里通过原型对象调用 isPrototypeof()方法检查了 person1和person2。因为这两个例子内部都有链接指向 Person.prototype,所以结果都返回 true。
10.ECMAScript的Object类型有一个方法叫 Object.getPrototypeof(),返回参数的内部特性 [[Prototype]]的值。例如:
console.log(Object.getPrototypeof(person1)==Person.prototype); //true
console.log(Object.getPrototypeof(person1).name);// 李四
第一行代码简单确认了 Object.getPrototypeof()返回的对象就是传入对象的原型对象。第二行代码取得了原型对象上的 name 属性的值,即 "李四"。使用 Object.getPrototypeof()可以方便地取得一个对象的原型,而这在通过原型实现继承时显得尤为重要。
11.Object类型还有一个 setPrototypeof()方法,可以向实例的私有特性 [[Prototypeo]]写入一个新值。这样就可以重写一个对象的原型继承关系:
let biped={
numLegs:2
};
let person={
name:"Chen"
}
Object.setPrototypeof(person,biped);
console.log(person.name); // Chen
console.log(person.numLegs); // 2
console.log(Object.getPrototypeof(person)===biped); //true
警告:Object.setPrototypeof()可能会严重影响代码性能。Mozilla文档说得很清楚:"在所有浏览器和JvavaScript引擎中,修改继承关系的影响都是微妙且深远的。这种影响且不仅时执行 Object.setPrototypeof()语那么简单,而且会涉及所有访问了那些修改过 [[Prototype]]"的对象的代码。
12.为避免使用 Object.setPrototypeof()可能造成的性能下降,可以通过 Object.create()来创建一个新对象,同时为其指定原型:
let biped={
numLegs:2
}
let person=Object.create(biped);
person.name="Chen";
console.log(person.name); // Chen
console.log(person.numLegs); // 2
console.log(Object.getPrototypeof(person)===biped); // true
13.本期的分享到了这里就结束啦,希望对你有所帮助,让我们一起努力走向巅峰!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。