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 的原型对象。
constructor
person 是构造函数,有prototype
指针指向原型对象,而person.prototype
也有一个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__
指针指向构造函数的原型对象。
正是因为创建的 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 的__proto__
又指向哪里呢?它指向这条链的终点:null,代表无。
object.getprototypeof(object.prototype); // null
回到上面所说的查找name
属性,如果在第二次都没有查找到name
属性,就会沿着这条链继续向上查找,直到终点都没有找到,就会返回 undefined。
function person(){} let p1 = new person(); p1.name; // undefined
上图中虚线部分由原型相互关联组成的链状结构就是所谓的原型链。
备注
不得不说,《javascript高级程序设计》这本书把原型讲得很透彻,今天再看一遍依然感觉通透。