JavaScript繼承的特性與實(shí)踐應(yīng)用深入詳解
本文詳細(xì)講述了JavaScript繼承的特性與實(shí)踐應(yīng)用。分享給大家供大家參考,具體如下:
繼承是代碼重用的模式。JavaScript 可以模擬基于類的模式,還支持其它更具表現(xiàn)力的模式。但保持簡(jiǎn)單通常是最好的策略。
JavaScript 是基于原型的語言,也就是說它可以直接繼承其他對(duì)象。
1 偽類
JavaScript 的原型不是直接讓對(duì)象從其他對(duì)象繼承,而是插入一個(gè)多余的間接層:通過構(gòu)造函數(shù)來產(chǎn)生對(duì)象。
當(dāng)一個(gè)函數(shù)被創(chuàng)建時(shí),F(xiàn)unction 構(gòu)造器產(chǎn)生的函數(shù)對(duì)象會(huì)運(yùn)行這樣類似的代碼:
this.prototype = {constructor : this};
新的函數(shù)對(duì)象新增了一個(gè) prototype 屬性,它是一個(gè)包含了 constructor 屬性且屬性值為該新函數(shù)的對(duì)象。
當(dāng)采用構(gòu)造器調(diào)用模式,即用 new 去調(diào)用一個(gè)函數(shù)時(shí),它會(huì)這樣執(zhí)行:
Function.method('new', function (){ var that = Object.create(this.prototype);//創(chuàng)建一個(gè)繼承了構(gòu)造器函數(shù)的原型對(duì)象的新對(duì)象 var other = this.apply(that, arguments);//調(diào)用構(gòu)造器函數(shù),綁定 this 到新對(duì)象 return (typeof other === 'object' && other) || that;//如果構(gòu)造器函數(shù)的返回值不是對(duì)象,就直接返回這個(gè)新對(duì)象 });
我們可以定義一個(gè)構(gòu)造器,然后擴(kuò)充它的原型:
//定義構(gòu)造器并擴(kuò)充原型 var Mammal = function (name) { this.name = name; }; Mammal.prototype.get_name = function () { return this.name; }; Mammal.prototype.says = function () { return this.saying || ''; };
然后構(gòu)造實(shí)例:
var myMammal = new Mammal('Herb the mammal'); console.log(myMammal.get_name());//Herb the mammal
構(gòu)造另一個(gè)偽類來繼承 Mammal(定義構(gòu)造器函數(shù)并替換它的 prototype):
var Cat = function (name) { this.name = name; this.saying = 'meow'; }; Cat.prototype = new Mammal();
擴(kuò)充原型:
Cat.prototype.purr = function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if (s) { s += '-'; } s += 'r'; } return s; }; Cat.prototype.get_name = function () { return this.says() + ' ' + this.name + ' ' + this.says(); }; var myCat = new Cat('Henrietta'); console.log(myCat.says());//meow console.log(myCat.purr(5));//r-r-r-r-r console.log(myCat.get_name());//meow Henrietta meow
我們使用 method 方法定義了 inherits 方法,來隱藏上面這些丑陋的細(xì)節(jié):
/** * 為 Function.prototype 新增 method 方法 * @param name 方法名稱 * @param func 函數(shù) * @returns {Function} */ Function.prototype.method = function (name, func) { if (!this.prototype[name])//沒有該方法時(shí),才添加 this.prototype[name] = func; return this; }; Function.method('inherits', function (Parent) { this.prototype = new Parent(); return this; });
這兩個(gè)方法都返回 this,這樣我們就可以以級(jí)聯(lián)的方式編程啦O(∩_∩)O~
var Cat = function (name) { this.name = name; this.saying = 'meow'; }.inherits(Mammal).method('purr', function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if (s) { s += '-'; } s += 'r'; } return s; }).method('get_name', function () { return this.says() + ' ' + this.name + ' ' + this.says(); }); var myCat = new Cat('Henrietta'); console.log(myCat.says());//meow console.log(myCat.purr(5));//r-r-r-r-r console.log(myCat.get_name());//meow Henrietta meow
雖然我們有了行為很像“類”的構(gòu)造器函數(shù),但沒有私有環(huán)境,所有的屬性都是公開的,而且不能訪問父類的方法。
如果在調(diào)用構(gòu)造函數(shù)時(shí)忘記加上 new 前綴,那么 this 就不會(huì)被綁定到新對(duì)象上,而是被綁定到了全局變量!?。∵@樣我們不但沒有擴(kuò)充新對(duì)象,還破壞了全局變量環(huán)境。
這是一個(gè)嚴(yán)重的語言設(shè)計(jì)錯(cuò)誤!為了降低出現(xiàn)這個(gè)問題的概率,所有的構(gòu)造器函數(shù)都約定以首字母大寫的形式來命名。這樣當(dāng)我們看到首字母大寫的形式的函數(shù),就知道它是構(gòu)造器函數(shù)啦O(∩_∩)O~
當(dāng)然,更好的策略是根本不使用構(gòu)造器函數(shù)。
2 對(duì)象說明符
有時(shí)候,構(gòu)造器需要接受一大堆參數(shù),這很麻煩。所以在編寫構(gòu)造器時(shí),讓它接受一個(gè)簡(jiǎn)單的對(duì)象說明符會(huì)更好:
var myObject = maker({ first: f, middle: m, last: l });
現(xiàn)在這些參數(shù)可以按照任意的順序排列咯,而且構(gòu)造器還能夠聰明地為那些沒有傳入的參數(shù)使用默認(rèn)值,代碼也變得更易閱讀啦O(∩_∩)O~
3 原型
基于原型的繼承指的是,一個(gè)新對(duì)象可以繼承一個(gè)舊對(duì)象的屬性。首先構(gòu)造出一個(gè)有用的對(duì)象,然后就可以構(gòu)造出更多與那個(gè)對(duì)象類似的對(duì)象。
/** * 原型 */ var myMammal = { name: 'Herb the mammal', get_name: function () { return this.name; }, says: function () { return this.saying || ''; } }; //創(chuàng)建新實(shí)例 var myCat = Object.create(myMammal); myCat.name = 'Henrietta'; myCat.saying = 'meow'; myCat.purr = function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if (s) { s += '-'; } s += 'r'; } return s; }; myCat.get_name = function () { return this.says() + ' ' + this.name + ' ' + this.says(); }; console.log(myCat.says());//meow console.log(myCat.purr(5));//r-r-r-r-r console.log(myCat.get_name());//meow Henrietta meow
這里用到了 create 方法來創(chuàng)建新的實(shí)例:
Object.create = function (o) { var F = function () { }; F.prototype = o; return new F(); }
4 函數(shù)化
目前為止看到的繼承模式的問題是:無法保護(hù)隱私,對(duì)象的所有屬性都是可見的。有一些無知的程序員會(huì)使用偽裝私有的模式,即給一個(gè)需要私有的屬性起一個(gè)古怪的名字,并希望其他使用代碼的程序員假裝看不到它們!
其實(shí)有更好的方法:應(yīng)用模塊模式。
我們先構(gòu)造一個(gè)生成對(duì)象的函數(shù),它有這些步驟:
①. 創(chuàng)建新對(duì)象。這有四種方式:
【1】構(gòu)造一個(gè)對(duì)象字面量。
【2】調(diào)用一個(gè)構(gòu)造器函數(shù)。
【3】構(gòu)造一個(gè)已存在對(duì)象的新實(shí)例。
【4】調(diào)用任意一個(gè)會(huì)返回對(duì)象的函數(shù)。
②. 定義私有實(shí)例變量與方法。
③. 為這個(gè)新對(duì)象擴(kuò)充方法,這些方法擁有特權(quán)去訪問這些參數(shù)。
④. 返回這個(gè)新對(duì)象。
函數(shù)化構(gòu)造器的偽代碼如下:
var constructor = function (spec, my){ var that, 其他私有變量; my = my || {}; //把共享的變量和函數(shù)添加到 my 中 that = 一個(gè)新對(duì)象 //添加給 that 的特權(quán)方法 return that; };
spec 對(duì)象包含了需要構(gòu)造一個(gè)新實(shí)例的所有信息,它可以被用到到私有變量或者其他函數(shù)中。
my 對(duì)象為在一個(gè)繼承鏈中的構(gòu)造器提供了共享的容器,如果沒有傳入,那么會(huì)創(chuàng)建一個(gè) my 對(duì)象。
創(chuàng)建特權(quán)方法的方式是:把函數(shù)定義為私有方法,然后再把它們分配給 that:
var methodical = function (){ ... }; that.methodical = methodical;
這樣分兩步定義的好處是:私有的 methodical 不受這個(gè)實(shí)例被改變的影響。
現(xiàn)在,我們把這個(gè)模式應(yīng)用到 mammal 示例中:
var mammal = function (spec) { var that = {}; that.get_name = function () { return spec.name; }; that.says = function () { return spec.saying || ''; }; return that; }; var myMammal = mammal({name: 'Herb'}); console.log(myMammal.get_name());//Herb var cat = function (spec) { spec.saying = spec.saying || 'meow'; var that = mammal(spec); that.purr = function (n) { var i, s = ''; for (i = 0; i < n; i += 1) { if (s) { s += '-'; } s += 'r'; } return s; }; that.get_name = function () { return that.says() + ' ' + spec.name + ' ' + that.says(); }; return that; }; var myCat = cat({name: 'Henrietta'}); console.log(myCat.says());//meow console.log(myCat.purr(5));//r-r-r-r-r console.log(myCat.get_name());//meow Henrietta meow
函數(shù)化模式還能調(diào)用父類的方法。這里我們構(gòu)造一個(gè) superior 方法,它會(huì)返回調(diào)用某個(gè)方法名的函數(shù):
//返回調(diào)用某個(gè)方法名的函數(shù) Object.method('superior', function (name) { var that = this, method = that[name]; return function () { return method.apply(that, arguments); }; });
現(xiàn)在創(chuàng)建一個(gè) coolcat,它擁有一個(gè)可以調(diào)用父類方法的 get_name:
var coolcat = function (spec) { var that = cat(spec), super_get_name = that.superior('get_name'); that.get_name = function (n) { return 'like ' + super_get_name() + ' baby'; }; return that; }; var myCoolCat = coolcat({name: 'Bix'}); console.log(myCoolCat.get_name());//like meow Bix meow baby
函數(shù)化模式有很大的靈活性,而且可以更好地實(shí)現(xiàn)封裝、信息隱藏以及訪問父類方法的能力。
如果對(duì)象所有的狀態(tài)都是私有的,那么就稱為防偽對(duì)象。這個(gè)對(duì)象的屬性可以被替換或刪除,但這個(gè)對(duì)象的狀態(tài)不受影響。如果用函數(shù)化模式來創(chuàng)建對(duì)象,并且這個(gè)對(duì)象的所有方法都不使用 this 或 that,那么這個(gè)對(duì)象就是持久性的,它不會(huì)被入侵。除非存在特權(quán)方法,否則不能訪問這個(gè)持久性對(duì)象的內(nèi)部狀態(tài)。
5 事件處理函數(shù)
可以構(gòu)造一個(gè)能夠給任何對(duì)象添加簡(jiǎn)單事件處理特性的函數(shù)。這里,我們給這個(gè)對(duì)象添加一個(gè) on 方法,fire 方法和私有的事件注冊(cè)對(duì)象:
var eventuality = function (that) { var registry = {}; /** * 觸發(fā)事件 * * 使用 'on' 方法注冊(cè)的事件處理程序?qū)⒈徽{(diào)用 * @param 可以是包含事件名稱的字符串,或者是一個(gè)擁有 type 屬性(值為事件名稱)的對(duì)象。 */ that.fire = function (event) { var array, func, handler, i, type = typeof event === 'string' ? event : event.type; //如果這個(gè)事件已被注冊(cè),則遍歷并依序執(zhí)行 if (registry.hasOwnProperty(type)) { array = registry[type]; for (i = 0; i < array.length; i += 1) { handler = array[i];//處理程序包含一個(gè)方法和一組可選的參數(shù) func = handler.method; if (typeof func === 'string') {//如果方法是字符串形式的名稱,則尋找它 func = this[func]; } //調(diào)用它。如果處理程序包含參數(shù),則傳遞過去,否則就傳遞事件對(duì)象 func.apply(this, handler.parameters || [event]); } } return this; }; /** * 注冊(cè)一個(gè)事件 * @param type * @param method * @param parameters */ that.on = function (type, method, parameters) { var handler = { method: method, parameters: parameters }; if (registry.hasOwnProperty(type)) {//如果已存在,就新增數(shù)組項(xiàng) registry[type].push(handler); } else {//新增 registry[type] = [handler]; } return this; }; return that; };
可以在任何單獨(dú)對(duì)象上調(diào)用 eventuality,授予它事件處理方法。也可以在 that 被返回前,在構(gòu)造函數(shù)中調(diào)用它:
eventuality(that);
JavaScript 弱類型的特性在此是一個(gè)巨大的優(yōu)勢(shì),因?yàn)槲覀儫o須處理對(duì)象繼承關(guān)系中的類型O(∩_∩)O~
感興趣的朋友還可以使用本站在線HTML/CSS/JavaScript代碼運(yùn)行工具:http://tools.jb51.net/code/HtmlJsRun測(cè)試上述代碼運(yùn)行結(jié)果。
更多關(guān)于JavaScript相關(guān)內(nèi)容還可查看本站專題:《javascript面向?qū)ο笕腴T教程》、《JavaScript錯(cuò)誤與調(diào)試技巧總結(jié)》、《JavaScript數(shù)據(jù)結(jié)構(gòu)與算法技巧總結(jié)》、《JavaScript遍歷算法與技巧總結(jié)》及《JavaScript數(shù)學(xué)運(yùn)算用法總結(jié)》
希望本文所述對(duì)大家JavaScript程序設(shè)計(jì)有所幫助。
- JavaScript寄生組合式繼承原理與用法分析
- JavaScript常見繼承模式實(shí)例小結(jié)
- JavaScript類的繼承操作實(shí)例總結(jié)
- JavaScript使用prototype原型實(shí)現(xiàn)的封裝繼承多態(tài)示例
- JavaScript原型鏈與繼承操作實(shí)例總結(jié)
- JavaScript實(shí)現(xiàn)多態(tài)和繼承的封裝操作示例
- JavaScript面向?qū)ο罄^承原理與實(shí)現(xiàn)方法分析
- JS實(shí)現(xiàn)面向?qū)ο罄^承的5種方式分析
- JavaScript類的繼承方法小結(jié)【組合繼承分析】
- 15分鐘深入了解JS繼承分類、原理與用法
相關(guān)文章
Javascript實(shí)現(xiàn)秒表計(jì)時(shí)游戲
這篇文章主要為大家詳細(xì)介紹了Javascript實(shí)現(xiàn)秒表計(jì)時(shí)游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-05-05JavaScript學(xué)習(xí)筆記之?dāng)?shù)組隨機(jī)排序
這篇文章主要介紹了JavaScript學(xué)習(xí)筆記之?dāng)?shù)組隨機(jī)排序的相關(guān)資料,需要的朋友可以參考下2016-03-03自己使用js/jquery寫的一個(gè)定制對(duì)話框控件
自己做一個(gè)通用的控件,雖然不是絕對(duì)通用啦,但在我這個(gè)項(xiàng)目里還是可以隨意調(diào)用的,思想的話也可以借鑒到別的項(xiàng)目中2014-05-05JavaScript new對(duì)象的四個(gè)過程實(shí)例淺析
這篇文章主要介紹了JavaScript new對(duì)象的四個(gè)過程,結(jié)合實(shí)例形式簡(jiǎn)單分析了javascript面向?qū)ο蟪绦蛟O(shè)計(jì)中new對(duì)象的四個(gè)過程相關(guān)原理與實(shí)現(xiàn)方法,需要的朋友可以參考下2018-07-07Next.js使用getServerSideProps進(jìn)行服務(wù)器端渲染demo
這篇文章主要為大家介紹了Next.js使用getServerSideProps進(jìn)行服務(wù)器端渲染demo,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12