MDN繼承和原型鏈章筆記

原文  developer.mozilla.org/en-US/docs/…

JS並不提供類的實現(class關鍵字是在ES2015引入的但僅是語法糖,JS自己還是基於原型的)。javascript

當談到繼承,JS僅有一種結構:對象。每個對象(object)都有一個指向其餘對象的私有屬性,這個屬性叫作這個對象的原型(prototype)。java

幾乎全部的對象都是Object的實例,也能夠說全部對象都繼承自Object,它是原型鏈的頂端。Object.protorype指向null,null是沒有prototype的。瀏覽器

當咱們去訪問某一個對象的屬性時,JS會今後對象沿着原型鏈往上找,直到找不到爲止。bash

Prototype:

----------如下重點---------最難理解的部分---------markdown

obj.[[Prototype]]是ES標準的記法,obj.__proto__是瀏覽器廠商實際使用的記法,Object.getPrototypeOf(obj)/Object.setPrototypeOf(child, parent) 是ES6提供的訪問器,這三種寫法本質上是同一種東西。函數

someFunction.prototype跟上面的不是一種東西,它是一種抽象的泛指,表示全部用someFunction看成構造器創造出來的實例的__proto__。他倆的關係相似於class和instance的關係。oop

若是你仔細觀察的話,會發現.prototype老是跟在function後面,.__proto__老是跟在創造出的對象後:性能

function doSomething(){}
doSomething.prototype.foo = "bar";
console.log( doSomething.prototype );複製代碼
結果:
{
    foo: "bar",
    constructor: ƒ doSomething(),
    __proto__: {
        constructor: ƒ Object(),
        hasOwnProperty: ƒ hasOwnProperty(),
        isPrototypeOf: ƒ isPrototypeOf(),
        propertyIsEnumerable: ƒ propertyIsEnumerable(),
        toLocaleString: ƒ toLocaleString(),
        toString: ƒ toString(),
        valueOf: ƒ valueOf()
    }
}複製代碼

當建立實例後優化

var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value"; // add a property onto the object
console.log( doSomeInstancing );複製代碼

結果this

{
    prop: "some value",
    __proto__: {
        foo: "bar",
        constructor: ƒ doSomething(),
        __proto__: {
            constructor: ƒ Object(),
            hasOwnProperty: ƒ hasOwnProperty(),
            isPrototypeOf: ƒ isPrototypeOf(),
            propertyIsEnumerable: ƒ propertyIsEnumerable(),
            toLocaleString: ƒ toLocaleString(),
            toString: ƒ toString(),
            valueOf: ƒ valueOf()
        }
    }
}複製代碼

你能夠看到,若是你找doSomeInstancing.__proto__,他指向的是doSomething.prototype。因此當你定義一個方法或類的時候,加在prototype上的屬性會變成之後這個類的實例(或後代)的原型鏈__proto__上的屬性。

實例化或者繼承在JS中是同樣的存在,都會是創造一個新的對象而後讓他加入原型鏈。

當你嘗試去在某對象中找屬性,JS就會從這個對象開始遍歷原型鏈直到最頂端,因此說繼承的越多(原型鏈越長)性能越差。

當寫任何一個東西的時候,JS會自動幫你添加原型鏈,這就是爲何說幾乎全部的對象都是Object的實例。除非你用var freshObj = Object.create(null);創造出的就是沒有繼承Object的

var o = {a: 1};

// The newly created object o has Object.prototype as its [[Prototype]]
// o has no own property named 'hasOwnProperty'
// hasOwnProperty is an own property of Object.prototype. 
// So o inherits hasOwnProperty from Object.prototype
// Object.prototype has null as its prototype.
// o ---> Object.prototype ---> null
// 再次提醒:上面體現的是o.__proto__ === Object.prototype, 而不是o.prototype,這玩意是undefined

var b = ['yo', 'whadup', '?'];

// Arrays inherit from Array.prototype 
// (which has methods indexOf, forEach, etc.)
// The prototype chain looks like:
// b ---> Array.prototype ---> Object.prototype ---> null
// 同理你在用Array的實例,上面語法實際上是new Array('yo', 'whadup', '?')

function f() {
  return 2;
}

// Functions inherit from Function.prototype 
// (which has methods call, bind, etc.)
// f ---> Function.prototype ---> Object.prototype ---> null
// 這裏在定義一個類或方法,因此上面是f.prototype === Function.prototype複製代碼

