JavaScript學(xué)習(xí)——函數(shù)

1. 本文是在學(xué)習(xí)廖雪峰先生的JavaScrip教程 后的歸納

一、JavaScript函數(shù)

  1. JavaScript的函數(shù)是"頭等公民",而且可以像變量一樣使用,具有強(qiáng)大的抽象能力
  2. 借助抽象,可以不關(guān)心底層具體實(shí)現(xiàn),而直接在最高層次上思考問(wèn)題
  3. 函數(shù)是最基本的代碼抽象的方式

二、函數(shù)的定義和調(diào)用

  1. 常規(guī)定義
function abs(x){
       return x===0;
}  
* `function` 關(guān)鍵字,指出函數(shù)的定義
* `abs`是函數(shù)的名稱
* `(x)`括號(hào)列出函數(shù)的參數(shù),多個(gè)參數(shù)以`,`分隔
* `{...}`之間的代碼是函數(shù)體,可以包含若干語(yǔ)句,也可以沒(méi)有任何語(yǔ)句
* 函數(shù)無(wú)論有沒(méi)有`return`都會(huì)返回結(jié)果,無(wú)return只是結(jié)果為`undefined`
* 由于JavaScript的函數(shù)是一個(gè)對(duì)象,函數(shù)名abs為一個(gè)函數(shù)對(duì)象,函數(shù)名abs可以視為指向該函數(shù)的變量
  1. 匿名函數(shù)定義
    var abs = function(x){
          return x===0;
    };
    
    • 此種情況下,function(x){..}為匿名函數(shù),沒(méi)有函數(shù)名,將該函數(shù)賦給變量abs,可以通過(guò)變量abs就可以調(diào)用該函數(shù)
    • 上述定義和常規(guī)完全等價(jià),但需注意要在匿名函數(shù)末尾加上;,表示賦值語(yǔ)句結(jié)束
  2. 函數(shù)的調(diào)用
    • 調(diào)用函數(shù)時(shí),按順序傳入?yún)?shù)即可
    • 由于JavaScript允許傳入任意個(gè)參數(shù)而不影響調(diào)用,因此,傳入的參數(shù)比定義的參數(shù)多也沒(méi)問(wèn)題
    • 傳入?yún)?shù)比定義的少也沒(méi)問(wèn)題,不過(guò),此時(shí)函數(shù)的參數(shù)將收到undefined,因此,為了避免收到undefine,需要進(jìn)行類型檢查
    function abs(x){
             if(typeof x!=='number'){
                throw 'Not a number';
             }
             return x;
          }
    
  3. arguments
    • Javascript 中還有一個(gè)關(guān)鍵字arguments,僅在函數(shù)內(nèi)部起作用,并且永遠(yuǎn)指向當(dāng)前函數(shù)的調(diào)用傳入的所有參數(shù),類似Array而非Array
    • 利用arguemnts,可以獲取調(diào)用者傳入的所有參數(shù),也就是,即使函數(shù)不定義任何參數(shù),還可以拿到參數(shù)的值
    • 小心return語(yǔ)句,由于JavaScript引擎有一個(gè)在行末自動(dòng)添加分號(hào)的機(jī)制
    function foo(){
        return              //實(shí)際解析這樣return ;
            {name:'foo'};   // {name:'foo'};
     }
    
    • 把a(bǔ)rguments對(duì)象轉(zhuǎn)換成一個(gè)真正的數(shù)組
     var args = Array.prototype.slice.call(arguments); 
    

