更新:謝謝你們的支持,最近折騰了一個博客官網出來,方便你們系統閱讀,後續會有更多內容和更多優化,猛戳這裏查看前端
------ 如下是正文 ------webpack
前端進階系列已經到第 5 期啦,本期正式開始原型 Prototype
系列。git
本篇文章重點介紹構造函數、原型和原型鏈相關知識,若是你還不知道 Symbol
是否是構造函數、constructor
屬性是否只讀、prototype
、[[Prototype]]
和 __proto__
的區別、什麼是原型鏈,建議你好好閱讀本文,但願對你有所幫助。github
下圖是本文的思惟導圖,高清思惟導圖和更多文章請看個人 Github。web
constructor
返回建立實例對象時構造函數的引用。此屬性的值是對函數自己的引用,而不是一個包含函數名稱的字符串。面試
// 木易楊
function Parent(age) {
this.age = age;
}
var p = new Parent(50);
p.constructor === Parent; // true
p.constructor === Object; // false
複製代碼
構造函數自己就是一個函數,與普通函數沒有任何區別,不過爲了規範通常將其首字母大寫。構造函數和普通函數的區別在於,使用 new
生成實例的函數就是構造函數,直接調用的就是普通函數。算法
那是否是意味着普通函數建立的實例沒有 constructor
屬性呢?不必定。跨域
// 木易楊
// 普通函數
function parent2(age) {
this.age = age;
}
var p2 = parent2(50);
// undefined
// 普通函數
function parent3(age) {
return {
age: age
}
}
var p3 = parent3(50);
p3.constructor === Object; // true
複製代碼
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()
,Chrome 認爲其不是構造函數,若是要生成實例直接使用 Symbol()
便可。(來自 MDN)
// 木易楊
new Symbol(123); // Symbol is not a constructor
Symbol(123); // Symbol(123)
複製代碼
雖然是基本數據類型,但 Symbol(123)
實例能夠獲取 constructor
屬性值。
// 木易楊
var sym = Symbol(123);
console.log( sym );
// Symbol(123)
console.log( sym.constructor );
// ƒ Symbol() { [native code] }
複製代碼
這裏的 constructor
屬性來自哪裏?實際上是 Symbol
原型上的,即 Symbol.prototype.constructor
返回建立實例原型的函數, 默認爲 Symbol
函數。
這個得分狀況,對於引用類型來講 constructor
屬性值是能夠修改的,可是對於基本類型來講是隻讀的。
引用類型狀況其值可修改這個很好理解,好比原型鏈繼承方案中,就須要對 constructor
從新賦值進行修正。
// 木易楊
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);
複製代碼
對於基本類型來講是隻讀的,好比 一、「muyiy」、true、Symbol
,固然 null
和 undefined
是沒有 constructor
屬性的。
// 木易楊
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
屬性並不安全。
說到這裏就要聊聊 new
的實現了,實現代碼以下。
// 木易楊
function create() {
// 一、建立一個空的對象
var obj = new Object(),
// 二、得到構造函數,同時刪除 arguments 中第一個參數
Con = [].shift.call(arguments);
// 三、連接到原型,obj 能夠訪問構造函數原型中的屬性
Object.setPrototypeOf(obj, Con.prototype);
// 四、綁定 this 實現繼承,obj 能夠訪問到構造函數中的屬性
var ret = Con.apply(obj, arguments);
// 五、優先返回構造函數返回的對象
return ret instanceof Object ? ret : obj;
};
複製代碼
以前寫過一篇文章解析 new
的模擬實現過程,若是你對實現過程還不瞭解的話點擊閱讀。「【進階3-5期】深度解析 new 原理及模擬實現」
prototype
JavaScript
是一種基於原型的語言 (prototype-based language),這個和 Java
等基於類的語言不同。
每一個對象擁有一個原型對象,對象以其原型爲模板,從原型繼承方法和屬性,這些屬性和方法定義在對象的構造器函數的 prototype
屬性上,而非對象實例自己。
從上面這張圖能夠發現,Parent
對象有一個原型對象 Parent.prototype
,其上有兩個屬性,分別是 constructor
和 __proto__
,其中 __proto__
已被棄用。
構造函數 Parent
有一個指向原型的指針,原型 Parent.prototype
有一個指向構造函數的指針 Parent.prototype.constructor
,如上圖所示,其實就是一個循環引用。
__proto__
上圖能夠看到 Parent 原型( Parent.prototype
)上有 __proto__
屬性,這是一個訪問器屬性(即 getter 函數和 setter 函數),經過它能夠訪問到對象的內部 [[Prototype]]
(一個對象或 null
)。
__proto__
發音 dunder proto,最早被 Firefox使用,後來在 ES6 被列爲 Javascript 的標準內建屬性。
[[Prototype]]
是對象的一個內部屬性,外部代碼沒法直接訪問。
遵循 ECMAScript 標準,someObject.[[Prototype]] 符號用於指向 someObject 的原型。
這裏用 p.__proto__
獲取對象的原型,__proto__
是每一個實例上都有的屬性,prototype
是構造函數的屬性,這兩個並不同,但 p.__proto__
和 Parent.prototype
指向同一個對象。
// 木易楊
function Parent() {}
var p = new Parent();
p.__proto__ === Parent.prototype
// true
複製代碼
因此構造函數 Parent
、Parent.prototype
和 p
的關係以下圖。
__proto__
屬性在 ES6
時才被標準化,以確保 Web 瀏覽器的兼容性,可是不推薦使用,除了標準化的緣由以外還有性能問題。爲了更好的支持,推薦使用 Object.getPrototypeOf()
。
經過改變一個對象的
[[Prototype]]
屬性來改變和繼承屬性會對性能形成很是嚴重的影響,而且性能消耗的時間也不是簡單的花費在obj.__proto__ = ...
語句上, 它還會影響到全部繼承自該[[Prototype]]
的對象,若是你關心性能,你就不該該修改一個對象的[[Prototype]]
。
若是要讀取或修改對象的 [[Prototype]]
屬性,建議使用以下方案,可是此時設置對象的 [[Prototype]]
依舊是一個緩慢的操做,若是性能是一個問題,就要避免這種操做。
// 木易楊
// 獲取
Object.getPrototypeOf()
Reflect.getPrototypeOf()
// 修改
Object.setPrototypeOf()
Reflect.setPrototypeOf()
複製代碼
若是要建立一個新對象,同時繼承另外一個對象的 [[Prototype]]
,推薦使用 Object.create()
。
// 木易楊
function Parent() {
age: 50
};
var p = new Parent();
var child = Object.create(p);
複製代碼
這裏 child
是一個新的空對象,有一個指向對象 p 的指針 __proto__
。
正如上面介紹的不建議使用 __proto__
,因此咱們使用 Object.create()
來模擬實現,優化後的代碼以下。
// 木易楊
function create() {
// 一、得到構造函數,同時刪除 arguments 中第一個參數
Con = [].shift.call(arguments);
// 二、建立一個空的對象並連接到原型,obj 能夠訪問構造函數原型中的屬性
var obj = Object.create(Con.prototype);
// 三、綁定 this 實現繼承,obj 能夠訪問到構造函數中的屬性
var ret = Con.apply(obj, arguments);
// 四、優先返回構造函數返回的對象
return ret instanceof Object ? ret : obj;
};
複製代碼
每一個對象擁有一個原型對象,經過 __proto__
指針指向上一個原型 ,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null
。這種關係被稱爲原型鏈 (prototype chain),經過原型鏈一個對象會擁有定義在其餘對象中的屬性和方法。
咱們看下面一個例子
// 木易楊
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
。
// 木易楊
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
複製代碼
下圖展現了原型鏈的運做機制。
Symbol
做爲構造函數來講並不完整,由於不支持語法 new Symbol()
,但其原型上擁有 constructor
屬性,即 Symbol.prototype.constructor
。constructor
屬性值是能夠修改的,可是對於基本類型來講是隻讀的,固然 null
和 undefined
沒有 constructor
屬性。__proto__
是每一個實例上都有的屬性,prototype
是構造函數的屬性,這兩個並不同,但 p.__proto__
和 Parent.prototype
指向同一個對象。__proto__
屬性在 ES6
時被標準化,但由於性能問題並不推薦使用,推薦使用 Object.getPrototypeOf()
。__proto__
指針指向上一個原型 ,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null
,這就是原型鏈。進階系列文章彙總以下,內有優質前端資料,以爲不錯點個star。
我是木易楊,網易高級前端工程師,跟着我每週重點攻克一個前端面試重難點。接下來讓我帶你走進高級前端的世界,在進階的路上,共勉!