"大哥,割草機借我用一下,我修整一下草坪。" ---- 談談this與JavaScript函數調用的不解之緣

来源:http://www.cnblogs.com/alai88/archive/2016/05/25/5525615.html
-Advertisement-
Play Games

在寫上一篇有關apply和call的博文時(閑聊JS中的apply和call),起初我還是擔心大家理解起來比較困難,因為要理解apply調用方式的前提是,至少先理解在JavaScript中函數是什麼?this到底代表什麼意思?等等。不過從大家的反饋來看,我的擔心是多餘的,諸位園友都是高手,理解這些基 ...


在寫上一篇有關apply和call的博文時(閑聊JS中的apply和call),起初我還是擔心大家理解起來比較困難,因為要理解apply調用方式的前提是,至少先理解在JavaScript中函數是什麼?this到底代表什麼意思?等等。不過從大家的反饋來看,我的擔心是多餘的,諸位園友都是高手,理解這些基礎的東東是小菜一碟。
話雖這樣講,不過今天我還是和大家聊聊JavaScript中與this相關的各種函數調用方式,可以把知識補充完整,日後回顧起來也比較方便。

【背景介紹】光明小區是一個別墅小區,家家戶戶門前屋後都有一塊小草坪。小區的物業公司為了提高業主的滿意度,為小區的業主提高了修整草坪的服務,並且還把割草機需要用的汽油列入每月的預算,確保隨時都能提供優質的服務。

提供割草服務的割草機

接下來我們就結合具體的業務場景,來講解JavaScript中的函數調用方式。

1. 方法調用

針對光明小區為業主提供修整草坪服務的情況,我們可以用如下代碼描述:

var GuangMing = {
    oilVolume:5000,
    cutGrass:function( grass_num ){
        var oilConsumption = grass_num * 20 ;    //假設每平米草坪需要耗油 20 ml
        console.log( '正在割 ' + grass_num + ' 平米的草坪,需要用油:' +  oilConsumption + ' mL。' );
        console.log( '原來有油:' + this.oilVolume + ' mL' );
        this.oilVolume = this.oilVolume - oilConsumption ;
        console.log( '用完之後,剩餘的油量為:' + this.oilVolume + ' mL' );
        return ;
    }
};
GuangMing.cutGrass( 15 );       //小區門口有15平米的草坪需要割
console.log( '>>光明小區中剩餘的油:' + GuangMing.oilVolume );

在瀏覽器的控制台運行之後,輸出結果為:

正在割 15 平米的草坪,需要用油:300 mL。
原來有油:5000 mL
用完之後,剩餘的油量為:4700 mL
>>光明小區中剩餘的油:4700

這就是所謂的方法調用,與我們預期中的一樣(特別是之前搞過Java等靜態語言的高手),毫無違和感。
"oilVolume 和 cutGrass 都是GuangMing這個對象的成員,在cutGrass這個方法中調用的this.oilVolume當然是指的是和它同屬於一個對象(GuangMing)的oilVolume 。"
不過,我們上面的這種理解從本質上來講,是錯誤的。
cutGrass函數體中引用的this.oilVolume指代了GuangMing.oilVolume這個變數,不是因為在GuangMing對象的'聲明'時,cutGrass和oilVolume都是GuangMing這個對象的成員,而是因為,我們是通過GuangMing.cutGrass( 15 ); 的方式來使用這個cutGrass這個函數的
也就是說:函數體內的this指向誰,不是看聲明時,而是看運行時。
完整的結論我們隨著後面的講解逐步來完善。

2. 函數調用

光明小區的這項服務推出之後,得到了廣大業主的好評。小區所屬的紅旗街道辦也知道了這事,街道辦的同志覺得這項服務是提升單位形象的好舉措,於是也宣稱可以提供割草的服務。不過,紅旗街道辦沒有去自己購買割草機設備,而是當有人要求服務的時候,直接通知光明小區的物業公司幫忙提供一下就可以了。

用代碼表示如下:

 1 var oilVolume = 8000 ;
 2 
 3 var GuangMing = {
 4     oilVolume:5000,
 5     cutGrass:function( grass_num ){
 6         var oilConsumption = grass_num * 20 ;    //假設每平米草坪需要耗油 20 ml
 7         console.log( '正在割 ' + grass_num + ' 平米的草坪,需要用油:' +  oilConsumption + ' mL。' );
 8         console.log( '原來有油:' + this.oilVolume + ' mL' );
 9         this.oilVolume = this.oilVolume - oilConsumption ;
10         console.log( '用完之後,剩餘的油量為:' + this.oilVolume + ' mL' );
11         return ;
12     }
13 };
14 
15 var hongQiCutgrass = GuangMing.cutGrass ;    //光明小區所在的紅旗街道也說給大家提供割草服務
16                                              //但是既然光明小區已經有割草機,所以街道辦就沒有單獨購買割草機,
17 
18 console.log( '>>光明小區中原來的油:' + GuangMing.oilVolume );                                             //而是直接使用光明小區的割草機。
19 hongQiCutgrass( 20 );
20 console.log( '>>光明小區中剩餘的油:' + GuangMing.oilVolume );

運行後的輸出結果如下:

>>光明小區中原來的油:5000
正在割 20 平米的草坪,需要用油:400 mL。
原來有油:8000 mL
用完之後,剩餘的油量為:7600 mL
>>光明小區中剩餘的油:5000

關鍵代碼解析如下:

第1行:為瞭解釋函數調用方式中,函數體內的this會指向全局對象(瀏覽器中就是window),增加一個全局變數oilVolume。

第15行:聲明瞭一個全局變數hongQiCutgrass指向GuangMing對象的成員函數cutGrass。

第19行:運行全局函數hongQiCutgrass。

從最後輸出的結果來看:cutGrass()中的this已經不是指代GuangMing對象了,而是指向'全局對象'。
我們再回顧一下前面得出的結論:
"函數體內的this指向誰,不是看聲明時,而是看運行時。"
而剛纔的場景中,運行時的代碼是:hongQiCutgrass( 20 );變數hongQiCutgrass指向一個函數,變數hongQiCutgrass沒有隸屬於任何的其他對象,所以,變數hongQiCutgrass是一個全局變數,隸屬於全局對象。
這也就意味著,運行hongQiCutgrass( 20 );時,
1. cutGrass函數體中的this是指向'全局對象',
2. this.oilVolume 就指代另外一個全局變數oilVolume。運行前的值是8000,運行之後變成7600。

講到這裡,有些同學可能會問,hongQiCutgrass不是指向了GuangMing.cutGrass了嗎?這可能需要理一下JavaScript中'變數'和'變數所指的對象'的關係。

(具體JavaScript引擎如何實現,我無從考證,下圖是我對在JavaScript中'變數'和'變數所指的對象'之間的關係的理解,不對之處,歡迎各位大俠指出。)

我們知道,在JavaScript中:

1. 對象傳遞,都是引用傳遞(包括:賦值、參數傳遞等)。

2. 函數也是對象。

代碼執行到第13行時,狀態如(a)所示,對象GuangMing的成員cutGrass指向具體的函數對象F008。

當代碼執行到第15行時,因為賦值的過程其實傳遞的也是引用,所以,GuangMing.cutGrass 和 hongQiCutgrass都指向了F008這個函數對象。

當代碼執行到第19行時,因為hongQiCutgrass不屬於某個具體的對象,所以,函數體內的this就指代全局對象,這就是所謂的'函數調用方式'。

如果把全局變數hongQiCutgrass理解為'全局對象'的成員,把全局變數oilVolume也理解為'全局對象'的成員,那麼,其實'方法調用方式'和'函數調用方式'的原理是一致的。

某一天,旁邊的東方小區見光明小區提供的'割草服務'很好,所以,它也向業主宣稱可以提供割草服務。但是,東方小區也沒有自己去購買一臺割草機,而是配了一把'光明小區'的割草機的鑰匙。如果用代碼表示如下:

//...前面的代碼不變,增加dongfang小區的聲明部分
var DongFang = {
    oilVolume:6000,
    cutGrass:GuangMing.cutGrass
};
DongFang.cutGrass( 6 );

運行後的輸出結果如下:

正在割 6 平米的草坪,需要用油:120 mL。
原來有油:6000 mL
用完之後,剩餘的油量為:5880 mL

這樣做,業務功能是達到了,但是存在的缺點也很明顯,一方面比較繁瑣,另一方面與常識不太相符,容易導致混亂,不好理解。
相當於小明家有兩個小孩,小紅家也有兩個小孩。

