4

JavaScript的原型继承是老生常谈。由于原型即prototype本身也是对象,所以“原型”继承可认为是一种特殊的“对象式”继承。”对象式“继承是笔者基于自己的理解,所提出的一个名词。本文就着重阐述这两种继承方式的异同之处。

原型继承

JavaScript里的原型即prototype是函数的特有属性,原型继承事先得有函数。

// 定义函数Foo
function Foo(name) {
    this.name = name;
}
// 定义Foo的原型
Foo.prototype.say = function() {
    console.log(this.name, 'say');
}

函数及其原型定义好了,就可以使用原型继承了。

var foo = new Foo('foo');
foo.say() //foo say
foo instanceof Foo; //true。 foo 是Foo的一个实例
foo.__proto__ === Foo.prototype // true

上面用的是new操作符,它实际是通过Object.create工作,其过程如下。所以new其实是Object.create的便利操作方式。

var foo = Object.create(Foo.prototype);
foo.name = 'foo'
foo.say();
foo instanceof Foo; //true。 foo 是Foo的一个实例
foo.__proto__ === Foo.prototype // true

请打起精神,Object.create(...)接受一个函数原型即object实例,以此实例为“模型”,创建新的object实例。新object实例的__proto__指向前function的prototype,自此新object实例也就拥有了前function prototype的字段和方法

既然Foo.prototype本身是object实例,那么我们是否可以给Object.create(...)传入一个普通的object实例呢?答案是可以的。这就是本文所要表述的“对象式”继承。

“对象式“继承

开门见山地用code来说明:

// 创建对象实例Foo
var Foo = {
    name: 'foo',
    say: function() {
        console.log(this.name, 'say');
    }
}

// 以Foo为“模型”,创建新的对象实例foo
var foo = Object.create(Foo);

foo.__proto__ == Foo;// true

// 所以foo也会拥有Foo的字段和方法,这点与原型继承类似。
foo.say(); // foo say. 

// 但是foo不是Foo的实例。
foo instanceof Foo; // TypeError: Right-hand side of 'instanceof' is not callable

如果Foo是个函数,结果基本是相同的,除了instanceof Foo 会等于false。

function Foo() {
}
Foo.say = function() {
    console.log(Foo.name, 'say');
}

// 以Foo为“模型”,创建新的对象实例foo
var foo = Object.create(Foo);

foo.__proto__ == Foo;// true

// 所以foo也会拥有Foo的字段和方法,这点与原型继承类似。
foo.say(); // Foo say. 

// 但是foo不是Foo的实例。
foo instanceof Foo; // false

“对象式”继承的一个应用

举个不太恰当的例子,如果我们需要对Math.abs做些“修正”,对于在-1和1之间的数值保持原值。

 var MMath = Object.create(Math);
 MMath.abs = function(val) {
    if (val >= -1 && val <=1) {
        return val;
    }
    return MMath.__proto__.abs(val); 
 }

 MMath.abs(0.5) // 0.5
 MMath.abs(-0.5) // -0.5
 MMath.abs(-2) // 2

揭秘Object.create魔术箱

不论原型继承还是“对象式”继承,其核心技术是Object.create(...)实现了对象实例的__proto__链。我们做个简单的实现:

Object.myCreate = function(obj) {
  if (obj instanceof Object || obj === null) {
     var newObj = {};
     Object.setPrototypeOf(newObj, obj)
     return newObj;
  } else {
    throw 'error happens. Should input a object';
  } 
}
function Fooo() {}
Fooo.say = function() {
     console.log(Fooo.name, 'say');
}

var myFooo = Object.myCreate(Fooo); 
myFooo.say(); // Fooo say

自此我们了解了Object.create(...)的黑魔法,也有助于我们理解Object.create({})和Oject.create(null)的区别,就是前者的__proto__是个object实例,拥有toString等方法。后者的__proto__是null,它不具有任何方法。在特别注重效率的情景,后者具有优势。

总结

本文提出“对象式”继承的概念,并与原型继承对比,阐述其区别和联系,希望有助于深化理解。虽然ES6越来越广泛应用了,了解函数原型等这些ES3/ES5的概念应该是有助于JS的深入学习。


杰克船长
269 声望15 粉丝