須要用hasOwnProperty()檢查某屬性是否在這個對象自己定義的。

擴展原型鏈的4種方法:

1. new

function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = new foo;
proto.bar_prop = "bar val";
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);複製代碼

優:瀏覽器支持最廣(除了ie5.5以前的),很是快,很是標準。

缺:new的函數必須是初始化過的。由於初始化時,他的構造器可能會存儲每一個對象必須生成的獨特的信息,然而這個信息只會生成一次,因此會致使潛在的問題。

2. Object.create()

版本1:
function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = Object.create(
  foo.prototype
);
proto.bar_prop = "bar val";
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);複製代碼
版本2:使用create的第二個參數設置屬性
function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = Object.create(
  foo.prototype,
  {
    bar_prop: {
      value: "bar val"
    }
  }
);
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop)複製代碼

優:大部分瀏覽器支持(除ie9以前)。容許直接設置__proto__,由於是一次性設置的因此瀏覽器能作相應的優化。能創造不繼承Object的對象。

缺:若是添加了第二個參數的話,對象初始化過程性能會變不好。由於每一個對象描述器(object-descriptor)屬性有它本身的描述器(descriptor)對象。

3. Object.setPrototypeOf()

版本1:第一個參數就是child
function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = {
  bar_prop: "bar val"
};
Object.setPrototypeOf(
  proto, foo.prototype
);
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);複製代碼
版本2:返回值是child
function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto;
proto=Object.setPrototypeOf(
  { bar_prop: "bar val" },
  foo.prototype
);
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop)複製代碼

優:大部分瀏覽器支持(除ie9以前)。能動態地操做對象原型,甚至強加prototype給無原型的對象(Object.create(null)創造的)。

缺:性能不好。瀏覽器大多會作對原型的優化且嘗試去猜某個方法在內存的位置在你調用某實例以前,而這種動態添加原型的方法基本上毀了他們的優化甚至強迫某些瀏覽器重編譯。

4. .__proto__

function foo(){}
foo.prototype = {
  foo_prop: "foo val"
};
function bar(){}
var proto = {
  bar_prop: "bar val",
  __proto__: foo.prototype
};
bar.prototype = proto;
var inst = new bar;
console.log(inst.foo_prop);
console.log(inst.bar_prop);複製代碼
版本2:從頭到腳直接用proto
var inst = {
  __proto__: {
    bar_prop: "bar val",
    __proto__: {
      foo_prop: "foo val",
      __proto__: Object.prototype
    }
  }
};
console.log(inst.foo_prop);
console.log(inst.bar_prop)複製代碼

優:大部分瀏覽器支持(除ie11以前)。把__proto__設置成非對象的值會隱式失敗,不會拋異常。

缺:已被標準棄用(儘管還有瀏覽器支持),性能不好。跟上面同樣,瀏覽器大多會作對原型的優化且嘗試去猜某個方法在內存的位置在你調用某實例以前,而這種動態添加原型的方法基本上毀了他們的優化甚至強迫某些瀏覽器重編譯。

看完MDN後的幾點疑惑及思考

1. 爲何在文檔上面提到不要func.prototype = {a: 'a'}這麼寫,由於會破壞原型鏈,而下面的好多例子又直接那麼寫呢?

// add properties in f function's prototype f.prototype.b = 3; f.prototype.c = 4; // do not set the prototype f.prototype = {b:3,c:4}; this will break the prototype chain複製代碼

由於若是你有不少層的繼承的話,顯而易見,你直接讓他指向一個其餘對象,那麼前面的鏈就斷了。但是例子裏面都只有一層繼承,他們上面都是Object.prototype,因此無所謂了


2. 爲何var b = {}; 或 b = []打印出來的b.prototype === undefined?

已經在前面講過了,對象(實例)要用__proto__


3. 爲啥說b = []的繼承鏈上一層是Array.prototype呢, 從哪看出來的?

打印一下就知道了:



4. class(用class關鍵字聲明的)怎麼繼承純obj?

const Animal = {
  speak() {
    console.log(this.name + ' makes a noise.');
  }
};

class Dog {
  constructor(name) {
    this.name = name;
  }
}

// If you do not do this you will get a TypeError when you invoke speak
Object.setPrototypeOf(Dog.prototype, Animal);

let d = new Dog('Mitzie');
d.speak(); // Mitzie makes a noise.複製代碼
相關文章
相關標籤/搜索