javascript系列--認識並理解構造函數,原型和原型鏈

1、前言javascript

介紹構造函數,原型,原型鏈。好比說常常會被問道:symbol是否是構造函數;constructor屬性是否只讀;prototype、[[Prototype]]和__proto__的區別;什麼是原型鏈?等等問題java

 

2、構造函數git

一、什麼構造函數github

構造函數就是經過new關鍵詞生成實例的函數。瀏覽器

js的構造函數和其餘語言不同,通常規範都是首字母大寫。安全

首先咱們來看一下這個栗子:ide

// saucxs function Parent(age) { this.age = age; } var p = new Parent(30); console.log(p); //見下圖 console.log(p.constructor); // ƒ Parent(age){this.age = age;} p.constructor === Parent; // true p.constructor === Object; // false

這就是一個典型的構造函數,構造函數自己也是個函數,與普通區別不大,主要區別就是:構造函數使用new生成實例,直接調用就是普通函數。函數

 

二、constructor屬性性能

返回建立實例對象的Object構造函數的引用。此屬性的值對函數自己的引用,而不是一個包含函數名稱的字符串。學習

全部對象都會從它的原型上繼承一個constructor屬性

var o = {}; o.constructor === Object; // true var o = new Object; o.constructor === Object; // true var a = []; a.constructor === Array; // true var a = new Array; a.constructor === Array // true var n = new Number(3); n.constructor === Number; // true

那麼普通函數建立的實例有沒有constructor屬性呢?

// saucxs // 普通函數 function parent2(age) { this.age = age; } var p2 = parent2(50); console.log(p2); // undefined // 普通函數 function parent3(age) { return { age: age } } var p3 = parent3(50); console.log(p3.constructor); //ƒ Object() { [native code] } p3.constructor === parent3; // false p3.constructor === Object; // true

上面代碼說明:

(1)普通函數在內部有return操做的就有constructor屬性,沒有return的沒有constructor屬性;

(2)有constructor屬性的普通函數的constructor屬性值不是普通函數自己,是Object。

 

三、symbol是構造函數嗎?

MDN 是這樣介紹 `Symbol` 的

The `Symbol()` function returns a value of type **symbol**, has static properties that expose several members of built-in objects, has static methods that expose the global symbol registry, and resembles a built-in object class but is incomplete as a constructor because it does not support the syntax "`new Symbol()`".

Symbol是基本數據類型,做爲構造函數它不完整,由於不支持語法new Symbol(),若是要生成實例直接使用Symbol()就能夠的。

// saucxs new Symbol(123); // Symbol is not a constructor Symbol(123); // Symbol(123)

雖然Symbol是基本數據類型,可是Symbol(1234)實例能夠獲取constructor屬性值。

// saucxs var sym = Symbol(123); console.log( sym ); // Symbol(123) console.log( sym.constructor ); // ƒ Symbol() { [native code] } sym.constructor === Symbol; //true sym.constructor === Object; //false

這裏的constructor屬性來自哪裏?實際上是Symbol原型上的,默認爲Symbol函數。

 

四、constructor的值是隻讀的嗎?

回答:若是是引用類型的constructor屬性值是能夠修改的,若是是基本類型的就是隻讀的。

引用類型的狀況,修改這個很好理解,好比原型鏈繼承的方案中,就是對constructor從新賦值的修正。

// saucxs function Foo() { this.value = 42; } Foo.prototype = { method: function() {} }; function Bar() {} // 設置 Bar 的 prototype 屬性爲 Foo 的實例對象 Bar.prototype = new Foo(); Bar.prototype.foo = 'Hello World'; Bar.prototype.constructor === Object; //true 
// 修正 Bar.prototype.constructor 爲 Bar 自己 Bar.prototype.constructor = Bar; var test = new Bar() // 建立 Bar 的一個新實例 console.log(test);

 

對於基本類型來講是隻讀的,好比:1, "saucxs", true, Symbol, null, undefined。null和undefined也是沒有constructor屬性的。

// saucxs function Type() { }; var types = [1, "muyiy", true, Symbol(123)]; for(var i = 0; i < types.length; i++) { types[i].constructor = Type; types[i] = [ types[i].constructor, types[i] instanceof Type, types[i].toString() ]; }; console.log( types.join("\n") ); // function Number() { [native code] },false,1 // function String() { [native code] },false,muyiy // function Boolean() { [native code] },false,true // function Symbol() { [native code] },false,Symbol(123)

