理解JavaScript--原型

四、原型


1.原型对象

绝大部分的函数(少数内建函数除外)都有一个prototype属性,这个属性是原型对象用来创建新对象实例,而所有被创建的对象都会共享原型对象,因此这些对象便可以访问原型对象的属性,例如hasOwnProperty()方法存在于Obejct原型对象中,它便可以被任何对象当做自己的方法使用.

object.hasOwnProperty( propertyName )
hasOwnProperty()函数的返回值为Boolean类型。如果对象object具有名称为propertyName的属性,则返回true,否则返回false

1
2
3
4
5
6
7
8
 var person = {
name: "Messi",
age: 29,
profession: "football player"
};
console.log(person.hasOwnProperty("name")); //true
console.log(person.hasOwnProperty("hasOwnProperty")); //false
console.log(Object.prototype.hasOwnProperty("hasOwnProperty")); //true

由以上代码可知,hasOwnProperty()并不存在于person对象中,但是person依然可以拥有此方法.

很多人此时会好奇,person对象是如何找到Object对象中的方法的呢?这其中的内部机制是什么?
这便是我们接下来要说的原型链.


2.__proto__[[Prototype]]

上一篇我们的示意图中曾经出现过__proto__,在ES6之前这个__proto__是大部分主流浏览器(IE除外)引擎提供的,还尚属非ECMA标准,在解析一个对象实例的时候为对象实例添加一个__proto__属性,此属性指向原型对象,我们便可以通过此属性找到原型对象.

1
2
3
4
5
6
7
8
function person(pname, page) {
this.name = pname;
this.age = page;
}
person.prototype.profession = "football player";
var person1 = new person("Messi", 29); //person1 = {name:"Messi", age: 29, profession: "football player"};
var person2 = new person("Bale", 28); //person2 = {name:"Bale", age: 28, profession: "football player"};
console.log(person1.__proto__ === person.prototype); //true

__proto__除了被主流浏览器支持外,还被Node.js支持,在ES2015进入到规范附录部分,算是被正式纳入了标准.

而在标准的语法里,实例对象是通过内置的内部属性[[Prototype]]来追踪原型对象的,这个[[Prototype]]的指针始终指向原型对象,此属性通常情况下是不可见的,我们需要用getPrototypeOf()来读取[[Prototype]]属性值(这个值就是原型对象).

1
2
var obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); //true

同时我们也可以用isPrototypeOf来检验某个对象是否是另一个对象的原型对象.

1
2
var obj = {};
console.log(Object.prototype.isPrototypeOf(obj)); //true

3.原型链

在我们了解了__proto__[[Prototype]]之后,就可以相对容易理解原型链了,由于__proto__[[Prototype]]功能相似,但是__proto__更容易测试方便学习,我们选择__proto__来进行原型链的讲解.

1
2
3
4
5
6
7
8
9
10
11
12
13
function person(pname, page) {
this.name = pname;
this.age = page;
}
person.prototype.profession = "football player";
var person1 = new person("Messi", 29); //person1 = {name:"Messi", age: 29, profession: "football player"};
var person2 = new person("Bale", 28); //person2 = {name:"Bale", age: 28, profession: "football player"};
person1.hasOwnProperty("name");
console.log(person1.hasOwnProperty("hasOwnProperty")); //fasle
console.log(person1.__proto__ === person.prototype); //true
console.log(person.prototype.hasOwnProperty("hasOwnProperty")); //false
console.log(person1.__proto__.__proto__ === person.prototype.__proto__); // true
console.log(person.prototype.__proto__.hasOwnProperty("hasOwnProperty")); //true

我们可以分析这个例子,看看person1对象实例是如何调用hasOwnProperty()这个方法的.

  1. 首先person1对象实例中寻找hasOwnProperty()方法,person1.hasOwnProperty("hasOwnProperty")返回false,发现不存在此方法,这时通过__proto__找到person1的原型对象.
  2. person1的原型对象person1.__proto__person.prototype中寻找hasOwnProperty()方法,person.prototype.hasOwnProperty("hasOwnProperty")返回false,依然没有找到,此时顺着person.prototype__proto__找到其原型对象.
  3. person.prototype原型对象person.prototype.__proto__Object.prototype中寻找hasOwnProperty()方法,Object.prototype.hasOwnProperty("hasOwnProperty")返回true,由于hasOwnProperty()Object.prototype内置方法,因此person1顺利找到此方法并调用.

总而言之,实例对象方法调用,是现在实力对象内部找,如果找到则立即返回调用,如果没有找到就顺着__proto__向上寻找,如果找到该方法则调用,没有找到会直接报错,这便是原型链.

如图所示,会更加直观.


4.ES6中的__proto__

虽然__proto__在最新的ECMA标准中被纳入了规范,但是由于__proto__前后的双下划线,说明它本质上是一个内部属性,而不是一个正式的对外的 API.
标准明确规定,只有浏览器必须部署这个属性,其他运行环境不一定需要部署,而且新的代码最好认为这个属性是不存在的。因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性,而是使用下面的Object.setPrototypeOf()(写操作)、Object.getPrototypeOf()(读操作)代替。

1
2
3
4
5
6
7
8
9
10
function person(pname, page) {
this.name = pname;
this.age = page;
}
person.prototype.profession = "football player";
var person1 = new person("Messi", 29);

console.log(Object.getPrototypeOf(person1) === person.prototype); //true
Object.setPrototypeOf(person1, {League: "La Liga"});
console.log(person1.League); //La Liga

以上为具体用法,但是值得注意的是Object.setPrototypeOf()在使用中有一个坑,如代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
function person(pname, page) {
this.name = pname;
this.age = page;
}
person.prototype.profession = "football player";
var person1 = new person("Messi", 29);
var person2 = new person("Bale", 28);

Object.setPrototypeOf(person1, { League: "La Liga"});

console.log(person1.League); //La Liga
console.log(person2.League); //undefind

也就是说不同于直接用person1.__proto__.League = "La Liga";会使得两个实例同时生效,Object.setPrototypeOf()只能生效一个实例对象.

-------------本文结束感谢您的阅读-------------
显示 Gitment 评论