第四章 函數(shù)

函數(shù)

JS設(shè)計(jì)得最出色的就是它的函數(shù)的實(shí)現(xiàn)蛇券。它幾乎接近完美。但是樊拓,想必你也能預(yù)料到纠亚,JS的函數(shù)也存在瑕疵。
所謂編程骑脱,就是將一組需求分解成一組函數(shù)與數(shù)據(jù)結(jié)構(gòu)的技能菜枷;

1. 函數(shù)對(duì)象

每個(gè)函數(shù)在創(chuàng)建時(shí)會(huì)附加兩個(gè)隱藏屬性:函數(shù)的上下文和實(shí)現(xiàn)函數(shù)行為的代碼;
函數(shù)的與眾不同之處在于它們可以被調(diào)用叁丧;

2. 函數(shù)字面量

3. 調(diào)用

調(diào)用一個(gè)函數(shù)會(huì)暫停當(dāng)前函數(shù)的執(zhí)行,傳遞控制權(quán)和參數(shù)給新函數(shù)岳瞭;

JS中一共有4種調(diào)用模式拥娄,方法調(diào)用模式,函數(shù)調(diào)用模式瞳筏,構(gòu)造器調(diào)用模式稚瘾,apply調(diào)用模式,這些模式在如何初始化關(guān)鍵參數(shù)this上存在差異姚炕;

4. 方法調(diào)用模式

當(dāng)一個(gè)函數(shù)被保存為對(duì)象的一個(gè)屬性時(shí)摊欠,我們稱它為一個(gè)方法。
當(dāng)一個(gè)方法被調(diào)用時(shí)柱宦,this被綁定到該對(duì)象些椒。

var myObject = {
    value: 0,
    increment: function( inc ){
         this.value += typeof inc === 'number' ? inc : 1;
    }
}

myObject.increment();
myObject.value ==> 1

myObject.increment(2);
myObject.value ==> 2

5. 函數(shù)調(diào)用模式

當(dāng)一個(gè)函數(shù)并非一個(gè)對(duì)象的屬性時(shí),那么它就是被當(dāng)做一個(gè)函數(shù)來(lái)調(diào)用的掸刊;

var add = function(a, b){
    return a + b;
}
var sum = add(3, 4);

以此模式調(diào)用函數(shù)時(shí)免糕,this被綁定到全局對(duì)象。這是語(yǔ)言設(shè)計(jì)上的一個(gè)錯(cuò)誤S遣唷Jぁ!蚓炬。
倘若語(yǔ)言設(shè)計(jì)正確松逊,那么當(dāng)內(nèi)部函數(shù)被調(diào)用時(shí),this應(yīng)該仍然綁定到外部函數(shù)的this變量肯夏。
這個(gè)設(shè)計(jì)錯(cuò)誤的后果就是方法不能利用內(nèi)部函數(shù)來(lái)幫助它工作经宏,因?yàn)閮?nèi)部函數(shù)的this被綁定了錯(cuò)誤的值楼咳,所以不能共享該方法對(duì)對(duì)象的訪問(wèn)權(quán)!

var add  = function(a,b){
  console.log(this)      //此處的this指向window對(duì)象V蛐簟D噶!
}

var obj = {
    age: 21,
    value: 2,
    double: function(){
        var helper = function(){
            this.value = this.age;
            console.log(this)
        }
        helper()
    }
}
console.log(obj.double())  ==> undefined;

//同樣
var obj = {
    age: 21,
    value: 2,
    double: function(){
        function helper(){
            this.value = this.age;
            console.log(this)
        }
        helper()
    }
}
console.log(obj.double())  ==> undefined;

得到undefined的原因:以函數(shù)調(diào)用模式調(diào)用函數(shù)時(shí)缚柏,this被綁定到了全局對(duì)象苹熏,在helper內(nèi)部的this是window
注意:之前我一直以為helper內(nèi)部的this是其內(nèi)部作用域,所以this只能代表其內(nèi)部币喧,而不能去訪問(wèn)父元素轨域,今天才知道這個(gè)this竟然是一個(gè)全局變量!I辈汀干发!

解決辦法:

var obj = {
    age: 21,
    value: 2,
    double: function(){
        var that = this;
        var helper = function(){
            that.value = that.age;
            console.log(that.value)
        }
        helper()
    }
}
console.log(obj.double())  ==> 21;

6. 構(gòu)造器調(diào)用模式

JS是一門(mén)基于原型繼承的語(yǔ)言。這意味著對(duì)象可以直接從其他對(duì)象繼承屬性史翘。該語(yǔ)言是無(wú)類型的枉长;
如果在一個(gè)函數(shù)面前帶上new來(lái)調(diào)用,那么背地里將會(huì)創(chuàng)建一個(gè)連接到該函數(shù)的prototype成員的新對(duì)象琼讽,同時(shí)this會(huì)被綁定到那個(gè)新對(duì)象上必峰。