爲何會這樣?由於建立他們的是隻讀的原生構造函數(native constructors),這個栗子說明依賴一個對象的constructor屬性並不安全。

 

3、原型

3.1 prototype屬性

每個對象都擁有一個原型對象,對象以其原型爲模板,從原型集成方法和屬性,這些屬相和方法都在對象的構造器函數的prototype屬性上,而不是對象實例自己上。

上圖發現:

一、Parent對象有一個原型對象Parent.prototype,原型對象上有兩個屬性,分別爲:constructor和__proto__,其中__proto__已被棄用。

二、構造函數Parent有一個指向原型的指針constructor;原型Parent.prototype有一個指向構造函數的指針Parent.prototype.constrcutor,其實就是一個循環引用。

 

3.2 __proto__屬性

上圖中能夠看到Parent原型(Parent.prototype)上有一個__proto__屬性,這是一個訪問器屬性(即getter函數和setter函數)。做用:經過__proto__能夠訪問到對象的內部[[Prototype]](一個對象或者null)

`__proto__` 發音 dunder proto,最早被 Firefox使用,後來在 ES6 被列爲 Javascript 的標準內建屬性。

`[[Prototype]]` 是對象的一個內部屬性,外部代碼沒法直接訪問。

// saucxs function Parent(){}; var p = new Parent(); console.log(p); console.log(Parent.prototype);

一、p.__proto__獲取的是對象的原型,__proto__是每個實例上都有的屬性

二、prototype是構造函數的屬性;

三、p.__proto__和Parent.prototype指向同一個對象。

// saucxs function Parent() {} var p = new Parent(); p.__proto__ === Parent.prototype // true

因此構造函數Parent,Parent.prototype和p之間的關係,以下圖所示:

 

注意1:`__proto__` 屬性在 `ES6` 時才被標準化

以確保 Web 瀏覽器的兼容性,可是不推薦使用,除了標準化的緣由以外還有性能問題。爲了更好的支持,推薦使用 `Object.getPrototypeOf()`。

若是要讀取或修改對象的 `[[Prototype]]` 屬性,建議使用以下方案,可是此時設置對象的 `[[Prototype]]` 依舊是一個緩慢的操做,若是性能是一個問題,就要避免這種操做。

若是要建立一個新對象,同時繼承另外一個對象的 `[[Prototype]]` ,推薦使用 `Object.create()`。

// saucxs function Parent() { age: 50 }; var p = new Parent(); var child = Object.create(p);

這裏 `child` 是一個新的空對象,有一個指向對象 p 的指針 `__proto__`。

 

4、原型鏈

每個對象擁有一個原型對象,經過__proto__指針指向上一個原型,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層層的,最終指向null。這種關係成爲原型鏈(prototype chain),做用:經過原型鏈一個對象會擁有定義在其餘對象中的屬性和方法

// saucxs function Parent(age) { this.age = age; } var p = new Parent(50); p.constructor === Parent; // true

p.constructor指向Parent,那麼是否是意味着p實例化存在constructor屬性呢?並不存在,打印一下p:

有圖能夠知道,實例化對象p自己沒有constructor屬性,是經過原型鏈向上查找__proto__,最終找到constructor屬性,該屬性指向Parent

// saucxs function Parent(age) { this.age = age; } var p = new Parent(50); p; // Parent {age: 50} p.__proto__ === Parent.prototype; // true p.__proto__.__proto__ === Object.prototype; // true p.__proto__.__proto__.__proto__ === null; // true

下圖展現原型鏈運行機制。

 

5、總結

一、Symbol是基本數據類型,做爲構造函數並不完整,由於不支持語法new Symbol(),可是原型上擁有constructor屬性,即Symbol.prototype.constructor。

二、引用類型constructor屬性值是能夠修改的,可是對於基本類型的是隻讀的,固然null和undefined沒有constructor屬性。

三、__proto__是每一個實例上都有的屬性,prototype是構造函數的屬性,這兩個不同,可是p.__proto__和Parent.prototype是指向同一個對象。

四、__proto__屬性在ES6時被標準化,可是由於性能問題並不推薦使用,推薦使用Object.getPropertyOf()。

五、每一個對象擁有一個原型對象,經過__ptoto_指針指向上一個原型,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層已成的,最終指向null,這就是原型鏈。

 

6、參考

一、原型對象

二、Objcet.prototype.constructor

三、Object.prototype.__proto__

四、Symbol

五、原型

 

文章首發地址(sau交流學習社區

相關文章
相關標籤/搜索