再论JavaScript原型继承和对象继承

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

原型继承

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

// 定义函数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的字段和方法this

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

“对象式“继承

开门见山地用code来讲明: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__链。咱们作个简单的实现:ip

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的深刻学习。

相关文章
相关标签/搜索