在掘金看到一篇很好的文章,在这里分享给大家一下,顺便自己也做个记录;这篇文章很清楚的说明了原型与继承之间的差别,而且还提出了新手经常犯得错误;

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

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是一个只有函数才有的属性。

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

__proto__

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

原型链

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,或者这样this.combineNamethis指向实例对象)。

这是网友提出的一些问题:

imagesimages

gongzhonghao
关注“小K前端杂谈”,得到更多学习资源