摘要:的繼承方式屬于原型式繼承,非常靈活。當使用關鍵字執行類的構造函數時,系統首先創建一個新對象,這個對象會繼承自構造函數的原型對象新對象的原型就是構造函數的屬性。也就是說,構造函數用來對生成的新對象進行一些處理,使這個新對象具有某些特定的屬性。
繼承這個東西在Javascript中尤其復雜,我掌握得也不好,找工作面試的時候在這個問題上栽過跟頭。Javascript的繼承方式屬于原型式繼承,非常靈活。因此Javascript的繼承方式除了基于類的繼承之外還有基于原型的原型式繼承。
繼承是什么看了這個詞的第一反應我聯想到了財產繼承,在一般情況下,父母的遺產由子女繼承,也就是說子女將會獲得財產的使用權。而Javascript中的繼承與現實生活中的財產繼承還是有區別的。首先,Javascript中的父類和子類可以同時并存,而遺產么,什么樣的財產叫遺產呢?其次,財產是花了就沒了,而JS里的繼承則是子類能夠繼承父類的方法,可以重復使用,減少代碼量。當幾個類都需要一個相似的方法時,使用繼承可以從同一個類中繼承相同的方法,而不用對每個類重復地復制粘貼了。
不用在定義上糾纏過久,下面討論一下實現繼承的一些方法。
繼承的基本方法開頭便說到了JS的繼承方式分為基于類的繼承(簡稱類式繼承)和基于原型鏈的繼承(簡稱原型式繼承)。
類式繼承類式繼承的特點就是使用函數聲明類,通過new關鍵字創建實例。
創建類的方法很簡單,一般可以寫成這樣:
function Blog(address) { this.address = address; } Blog.prototype.getAddress = function() { return this.address; }
創建該類的實例通過new關鍵字即可:
var blog = new Blog("classicemi.github.io"); blog.getAddress(); // "classicemi.github.io"
當使用new關鍵字時,它和通過一般方式執行函數的區別在于函數的執行方式會改變。當使用new關鍵字執行Blog類的構造函數時,系統首先創建一個新對象,這個對象會繼承自構造函數Blog的原型對象(新對象的原型就是構造函數的prototype屬性)。再將this關鍵字綁定到新對象上,再返回該新對象。也就是說,構造函數用來對生成的新對象進行一些處理,使這個新對象具有某些特定的屬性。
創建一個繼承Blog的類就需要手工完成和new運算符相似的工作了。
function MyBlog(address, author) { Blog.call(this, address); this.author = author; }
當通過new關鍵字調用MyBlog構造函數時,系統會創建一個新對象(這步是自動的,this即為這個新對象),然后在this上調用超類Blog的構造函數,再給this添加一些MyBlog類特有的不是繼承自Blog類的屬性,最后將新對象this返回(這步也是自動的)。
下面為了繼承Blog類的方法,需要設置原型鏈,使MyBlog類能夠在原型鏈上找到繼承自Blog類的方法。
MyBlog.prototype = new Blog(); // MyBlog類的原型是Blog類的一個實例 MyBlog.prototype.constructor = MyBlog; // 上一步執行后prototype的constructor屬性會變成Blog,需要修改回來 MyBlog.prototype.getAuthor = function() { // 給MyBlog添加自己的方法 return this.author; }
通過這些操作后,MyBlog類就聲明好了,它繼承了Blog類的屬性和方法,創建MyBlog類實例的方法和創建Blog類實例的方法一樣,直接使用new關鍵字調用構造函數即可。
原型式繼承之前的類式繼承是為了模仿其他一些面向對象語言的特點而創造的,并沒有真正體現Javascript語言本身的特點,下面要說的原型式繼承則是利用JS的原型特性而實現的繼承方式。
使用原型式繼承時,不需要像類式繼承一樣用一個類(構造函數)來定義對象的結構,而可以用對象字面量的方式直接創建一個對象,這個對象是作為原型存在的,被稱作原型對象(prototype object)。就像工廠生產車間里的模具一樣,為以后生產出的零件提供了參考原型。
還是以之前的Blog和MyBlog類為例:
// Blog原型對象 var Blog = { address: "classicemi.github.io", // 屬性只是作為默認值,一般都會被改寫 getAddress: function() { return this.address; } };
這里沒有像用一個構造函數來定義Blog類的結構,將方法添加在Blog.prototype上。這里定義的Blog對象只是作為原型存在,為繼承Blog類的對象提供一些方法。
現在原型對象有了,要創建繼承該原型對象對應的類的新類應該怎么做呢?利用JS的原型鏈特性,只要將新類的原型設為該原型對象即可。按照這種思路可以寫出子類MyBlog的創建方法:
function MyBlogConstrucFunc() {} MyBlogConstrucFunc.prototype = Blog; var MyBlog = new MyBlogConstrucFunc();
通過new運算符調用MyBlogConstrucFunc函數,返回的是一個空對象,這個空對象的prototype屬性指向原型對象Blog。在返回的空對象中,還可以添加MyBlog類自有的屬性和方法。
不過通過這三行代碼實現子類對超類的繼承還是有些冗余,我們可以實現一個方法來實現對超類的繼承,將超類作為該方法的參數傳入并在最后將空對象返回即可。
function clone(object) { function F() {} F.prototype = object; return new F(); }Mixin Class
以上所討論的是比較嚴格的繼承方式,有的時候,我們可能只想對某個函數進行重用,并不需要完全的繼承,那么我們可以將函數以擴充的方式在類之間進行共享。對于重用頻率比較高的方法,我們可以將它們歸并在一個類中,然后用這個類去擴充其他的類。這種方法稱為摻元類(mixin class)。這種處理方法在很多JS庫(比如jQuery和Underscore)中都有用到,是一種擴充工具函數的好方法。
var Mixin = function() {} Mixin.prototype = { serialize: function() { var output = []; for(key in this) { output.push(key + ": " + this[key]); } return output.join(", "); } ... };
為了能方便地將Mixin類中的方法添加到其他類中,我們可以擴展工具函數augment:
function augment(receivingClass, givingClass) { if (arguments[2]) { // 可接受三個參數,第三個參數為需添加的方法,多個方法可用數組將方法名傳入 if (arguments[2] instanceOf Array) { for (var i = 0, len = arguments[2].length; i < len; i++) { if (!receivingClass.prototype[arguments[2][i]]) { receivingClass.prototype[arguments[2][i]] = givingClass.prototype[arguments[2][i]]; } } } else if (typeof arguments[2] === "string") { if (!receivingClass.prototype[arguments[2]]) { receivingClass.prototype[arguments[2]] = givingClass.prototype[arguments[2]]; } } } else { for (methodName in givingClass.prototype) { if (!receivingClass.prototype[methodName]) { receivingClass.prototype[methodName] = givingClass.prototype[methodName]; } } } }
這時我們如果要給其他類添加Mixin中的方法的話可以直接這樣寫:
augment(MyBlog, Mixin);類式繼承和原型式繼承的對比
類式繼承存在的意義很大一部分是為了滿足對Javascript的特性還不熟悉的程序員,畢竟這種方法是強行為了模仿其他面向對象語言的特性而創造的。Javascript的原型式特征在父類和子類之間建立了一種雙向的聯系,這是JS區別于其他語言的特點。
原型式繼承發揮了JS的特性,所有的子類繼承的方法會通過原型鏈逐級向父類查找,因此用于繼承的方法在內存中只會保存一份,這樣可以節約內存。只有在對子類的某個方法進行直接設置,將繼承而來的方法覆蓋的時候才會對新方法多帶帶生成副本。
一個經過封裝的類,它的公用方法和特權方法可以被繼承下來,因為它們是添加在原型鏈上的,在作為構造函數的時候可以繼承給子類。而私用方法相當于作為了閉包中的變量,與原型鏈無關,因此不會被繼承。
父類中的特權方法可以訪問父類中的私用屬性,而特權方法會被子類繼承,因此子類也可以通過繼承的特權方法間接訪問父類的私用屬性。但子類中新添加的特權方法不能訪問父類中的私用屬性,因為缺少了到達父類內部的原型鏈“通道”。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/87521.html
摘要:語言精粹讀書筆記第四章函數函數字面量函數字面量包含個部分第一部分,保留字第二部分,函數名,它可以被忽略。這個超級延遲綁定使得函數對高度復用。構造器調用模式一個函數,如果創建的目的就是希望結合的前綴來調用,那它就被稱為構造器構造。 《JavaScript 語言精粹》 讀書筆記 第四章 函數 Functions 函數字面量 函數字面量包含4個部分: 第一部分, 保留字 function...
摘要:繼承的是超類型中構造函數中的屬性,如上繼承了屬性,但沒有繼承原型中的方法。上述造成的結果是子類型實例中有兩組超類型的構造函數中定義的屬性,一組在子類型的實例中,一組在子類型實例的原型中。 ECMAScript只支持實現繼承,主要依靠原型鏈來實現。與實現繼承對應的是接口繼承,由于script中函數沒有簽名,所以無法實現接口繼承。 一、原型鏈 基本思想:利用原型讓一個引用類型繼承另一個引用...
摘要:使用構造器有個嚴重的危害,如果在調用構造器函數的時候忘記使用前綴,不僅不會綁定到新對象,還會污染全局變量原型模式原型模式中,我們采用對象來繼承。 構造器調用模式 當一個函數對象被創建時,Function構造器會運行類似這樣的代碼: this.prototype = {constructor: this} new一個函數事會發生: Function.method(new, functio...
摘要:最近閱讀了編寫可維護的,在這里記錄一下讀書筆記。禁止使用,,,的字符串形式。避免使用級事件處理函數。讓事件處理程序成為接觸到對象的唯一函數。檢測函數是檢測檢測函數的最佳選擇。為特定瀏覽器的特性進行測試,并僅當特性存在時即可應用特性檢測。 最近閱讀了《編寫可維護的 JavaScript》,在這里記錄一下讀書筆記。書中主要基于三個方向來講解怎么增加代碼的可維護性:編程風格、編程實踐、自動化...
摘要:對象字面量定義一個空對象這里的空指的是其自身屬性為空,對象繼承了的屬性和方法添加屬性方法完全刪除屬性方法自定義構造函數用操作符調用構造函數時,函數內部會發發生以下情況創建一個新對象,并且引用了該對象并繼承了該函數的原型屬性和方法被加入到的引 對象字面量 //定義一個空對象,這里的空指的是其自身屬性為空,dog對象繼承了Object.prototype的屬性和方法 var dog={} ...
閱讀 1991·2023-04-26 01:41
閱讀 2468·2021-11-24 09:39
閱讀 1922·2021-11-24 09:38
閱讀 1947·2021-11-19 09:40
閱讀 3760·2021-11-11 11:02
閱讀 3294·2021-10-20 13:48
閱讀 3157·2021-10-14 09:43
閱讀 4360·2021-09-02 15:11