再論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的深刻學習。

相關文章
相關標籤/搜索