javascript的繼承與原型鏈
阿新 • • 發佈:2017-11-01
show array this 需要 子類 行為 依次 ann struct 某個對象的一個屬性,外界一般是不可見(在chrome中可以通過
由於js功底一直很差勁,所以覺得自己很多基礎知識都不牢靠,現在花點時間了解下,盡管完成這篇博文我可能還是沒有了如指掌,但是記錄下來至少我還會再三翻閱的,並加深印象或者獲得與現在不一樣的理解。
什麽是繼承?
往往很多代碼你已經完成構造後,又需要構造另一個與之雷同的功能函數;很多類似的舉例比如:交通工具/汽車,Person/man,建築物/學校,等;個人理解成:繼承?子類繼承父類?一個對象復制另一個對象?差不多,這樣容易理解,類與類之間的復制(覆蓋)行為,一個對象擁有另一個對象的屬性和方法。這也解決了如何實現繼承的思路及邏輯。
什麽是原型鏈?
原型?我簡單理解成===> Object.prototype
__proto__
獲取),我們一般把它叫作[[Prototype]]
。原型鏈?obj1.[[Prototype]]
===> obj2.[[Prototype]]
===> obj3.[[Prototype]]
…. ===> Object.prototype。
基於原型鏈的繼承
繼承屬性
js對象是動態的屬性包(有自己的屬性),有一個指向一個原型對象的鏈。當試圖訪問一個對象的屬性時,它不僅僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。如上面。
遵循ECMAScript標準,someObject.[[Prototype]] 符號是用於指向 someObject的原型。從 ECMAScript 6 開始,[[Prototype]] 可以用Object.getPrototypeOf()和Object.setPrototypeOf()訪問器來訪問。這個等同於 JavaScript 的非標準但許多瀏覽器實現的屬性 __proto__。 它不應該與函數(function)的func.prototype屬性相混淆,func.prototype的作用是使用 new func() 創建的對象的實例的 [[Prototype]]。Object.prototype屬性表示Object的原型對象。
舉個栗子(嘗試訪問屬性時會發生什麽?將屬性設置為對象將創建自己的屬性。獲取和設置屬性的唯一限制是內置 getter 或 setter 的屬性。):
// 讓我們假設我們有一個對象 o, 其有自己的屬性 a 和 b: // {a: 1, b: 2} // o 的原型 o.__proto__有屬性 b 和 c: // {b: 3, c: 4} // 最後, o.__proto__.__proto__ 是 null. // 這就是原型鏈的末尾,即 null, // 根據定義,null 沒有__proto__. // 綜上,整個原型鏈如下: // {a:1, b:2} ---> {b:3, c:4} ---> null console.log(o.a); // 1 // a是o的自身屬性嗎?是的,該屬性的值為1 console.log(o.b); // 2 // b是o的自身屬性嗎?是的,該屬性的值為2 // o.__proto__上還有一個‘b‘屬性,但是它不會被訪問到.這種情況稱為"屬性遮蔽 (property shadowing)". console.log(o.c); // 4 // c是o的自身屬性嗎?不是,那看看o.__proto__上有沒有. // c是o.__proto__的自身屬性嗎?是的,該屬性的值為4 console.log(o.d); // undefined // d是o的自身屬性嗎?不是,那看看o.__proto__上有沒有. // d是o.__proto__的自身屬性嗎?不是,那看看o.__proto__.__proto__上有沒有. // o.__proto__.__proto__為null,停止搜索, // 沒有d屬性,返回undefined
繼承方法
在 JavaScript 裏,任何函數都可以添加到對象上作為對象的屬性。函數的繼承與其他的屬性繼承沒有差別,包括上面的“屬性覆蓋”(這種情況相當於其他語言的方法重寫)。
註意的是,當繼承的函數被調用時,this 指向的是當前繼承的對象,而不是繼承的函數所在的原型對象。
var o = { a: 2, m: function(){ return this.a + 1; } }; console.log(o.m()); // 3 // 當調用 o.m 時,‘this‘指向了o. var p = Object.create(o); // p是一個對象, p.__proto__是o. p.a = 4; // 創建 p 的自身屬性a. console.log(p.m()); // 5 // 調用 p.m 時, ‘this‘指向 p. // 又因為 p 繼承 o 的 m 函數 // 此時的‘this.a‘ 即 p.a,即 p 的自身屬性 ‘a‘
使用不同的方法來創建對象和生成原型鏈
下面的方法就簡單舉個例子便於自己理解
普通
var o = {a: 1}; // o這個對象繼承了Object.prototype上面的所有屬性 // 所以可以這樣使用 o.hasOwnProperty(‘a‘). // hasOwnProperty 是Object.prototype的自身屬性。 // Object.prototype的原型為null。 // 原型鏈如下: // o ---> Object.prototype ---> null var a = ["yo", "whadup", "?"]; // 數組都繼承於Array.prototype // (indexOf, forEach等方法都是從它繼承而來). // 原型鏈如下: // a ---> Array.prototype ---> Object.prototype ---> null function f(){ return 2; } // 函數都繼承於Function.prototype // (call, bind等方法都是從它繼承而來): // f ---> Function.prototype ---> Object.prototype ---> null
構造器(即new)
new僅僅是函數的調用一種方式,
用new來調用函數有什麽不同的呢?new其實做了三件事:
- 創建一個新對象
- 將這個新對象的
[[Prototype]]
連接到調用函數的prototype
上 - 綁定調用函數的
this
並調用
這樣能解決很多思路比如先舉個簡單易懂的栗子:
function A(){ } var b = { show:function(){ console.log("這是b的show") } } //怎樣讓對象a和對象b的__proto__相連實現a繼承b? //a的“構造函數”的[[prototype]]鏈接b A.prototype = b; A.prototype.construtor = A; var a = new A();//已經實現繼承了; a.show();
樓下與樓上互不聯系,只是多舉一例而已
function Graph() { this.vertices = []; this.edges = []; } Graph.prototype = { addVertex: function(v){ this.vertices.push(v); } }; var g = new Graph(); // g是生成的對象,他的自身屬性有‘vertices‘和‘edges‘. // 在g被實例化時,g.__proto__指向了Graph.prototype.
Object.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
模擬類繼承
/** * 實現 A 繼承 B */ function B(b) { this.b = b } function A(a, b) { // 調用B並綁定this B.call(this, b) this.a = a } A.prototype = Object.assign({}, B.prototype) A.prototype.constructor = A var c = new A(1, 2) console.log(c.a) // 1 // c 擁有了只有B的實例才擁有的 b 屬性 console.log(c.b) // 2
javascript的繼承與原型鏈