3

原型模式

 3 years ago
source link: https://segmentfault.com/a/1190000038678436
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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 看出各个对象之间的关系。

aaIf6fb.png!mobile

图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.本期的分享到了这里就结束啦,希望对你有所帮助,让我们一起努力走向巅峰!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK