JS: 原型与原型链

原型与原型链

javascript 创建对象


类与构造函数是大多数编程语言所拥有的,而借鉴了 C 与 JAVA 的 javascript 也是有类和构造函数的,不过 javascript 的实现不太一样。

// before ES6
// 构造函数模式
function Person(name){
  this.name = name;
    this.sayName = function() {
      console.log(this.name);
    }
}

var p = new Person();
console.log(p instanceof Person); // true

上面的例子中,Person就是构造函数,p就是它的实例,但是它对共享性不太好,所以有了原型模式。

// 原型模式
function Person(){}
Person.prototype.name = 'kobe';
Person.prototype.sayName = function() {
  console.log(this.name);
}

var p1 = new Person();
var p2 = new Person();

p1.sayName(); // kobe
p2.sayName(); // kobe

原型模式创建的实例都可以共享Person.prototype的属性和方法,而prototype是 javascript 实现类的一个关键所在。

prototype


在 javascript 中,每个函数都有一个prototype属性,这个属性指向函数的原型对象,上面的例子,可以用下图解释,图中用Person.prototype表示 Person 的原型对象。

prototype

constructor


Person 是构造函数,有prototype指针指向原型对象,而Person.prototype也有一个constructor属性指向构造函数。

constructor

简单来说,就是 javascript 会在函数创建的时候为函数添加一个prototype属性,这个属性是指向函数的原型对象的指针,而原型对象也会获得一个constructor属性,这个属性是一个指向函数本身的指针,即指向构造函数。

function Person(){}
Person.prototype.name = 'kobe';
Person.prototype.sayName = function() {
  console.log(this.name);
}

Person.prototype.constructor == Person; // true

__proto__


函数有prototype,那对象呢?其实对象内部也有一个[[prototype]]指针,这个指针指向创建实例对象的构造函数的原型对象。

在 ES5 之前的语言标准里,是不允许访问这个属性的,但浏览器都实现了一个__proto__[[prototype]]进行访问操作,所以 ES5 增加了一个Object.getPrototypeOf()方法来访问[[prototype]]

Object.getPrototypeOf(object) 方法返回指定对象的原型(内部[[Prototype]]属性的值)。

在 ES5 中,如果参数不是一个对象类型,将抛出一个 TypeError 异常。在 ES6 中,参数会被强制转换为一个 Object。

建议在代码中使用Object.getPrototypeOf()来访问内部[[Prototype]]属性,不建议使用非标准的__proto__,为了方便区别,文中使用__proto__来表示[[prototype]]

回到上面的例子,当调用 Person 来创建实例的时候,该实例会获得一个__proto__指针指向构造函数的原型对象。

__proto__

正是因为创建的 p1、p2 实例都拥有__proto__指向构造函数(Person)的原型对象(Person.prototype),它们才能共享Person.prototype的属性和方法。

function Person(){}
Person.prototype.name = 'kobe';
Person.prototype.sayName = function() {
  console.log(this.name);
}

let p1 = new Person();
let p2 = new Person();

Object.getPrototypeOf(p1) == Object.getPrototypeOf(p2); // true

简单来说,就是在 javascript 中,每一个对象(除null)在创建的时候都会将其与另一个对象进行关联,而关联的这个对象就是所谓的原型。

使用isPrototypeOf()方法就能确定这种关联:

Person.prototype.isPrototypeOf(p1); // true
Person.prototype.isPrototypeOf(p2); // true

屏蔽性


还是这个例子:

function Person(){}
Person.prototype.name = 'kobe';
Person.prototype.sayName = function() {
  console.log(this.name);
}

let p1 = new Person();
// 访问 p1 的 name
p1.name; // kobe

当我们通过实例 p1 访问name属性的时候,javascript 引擎会先在实例 p1 中查找name,当发现 p1 没有这个属性,就会通过__proto__向它指向的原型对象进行查找,找到就返回这个属性的值,也就是说访问name执行了两次查找。

如果 p1 自己添加了name属性,就会在第一次查找时返回值:

// 在 p1 中添加 name
p1.name = 'jordan';

// 只会执行一次查询
p1.name; // jordan

// 原型中的值并未改变
Object.getPrototypeOf(p1).name; // kobe

也就是说,在实例上添加name属性后,该属性就会屏蔽原型中的那个属性,但并不会改变原型中的该属性的值。

原型链


既然在 javascript 中,所有对象(除null)都有__proto__,而实例 p1 指向Person.prototype,那Person.prototype对象的__proto__又指向哪里呢?

Object.getPrototypeOf(Person.prototype) == Object.prototype; // true

答案是Object.prototype,因为原型对象是通过构造函数 Object 来创建的,所以它指向Object.prototype

Object.prototype

那 Object.prototype 的__proto__又指向哪里呢?它指向这条链的终点:null,代表无。

Object.getPrototypeOf(Object.prototype); // null

null

回到上面所说的查找name属性,如果在第二次都没有查找到name属性,就会沿着这条链继续向上查找,直到终点都没有找到,就会返回 undefined。

function Person(){}

let p1 = new Person();
p1.name; // undefined

上图中虚线部分由原型相互关联组成的链状结构就是所谓的原型链。

备注


不得不说,《javascript高级程序设计》这本书把原型讲得很透彻,今天再看一遍依然感觉通透。

posted @ 2019-02-28 14:41  郭佬  阅读(367)  评论(0编辑  收藏  举报
我终究成长为一个不特别的人