三、變量作用域

  1. 作用域
    • var 聲明的變量是有作用域的
    • 變量在函數(shù)體內(nèi)部聲明,則該變量的作用域?yàn)檎麄€(gè)函數(shù)體,在函數(shù)體外不可引用該變量
    • 不同函數(shù)內(nèi)部的同名變量相互獨(dú)立,不受影響
    • 由于JavaScript的函數(shù)可以嵌套,內(nèi)部函數(shù)可以訪問(wèn)外部函數(shù)定義的變量,反過(guò)來(lái),則不行
    • JavaScript的函數(shù)在查找變量時(shí)從自身函數(shù)定義開(kāi)始,從"內(nèi)"向"外"查找,如果內(nèi)部函數(shù)定義了與外部函數(shù)重名的變量,則內(nèi)部函數(shù)的變量將"屏蔽"外部函數(shù)的變量
  2. 變量提升
    • JavaScript 會(huì)先掃描整個(gè)函數(shù)體的語(yǔ)句,把所有聲明的變量"提升"到函數(shù)頂部
    • JavaScript 引擎自動(dòng)提升了變量y的聲明,但不會(huì)提升變量y的賦值
    • 要嚴(yán)格遵守在函數(shù)內(nèi)部首先申明所有變量這一規(guī)則,最常見(jiàn)的做法是用一個(gè)var申明函數(shù)內(nèi)部用的變量
    function foo(){
        var x = 1,
            y = x + 1,
            z, i;
        for(i=0;i<100;i++){
            ....
        }
    }
    
  3. 全局作用域
    • 不在任何函數(shù)內(nèi)定義的變量就是具有全局作用域
    • JavaScript默認(rèn)有一個(gè)全局對(duì)象window,全局作用域?qū)嶋H上被綁定window的一個(gè)屬性
      'use strict'
       var course = 'JavaScript';
       alert(course);
       alert(window.course);
      
    • 直接訪問(wèn)全局變量course和訪問(wèn)window.course是完全一樣的
    • 頂層函數(shù)的定義被視為一個(gè)全局變量,并綁定到windows對(duì)象
    • alert()函數(shù)也是window的一個(gè)變量
    • JavaScript實(shí)際上只有一個(gè)全局作用域,任何變量(函數(shù)也是變量),如果沒(méi)有在當(dāng)前函數(shù)作用域中找到,就會(huì)繼續(xù)往上查找,最后在全局作用域中沒(méi)有找到,則報(bào)ReferenceError錯(cuò)誤
  4. 名字空間
    • 全局變量會(huì)綁定到window上,不同的JavaScript文件如果使用了相同的變量,或者定義了相同名字的頂層函數(shù),會(huì)造成命名沖突
    • 減少?zèng)_突的一個(gè)方法是把自己的所有變量和函數(shù)全部綁定到一個(gè)全局變量中
    • 把自己的代碼全部放入唯一的名字中,會(huì)大大減少全局變量沖突的可能,許多著名的JavaScript庫(kù)就是這樣干的:如JQuery
  5. 局部作用域
    • for循環(huán)等語(yǔ)句塊中是無(wú)法定義具有局部作用域的變量
    • 為了解決塊級(jí)作用域,ES6引入了新的關(guān)鍵字let,用let代替var可以申明一個(gè)塊級(jí)作用域的變量
  6. 常量
    • 在ES6之前,無(wú)法申明一個(gè)常量,通常采用大寫(xiě)的變量來(lái)表示"這是一個(gè)常量,不要修改它的值"
    • ES6標(biāo)準(zhǔn)引入了新的關(guān)鍵字const來(lái)定義常量,constlet都具有塊級(jí)作用域

四、方法

  1. this 關(guān)鍵字
    • 在一個(gè)對(duì)象中綁定函數(shù),稱為這個(gè)對(duì)象的方法
    • 示例:
    var xiaoming={
        name:'小明',
        birth:1990,
        age:function(){
            var y = new Date().getFullYear();
            return y - this.birth;
        }
    };
    xiaoming.age;//function xiaoming.age()
    xiaoming.age();//返回小明的年紀(jì)
    
    • 在一個(gè)方法內(nèi)部,this是一個(gè)特殊變量,它始終指向當(dāng)前對(duì)象
    • 要保證this的正確調(diào)用,必須用obj.xxx()的形式調(diào)用
    • strict模式下讓直接調(diào)用的函數(shù)中的this指向undefined
    • strict模式下,通常在函數(shù)沒(méi)有指向正確的位置,那它將指向全局對(duì)象的window
    • 在方法內(nèi)部定義其他函數(shù)時(shí),可以在方法內(nèi)部加上var that=this;方便函數(shù)調(diào)用對(duì)象的屬性
  2. apply
    • 在獨(dú)立的函數(shù)調(diào)用中,根據(jù)是否是strict模式,this指向undefinedwindow
    • 函數(shù)本身的apply方法,接受兩個(gè)參數(shù):
      • 需要綁定的this變量
      • Array,表示函數(shù)本身的參數(shù)
    • apply()的類似方法是call(),唯一區(qū)別是:
      • apply() 把參數(shù)打包成Array在傳入
      • cal() 把參數(shù)按順序傳入
    • 對(duì)普通函數(shù)調(diào)用,通常把this綁定為null
  3. 裝飾器
    • 利用apply(),我們可以動(dòng)態(tài)改變函數(shù)的行為
    • JavaScript的所有對(duì)象都是動(dòng)態(tài)的,即使內(nèi)置的函數(shù),也可以重新指向新的函數(shù),如統(tǒng)計(jì)某內(nèi)置函數(shù)的調(diào)研次數(shù),可以用自定義函數(shù)替換掉默認(rèn)的內(nèi)置函數(shù)
    var count = 0;
    var oldParseInt = parseInt; // 保存原函數(shù)
    
    window.parseInt = function () {
        count += 1;
        return oldParseInt.apply(null, arguments); // 調(diào)用原函數(shù)
    };
    
    // 測(cè)試:
    parseInt('10');
    parseInt('20');
    parseInt('30');
    count; // 3
    

五、高階函數(shù)

  1. 定義
    • Higher-order function 高級(jí)函數(shù)
    • JavaScript的函數(shù)其實(shí)都是指向某個(gè)變量
    • 一個(gè)函數(shù)可以接收另一個(gè)函數(shù)作為參數(shù),這種函數(shù)稱之為高階函數(shù)
    • 編寫(xiě)高級(jí)函數(shù)就是讓函數(shù)能夠接收別的函數(shù)
    • 高級(jí)函數(shù)有強(qiáng)大的抽象能力,可以使核心代碼保持得非常簡(jiǎn)潔
    function add(x,y,f){
        return f(x)+f(y);
    }
    
  2. map/reduce
    • 示例
      function pow(x){
        return x*x;
      }
      var arr = [1,2,3,4,5,6,7,8,9];
      arr.map(pow);
      
    • map()作為高階函數(shù),事實(shí)上是把運(yùn)算規(guī)則抽象,不但可以計(jì)算簡(jiǎn)單的函數(shù),也可以計(jì)算任意復(fù)雜的函數(shù)
    • 如可以吧Array的所有數(shù)字轉(zhuǎn)化為字符串: var arr=[1,2,]; arr.map(String);
    • Array的reduce()把一個(gè)函數(shù)作用域Array[x1,x2,x3,..]上,這個(gè)函數(shù)必須接收兩個(gè)參數(shù),reduce()把結(jié)果繼續(xù)和序列的下一個(gè)元素做累積計(jì)算
    [x1,x2,x3,x4].reduce(f)=f(f(f(x1,x2),x3),x4);//reduce類似遞歸
    
    var arr=[1,3,5,7,9];
    arr.reduce(function(x,y){
        return x+y;
    });
    
  3. filter
    • 也是一個(gè)常用的操作,它用于把Array的某些元素過(guò)濾掉,然后返回剩下的元素
    • map()類似,Arrayfilter()也接收一個(gè)函數(shù),和map()不同的時(shí),filter()把傳入的函數(shù)依次作用于每個(gè)元素,然后根據(jù)返回值是true還是false決定保留還是丟棄該元素
  4. sort
    • 排序算法: 比較的過(guò)程必須通過(guò)函數(shù)抽象出來(lái),通常規(guī)定,對(duì)于兩個(gè)元素xy,如果認(rèn)為x<y,則返回-1,如果認(rèn)為x===y,則返回0,如果認(rèn)為x>y,返回1
    • 排序算法不用關(guān)心具體的比較過(guò)程,而根據(jù)比較結(jié)果直接排序
    • JavaScript的Arraysort()方法是用于排序,排序規(guī)則如下:
      • 字符串根據(jù)ASCII碼進(jìn)行排序(小寫(xiě)字母的ASCII碼在大寫(xiě)字母之后)
      • 默認(rèn)將所有的元素先轉(zhuǎn)換成String在排序
      • 可以接收一個(gè)比較函數(shù)來(lái)實(shí)現(xiàn)自定義排序
    • 示例
    var arr = [10, 20, 1, 2];
    arr.sort(function (x, y) {
        if (x < y) {
            return 1;
        }
        if (x > y) {
            return -1;
        }
        return 0;
    }); // [20, 10, 2, 1]
    

六、閉包

  1. 函數(shù)作為返回值
    • 高級(jí)函數(shù)可以接收函數(shù)做參數(shù),還可以把函數(shù)作為結(jié)果值返回
    • 示例
    function lazy_sum(arr){
        var sum = function(){
            return arr.reduce(function(x,y){
               return x+y;
            }
        }
    }
     
    var f= lazy_sum([1,2,3,4,5]);
    f();
    
    • 在上述例子中,函數(shù)layz_sum中定義了函數(shù)sum,并且,內(nèi)部函數(shù)sum可以引用外部函數(shù)lazy_sum的參數(shù)和局部變量,當(dāng)lazy_sum返回函數(shù)sum時(shí),相關(guān)參數(shù)和變量都保存在返回的函數(shù)中,這種稱為"閉包(Closure)"的程序結(jié)構(gòu)擁有巨大威力
    • 當(dāng)調(diào)用`lazy_sum()時(shí),每次調(diào)用都會(huì)返回一個(gè)新的函數(shù),即使傳入相同的參數(shù)
     var f1 = lazy_sum([1, 2, 3, 4, 5]);
     var f2 = lazy_sum([1, 2, 3, 4, 5]);
     f1 === f2; // false
     //`f1()和`f2()的調(diào)用結(jié)果互不影響
    
  2. 閉包作用
    • 返回的函數(shù)在其定義內(nèi)部引用了局部變量arr,當(dāng)一個(gè)函數(shù)返回了一個(gè)函數(shù)后,其內(nèi)部的局部變量還被新函數(shù)引用,函數(shù)并沒(méi)有立刻執(zhí)行,而是調(diào)用了f()才行
    • 返回函數(shù)不要引用任何循環(huán)變量,或者后續(xù)會(huì)發(fā)生變化的變
    • 若一定引用循環(huán)變量: 方法是在再創(chuàng)建一個(gè)函數(shù),用該函數(shù)的參數(shù)綁定循環(huán)變量當(dāng)前的值,無(wú)論該循環(huán)變量后續(xù)如何更改,已綁定函數(shù)參數(shù)的值不變
    function count() {
        var arr = [];
        for (var i=1; i<=3; i++) {
            arr.push((function (n) {
                return function () {
                    return n * n;
                }
            })(i));
        }
        return arr;
    }
    
    var results = count();
    var f1 = results[0];
    var f2 = results[1];
    var f3 = results[2];
    
    f1(); // 1
    f2(); // 4
    f3(); // 9
    
    • 創(chuàng)建一個(gè)匿名函數(shù)并立刻執(zhí)行可以這么寫(xiě):
      (function(x) {return x*x;})(3);
    • JavaScript 借助閉包,可以封裝一個(gè)私有變量,閉包就是攜帶狀態(tài)的函數(shù),并且它的狀態(tài)可以完全對(duì)外隱藏起來(lái)
    'use strict';
    function create_counter(initial){
        //私有變量
        var x = initial||0;
        return {
            inc: function(){
                x +=1;
                return x;
            }
        };
    }
    
    var c1 = create_counter();
    c1.inc(); // 1
    c1.inc(); // 2
    c1.inc(); // 3
    
    var c2 = create_counter(10);
    c2.inc(); // 11
    c2.inc(); // 12
    c2.inc(); // 13
    
    • 閉包就是攜帶狀態(tài)的函數(shù),并且它的狀態(tài)可以完全對(duì)外隱藏起來(lái)
    • 閉包還可以把多參數(shù)的函數(shù)變成單參數(shù)的函數(shù)
    function make_pow(n) {
        return function (x) {
            return Math.pow(x, n);
        }
    }
    
    // 創(chuàng)建兩個(gè)新函數(shù):
    var pow2 = make_pow(2);
    var pow3 = make_pow(3);
    
    pow2(5); // 25
    pow3(7); // 343
    

七、generator

  1. 定義
    • generator(生成器)是ES6標(biāo)準(zhǔn)引入的新的數(shù)據(jù)類型。一個(gè)generator看上去像一個(gè)函數(shù),但可以返回多次
    • generator 有function*定義,除了return語(yǔ)句,還可以使用yield返回多次
  2. 使用
    • 示例
    function* fib(max) {
        var
            t,
            a = 0,
            b = 1,
            n = 1;
        while (n < max) {
            yield a;
            t = a + b;
            a = b;
            b = t;
            n ++;
        }
        return a;
    }
    
    • 直接調(diào)用generator和調(diào)用函數(shù)不一樣,僅僅是創(chuàng)建了一個(gè)generator對(duì)象,還沒(méi)有去執(zhí)行它
    • 調(diào)用generator對(duì)象有兩種方法:
      • 不斷調(diào)用generator對(duì)象的next()方法
      var f = fib(5);
      f.next(); // {value: 0, done: false}
      f.next(); // {value: 1, done: false}
      f.next(); // {value: 1, done: false}
      f.next(); // {value: 2, done: false}
      f.next(); // {value: 3, done: true}
      
      next()方法會(huì)執(zhí)行時(shí),每次遇到yield x;就返回一個(gè)對(duì)象 {value:x,done:true/false},然后暫停,返回value表示yield的返回值,done表示這個(gè)generator對(duì)象是否已經(jīng)執(zhí)行結(jié)束,如果donetrue,則value就是return的返回值。
      • 直接用for...of循環(huán)迭達(dá)generator對(duì)象,這種方式不需要自己判斷done
       for (var x of fib(5)) {
          console.log(x); // 依次輸出0, 1, 1, 2, 3
      }
      
    • generator可以把異步回調(diào)代碼變成"同步"代碼
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容