代碼舉例:

//注意,按照約定钻蹬,構(gòu)造函數(shù)命名應(yīng)該以大寫(xiě)字母開(kāi)頭
var Quo = function(string){
    this.status = string;
}

//給 Quo 所有實(shí)例提供一個(gè)名為get_status的公共方法
Quo.prototype.get_status = function() {
    return this.status;
}

//構(gòu)造一個(gè) Quo 實(shí)例
var myQuo = new Quo('confused');

myQuo.get_status() ==> confused;

一個(gè)函數(shù)吼蚁,如果創(chuàng)建的目的就是希望結(jié)合 new 前綴來(lái)調(diào)用,那它就被稱為構(gòu)造函數(shù)

我不推薦使用這種形式的構(gòu)造函數(shù)问欠。在下一章中我們會(huì)看到更好的替代方式(《JavaScript語(yǔ)言精粹》這本書(shū)一大特點(diǎn)就是肝匆,一個(gè)東西講完后,它會(huì)告訴你顺献,這個(gè)東西我們不建議使用旗国,要想看到更好的替代方式,請(qǐng)看下一章滚澜,真是逼著你去學(xué)習(xí)下一章4植帧!设捐!)

7. Apply 調(diào)用模式

apply方法讓我們構(gòu)建一個(gè)參數(shù)數(shù)組傳遞給調(diào)用函數(shù)借浊,它也允許我們選擇this的值。
apply方法接收兩個(gè)參數(shù)萝招,第1個(gè)是要綁定給this的值蚂斤,第2個(gè)就是一個(gè)參數(shù)數(shù)組。

var add = function(a, b) {
    return a + b;
}

//構(gòu)造一個(gè)包含兩個(gè)數(shù)字的數(shù)組槐沼,并將它們相加

var array = [3,4];
var sum = add.apply(null, array)   // sum值為7

參數(shù)

當(dāng)函數(shù)被調(diào)用時(shí)曙蒸,會(huì)得到一個(gè)“免費(fèi)”配送的參數(shù)捌治,那就是arguments數(shù)組。函數(shù)可以通過(guò)此參數(shù)訪問(wèn)所有它被調(diào)用時(shí)傳遞給它的參數(shù)列表纽窟,包括那些沒(méi)有被分配給函數(shù)聲明時(shí)定義的形式參數(shù)的多余參數(shù)肖油。

var sum = function(){
    var i, sum = 0;
    for(i = 0; i < arguments.length; i++){
        sum += arguments[i];
    }
    return sum;
}
sum(4,8,15,16,23,42) ==> 108

因?yàn)檎Z(yǔ)言的一個(gè)設(shè)計(jì)錯(cuò)誤,arguments并不是一個(gè)真正的數(shù)組臂港。它只是一個(gè)“類似數(shù)組”的對(duì)象森枪,arguments擁有一個(gè)length屬性,但它沒(méi)有任何數(shù)組的方法审孽;

1. 返回

return 語(yǔ)句可用來(lái)使函數(shù)提前返回县袱,當(dāng)return被執(zhí)行時(shí),函數(shù)立即返回而不再執(zhí)行余下的語(yǔ)句佑力;
如果一個(gè)函數(shù)沒(méi)有指定返回值式散,則返回 undefined;

var Vue = function(a){
   return this
}
Vue.prototype.returnThis = function(){
   return this;
}
var vue = new Vue();
vue.returnThis() ==》 Vue{}

2. 異常

(1) throw

var add = function(a, b){
   if( typeof a !== 'number' || typeof b !== 'number' ){
       throw {
           name: 'TypeError',
           message: 'add needs numbers'
       };
   }
   return a + b;
}

add(3,'ww') ==> { name: 'TypeError', message: 'add needs numbers' }

(2) try語(yǔ)句的catch從句

var try_it = function(){
    try {
        add('seven');
    } catch(e) {
        document.writenIn(e.name + ':' + e.message);
    }
}
try_it();

一個(gè)try語(yǔ)句只會(huì)有一個(gè)捕獲所有異常的catch代碼塊

3. 擴(kuò)充類型的功能

通過(guò)給基本類型增加方法打颤。我們可以極大地提高語(yǔ)言的表現(xiàn)力暴拄。

給Number原型增加方法來(lái)提取數(shù)字中的整數(shù)部分

Number.method('integer', function(){
    return Math[this < 0 ? 'ceil' : 'floor'](this);
})

(-10/3).integer();

給String原型添加一個(gè)移除字符串首尾空白的方法

String.method('trim', function(){
    return this.replace(/^\s+|\s+$/g, '');
})

通過(guò)給基本類型添加方法,我們可以極大地提高語(yǔ)言表現(xiàn)力瘸洛。
其實(shí)揍移,對(duì)于這一點(diǎn),我個(gè)人建議盡量使用Jquery反肋,underscore等庫(kù)來(lái)實(shí)現(xiàn)各種基本類型和引用類型的處理。因?yàn)樵谠玩溕咸砑舆^(guò)多方法會(huì)出現(xiàn)一些問(wèn)題踏施,比如結(jié)構(gòu)混亂石蔗,代碼不好維護(hù)等。

4. 遞歸

遞歸函數(shù)就是會(huì)直接或間接地調(diào)用自身的一種函數(shù)畅形。一般來(lái)說(shuō)养距,一個(gè)遞歸函數(shù)調(diào)用自身去解決它的子問(wèn)題;
遞歸函數(shù)可以非常高效的操作樹(shù)形結(jié)構(gòu)日熬。比如瀏覽器的文檔對(duì)象模型(DOM)棍厌,每次遞歸調(diào)用時(shí)處理指定的樹(shù)的一段。

5. 作用域

在編程語(yǔ)言中竖席,作用域控制著變量與參數(shù)的可見(jiàn)性及生命周期耘纱。它減少了命名沖突,并且提供了自動(dòng)內(nèi)存管理毕荐。

var foo = function(){
    var a = 3, b = 5;

    var bar = function(){
        var b = 7, c = 11;

//此時(shí)束析,a = 3, b = 7, c = 11

        a += b + c;
//此時(shí),a = 21, b = 7, c = 11

    }
//此時(shí)憎亚,a = 3, b = 5, c未定義
    bar();
//此時(shí)员寇,a = 21, b = 5  
}

任何一對(duì)花括號(hào)({和})中的語(yǔ)句集都屬于一個(gè)塊弄慰,在這之中定義的所有變量在代碼塊外都是不可見(jiàn)的,我們稱之為塊級(jí)作用域蝶锋。
函數(shù)作用域就好理解了(__) 陆爽,定義在函數(shù)中的參數(shù)和變量在函數(shù)外部是不可見(jiàn)的。

JS并不支持塊級(jí)作用域扳缕,它只支持函數(shù)作用域慌闭,而且在一個(gè)函數(shù)中的任何位置定義的變量在該函數(shù)中的任何地方都是可見(jiàn)的

因?yàn)镴S缺少塊級(jí)作用域,只支持函數(shù)作用域第献, 所以贡必,最好的做法是在函數(shù)體的頂部聲明函數(shù)中可能用到的所有變量。因?yàn)橛购粒谝粋€(gè)函數(shù)體內(nèi)任何位置定義的變量仔拟,在函數(shù)內(nèi)部任何地方都可見(jiàn)。

functin test(){ 
  for(var i=0;i<3;i++){ 
  } 
  alert(i); 
} 
test();

//因?yàn)镴S沒(méi)有塊級(jí)作用域飒赃,所以會(huì)彈出 i 為 3利花,可見(jiàn),在塊外载佳,塊中定義的變量i仍然是可以訪問(wèn)的

是否還記得炒事,在一個(gè)函數(shù)中定義的變量,當(dāng)這個(gè)函數(shù)調(diào)用完后蔫慧,變量會(huì)被銷毀挠乳,我們是否可以用這個(gè)特性來(lái)模擬出JS的塊級(jí)作用域呢?

function test(){
  (function(){
     for(var i = 0; i < 3; i++ ){

     }
  })();
  alert(i);
}
test();

會(huì)報(bào)錯(cuò):i is not defined
這里姑躲,我們把for語(yǔ)句塊放到了一個(gè)閉包之中睡扬,然后調(diào)用這個(gè)函數(shù),當(dāng)函數(shù)調(diào)用完畢黍析,變量i自動(dòng)銷毀卖怜,因此,我們?cè)趬K外便無(wú)法訪問(wèn)了

閉包

作用域的好處是內(nèi)部函數(shù)可以訪問(wèn)定義它們的外部函數(shù)的參數(shù)和變量(除了this和arguments)阐枣,這太美妙啦马靠!

一個(gè)更有趣的情形是:內(nèi)部函數(shù)擁有比它的外部函數(shù)更長(zhǎng)的生命周期。

var myObject = {
    value: 0,
    increment: function( inc ){
         this.value += typeof inc === 'number' ? inc : 1;
    }
}

如果我們要實(shí)現(xiàn)保護(hù)myObject中的value值不被非法修改蔼两,怎么做甩鳄?

var myObject = (function() {
    var value = 0;

    return {
        increment: function( inc ){
             value += typeof inc === 'number' ? inc : 1;
        },
        getValue: function(){
             return value;
        }     
    };
}());