>>開始時小明的媽媽跟小明交代說,你去買一個冰淇淋給你弟弟吃。(相當於聲明時的樣子)
>>後來,小明的媽媽遠遠地看到有人在喂冰淇淋。心裡想著:'應該是小明在給他弟弟小亮喂冰淇淋吧。'
    而實際上呢,是小明在給小紅的弟弟(小藍)喂冰淇淋。(相當於運行時的樣子)
>>這和'小明媽媽'的預期是不一樣的!!
就是說,設計 DongFang 這個對象的設計師的這種搞法,GuangMing這個對象的設計師是預期不到的!某天GuangMing.cutGrass裡面的功能變化了,DongFang.cutGrass的行為也會跟著變化,而這種關係,在代碼中表現得並不明顯。

所以,如果把某個'方法f'是'聲明'在一個'對象A'里的,那麼,這個對象的設計者當然是希望這個方法中的this,就是指向'對象A',其他的對象如果要使用這個'方法f'也可以,但是,不能偷偷摸摸的'引用',而是應該明確指出:'對象B借用了對象A的方法f'。'對象B'借用'對象A'的方法f,沒錯,這就是我們接下來馬上要介紹的apply調用。
(看來JavaScript的設計者也是用心良苦哈,為了可能會'亂用'對象方法的熊孩子操碎了心^_^~~)

有人也許會問,JavaScript對this的處理方式,確實是不好理解哈,除了容易引起邏輯混亂之外,難道有什麼好處嗎?好處其實也是有的,我們這裡先留一個懸念,等在講解構造器調用時再細講。

3. apply調用

關於apply調用模式的細節,前一篇博文"兄臺息怒,關於arguments,您的想法和大神是一樣一樣的----閑聊JS中的apply和call" 已經講得比較詳細,這裡就順著今天我們的'割草機'的例子,展現一下相關的代碼,一起回顧一下。

var GuangMing = {
    oilVolume:5000,
    cutGrass:function( grass_num ){
        var oilConsumption = grass_num * 20 ;    //假設每平米草坪需要耗油 20 ml
        console.log( '正在割 ' + grass_num + ' 平米的草坪,需要用油:' +  oilConsumption + ' mL。' );
        console.log( '原來有油:' + this.oilVolume + ' mL' );
        this.oilVolume = this.oilVolume - oilConsumption ;
        console.log( '用完之後,剩餘的油量為:' + this.oilVolume + ' mL' );
        return ;
    }
};

var DongFang = {
    oilVolume:6000
};
//光明小區是全國文明小區,你要借用就光明正大的借用,偷偷配把鑰匙放在自己的辦公室就不應該了嘛
//正確的'借用'姿勢
//因為我們只想割6平米的草坪,相當於只有一個'參數列表',而不是一個參數數組,所以用call,而不是apply 
GuangMing.cutGrass.call( DongFang , 6 ); 

運行後的輸出結果如下:

正在割 6 平米的草坪,需要用油:120 mL。
原來有油:6000 mL
用完之後,剩餘的油量為:5880 mL

4. 構造器調用

這種方式也許是應用最廣泛的調用方式,特別是當我們剛剛學慣用JavaScript寫面向對象的應用時。儘管用這種方式存在沒有私密性等諸多問題,大神Douglas Crockford也極力反對用這種方式創建對象,但是,因為這種方式"太方便"了,與傳統的靜態語言(Java等)的對象建模方式比較像,所以,似乎非常好'理解'。
閑話休說,我們還是回到我們的場景。據說光明小區物業配備'割草機'為廣大業主割草的事獲得了紅旗街道辦的好評,於是紅旗街道辦就給出了規定,轄區內每個小區的物業都應該配備這樣的設備,提供割草服務,並指定相關的規章制度。詳細的規章制度由小明來制定。於是,小明開始挑燈夜戰,寫下瞭如下的代碼:

var Area = function( area_name ){
    this.name = area_name ;
    this.oilVolume = 5000 ;        //每個小區預設配備5000mL的油用於割草服務
}
Area.prototype.cutGrass = function( grass_num ){
        console.log( '**'+ this.name + '小區**正在提供割草服務:' );
        var oilConsumption = grass_num * 20 ;    //假設每平米草坪需要耗油 20 ml
        console.log( '正在割 ' + grass_num + ' 平米的草坪,需要用油:' +  oilConsumption + ' mL。' );
        console.log( '原來有油:' + this.oilVolume + ' mL' );
        this.oilVolume = this.oilVolume - oilConsumption ;
        console.log( '用完之後,剩餘的油量為:' + this.oilVolume + ' mL' );
        return ;
}

//創建一個東方小區
var dongfang = new Area( '東方' );
dongfang.cutGrass( 2 );
//創建一個勞動小區
var laodong = new Area( '勞動' );
laodong.cutGrass( 10 );

運行後的輸出結果如下:

**東方小區**正在提供割草服務:
正在割 2 平米的草坪,需要用油:40 mL。
原來有油:5000 mL
用完之後,剩餘的油量為:4960 mL
**勞動小區**正在提供割草服務:
正在割 10 平米的草坪,需要用油:200 mL。
原來有油:5000 mL
用完之後,剩餘的油量為:4800 mL

在這裡,我們做瞭如下的動作:
1. 構建構造器函數Area,以便可以用來創建不同的小區。
2. 為構造器函數Area的原型對象(prototype)增加割草服務的方法:cutGrass。因為我們知道,即使是不同的小區,提供割草服務的作業流程是一樣的,所以把方法定義到原型對象中。
3. 使用new運算符,依次創建了'東方小區'(dongfang)和'勞動小區'(laodong),並分別調用了它們的割草服務(cutGrass)。
發現運行的結果和我們預期的一樣。

現在,我們就來看一下new運算符(構造器調用)的原理,以var dongfang = new Area( '東方' ); 為例說明:

1. 執行new運算時,JavaScript引擎會生成一個全新的對象,這個對象的原型鏈,會'鏈向'構造函數的原型對象(prototype)。例子中的構造函數就是Area。
   註意:是一個全新的對象哦,就是JavaScript引擎會單獨開闢一塊空間給新建的對象。
   ('變數'和'變數所引用的對象'的關係,我們前面有介紹,這裡就不再贅述。)
2. 在將新建的'全新對象'返回之前,JavaScript引擎會執行'構造函數'(Area)函數體內的代碼,
   這時候,函數體內的this就是指代這個'新建的全新的對象'。 (註意:這就是構造器調用與其他調用方式的不同所在。)
   顯然,如果沒有這個new運算符而直接調用構造器函數,那麼,依據前面我們分析的規則,函數體中的this將會指向'全局對象',

   而這種情況發生時,JavaScript不會報任何的錯誤!這也是許多大神為什麼對'構造器調用'方式創建對象比較詬病的原因。
3. 構造器函數執行到最後,
    如果用return語句返回一個不是對象類型的值,或者沒有執行return語句,則返回前面創建的全新的對象(這種情況是符合我們預期的)。
    如果用return語句返回一個是'對象類型'的值,那麼,返回的就是指定的這個'對象',而不一定是之前創建的全新的對象。
    (後面這種情況是我們需要避免的,試想,辛辛苦苦創建了一個對象,JavaScript引擎為其開闢了空間,但是被沒有返回,沒有變數引用它,意味著在瞎搞。)

關於構造器調用方式,整個過程就是這樣。

我們再回去分析一下在講解'函數調用'時留下的懸念,當時經過我們的分析,JavaScript的this動態綁定特性(函數體內的this指向誰,不是看聲明時,而是看運行時。)似乎除了把問題搞複雜,沒有帶來什麼好處。現在,我們來剖析一下例子中的dongfang.cutGrass( 2 );這個語句的來龍去脈,就能體會到JavaScript中的這個特性還是有它的意義的。
我們知道:

1. dongfang這個對象是前面通過new運算符創建的全新對象(約定:'變數'和'變數所指的對象'在不影響語義理解的情況下,我們就不作區別)。
2. 我們並沒有為dongfang這個對象增加cutGrass的成員屬性。

那麼,為什麼我們能夠這樣執行dongfang.cutGrass( 2 );呢?
答案是:JavaScript的原型繼承機制。

