傻傻分不清的__proto__与prototype

29

今天同事小英童鞋问了我一个问题:

function Foo(firstName, lastName){
    this.firstName = firstName;
    this.lastName = lastName; 
}
Foo.prototype.logName = function(){
    Foo.combineName();
    console.log(this.fullName);
}
Foo.prototype.combineName = function(){
    this.fullName = `${this.firstName} ${this.lastName}`
}

var foo = new Foo('Sanfeng', 'Zhang');
foo.logName(); // Uncaught TypeError: Foo.combineName is not a function

小英童鞋认为Foo的原型对象是Foo.prototype,所以Foo会继承Foo.prototype的属性,调用Foo.combineName()相当于调用Foo.prototype.combineName(),但结果Foo.combineName()不是一个方法。

会造成这个问题的原因一定是因为小英童鞋弄混了原型和继承的一些原理,下面我们来整理一下原型和继承的相关原理,找出问题的根本原因。

prototype

prototype是一个拥有 [[Construct]] 内部方法的对象才有的属性。

例如函数,对象的方法,ES6 中的类。注意 ES6 中的箭头函数没有 [[Construct]] 方法,因此没有prototype这个属性,除非你为它添加一个。

当创建函数时,JavaScript 会为这个函数自动添加prototype属性,这个属性指向的是一个原型对象Functionname.prototype。我们可以向这个原型对象添加属性或对象,甚至可以指向一个现有的对象。

__proto__

接下来我们说说继承,每个对象都有一个__proto__属性,这个属性是用来标识自己所继承的原型。

注意: JavaScript 中任意对象都有一个内置属性 [[Prototype]] ,在ES5之前没有标准的方法访问这个内置属性,但是大多数浏览器都支持通过__proto__来访问。以下统一使用__proto__来访问 [[Prototype]],在实际开发中是不能这样访问的。

原型链

JavaScript 可以通过prototype__proto__在两个对象之间创建一个关联,使得一个对象就可以通过委托访问另一个对象的属性和函数。

这样的一个关联就是原型链,一个由对象组成的有限对象链,用于实现继承和共享属性。

构造函数创建对象实例

JavaScript 函数有两个不同的内部方法:[[Call]][[Construct]]

如果不通过new关键字调用函数,则执行 [[Call]] 函数,从而直接执行代码中的函数体。

当通过new关键字调用函数时,执行的是 [[Construct]] 函数,它负责创建一个实例对象,把实例对象的__proto__属性指向构造函数的prototype来实现继承构造函数prototype的所有属性和方法,将this绑定到实例上,然后再执行函数体。

模拟一个构造函数:

function createObject(proto) {
    if (!(proto === null || typeof proto === "object" || typeof proto === "function"){
        throw TypeError('Argument must be an object, or null');
    }
    var obj = new Object();
    obj.__proto__ = proto;
    return obj;
}

var foo = createObject(Foo.prototype);

至此我们了解了prototype__proto__的作用,也了解使用构造函数创建对象实例时这两个属性的指向,以下使用一张图来总结一下如何通过prototype__proto__实现原型链。

proto

从上图我们可以找出foo对象和Foo函数的原型链:

foo.__proto__ == Foo.prototype;
foo.__proto__.__proto__ == Foo.prototype.__proto__ == Object.prototype;
foo.__proto__.__proto__.__proto__ == Foo.prototype.__proto__.__proto__ == Object.prototype.__proto__ == null;

foo

Foo.__proto__ == Function.prototype;
Foo.__proto__.__proto__ == Function.prototype.__proto__;
Foo.__proto__.__proto__.__proto__ == Function.prototype.__proto__.__proto__ == Object.prototype.__proto__ == null;

class-foo

构造函数Foo的原型链上没有Foo.prototype,因此无法继承Foo.prototype上的属性和方法。而实例foo的原型链上有Foo.prototype,因此foo可以继承Foo.prototype上的属性和方法。

到这里,我们可以很简单的解答小英童鞋的问题了,在Foo的原型链上没有Foo.prototype,无法继承Foo.prototype上的combineName方法,因此会抛出Foo.combineName is not a function的异常。要想使用combineName方法,可以这样Foo.prototype.combineName.call(this),或者这样this.combineName()this指向实例对象)。

欢迎关注:Leechikit
原文链接:segmentfault.com

到此本文结束,欢迎提问和指正。
写原创文章不易,若本文对你有帮助,请点赞、推荐和关注作者支持。

你可能感兴趣的

13 条评论
再见尼克 · 2017年11月01日

而且 __proto__ 不是ES的标准,他是为了方便让用户可以直接获取,修改 instance 的 [[Prototype]] 的一种方式(非标准,其实不建议使用)。其实ES本身是不能让用户直接操作 instance 的 [[Prototype]] 的。

+8 回复

4

是的,我这里只是使用__proto__来访问[[Prototype]],更好的理解原型链。

Leechikit 作者 · 2017年11月01日
再见尼克 · 2017年11月01日

[[Prototype]] 不是函数才有的 property, <You Don‘t know JS> 里面说明过:
Objects in JavaScript have an internal property, denoted in the specification as [[Prototype]], which is simply a reference to another object. Almost all objects are given a non-null value for this property, at the time of their creation.
每个Object都有这个property,function 的 type 是 object, 所以function理所当然有 prototype, 但是其他的 object 也有这个 prototype

+6 回复

3

谢谢你的评论,我想你搞混了,我这里的prototype并不是对象的内部属性[[Prototype]],而是原型对象的引用。而这个内部属性[[Prototype]]是使用__proto__来获取。从下面的代码也证明只有函数有prototype属性,普通对象是没有的。

function A(){};
var b = { };
A.hasOwnProperty('prototype')
// true
b.hasOwnProperty('prototype')
// false
Leechikit 作者 · 2017年11月01日
2

var a = { method:function(){} };
a.method.hasOwnProperty('prototype'); // true

那你又怎么解释呢? 如果你知道 function 和 method 的区别

再见尼克 · 2017年11月01日
1

我是针对你的那句 prototype是一个只有函数才有的属性 这句话,觉得有点儿欠妥,值得商榷。

再见尼克 · 2017年11月01日
will · 2017年11月04日

个人觉得引入 js 引擎层面的概念来对不懂 js prototype 的人解释 js 中的概念,会让不懂的人更加无法理解。一会儿一个非标准属性 __proto__, 一会儿一个 function.prototype, 一会儿一个 [[prototype]],又弄出来 [[Construct]] , 哪些概念是 JS 层面的?哪些概念是 JS 引擎实现层面的?要是能单从 JS 语言层面来解释 JS 的现象会更好理解。

个人理解是,JS本身是打算让对象来继承对象,用 ES5 中的 Object.create 超级好解释。 var a = Object.create(b); 那我们就说对象 a 继承了对象 b,对象 b 是对象 a 的原型,有 Object.getPrototypeOf(a) === b 。“原型” 这个术语就是这个意思,一个对象的原型就是造这个对象的时候用的模子。

本来原型继承的事情就这样结束了,原型和继承为什么又变成前端老生常谈的模糊概念了呢?这是因为 js 设计的时候又留了一手,为了能模拟传统的 class 继承,js 把原型和 function 又纠缠在了一起,function 本身是对象这一点就够让人不好理解的,js 还特意给 function 设定了一个属性:“prototype”,然后 “函数”、“构造器”、“原型”就彻底乱了。还好有 ES6 的 class 语法糖,不用再和 function.prototype 打交道了。

+2 回复

鸠摩智 · 2017年11月01日

有尼玛月经贴

回复

载入中...