摘要:每一個對象直接量都是的子類,即構(gòu)造函數(shù)中的構(gòu)造函數(shù)與普通函數(shù)并沒有什么兩樣,只不過在調(diào)用時,前面加上了關(guān)鍵字,就當(dāng)成是構(gòu)造函數(shù)了。由于沒有傳入變量,在調(diào)用的構(gòu)造函數(shù)時,會出錯這個問題可以通過一個空對象來解決改自。
對于 OO 語言,有一句話叫“Everything is object”,雖然 JavaScript 不是嚴格意義上的面向?qū)ο笳Z言,但如果想要理解 JS 中的繼承,這句話必須時刻銘記于心。
JS 的語法非常靈活,所以有人覺得它簡單,因為怎么寫都是對的;也有人覺得它難,因為很難解釋某些語法的設(shè)計,誰能告訴我為什么 typeof null 是 object 而 typeof undefined 是 undefined 嗎?并且這是在 null == undefined 的前提下。很多我們自認為“懂”了的知識點,細細琢磨起來,還是會發(fā)現(xiàn)有很多盲點,“無畏源于無知”吧……
1. 簡單對象既然是講繼承,自然是從最簡單的對象說起:
var dog = { name: "tom" }
這便是對象直接量了。每一個對象直接量都是 Object 的子類,即
dog instanceof Object; // true2. 構(gòu)造函數(shù)
JS 中的構(gòu)造函數(shù)與普通函數(shù)并沒有什么兩樣,只不過在調(diào)用時,前面加上了 new 關(guān)鍵字,就當(dāng)成是構(gòu)造函數(shù)了。
function Dog(name) { this.name = name; } var dog = new Dog("tom"); dog instanceof Dog; // true
兩個問題,第一,不加 new 關(guān)鍵字有什么后果?
那么 Dog 函數(shù)中的 this 在上下文(Context)中被解釋為全局變量,具體在瀏覽器端的話是 window 對象,在 node 環(huán)境下是一個 global 對象。
第二,dog 的值是什么?很簡單,undefined 。Do>g 函數(shù)沒有返回任何值,執(zhí)行結(jié)束后,dog 的值自然是 undefined 。
關(guān)于 new 的過程,這里也順便介紹一下,這個對后面理解原型(prototype)有很大的幫助:
創(chuàng)建一個空的對象,僅包含 Object 的屬性和方法。
將 prototype 中的屬性和方法創(chuàng)建一份引用,賦給新對象。
將 this 上的屬性和方法新建一份,賦給新對象。
返回 this 對象,忽略 return 語句。
需要明確的是,prototype 上的屬性和方法是實例間共享的,this 上的屬性和方法是每個實例獨有的。
現(xiàn)在為 Dog 函數(shù)加上 prototype,看一個例子:
function Dog(name) { this.name = name; this.bark = function() {}; } Dog.prototype.jump = function() {}; Dog.prototype.species = "Labrador"; Dog.prototype.teeth = ["1", "2", "3", "4"]; var dog1 = new Dog("tom"), dog2 = new Dog("jerry"); dog1.bark !== dog2.bark; // true dog1.jump === dog2.jump; // true dog1.teeth.push("5"); dog2.teeth; // ["1", "2", "3", "4", "5"]
看到有注釋的那三行應(yīng)該可以明白“引用”和“新建”的區(qū)別了。
那么我們經(jīng)常說到的“原型鏈”到底是什么呢?這個術(shù)語出現(xiàn)在繼承當(dāng)中,它用于表示對象實例中的屬性和方法來自于何處(哪個父類)。好吧,這是筆者的解釋。
- Object bark: Dog/this.bark() name: "tom" - __proto__: Object jump: Dog.prototype.jump() species: "Labrador" + teeth: Array[4] + constructor: Dog() + __proto__: Object
上面的是 dog1 的原型鏈,不知道夠不夠直觀地描述“鏈”這個概念。
其中,bark 和 name 是定義在 this 中的,所以最頂層可以看到它倆。
然后,每一個對象都會有一個 proto 屬性(IE 11+),它表示定義在原型上的屬性和方法,所以 jump、species 和 teeth 自然就在這兒了。
最后就一直向上找 proto 中的屬性和方法。
4. 繼承的幾種實現(xiàn) 4.1 通過 call 或者 apply繼承在編程中有兩種說法,一個叫 inherit,另一個是 extend 。前者是嚴格意義上的繼承,即存在父子關(guān)系,而后者僅僅是一個類擴展了另一個類的屬性和方法。那么 call 和 apply 就屬于后者的范疇。怎么說?
function Animal(gender) { this.gender = gender; } function Dog(name, gender) { Animal.call(this, gender); this.name = name; } var dog = new Dog("tom", "male"); dog instanceof Animal; // false
雖然在 dog 對象中有 gender 屬性,但 dog 卻不是 Animal 類型。甚至,這種方式只能“繼承”父類在 this 上定義的屬性和方法,并不能繼承 Animal.prototype 中的屬性和方法。
4.2 通過 prototype 實現(xiàn)繼承要實現(xiàn)繼承,必須包含“原型”的概念。下面是很常用的繼承方式。
function Dog(name) { Animal.call(this); } Dog.prototype = new Animal(); // 先假設(shè) Animal 函數(shù)沒有參數(shù) Dog.prototype.constructor = Dog; var dog = new Dog("tom"); dog instanceof Animal; // true
繼承的結(jié)果有兩個:
獲得父類的屬性和方法;
正確通過 instanceof 的測試。
prototype 也是對象,它是創(chuàng)建實例時的裝配機,這個在前面有提過。new Animal() 的值包含 Animal 實例所有的屬性和方法,既然它賦給了 Dog 的 prototype,那么 Dog 的實例自然就獲得了父類的所有屬性和方法。
并且,通過這個例子可以知道,改變 Dog 的 prototype 屬性可以改變 instanceof 的測試結(jié)果,也就是改變了父類。
然后,為什么要在 Dog 的構(gòu)造函數(shù)中調(diào)用 Animal.call(this)?
因為 Animal 中可能在 this 上定義了方法和函數(shù),如果沒有這句話,那么所有的這一切都會給到 Dog 的 prototype 上,根據(jù)前面的知識我們知道,prototype 中的屬性和方法在實例間是共享的。
我們希望將這些屬性和方法依然保留在實例自身的空間,而不是共享,因此需要重寫一份。
至于為什么要修改 constructor,只能說是為了正確的顯示原型鏈吧,它并不會影響 instanceof 的判斷。或者有其他更深的道理我并不知道……
4.3 利用空對象實現(xiàn)繼承上面的繼承方式已經(jīng)近乎完美了,除了兩點:
Animal 有構(gòu)造參數(shù),并且使用了這些參數(shù)怎么辦?
在 Dog.prototype 中多了一份定義在 Animal 實例中冗余的屬性和方法。
function Animal(name) { name.doSomething(); } function Dog(name) { Animal.call(this, name); } Dog.prototype = new Animal(); // 由于沒有傳入name變量,在調(diào)用Animal的構(gòu)造函數(shù)時,會出錯 Dog.prototype.constructor = Dog;
這個問題可以通過一個空對象來解決(改自 Douglas Crockford)。
function DummyAnimal() {} DummyAnimal.prototype = Animal.prototype; Dog.prototype = new DummyAnimal(); Dog.prototype.constructor = Dog;
他的原始方法是下面的 object:
function object(o) { function F() {} F.prototype = o; return new F(); } Dog.prototype = object(Animal.prototype); Dog.prototype.constructor = Dog;4.4 利用 proto 實現(xiàn)繼承
現(xiàn)在就只剩下一個問題了,如何把冗余屬性和方法去掉?
其實,從第 3 小節(jié)介紹原型的時候就提到了 proto 屬性,instanceof 運算符是通過它來判斷是否屬于某個類型的。
所以我們可以這么繼承:
function Dog() { Animal.call(this); } Dog.prototype = { __proto__: Animal.prototype, constructor: Dog };
如果不考慮兼容性的話,這應(yīng)該是從 OO 的角度來看最貼切的繼承方式了。
4.5 拷貝繼承這個方式也只能稱之為 extend 而不是 inherit,所以也沒必要展開說。
像 Backbone.Model.extend、jQuery.extend 或者 _.extend 都是拷貝繼承,可以稍微看一下它們是怎么實現(xiàn)的。(或者等我自己再好好研究之后過來把這部分補上吧)
5. 個人小結(jié)當(dāng)我們在討論繼承的實現(xiàn)方式時,給我的感覺就像孔乙己在炫耀“茴香豆”的“茴”有幾種寫法一樣。繼承是 JS 中占比很大的一塊內(nèi)容,所以很多庫都有自己的實現(xiàn)方式,它們并沒有使用我認為的“最貼切”的方法,為什么?JS 就是 JS,它生來就設(shè)計得非常靈活,所以我們?yōu)槭裁床焕眠@個特性,而非得將 OO 的做法強加于它呢?
通過繼承,我們更多的是希望獲得父類的屬性和方法,至于是否要保證嚴格的父類/子類關(guān)系,很多時候并不在乎,而拷貝繼承最能體現(xiàn)這一點。對于基于原型的繼承,會在代碼中看到各種用 function 定義的類型,而拷貝繼承更通用,它只是將一個對象的屬性和方法拷貝(擴展)到另一個對象而已,并不關(guān)心原型鏈是什么。
當(dāng)然,在我鼓吹拷貝繼承多么多么好時,基于原型的繼承自然有它不可取代的理由。所以具體問題得具體分析,當(dāng)具體的使用場景沒定下來時,就不存在最好的方法。
個人見解,能幫助大家更加理解繼承一點就最好,如果有什么不對的,請多多指教!
文章來自:http://1ke.co/course/393
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/91511.html
摘要:關(guān)于中的坑大家都踩過。那這里的和是嚴格相等的。這里介紹的是通過創(chuàng)建對象時的。提示一下,數(shù)組對象的函數(shù)本身就是有這個功能的,也就是說可以達到要求。事件有兩種記法,一個是也是類似,那么在中出現(xiàn)的表示觸發(fā)該事件的元素,也就是。 TL;DR: this 指向調(diào)用該方法的對象,只有函數(shù)執(zhí)行時,this 才有定義。 關(guān)于 JavaScript 中 this 的坑大家都踩過。像本文開頭的這句話,道理...
摘要:系列種優(yōu)化頁面加載速度的方法隨筆分類中個最重要的技術(shù)點常用整理網(wǎng)頁性能管理詳解離線緩存簡介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問性能優(yōu)化方案實現(xiàn)的大排序算法一怪對象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁面加載速度的方法 隨筆分類 - HTML5 HTML5中40個最重要的技術(shù)點 常用meta整理 網(wǎng)頁性能管理詳解 HTML5 ...
摘要:系列種優(yōu)化頁面加載速度的方法隨筆分類中個最重要的技術(shù)點常用整理網(wǎng)頁性能管理詳解離線緩存簡介系列編寫高性能有趣的原生數(shù)組函數(shù)數(shù)據(jù)訪問性能優(yōu)化方案實現(xiàn)的大排序算法一怪對象常用方法函數(shù)收集數(shù)組的操作面向?qū)ο蠛驮屠^承中關(guān)鍵詞的優(yōu)雅解釋淺談系列 H5系列 10種優(yōu)化頁面加載速度的方法 隨筆分類 - HTML5 HTML5中40個最重要的技術(shù)點 常用meta整理 網(wǎng)頁性能管理詳解 HTML5 ...
閱讀 2847·2021-09-10 10:51
閱讀 2215·2021-09-02 15:21
閱讀 3206·2019-08-30 15:44
閱讀 869·2019-08-29 18:34
閱讀 1652·2019-08-29 13:15
閱讀 3322·2019-08-26 11:37
閱讀 2697·2019-08-26 10:46
閱讀 1107·2019-08-26 10:26