大方無隅,大器晚成,大音希聲,大象無形。——《道德經》javascript
首先要了解幾個屬性constructor
、prototype
、[[prototype]]
、__proto__
分別做用是什麼,還要理解幾個概念原型、原型鏈、構造函數。函數
結合代碼先把上面的的屬性和記錄清楚。post
constrcutor
是一種用於建立和初始化class
建立的對象的特殊方法。 構造函數
自己就是一個函數,與普通函數沒有
任何區別,不過爲了規範通常將其首字母
大寫。構造函數
和普通函數
的區別在於,使用 new
生成實例的函數就是構造函數
,直接調用的就是普通函數
。下面示例代碼:性能
function ConstructorFun (name) {
this.name = name;
}
// 經過new關鍵字建立實例
let constructorfun = new ConstructorFun();
複製代碼
其實ConstructorFun
就是一個普通函數,可是在經過new
關鍵字生成實例的時候,就能夠把這個函數叫作構造函數;ui
除了null
、undefined
其餘不管是經過new
生成的實例,仍是經過字面量生成的變量,普通的函數都是有constructor
屬性的。this
代碼以下:spa
function ConstructorFun (name) {
this.name = name;
}
// 經過new關鍵字建立實例
var constructorfun = new ConstructorFun();
constructorfun.constructor === ConstructorFun; // true
var number = 111;
console.log(number.constructor); // ƒ Number() { [native code] }
複製代碼
let a = {}
實際上是 let a = new Object()
的語法糖let a = []
實際上是 let a = new Array()
的語法糖function Foo()
{ … } 實際上是 var Foo = new Function(…)instanceof
判斷一個函數是否爲一個變量的構造函數手動實現一個instanceof
函數以下:
// 模擬實現instanceof
function selfInstanceof (left, right) { //left 表示左表達式,right 表示右表達式
let cur = left.__proto__; // 取的cur的隱式原型
let parent = right.prototype; // 取的right的顯式原型
while(true) {
if (cur === null) { // 若是cur爲null 直接返回false
return false;
}
if (cur === parent) { // 若是cur與parent相同 返回true
return true;
}
cur = cur.__proto__; // 上面兩個條件都不知足,繼續向上一層原型鏈查找
}
}
複製代碼
對於引用類型來講constructor
屬性值是能夠修改的,可是對於基本類型來講是隻讀的。
注意:
null
和undefined
是沒有constructor
屬性的。
官方解釋原型:"JavaScript常被描述爲一種基於原型的語言(prototype-based language)————每一個對象擁有一個原型對象,對象以其原型爲模板、從原型繼承方法和屬性。" 每一個函數都有一個特殊的屬性就叫做原型(prototype)
,請看下面代碼:
function Foo () {}
console.log(Foo.prototype);
複製代碼
效果以下圖所示:
Foo.prototype
上有兩個屬性,一個是
constructor
它指向了函數自己;另外一個是
__proto__
它指向了
Object.prototype
。
構造函數Foo
有一個指向原型的指針,原型Foo.prototype
有一個指向構造函數的指針Foo.prototype.constructor
,用下面的圖來表示更清晰一點:
其實更重要的是任何一個prototype
對象都有一個constructor
屬性,指向這個構造函數。
在上面看到__proto__
這個屬性,每一個實例對象(object)都有一個隱式原型
屬性(稱之爲__proto__
)指向了建立該對象的構造函數的原型
。
function Foo (name) { this.name = name; }
var foo = new Foo();
複製代碼
效果圖以下:
當經過new Foo()
生成的實例對象
foo
,它有一個
__proto__
屬性指向
Foo.prototype
,能夠經過如下代碼驗證:
foo.__proto__ === Foo.prototype; // true
複製代碼
Foo
、Foo.prototype
、Foo.prototype.constructor
、foo.__proto__
三者的關係以下圖所示:
__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);
複製代碼
[[Prototype]]
是對象的一個內部屬性,外部代碼沒法直接訪問。
遵循 ECMAScript 標準,
someObject.[[Prototype]]
符號用於指向 someObject 的原型
每一個對象擁有一個原型對象,經過 __proto__
指針指向上一個原型 ,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null
。這種關係被稱爲原型鏈 (prototype chain)
,經過原型鏈一個對象會擁有定義在其餘對象中的屬性和方法。看一面一張經典的圖可能更直觀:
看一下面的代碼:
function Foo () {}
var foo = new Foo();
foo.__proto__ === Foo.prototype; // true
foo.__proto__.__proto__ === Object.prototype; // true
foo.__proto__.__proto__.__proto__ === null; // true
複製代碼
下面的圖能夠很好的展現上面的代碼prototype
和__proto__
指向問題。
Symbol
是基礎數據類型,它能夠經過Symbol(123)
生成實例,不能經過new Symbol()
生成實例,Symbol
不是構造函數,可是它有constructor
屬性。
let sSymbol = Symbol('symbol');
let errSymbol = new Symbol('symbol'); // Uncaught TypeError: Symbol is not a constructor
Symbol.constructor; // ƒ Symbol() { [native code] }
複製代碼
prototype
屬性,指向函數對象的原型,原型對象上有一個constructor
屬性指向構造函數自己。constructor
屬性值是能夠修改的,可是對於基本類型來講是只讀的,固然 null
和 undefined
沒有 constructor
屬性。__proto__
屬性在 ES6
時被標準化,但由於性能問題並不推薦使用,推薦使用 Object.getPrototypeOf()
。__proto__
是每一個實例上都有的屬性,prototype
是構造函數的屬性,在實例上並不存在,因此這兩個並不同,但 foo.__proto__
和 Foo.prototype
指向同一個對象。__proto__
指針指向上一個原型 ,並從中繼承方法和屬性,同時原型對象也可能擁有原型,這樣一層一層,最終指向 null
,這就是原型鏈。