67

傻傻分不清的__proto__与prototype - Leechikit的专栏 - SegmentFault

 6 years ago
source link: https://segmentfault.com/a/1190000011801127?
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.

傻傻分不清的__proto__与prototype

发布于 2017-10-31

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

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__实现原型链。

从上图我们可以找出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.__proto__ == Function.prototype;
Foo.__proto__.__proto__ == Function.prototype.__proto__;
Foo.__proto__.__proto__.__proto__ == Function.prototype.__proto__.__proto__ == Object.prototype.__proto__ == null;

构造函数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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK