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(...)實現了對象實例的__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的深刻學習。