當JavaScript引擎發現dongfang並沒有cutGrass的成員屬性時,它就會順著'原型鏈'去找,而根據前面的new運算符的特點,dongfang這個全新的對象所'鏈向'的'原型對象正是Area.prototype這個對象,在Area.prototype對象中發現有cutGrass這個函數,於是就調用了Area.prototype中的cutGrass這個函數。
(註意:我們的行文中用'鏈向'這個詞,以便於區別'指向'或'引用'的含義,關於原型鏈的前世今生,我們有空再聊。)
整個過程的來龍去脈已經有點眉目了。
我們再綜合後面的語句laodong.cutGrass( 10 ),從執行結果來看,laodong 和 dongfang 是兩個獨立的對象,它們都調用了Area.prototype中定義的cutGrass。
正是因為(函數體內的this指向誰,不是看聲明時,而是看運行時。)這個特性,才確保了:
- 執行 dongfang.cutGrass( 2 ); 時,cutGrass()中的this指向 dongfang 這個對象。
- 執行 laodong.cutGrass( 10 ); 時,cutGrass()中的this指向 laodong 這個對象。
如果JavaScript不是採用這種'動態'的機制,cutGrass中的this就只能指向Area.prototype,那麼,'函數'這種重要的數據類型,在'原型繼承'的這種機制中就很難發揮強大的作用。

用閉包實現業務建模

4種調用模式已經講解完畢,我們舉了一個'小區提供割草服務'這樣的例子,顯然,前面的例子主要是為了講解特性的方便而設計的,從'業務建模'的角度來看,是存在很多不足的。
如果真的要求創建一個小區這樣的對象,然後對外提供割草服務該如何搞呢?請看代碼:

var createArea = function( area_name ){
    var _name = area_name ;
    var _oilVolume = 5000 ;    //預設還是配備5000mL的油
    
    var cutGrass = function( grass_num ){
        console.log( '**'+ _name + '小區**正在提供割草服務:' );
        var oilConsumption = grass_num * 20 ;    //假設每平米草坪需要耗油 20 ml
        console.log( '正在割 ' + grass_num + ' 平米的草坪,需要用油:' +  oilConsumption + ' mL。' );
        console.log( '原來有油:' + _oilVolume + ' mL' );
        _oilVolume = _oilVolume - oilConsumption ;
        console.log( '用完之後,剩餘的油量為:' + _oilVolume + ' mL' );
        return ;
    }
    var new_area = {};
    new_area.cutGrass = cutGrass ;
    new_area.setOilVolume = function( oil_volume ){
        _oilVolume = oil_volume ;
        console.log( _name + '小區中割草機用的汽油配備到:' + oil_volume + 'mL' );
        return _oilVolume;
    }
    new_area.getOilVolume = function( ){
        return _oilVolume;
    }
    return new_area ;
};

var dongfang = createArea( '東方' );
dongfang.cutGrass( 2 );
console.log( '還剩餘可用的油:' + dongfang.getOilVolume() );
dongfang.setOilVolume( 9000 );
dongfang.cutGrass( 7 );

運行後的輸出結果如下:

**東方小區**正在提供割草服務:
正在割 2 平米的草坪,需要用油:40 mL。
原來有油:5000 mL
用完之後,剩餘的油量為:4960 mL
還剩餘可用的油:4960
東方小區中割草機用的汽油配備到:9000mL
**東方小區**正在提供割草服務:
正在割 7 平米的草坪,需要用油:140 mL。
原來有油:9000 mL
用完之後,剩餘的油量為:8860 mL

代碼中展示的業務模型,基於如下的業務理解:
1. 小區的名稱,僅僅在創建小區的時候通過參數傳入設置一下,之後就不允許修改了。
    就像在現實生活中,小區的門口會搬一塊大石頭放到那裡,上面刻上"愛情灣畔"幾個大字,整好之後就不會去改了。
2. 小區對外提供割草服務(cutGrass)。
    可以看看小區現在還有多少割草機的儲備油(getOilVolume),以便判斷是否夠這次割草作業。
    也可以給小區添加儲備的油(setOilVolume),例如:物業公司規定,到了月底,儲備的油要增加到8000mL,於是可以執行dongfang.setOilVolume( 8000 );。

