《JavaScript啓示錄》讀後筆記

1、原型編程

每一個函數都有一個prototype屬性,這個屬性是指向一個對象的引用,這個對象稱爲原型對象,原型對象包含函數實例共享的方法和屬性,也就是說將函數看成構造函數來調用的時候(使用new操做符調用),新建立的對象會從原型對象上繼承屬性和方法。瀏覽器

不管何時,只要建立了一個新函數,就會根據一組特定的規則爲該函數建立一個prototype屬性,默認狀況下prototype屬性會默認得到一個constructor(構造函數)屬性,這個屬性是一個指向prototype屬性所在函數的指針。編程語言

var helloworld = new Function('return "Hello World";');
console.log(helloworld());//輸出Hello World
console.log(helloworld.prototype);//Object{}
console.log(helloworld.prototype.constructor);//輸出function anonymous() {return "Hello World";}

2、構造函數實例都擁有指向構造函數的constructor屬性
函數

默認狀況下prototype屬性都會得到一個constructor屬性,所以任何對象實例化時,在幕後都會爲實例添加constructor屬性,而且這個屬性指向建立對象的構造函數。下面建立一個Object對象,保存在變量foo中,而後驗證constructor屬性在建立對象後是否可用。性能

var foo = {};
console.log(foo.hasOwnProperty('constructor'));//輸出false
console.log(foo.constructor === Object); //輸出true,由於object()構建了foo
console.log(foo.constructor); //輸出Object構造函數:function Object() { [native code] }

特別的,在原始值上使用constructor屬性可以指向正確的構造函數。舉一個例子:this

var foo = 23;
console.log(foo.constructor === Number); // 輸出true

constructor屬性也適用於用戶自定義的構造函數。以下代碼中,咱們定義了一個Person()構造函數,而後使用new關鍵字調用構造函數來生成一個對象,一旦建立了對象,就可使用constructor屬性了。
spa

var Person = function Person() { return 'Wow!'; };
var person = new Person();
console.log(person.constructor === Person); // 輸出true
console.log(person.constructor);//輸出function Person() { return 'Wow!'; }

3、原型在全部function()實例上都是標準的prototype

即便不直接使用Function()構造函數(如var add = new Function('x', 'y', 'return x + y;');),而是使用字面量表示法(如var add = function(x, y) { return x + y; }),結果也都是同樣,它老是擁有一個prototype屬性,默認的prototype屬性是一個空對象。設計

var helloworld = function () { return 'Hello World'; };
console.log(helloworld.prototype);//輸出Object{},若是給function命名爲Foo,則這裏顯示爲Foo {}

事情上,上面這段代碼至關於以下的代碼:指針

var helloworld = function() { return 'Hello World'; };
foo.prototype = {};
console.log(helloworld.prototype);//輸出Object{}

4、將構造函數建立的實例連接至構造函數的prototype屬性

當調用構造函數建立一個實例的時候,實例內部將包含一個內部指針(不少瀏覽器這個指針名字爲__proto__)指向構造函數的prototype,這個鏈接存在於實例和構造函數的prototype之間,而不是實例與構造函數之間。

Array.prototype.foo = 'foo';
var myArray = new Array();
console.log(myArray.__proto__.foo);//輸出foo
//__proto__並非ECMA標準,更通用的方法以下
console.log(myArray.constructor.prototype.foo);//輸出foo

5、原型鏈的最後是Object.prototype

每一個對象都有一個鏈接到原型的隱藏鏈接(__proto__),對象字面量產生的對象鏈接到Object.prototype,函數對象鏈接到Function.prototype(該原型對象自己鏈接到Object.prototype)。

原型鏈只有在檢索值的時候才被用到,若是咱們嘗試去獲取對象的某個屬性值,但該對象沒有此屬性,那麼JavaScript會試着從原型對象中獲取屬性值。若是那個原型對象也沒有該屬性,那麼再從它的原型中尋找,依此類推,直到該過程最後到達終點Object.prototype。若是這個屬性徹底不存在於原型鏈中,那麼結果就是undefined值。

6、用新對象替換prototype屬性會刪除默認構造函數屬性

使用構造函數建立出來的實例,其constructor屬性是從prototype裏繼承來的,但依然用instanceof判斷類型。

var Foo = function Foo() {};
var Bar = function Bar() {};
Foo.prototype = { constructor: Bar };
var foo = new Foo();
console.log(foo.constructor);//輸出function Bar() {} 
console.log(foo.constructor === Bar);//輸出true
console.log(foo instanceof Foo);//輸出true
console.log(foo instanceof Bar);//輸出false

7、建立繼承鏈

設計原型繼承的目的是要在傳統的面向對象編程語言中找到模仿繼承模式的繼承鏈,繼承只是一個對象能夠訪問另一個對象的屬性。

var Person = function Person() {
    //this.foo = 'thisFOO';
};
Person.prototype.foo = 'FOO';

var Teacher = function Teacher() {
};
Teacher.prototype.bar = 'BAR';

Teacher.prototype = new Person();

var teacher = new Teacher();

console.log(teacher.constructor);
//輸出Person() {},由於這裏teacher對象與new Person()對象都沒有constructor,因此這裏的constructor是Person原型的constructor。
console.log(teacher instanceof Teacher);
//輸出true,雖然teacher.constructor爲Person,但teacher爲Teacher的實例。
/**
 * 一、teacher沒有foo、bar屬性,去teacher的原型裏找,這裏teacher的原型是Person的實例;
 * 二、在Person實例裏依然找不到這2個屬性;
 * (若是打開註釋this.foo = 'thisFOO';,則foo屬性至此已找到,輸出thisFOO)
 * 三、由於在teacher的原型裏沒有找到foo、bar屬性,則繼續在Person實例的原型裏找
 * (這裏指的是Person.prototye),這一步找到了foo屬性,輸出FOO;
 * 四、而bar屬性到最後的Object.prototype依然找不到,因此輸出undefined
 */
console.log(teacher.foo, teacher.bar);
相關文章
相關標籤/搜索