1.物件的 [[Prototype]]屬性
每個物件都有[[Prototype]]屬性,用於指向物件的原型物件。
當試圖訪問一個物件的屬性時,它不僅僅在該物件上搜尋,還會搜尋該物件的原型,以及該物件的原型的原型,依次層層向上搜尋,直到找到一個名字匹配的屬性或到達原型鏈的末尾。
遵循ECMAScript標準,someObject.[[Prototype]] 符號是用於指向 someObject的原型。從 ECMAScript 6 開始,[[Prototype]] 可以通過Object.getPrototypeOf()和Object.setPrototypeOf()訪問器來訪問。這個等同於 JavaScript 的非標準但許多瀏覽器實現的屬性__proto__ 。
下面有個例子,手動修改函式的[[Prototype]]屬性來構造一個原型鏈。
// http://jsbin.com/wovunob/4/edit?js,console
const a = {x: 1};
const b = {y: 2};
const c = {z: 3};
a.__proto__ = b
b.__proto__ = c
console.log(a.x) //1
console.log(a.y) //2
console.log(a.z) //3
複製程式碼
因此 [[Prototype]]屬性是javascript原型鏈的基礎。
這引出一個問題,可是到底物件的原型又是什麼呢?下面會講到
關於程式碼中直接操作__proto__屬性: 其實官方是建議使用Object.setPrototypeOf 和 Object.getPrototypeOf ,我這裡偷個懶,工作中還是要認真做。
2. 函式的prototype屬性
只有函式才有prototype屬性。
當你建立函式時,JS會為這個函式自動新增prototype屬性,指向一個物件。這個物件含有constructor屬性,你也可以在裡面新增一些方法來實現物件之間的方法函式的共享。
而一旦你把這個函式當作建構函式(constructor)呼叫(即通過new關鍵字呼叫),那麼JS就會幫你建立該建構函式的物件例項。我們前面知道物件都是有[[Prototype]]屬性的,該物件的屬性指向自己的原型物件,我們發現原型物件正是建構函式的prototype指向的物件。正因為如此,新生成的物件才可以繼承建構函式的所有屬性和方法。
從上面的例子中,我們對物件的原型有個直觀的瞭解。
3.原型物件中的constructor屬性
constructor屬性指向的是物件的建構函式。
4.不同生成物件的方法和原型鏈
4.1 語法結構建立的物件
var o = {a: 1};
// o 這個物件繼承了Object.prototype上面的所有屬性
// o 自身沒有名為 hasOwnProperty 的屬性
// hasOwnProperty 是 Object.prototype 的屬性
// 因此 o 繼承了 Object.prototype 的 hasOwnProperty
// Object.prototype 的原型為 null
// 原型鏈如下:
// o ---> Object.prototype ---> null
var a = ["yo", "whadup", "?"];
// 陣列都繼承於 Array.prototype
// (Array.prototype 中包含 indexOf, forEach等方法)
// 原型鏈如下:
// a ---> Array.prototype ---> Object.prototype ---> null
function f(){
return 2;
}
// 函式都繼承於Function.prototype
// (Function.prototype 中包含 call, bind等方法)
// 原型鏈如下:
// f ---> Function.prototype ---> Object.prototype ---> null
複製程式碼
4.2 構造器建立的物件
在 JavaScript 中,構造器其實就是一個普通的函式。當使用 new 操作符 來作用這個函式時,它就可以被稱為構造方法(建構函式)。
function Graph() {
this.vertices = [];
this.edges = [];
}
Graph.prototype = {
addVertex: function(v){
this.vertices.push(v);
}
};
var g = new Graph();
// g是生成的物件,他的自身屬性有'vertices'和'edges'.
// 在g被例項化時,g.[[Prototype]]指向了Graph.prototype.
複製程式碼
4.3 Object.create 建立的物件
ECMAScript 5 中引入了一個新方法:Object.create()。可以呼叫這個方法來建立一個新物件。新物件的原型就是呼叫 create 方法時傳入的第一個引數
這實際就是進行繼承了物件方法
var a = {a: 1};
// a ---> Object.prototype ---> null
var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (繼承而來)
var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null
var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); // undefined, 因為d沒有繼承Object.prototype
複製程式碼
Object.create的內部實現:
if (typeof Object.create !== "function") {
Object.create = function (proto, propertiesObject) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object: ' + proto);
} else if (proto === null) {
throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
}
if (typeof propertiesObject != 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");
function F() {}
F.prototype = proto;
return new F();
};
}
複製程式碼
執行程式碼 jsbin.com/wovunob/7/e…
5.類的繼承
繼承是一種在新物件上覆用現有物件的屬性的形式,這有助於避免重複程式碼和重複資料。 js中的繼承其實就是將建構函式的prototype指向一個要繼承的類的物件。
執行程式碼 jsbin.com/wovunob/8/e…
function Person(){}
Person.prototype.dance = function(){}
function Ninja(){}
Ninja.prototype = new Person()
const n = new Ninja();
console.log(n instanceof Ninja) //true
console.log(n instanceof Person) //true
console.log('dance' in n) //true
console.log(n.constructor === Person) //true
複製程式碼
此時Ninja其實已經繼承了Person,但是要還要修改原型物件的constructor屬性 這裡使用Object.defineProperty來改變屬性。 之所以不直接對Ninja.prototype.constructor進行賦值,是因為這麼做會導致constructor屬性被遍歷到。
執行程式碼 jsbin.com/wovunob/10/…
function Person(){}
Person.prototype.dance = function(){}
function Ninja(){}
Ninja.prototype = new Person()
Object.defineProperty(Ninja.prototype , 'constructor' ,{
enumerable:false,
writable:true,
value:Ninja
})
const n = new Ninja();
console.log(n instanceof Ninja) //true
console.log(n instanceof Person) //true
console.log('dance' in n) //true
console.log(n.constructor === Ninja) //true
console.log(Object.keys(Ninja.prototype)) // []
複製程式碼
6.ES6中的class
ES6中引入了新的關鍵字class,它提供了一種更為優雅的建立物件和實現繼承的方式,底層仍然是原型鏈。
1.使用class建立類
使用方法很簡單
class Person{
constructor(name){
this.name = name
}
dance(){
return true
}
}
const p = new Person('lilei');
console.log(p.name) //"lilei"
console.log(p.dance()) //true
複製程式碼
其實class建立的類的型別還是一個函式,仍然具有prototype屬性,物件的[[prototype]]仍然指向這個原型物件,有區別的是constructor變了class,這個暫時無法理解。
2.使用class進行繼承
class Ninja extends Person 就可以了
不用再考慮原型或者覆蓋屬性的副作用了。
http://jsbin.com/wovunob/11/edit?js,console
class Person{
constructor(name){
this.name = name
}
dance(){
return true
}
}
class Ninja extends Person{
constructor(name,weapon){
super(name)
this.weapon = weapon
}
wieldWeapon(){
return true
}
}
const p = new Person('lilei');
console.log(p.name) //"lilei"
console.log(p.dance()) //true
const n = new Ninja('Yoshi','sword')
console.log(n instanceof Person) //true
console.log(n instanceof Ninja) //true
console.log(n.name) //"Yoshi"
console.log(n.dance()) //true
console.log(n.wieldWeapon()) //true
console.log(n.constructor === Ninja) //true
console.log(n.__proto__ === Ninja.prototype) //true
console.log(n.__proto__.__proto__ === Person.prototype) //true
複製程式碼
7.其他
7.1 instanceof
7.2 hasOwnProperty
7.3 改變原型物件的副作用
參考文章
developer.mozilla.org/zh-CN/docs/Web/JavaScript/Inheritance_and_the_prototype_chain
github.com/creeperyang/blog/issues/9
javascript忍者祕籍