這是一個採用'閉包'的方式構建的對象,我們發現,構建出來的dongfang對象,似乎也能滿足我們的業務要求,創建好對象之後,不能改變小區的名稱,只能通過setOilVolume和getOilVolume操作變數_oilVolume。更重要的特點時,居然'沒有用到this'!
把這個例子補充在這裡作為對比,我們不難發現:"在應用開發的過程中,一切應該以業務目標為導向,語言的各種特性只是一種手段"。
因為例子中的小區對象是一個非常具體的'業務對象',所以可以不使用this。如果你是在寫框架性質的對象,那你一定要理解this,一定要理解apply調用方式。"
閉包是一個"簡單但很有內涵"的特性,前面我們聊過'閉包'與'原型繼承'的使用,後續如果有時間,我們可以聊聊閉包的其他故事。

【總結】

JavaScript中函數的多種調用方式,是它作為動態語言,具有強大表現力的基礎,此外,不對函數的參數類型進行校驗、可以把函數作為對象到處傳遞,也是JavaScript的動態語言特性的體現。我們可以與其他靜態語言作一個對比。

1. 類型檢查

靜態語言中,對傳入的參數會做類型檢查。而動態語言中,則不會對傳入的參數類型以及個數做檢查。
在Java社區,一天熊孩子小東去跟社區的王叔叔請求幫助。
小東:"張叔叔,今天我和小傑想去'衛星農場'割草,你可不可以幫我?"
老張:"好啊,你在這裡登記一下,這是割草機的鑰匙,讓王叔叔跟你們一起去。"
小東找到老王,讓他幫忙去'衛星農場'去割草,老王問了一下情況,跟小東說:"'衛星農場'哪裡有草坪啊?那時麥地!"。

同樣的場景,在JavaScript社區就會發生不一樣的情況,
小東:"張叔叔,今天我和小傑想去'衛星農場'割草,你可不可以幫我?"
老張:"好啊,這是割草機的鑰匙,你拿去用吧。"
到了'衛星農場',小東就啟動"割草機",不一會兒功夫,就把一塊麥地里的小麥放倒了。
回來之後的日記是這麼寫的:"今天,天氣不錯,萬里無雲的天空飄著朵朵白雲......這真是有意義的一天啊!"
【分析】
在靜態語言(例如:Java)中,在編譯階段就會對方法的參數類型以及參數數量做檢查,發現不對勁就提示'編譯錯誤',相當於每個方法邊上都站著一個'老王',負責對方法的類型和數量進行檢查。
在動態語言(例如:JavaScript)中,則不會對方法做這方面的檢查,如果把方法比作'割草機',那麼對於JavaScript引擎來說,它才不管你割的是'小草'還是'小麥',不管你往'割草機'的油箱中加入的是'汽油'還是'醬油',等運行的時候,發現錯誤才給你指出這裡出錯了。
雖然JavaScript在函數這個層面沒有提供類型檢查的服務,作為補充,它也提供了typeof, instanceof等檢查類型的方式,所以,我們經常看到一些函數的函數體的開始部分,是一堆的if-else,對傳入的參數進行檢查。
想起了高考考場的門衛保全:'有沒有帶准考證?沒帶的出去。有沒有帶手機?帶了的也出去。脫一下....'
沒辦法,畢竟'考生'不是'保全','保全'也不是'考生'。
"小李,這是杜老闆的兒子。"
"..."

2. 方法調用

在靜態語言(Java)中,當我們需要使用某個對象(類)的方法的時候,我們至少是要通過這個對象(類)來調用的,或者繼承這個類,然後在子類中使用父類的方法,或者將這個類的對象作為當前對象的一個成員,然後通過這個成員調用。相當於,要使用社區的'割草機',至少是應該打聲招呼登記一下的。
但是在動態語言(JavaScript)中,函數在記憶體中似乎是獨立存在的(我們知道:在JavaScript中,函數也是對象),儘管聲明時好像這個函數是'屬於'對象A的,但只有真正使用的時候(運行時)才表現出來它是'屬於'誰的,函數體內的this是指代誰的。
並且,引用某個函數並沒有太多的限制,就像你有割草機的鑰匙,你就可以啟動這台割草機,使用這台割草機。

