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
四、Symbol
五、原型
文章首發地址(sau交流學習社區)