該函數(shù)返回一個(gè)對(duì)象字面量。返回一個(gè)包含兩個(gè)方法的對(duì)象宪哩,并且這些方法繼續(xù)享有訪問(wèn)value變量的特權(quán)娩贷。
myObject中的value對(duì)于increment和getValue方法總是可用的,但函數(shù)的作用域使得它對(duì)其他的程序來(lái)說(shuō)是不可見(jiàn)的K稀彬祖!

我們?cè)賮?lái)看一個(gè)例子:

var quo = function(status){
    return {
        get_status: function(){
            return status;
        }
    };
};
var myQuo = quo('amazed');
myQuo.get_status();

一個(gè)有用的例子:把DOM節(jié)點(diǎn)設(shè)置為黃色茁瘦,然后漸變?yōu)榘咨?/p>

var fade = function(node){
    var level = 1;
    var step = function(){
      var hex = level.toString(16);
      node.style.backgroundColor = '#FFFF' + hex + hex;
      if( level < 15 ){
            level++;
            setTimeout(step,100)
      }
    }
    setTimeout(step, 100)
}
fade(document.body);

7 模塊

通過(guò)使用函數(shù)產(chǎn)生模塊,我們幾乎可以完全摒棄全局變量的使用储笑,從而緩解這個(gè)js的最為糟糕的特性之一所帶來(lái)的影響

模塊形式舉例:

var obj = function(){
    var seq = 0;
    var preFix = '';
    return {
        set_prefix: function(p){
           prefix = String(p)
        },
        set_seq: function(s){
           seq = s;
        }
    }
}

8 級(jí)聯(lián)

其實(shí)就是通過(guò)在函數(shù)內(nèi)部 return this 來(lái)實(shí)現(xiàn)函數(shù)的鏈?zhǔn)秸{(diào)用甜熔;

9 柯里化

10 記憶

通過(guò)緩存變量,從而避免無(wú)謂的重復(fù)計(jì)算突倍;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末腔稀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子羽历,更是在濱河造成了極大的恐慌焊虏,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秕磷,死亡現(xiàn)場(chǎng)離奇詭異诵闭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)澎嚣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)疏尿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人易桃,你說(shuō)我怎么就攤上這事褥琐。” “怎么了晤郑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵敌呈,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我造寝,道長(zhǎng)驱富,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任匹舞,我火速辦了婚禮,結(jié)果婚禮上线脚,老公的妹妹穿的比我還像新娘赐稽。我一直安慰自己,他們只是感情好浑侥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布姊舵。 她就那樣靜靜地躺著,像睡著了一般寓落。 火紅的嫁衣襯著肌膚如雪括丁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天伶选,我揣著相機(jī)與錄音史飞,去河邊找鬼尖昏。 笑死,一個(gè)胖子當(dāng)著我的面吹牛构资,可吹牛的內(nèi)容都是我干的抽诉。 我是一名探鬼主播,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吐绵,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼迹淌!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起己单,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤唉窃,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后纹笼,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體纹份,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年允乐,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了矮嫉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡牍疏,死狀恐怖蠢笋,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情鳞陨,我是刑警寧澤昨寞,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布,位于F島的核電站厦滤,受9級(jí)特大地震影響援岩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掏导,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一享怀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧趟咆,春花似錦添瓷、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至虐唠,卻和暖如春搀愧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工咱筛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搓幌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓眷蚓,卻偏偏與公主長(zhǎng)得像鼻种,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子沙热,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法叉钥,類相關(guān)的語(yǔ)法,內(nèi)部類的語(yǔ)法篙贸,繼承相關(guān)的語(yǔ)法投队,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,625評(píng)論 18 399
  • 繼承 一爵川、混入式繼承 二敷鸦、原型繼承 利用原型中的成員可以被和其相關(guān)的對(duì)象共享這一特性,可以實(shí)現(xiàn)繼承寝贡,這種實(shí)現(xiàn)繼承的...
    magic_pill閱讀 1,062評(píng)論 0 3
  • 函數(shù)的基本知識(shí) 函數(shù)是對(duì)一系列計(jì)算或操作的過(guò)程的抽象函數(shù)可以把大的計(jì)算任務(wù)分解成若干個(gè)較小的任務(wù)扒披,程序設(shè)計(jì)人員可以...
    陽(yáng)光下的螞蟻閱讀 378評(píng)論 0 0
  • 占坑
    米線兒_2017閱讀 231評(píng)論 2 0
  • 年末歲尾颇蜡,幾個(gè)商場(chǎng)大力推銷商品价说,廣告鋪天蓋地,來(lái)就送风秤,一百萬(wàn)免費(fèi)產(chǎn)品送給你鳖目。買(mǎi)五百送五百,買(mǎi)一千送一千一缤弦,晚上營(yíng)業(yè)...
    買(mǎi)喜堂閱讀 77評(píng)論 0 4