很顯然,JavaScript的某些特性(原型鏈、apply調用方式、高階函數屬性、閉包等等)使JavaScript變得非常靈活,當然也容易出錯。這跟現實生活一樣,太多的條條框框,你會覺得不自由,行為受到束縛,但是,沒有了一些規則約束,沒有了父母的嘮叨和提醒,有時也確實會犯錯誤。
所以,在使用JavaScript開發應用時,一方面我們通過編程規範(共同的約定)來減少錯誤的發生。比如:在社區中,大家約定從'業主之家'借的梯子,用完之後要還回去。另一方面,我們也可以利用JavaScript中的某些特性(例如:閉包),來構建封裝性、穩定性更好的具有彈性的應用。比如:我們擔心熊孩子在使用社區的割草機時,可能會把'醬油'當做'汽油'倒到割草機的油箱中,這時候我們可以把割草機的油箱鎖起來,不讓隨便往裡面加東西。
顯然熊孩子是玩不好JavaScript的,在更多的時候,我們要使用JavaScript,是因為我們要開發的應用只能用它來開發,既然沒得其他的選擇,那就只有'好好學習'一條路了。正如某位前輩所言,只有我們理解了它的精華,也明白它的糟粕,才能真正用好它。
好像有人在敲門,應該是網上預定的宵夜送到了.....
好,今天就和大伙聊到這裡,感謝諸位捧場。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 說明:信息系統實踐手記系列是系筆者在平時研發中先後遇到的大小的問題,也許朴實和細微,但往往卻是經常遇到的問題。筆者對其中比較典型的加以收集,描述,歸納和分享。 摘要:此文描述了筆者接觸過的部分信息系統或平臺之間的對接構型和情況,掛一漏萬的總結分享之。 正文 系列隨筆目錄:信息系統實踐手記 (http ...
  • 本文介紹了工廠方法模式的概念,優缺點,實現方式,UML類圖,並介紹了工廠方法(未)遵循的OOP原則 原創文章。同步自 "作者個人博客" "http://www.jasongj.com/design_pattern/factory_method/" 工廠方法模式解決的問題 上文《 "簡單工廠模式不簡單 ...
  • 概述 代碼是從命名開始的,我們給類、方法、變數和參數命名,我們也給解決方案、工程、目錄命名。在編碼時,除了應該遵守編程語言本身的命名規範外,我們應該提供好的命名。好的命名意味著良好的可讀性,讀你代碼的人無需太多的註釋,就能通過名稱知道它是什麼,它能做什麼事兒,以及它應該怎麼用。 我們命名、命名,不斷 ...
  • 填寫完表單數據之後,很多用戶喜歡直接按回車提交,感覺速度比較快,省去了拿滑鼠找“提交”按鈕再單擊的時間。 今天我們就來實現一下: 原理: 1,獲取“Enter”按鍵的code: 相容不同的瀏覽器的寫法如下: 2,監聽鍵盤onkeyup事件: 3,判斷code鍵碼是否為13(代表enter的鍵碼) 詳 ...
  • 上篇提到過關於貼圖的兩個限制: 1、跨域安全限制 跟AJAX之類差不多,但是沒有測試根目錄下具有安全配置文件(一些xml)的情況。當然,不出意外,本地瀏覽(file協議)調用相對路徑圖片也是不可以的。所以,連測試只能在一個web平臺下進行。 2、圖片格式問題 MDN上有個提示: https://de ...
  • 本系列文章實際上就是官網文檔的翻譯加上自己實踐過程中的理解。 伴隨著websites演化至web apps的過程,有三個現象是很明顯的: 頁面中有越來越多的Js。 客戶端能做的事情越來越多。 越來越少的頁面重載(當然也伴隨著更複雜的代碼)。 這些現象導致了什麼?大量的前端代碼。 龐大的代碼庫需要被高 ...
  • 1、jQuery對象轉換成DOM對象 a.var $cr = $("#cr"); //jQuery對象var cr=$cr[0]; //DOM對象alert(cr.checked); //檢測checkbox是否被選中了b.var $cr=$("#cr"); //jQuery對象var cr=$cr ...
  • 很多人對盒子模型搞暈頭了,下麵通過一個簡單的代碼來分析盒子模型的結構! 為了方便方便觀看!在第一個div中畫了一個表格,並將其尺寸設置成與div內容大小一樣!且設置body的margin和padding的屬性都為0px; 本例子採用行內CSS樣式! 代碼如下: 1 2 3 4 5 6 7 8 9 1 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...