摘要:可能因為先入為主,在編程之中,往往不由自主地以的邏輯編程思路設計模式進行開發(fā)。這是原型模式很重要的一條原則。關(guān)于閉包與內(nèi)存泄露的問題,請移步原型模式閉包與高階函數(shù)應該可以說是設計模式的基礎要領(lǐng)吧。在下一章,再分享一下的幾種常用設計模式。
前
在學習使用Javascript之前,我的程序猿生涯里面僅有接觸的編程語言是C#跟Java——忽略當年在大學補考了N次的C與VB。
從靜態(tài)編程語言,轉(zhuǎn)到動態(tài)語言JS,剛開始的時候,遇到不少困難與錯誤。可能因為先入為主,在JS編程之中,往往不由自主地以C#的邏輯、編程思路、設計模式進行JS開發(fā)。
時間長了,漸漸發(fā)現(xiàn)JS設計模式對于前端開發(fā)的重要性,但在分享JS設計模式之前,希望首先分享幾點個人覺得很重要的JS設計模式基礎。有些地方,可能研究得也不是很深入,有什么錯誤的話,望指正。
動態(tài)類型語言靜態(tài)類型語言,在編譯階段,就需要確定變量的類型。但動態(tài)類型語言,只有程序運行時,根據(jù)當時語言執(zhí)行環(huán)境,變量才會賦予其類型。
所以C#,不能拋開runtime與編譯器多帶帶運行,Java也離不開JVM。同時也有一大堆問題需要考慮的,跨平臺、類型轉(zhuǎn)換、反射、Ioc、接口編程、抽象化、動態(tài)代理等等一大堆概念技術(shù)的產(chǎn)生,還不是因為靜態(tài)語言的強類型特性,需要更多的手段讓其得到靈活多態(tài)的特性。
public class Main { public void Function() { //在編譯的時候,.net runtime必須要知道dog是一只狗 Dog dog = new Dog(); Console.WriteLine(dog.Name); } }
但,JS只需要個文本編輯器,沒有編譯通過不通過的概念,加載了瀏覽器就能跑。
var a = 1; console.log(typeof a);//number a = "a"; console.log(typeof a);//string原型模式
ECMAScript 5之前JS是沒有類的,W3C的大牛是有所發(fā)現(xiàn)的,所以ES6會新的類語法,但是各大瀏覽器(IE)猴年馬月才跟進就不得而知了——絕對不是針對IE。
在靜態(tài)類型語言開發(fā)中,類是任意實體的結(jié)構(gòu),當需要這個實體的時候,系統(tǒng)依照這個結(jié)構(gòu),“畫”出這個==對象==。
譬如,要設計車,首先是要設計稿,設計稿就是類,然后照著設計稿生產(chǎn)的每一臺車就是具體的對象。
所以,靜態(tài)類型語言當中,得到一個對象的最關(guān)鍵就是先有這個對象的設計稿(類),就是知道它的類型。
但是,JS不一樣。
JS是以原型模式作為設計基礎。得到一個對象之前,我們不關(guān)心對象屬于什么類型,它的設計稿是怎么樣的,JS是通過拷貝一個其他對象而獲得對象的。
//定義超人是超人,超人可以飛 var superman = { name: "superman", fly: function () { console.log(this.name + " is flying"); } }; superman.fly();//superman is flying //萬一,想要個妹子呢,搞個女超人如何?女超人也會飛?要不就復制一個超人的對象吧。 //通過Object.create復制一個超人,但定義它是女超人。 var superwomen = Object.create(superman); superwomen.name = "superwomen"; superwomen.fly();//superwomen is flying //DC的不喜歡,來個漫威吧,再搞個鋼鐵俠?鋼鐵俠也會飛 var ironman = Object.create(superman); ironman.name = "ironman"; ironman.fly();//ironman is flying
千萬別誤解了superman就是類,而superwomen、ironman就是superman實例化得到的對象,superman、superwomen、ironman都是對象,superwomen、ironman都是superman對象拷貝的結(jié)果。
1、所有的數(shù)據(jù)都是對象上面的例子,通過var obj={}創(chuàng)建對象,其等價于var obj=new Object()。Object對象可以創(chuàng)建對象,但不僅是Object才是對象,所有的數(shù)據(jù)都是對象,包括Number、Boolean、String、Function、Object。這是原型模式很重要的一條原則。
正是這一點,我們也可以利用function定義對象:function obj(){};。
不過與var obj=new Object()不同,function定義的對象,是一個帶構(gòu)造器的函數(shù),通過new關(guān)鍵字調(diào)用構(gòu)造器可以獲取其原型對象。參考以下例子。
//定義一個對象 var superman = { name: "superman", fly: function () { console.log(this.name + " is flying"); } }; superman.fly();//superman is flying //定義一個函數(shù),同時也是一個帶構(gòu)造器的函數(shù) function superman2() { this.name = "superman"; this.fly = function () { console.log(this.name + " is flying"); }; }; //錯誤:superman2是函數(shù),fly()不是函數(shù) superman2.fly();//error:undefined is not a function //錯誤:superman2()是調(diào)用函數(shù),但函數(shù)沒有返回帶fly()函數(shù)的對象 superman2().fly();//error:Cannot read property "fly" of undefined //正確:new不是C#實例化關(guān)鍵字,new是調(diào)用superman2的構(gòu)造器并返回其對象 var sm = new superman2(); sm.fly();//superman is flying2、對象的屬性請求,會從對象傳遞到對象原型
當需要獲得對象屬性時,會首先請求對象本身,如果,對象本身沒有屬性可以響應,則響應其對象原型prototype;如果prototype也沒有屬性可以響應,則進一步請求原型的原型,從而形成一條原型鏈。當然,原型鏈的層級不能太長,具體長度沒有研究過,我覺得3級也差不多了。
//定義人 function human() { this.sexy = "male"; }; //定義所有英雄的原型 function heroBase() { this.trait = "strong cool"; }; //英雄的原型是人 heroBase.prototype = new human(); //定義超人 function superman() { this.fly = function () { console.log("superman"); }; }; //超人的原型就是英雄 superman.prototype = new heroBase(); console.log(new superman().sexy);//male console.log(new superman().trait);//strong cool
這個步驟就是:
超人的性別是神馬?
光看超人不知道,要看超人原型:英雄
看英雄也不知道,要看英雄原型:人
人的sexy屬性告訴我們,他是男人
好吧,也許你發(fā)現(xiàn)了,上面例子是有bug的,超人是外星人;但你肯定發(fā)現(xiàn)了,這不就是繼承嗎?繼承都有了,多態(tài)還能遠嗎?
3、JS就是通過原型模式實現(xiàn)繼承的,且是單一繼承我個人的理解是,JS的繼承跟C#的繼承雖然實現(xiàn)形式不同,但有一點原則是相同的,單一繼承非多繼承。如上例子換成以下代碼:
superman.prototype = new heroBase();
superman.prototype = new human();
最終結(jié)果是,超人的原型首先定為英雄,但又被人所覆蓋了。符合單一繼承的原則。
之前一直以為搞懂了原型模式,最近看到一篇很好文章 [學習筆記] 小角度看JS原型鏈 夢禪,漲姿勢了。
在 segmentfault 上看到這樣一道題目:
var F = function(){}; Object.prototype.a = function(){}; Function.prototype.b = function(){}; var f = new F();
問:f 能取到a,b嗎?原理是什么?
關(guān)鍵理解:
prototype是對象原型
__proto__是對象構(gòu)造器的原型
關(guān)于兩者區(qū)別,推薦一遍博文,解釋非常詳細,理解js中的原型鏈,prototype與__proto__的關(guān)系
自己列了例子,應該比較容易理解
var F = function () { }; Object.prototype.a = function () { console.log("a"); }; Function.prototype.b = function () { console.log("b"); }; var f = new F(); //注意:對象的__proto__屬性,指向?qū)ο蟮母讣墭?gòu)造器的prototype原型 console.log(f); console.log(f.__proto__); //F.prototype console.log(F.prototype.__proto__); //Ojbect.prototype console.log(Object.prototype.a()); //a console.log(f.__proto__.__proto__.a()); //a /****************************************************************/ console.log(f.constructor); //F console.log(F.__proto__); //Function.prototype console.log(Function.prototype.b()); //b console.log(f.constructor.__proto__.b()); //b //結(jié)論:所有對象的__proto__都指向其構(gòu)造器的prototype //結(jié)論:所有構(gòu)造器/函數(shù)的__proto__都指向Function.prototype,它是一個空函數(shù)(Empty function) //結(jié)論:Function.prototype的__proto__最終指向Object.prototype
所有對象的__proto__都指向其構(gòu)造器的prototype
所有構(gòu)造器/函數(shù)的__proto__都指向Function.prototype,它是一個空函數(shù)(Empty function)
Function.prototype的__proto__最終指向Object.prototype
多態(tài)JS的多態(tài)是通過兩點實現(xiàn)的:1、原型模式實現(xiàn)繼承;2、動態(tài)類型實現(xiàn)泛型
回想一下,我們剛學習C#的那個經(jīng)典例子
public class Hero { public virtual void Fly() { Console.WriteLine("hero fly"); } } public class SuperMan : Hero { public override void Fly() { base.Fly(); Console.WriteLine("SuperMan fly"); } } public class IronMan : Hero { public override void Fly() { base.Fly(); Console.WriteLine("IronMan fly"); } } public class Main { public void MainFunction() { LetsFly(new SuperMan()); LetsFly(new IronMan()); } public void LetsFly(Hero hero) { hero.Fly(); } }
再來看看JS的實現(xiàn)
function superman() { this.fly = function () { console.log("superman fly"); }; } function bird() { this.fly = function () { console.log("bird fly"); }; }; function letsFly(hero) { hero.fly(); } var sm = new superman(); var b = new bird(); letsFly(sm);//superman fly letsFly(b);//bird fly;
LetsFly()方法,C#跟JS接受的同樣都是Hero這個對象
對于C#而言,LetsFly方法要求不管是神馬Hero,只要實現(xiàn)了基類Hero的Fly,它就能執(zhí)行LetsFly方法,以后即使有再多的英雄,繼承Hero即可。
而JS,LetsFly方法要求不管是神馬對象,只要有Fly方法,它就能執(zhí)行LetsFly方法,以后即使有再多的英雄,英雄會Fly即可。
對比之下,優(yōu)缺點是顯而易見的,前者受到束縛,尤其在復雜類型之間繼承與引用提現(xiàn)的更加凸顯,但類型安全性高;
后者無拘無束,但類型安全性低,如上例子,LetsFly傳入的可能不是Hero,很可能是Bird。
閉包與高階函數(shù)關(guān)于閉包“closure”,剛開始接觸的時候,百度了N遍相關(guān)教程還是看得我一頭霧水,
聽說還會有內(nèi)存泄露的問題,好吧,在開發(fā)中干脆就不用吧。直到有一天,偶爾看到高階函數(shù),對于閉包以及整個JS語言的理解可以說產(chǎn)生了翻天覆地的變化。
在這之前,先給大家回顧一下,閉包之所以成為閉包之前,有一個很重要的前提概念:變量的生命周期。
在函數(shù)體內(nèi),var定義的變量,變量屬于函數(shù)內(nèi),函數(shù)外無法訪問;反之如果沒有定義var的變量,像x=12,x屬于全局變量。
全局變量的生命周期是永久的,由頁面加載到關(guān)閉;而內(nèi)部變量,從函數(shù)執(zhí)行到結(jié)束,GC檢測到內(nèi)部變量沒有再被使用則會銷毀它。
var fn = function () { var a = "a"; b = "b"; var fni = function () { c = "c"; }(); }(); //console.log(a);//a is not defined console.log(b);//b console.log(c);//c
關(guān)鍵就在這,如果內(nèi)部變量在函數(shù)執(zhí)行結(jié)束后,仍然有被使用呢?
解決方案就是——高階函數(shù)。
高階函數(shù)名字看起來高端,實際很簡單,滿足以下條件其一就是高階函數(shù):
1、函數(shù)的參數(shù)是函數(shù);
2、函數(shù)的返回值是函數(shù);
function myFunction() { console.log("我是個函數(shù)"); }; //高階函數(shù)1——函數(shù)的參數(shù)是函數(shù) function fnHO1(fn) { fn(); } //高階函數(shù)2——函數(shù)的返回值是函數(shù) function fnHO2() { return myFunction; }; //調(diào)用高階函數(shù)1 fnHO1(myFunction);//我是個函數(shù) //調(diào)用高階函數(shù)2 fnHO2();//神馬都沒有發(fā)生 //調(diào)用高階函數(shù)2,獲得的是它的內(nèi)部函數(shù)體 var f2 = fnHO2(); //再執(zhí)行,才是調(diào)用它的內(nèi)部函數(shù) f2();//我是個函數(shù)
在高階函數(shù)的基礎,我們來看看,史上最經(jīng)典的閉包實例。
function add_outer() { var i = 1; //返回值是內(nèi)部函數(shù),inner調(diào)用了其外部的變量 //所以inner執(zhí)行結(jié)束時,i沒有被銷毀 return function inner() { i++; return i; }; }; //執(zhí)行add_outer獲取的是它的內(nèi)部函數(shù)體inner,但沒有執(zhí)行 var inner = add_outer(); //真正執(zhí)行的時候,變量i沒有被銷毀,形成遞增 console.log(inner());//2 console.log(inner());//3 console.log(inner());//4
我對于閉包的理解很簡單:==函數(shù)返回值是個內(nèi)部函數(shù),內(nèi)部調(diào)用了函數(shù)的內(nèi)部變量==。
兩個條件,缺一不可。如果將以上例子改一下,內(nèi)部有函數(shù),但不是返回值,就不是閉包了。
function add_outer() { var i = 1; //返回值不是函數(shù),形成不了閉包 function inner() { i++; console.log(i); }; inner(); }; //執(zhí)行多次,值也不會變 add_outer();//2 add_outer();//2
也許大家會疑問,既然內(nèi)部變量i在函數(shù)結(jié)束后仍然使用,導致GC無法回收其內(nèi)存,那不就是內(nèi)存泄露嗎?
是的。其實我們反過來想,如果我們不使用閉包的方式實現(xiàn)以上累加的例子,改為使用全局變量存放變量i,全局變量i是否同樣也是不能被回收呢?!
//function add_outer() { var i = 1; function inner() { i++; return i; }; //}; ////執(zhí)行add_outer獲取的是它的內(nèi)部函數(shù)體inner,但沒有執(zhí)行 //var inner = add_outer(); //真正執(zhí)行的時候,變量i沒有被銷毀,形成遞增 console.log(inner());//2 console.log(inner());//3 console.log(inner());//4
因此,閉包與高階函數(shù),只是函數(shù)式編程的編寫方式,即使并不是造成內(nèi)存泄露的原因。關(guān)于閉包與內(nèi)存泄露的問題,請移步 http://www.cnblogs.com/yakun/p/3932026.h...
原型模式、閉包與高階函數(shù)應該可以說是JS設計模式的基礎要領(lǐng)吧。在下一章,再分享一下JS的幾種常用設計模式。
還是像前面所說的,有什么地方說錯,望大家指正。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/85927.html
摘要:設計模式是以面向?qū)ο缶幊虨榛A的,的面向?qū)ο缶幊毯蛡鹘y(tǒng)的的面向?qū)ο缶幊逃行┎顒e,這讓我一開始接觸的時候感到十分痛苦,但是這只能靠自己慢慢積累慢慢思考。想繼續(xù)了解設計模式必須要先搞懂面向?qū)ο缶幊?,否則只會讓你自己更痛苦。 JavaScript 中的構(gòu)造函數(shù) 學習總結(jié)。知識只有分享才有存在的意義。 是時候替換你的 for 循環(huán)大法了~ 《小分享》JavaScript中數(shù)組的那些迭代方法~ ...
摘要:請記住,這些書中的一些可能不是最新的,但概念和基礎仍應適用。是最好的老師之一。的秘密由部分組成。在你完成這些書后,查看書籍和最好的本土書籍。 我看過三本,第1本,第二本,第四本。第一本買的的實體書,其他兩本看的是電子書。第一本是大名鼎鼎老道寫的,書很薄,但是非常經(jīng)典。javascirpt忍者秘籍是jquery的作者寫的,也是非常經(jīng)典。you dont kown js系列也是非常好??戳?..
摘要:是文檔的一種表示結(jié)構(gòu)。這些任務大部分都是基于它。這個實踐的重點是把你在前端練級攻略第部分中學到的一些東西和結(jié)合起來。一旦你進入框架部分,你將更好地理解并使用它們。到目前為止,你一直在使用進行操作。它是在前端系統(tǒng)像今天這樣復雜之前編寫的。 本文是 前端練級攻略 第二部分,第一部分請看下面: 前端練級攻略(第一部分) 在第二部分,我們將重點學習 JavaScript 作為一種獨立的語言,如...
摘要:首先,需要來理清一些基礎的計算機編程概念編程哲學與設計模式計算機編程理念源自于對現(xiàn)實抽象的哲學思考,面向?qū)ο缶幊淌瞧湟环N思維方式,與它并駕齊驅(qū)的是另外兩種思路過程式和函數(shù)式編程。 JavaScript 中的原型機制一直以來都被眾多開發(fā)者(包括本人)低估甚至忽視了,這是因為絕大多數(shù)人沒有想要深刻理解這個機制的內(nèi)涵,以及越來越多的開發(fā)者缺乏計算機編程相關(guān)的基礎知識。對于這樣的開發(fā)者來說 J...
摘要:函數(shù)式編程前端掘金引言面向?qū)ο缶幊桃恢币詠矶际侵械闹鲗Х妒?。函?shù)式編程是一種強調(diào)減少對程序外部狀態(tài)產(chǎn)生改變的方式。 JavaScript 函數(shù)式編程 - 前端 - 掘金引言 面向?qū)ο缶幊桃恢币詠矶际荍avaScript中的主導范式。JavaScript作為一門多范式編程語言,然而,近幾年,函數(shù)式編程越來越多得受到開發(fā)者的青睞。函數(shù)式編程是一種強調(diào)減少對程序外部狀態(tài)產(chǎn)生改變的方式。因此,...
摘要:前端入門的門檻相對較低,學習曲線是越來越陡峭,由淺入深,可以分為四個階段。第二階段高級程序設計有的書是用來成為經(jīng)典的,比如犀牛書還有些書是用來超越經(jīng)典的,顯然這本書就是。接下來可以看看教程,看看源代碼,嘗試著寫一寫這些效果。 前端入門的門檻相對較低,學習曲線是越來越陡峭,由淺入深,可以分為四個階段。 第一階段:《JavaScript DOM編程藝術(shù)》 看這本書之前,請先確認你對J...
閱讀 3233·2021-11-11 16:55
閱讀 2498·2021-10-13 09:39
閱讀 2427·2021-09-13 10:27
閱讀 2164·2019-08-30 15:55
閱讀 3093·2019-08-30 15:54
閱讀 3137·2019-08-29 16:34
閱讀 1829·2019-08-29 12:41
閱讀 1073·2019-08-29 11:33