(60)Wangdao.com第十天_JavaScript 函數_做用域_閉包_IIFE_回調函數_eval

函數        html

實現特定功能的 n 條語句封裝體。c++

 

1. 建立一個函數對象面試

var myFunc = new Function();    // typeof myFunc 將會打印 function
  • 將要封裝的代碼以字符串的方式傳遞給構造函數
    • var aFunc = new Function("console.log('Hello!');");
      console.log(aFunc);
      // 打印:
          "
              function anonymous(){
                  console.log("Hello!");
              }           
          "
  • 使用函數聲明來建立一個函數
    • function myFunc(){
          console.log("Hello myFunc !");  
      }
      // 會 函數聲明 提高
  • 使用函數表達式來建立一個函數
    • var myFunc = function(){
          console.log("Hello myFunc ! ");
      };
      // 會 變量聲明 提高

       

    • var myAdd = function(a,b){                // 傳遞多個參數使用,隔開
          console.log(a+b); 
      };

      注意:函數解析器不會檢查實參的類型;    多餘的參數將不會被使用;    缺乏的參數定義爲 undefined數組

2. 函數的返回值        外界須要函數處理的值安全

使用 return 關鍵字返回指定結果,並結束函數。閉包

不寫 return 默認爲 return ;        此時的函數返回值爲 undefinedapp

 

3. 函數的參數        當沒法肯定 n 個變量的值時, 使用 n 個參數 傳遞____形參異步

  • 函數的 length 屬性返回函數預期傳入的參數個數,即函數定義之中的參數個數
    • length 屬性就是定義時的參數個數。無論調用時輸入了多少個參數,length 屬性始終等於 形參個數
      function f(a, b){}
      f.length    // 2
  • 同名參數
    • 若是有同名的參數,則取最後出現的那個值
      function f(a, a) {
        console.log(a);
      }
      
      f(1, 2)    // 2
      f(1)    // undefined
      
      // 這時,若是要得到第一個a的值,可使用arguments對象。
      function f(a, a) {
        console.log(arguments[0]);
      }
      
      f(1)    // 1

只有函數調用時,才能動態肯定 this 的指向函數

.call(obj, 12, 13);工具

.apply(obj, [12, 13]);

 

.bind(obj, 12, 13);         重構建函數

不會當即執行函數,而是會建立一個新函數,新函數的 this 指向 obj,參數也被傳遞

也不會修改原函數的 this

  • arguments 實參列表對象             只有數組的 length 屬性,可以經過 index 讀寫數據
    • 因爲 JavaScript 容許函數有不定數目的參數,因此須要一種機制,能夠在函數體內部讀取全部參數。
    • 包含了函數運行時的全部參數,arguments[0]就是第一個參數,arguments[1]就是第二個參數,以此類推。這個對象只有在函數體內部,纔可使用。
      var f = function (one) {
        console.log(arguments[0]);
        console.log(arguments[1]);
        console.log(arguments[2]);
      }
      
      f(1, 2, 3)
      // 1
      // 2
      // 3
      • 正常模式下,arguments對象能夠在運行時修改。
        var f = function(a, b) {
          arguments[0] = 3;
          arguments[1] = 2;
          return a + b;
        }
        
        f(1, 1)    // 5
      • 嚴格模式下,arguments對象是一個只讀對象,修改它是無效的,但不會報錯。
        var f = function(a, b) {
          'use strict';    // 開啓嚴格模式
        
          arguments[0] = 3;    // 無效
          arguments[1] = 2;    // 無效
          return a + b;
        }
        
        f(1, 1)    // 2
    • 經過 arguments 對象的 length 屬性,能夠判斷函數調用時到底帶幾個參數。
    • 雖然 arguments 很像數組,但它是一個對象。數組專有的方法(好比 slice 和 forEach),不能在 arguments 對象上直接使用。
      • 若是要讓arguments對象使用數組方法,真正的解決方法是將arguments轉爲真正的數組。下面是兩種經常使用的轉換方法:
        //slice方法
        var args = Array.prototype.slice.call(arguments);
        
        //逐一填入新數組。 var args = []; for (var i = 0; i < arguments.length; i++) { args.push(arguments[i]); }
    • arguments.callee 屬性,返回它所對應的原函數
      • var f = function () {
          console.log(arguments.callee === f);
        }
        
        f()    // true
        // 能夠經過arguments.callee,達到調用函數自身的目的。
        // 這個屬性在嚴格模式裏面是禁用的,所以不建議使用。
  • 實參能夠是任意數據類型
  • 函數參數不是必需的,Javascript 容許省略參數
    • 運行時不管提供多少個參數(或者不提供參數),JavaScript 都不會報錯。省略的參數的值就變爲 undefined
    • 可是,沒有辦法只省略靠前的參數,而保留靠後的參數。若是必定要省略靠前的參數,只有顯式傳入 undefined
      function f(a, b) {
          return a;
      } 
      
      f( , 1)    // 報錯 SyntaxError: Unexpected token ,(…)
      f(undefined, 1)    // undefined
  • 函數被稱爲 "第一等公民"
    • 將函數看做一種值,與其它值(數值、字符串、布爾值等等)地位相同。凡是可使用值的地方,就能使用函數。
  • 當數據有規律,有必要的話能夠存到一個對象裏,傳入函數。
    • var sun = {
                         name:"孫悟空",
                         gender:"男",
                         age:600
                     };
      
      function sayHello(obj){
          document.write("你們好,我是"+obj.name+", "+obj.gender+", "+obj.age+"歲了");
      }
      
      sayHello(sun);
  • 實參還能夠是函數
    • function haha(someFunc){
          someFunc();
      }
      
      haha(sayHello);
  • 返回值能夠對象
  • 返回值也能夠是函數
  • break;    用於終止函數
  • continue;    用於終止本次循環,直接開始下一次循環
  • return;    用於設置返回值,也會立刻結束函數

 


 4. 函數的屬性和方法

  • .name 屬性,返回函數的名字
    • function f1() {}
      f1.name     // "f1"
      
      
      var f2 = function () {};
      f2.name     // "f2"
      
      
      var f3 = function myName() {};
      f3.name    // 'myName'

       

  • .toString() 方法返回一個字符串,內容是函數的源碼
    • function f() {
        a();
        b();
        c();    // 這是註釋
      }
      
      f.toString()
      // function f() {
      //  a();
      //  b();
      //  c();    // 這是註釋
      // }

5. 做用域(scope)   

變量存在的範圍。

不一樣做用域的同名變量不會衝突

  • 在 ES5 的規範中,Javascript 只有兩種做用域:
    • 全局做用域    全局變量(global variable)
      • 在函數外部聲明的變量,變量在整個程序中一直存在,全部地方均可以讀取,甚至能夠在函數內部讀取;
    • 函數做用域    局部變量(local variable)
      • 在函數內部定義的變量,變量只在函數內部存在,外部沒法讀取。
      • 做用域 是靜態的 在函數建立時,就已經肯定了,並不在變化,且一直存在
      • 函數上下文 是動態的,在調用函數時建立並肯定,在函數執行完了就被釋放
      • var v = 1;
        
        function f(){
            var v = 2;
            console.log(v);
        }
        
        f()    // 2
        v    // 1
      • 只有函數做用域 和 全局做用域 ,沒有其餘做用域
  • 必會面試題
  • function Foo() {
            getName = function () { alert (1); };
            return this;
        }
        Foo.getName = function () { alert (2);};
        Foo.prototype.getName = function () { alert (3);};
        var getName = function () { alert (4);};
        function getName() { alert (5);}
    
    
        //請寫出如下輸出結果:
        Foo.getName();
        getName();
        Foo().getName();
        getName();
        new Foo.getName();
        new Foo().getName();
        new new Foo().getName();
  • 分析:
  •     var getName;
        window.getName = function () { alert (4);};
        
        function Foo() {    // window.Foo
            getName = function () { alert (1); };    // 覆蓋 window.getName
            return this;
        };
        window.Foo.prototype.getName = function () { alert (3);};
        window.Foo.getName = function () { alert (2);};
        
    
    
    //請寫出如下輸出結果:
        Foo.getName();    // window.Foo.getName()    2
        getName();    // window.getName()    4
    
        // this.getName()    window.getName()    1    注意是在何時被覆蓋的
        Foo().getName();
    getName();
    // window.getName() 1 new Foo.getName(); // window.Foo.getName() 2 // 注意 new 返回的是對象 直接調用返回看return // f.getName() window.f.getName() window.Foo.prototype.getName 3 new Foo().getName();
    new new Foo().getName(); // new window.f.getName() 3
  • ES6 又新增了塊級做用域

 6. 閉包 closure

 嵌套: 做爲函數裏面的 函數 fn2

② fn2 引用了外部函數的局部變量

外部函數 被調用執行時產生閉包,在 new 的時候也至關於執行了____實質是在 第一個知足以上條件 的函數被解析到是,產生閉包

此時就產生了一個閉包____ fn2.Scopes[0] = fn2.closure————包含了被引用局部變量的一個"對象",存在於嵌套的內部函數中,可使用調試工具查看到。

其中 [[Scopes]] 是由底層 c/c++ 實現

  • 一個外層函數,嵌套多個函數,這幾個內部函數 共用一個 閉包,該閉包不會在外層函數執行完後銷燬,也就意味着延長了變量的存活時間
  • 閉包內的變量,沒法直接操做,只能經過內層函數間接操做閉包內的變量
  • 閉包存在於 內部函數 fn2 中, 只要 fn2 存在,則閉包存在____當再也沒有 變量 指向這個 fn2 ,則 fn2 不存在,閉包也就不存在了

缺點:

因爲閉包的存在,未釋放,致使佔用內存時間變長。易形成內存泄漏

怕的就是出現 很是多個閉包

解決:

能不用閉包就不用閉包

及時釋放____讓 指向含閉包函數的 變量,指向 null

  • 做用有
    • 使得能夠 在函數外部 間接操做 函數內部的變量。
    • 讓這些變量始終保持在內存中, 不會在函數調用結束後,被垃圾回收機制回收。
    • 封裝對象的私有屬性和私有方法。
      • function Person(name) {
            var _age;
            function setAge(n) {
                _age = n;
            }
            function getAge() {
                return _age;
            }
        
            return {
                name: name,
                getAge: getAge,
                setAge: setAge
            };
        }
        
        var p1 = new Person('張三');
        p1.setAge(25);    // 操做的是 閉包中的 _age
        p1.getAge()    // 訪問的是 閉包中的 _age
        // 上面代碼中,函數的內部變量,經過閉包和,變成了返回對象的私有變量。
        Person_agegetAgesetAgep1

        注意

        • 外層函數每次運行,都會生成一個新的閉包,而這個閉包又會保留外層函數的內部變量,因此內存消耗很大。

        • 所以不能濫用閉包,不然會形成網頁的性能問題。

  • 是Javascript 語言的一個難點,也是它的特點。
  • 閉包最大的特色,就是它能夠 「記住」 誕生的環境,好比 fn2 記住了它誕生的環境 fn1 的內部變量。在本質上,閉包就是將函數內部和函數外部鏈接起來的一座橋樑。
  • 不少高級應用都要依靠閉包實現。 
  • 理解閉包,首先必須理解變量做用域。
  • "做用域鏈"(scope chain)
    • JavaScript 語言特有的結構
    • 子對象會一級一級地向上尋找全部父對象的變量。因此,父對象的全部變量,對子對象都是可見的,反之則不成立。

 

  • 正常狀況下,獲得函數內的局部變量是辦不到的
    • 只有經過變通方法才能實現
    • 那就是在函數的內部,再定義一個函數。
    • 既然子函數能夠讀取父函數的局部變量,那麼只要把子函數做爲返回值,咱們不就能夠在父函數外部讀取它的內部變量了嗎!
      function f1() {
        var n = 999;
        function f2() {
          console.log(n);
        }
        return f2;
      }
      
      var result = f1();
      result(); // 999
      // 函數的返回值就是函數,因爲能夠讀取的內部變量,因此就能夠在外部得到的內部變量了
      f1f2f2f1f1
    • 閉包就是返回的 子函數 f2,即可以讀取其餘函數內部變量的函數。

 

  •  請看下面的例子,閉包使得內部變量記住上一次調用時的運算結果。
    • function createIncrementor(start) {
          return function () {
              return start++;
          };
      }
      
      var inc = createIncrementor(5);    // 執行了 外層函數,而後銷燬了 函數上下文,即 全部外層函數的變量都銷燬了
      
      inc();    // 5
      
      inc();    // 6
      
      inc();    // 7

       

 


7. 當即調用的函數表達式(IIFE)

 自調用匿名函數

____封裝時, 隱藏函數內部具體實現, 防止外部修改函數內的代碼

____防止污染外部命名空間

    • 以圓括號開頭,引擎就會認爲後面跟的是一個表示式,而不是函數定義語句,避免出現錯誤。

這就叫作「當即調用的函數表達式」(Immediately-Invoked Function Expression),簡稱 IIFE。

    • 目的有兩個:
      • 一是沒必要爲函數命名,避免了污染全局變量;
      • 二是 IIFE 內部造成了一個單獨的做用域,能夠封裝一些外部沒法讀取的私有變量。
        // 寫法一
        var tmp = newData;
        processData(tmp);
        storeData(tmp);
        
        
        // 寫法二    更好,由於徹底避免了污染全局變量。
        (function(w){    // 
            var tmp = newData;
            processData(tmp);
            storeData(tmp);
        }(window));    // 方便對代碼進行壓縮, 變量 window 改爲 w, 減少代碼體積
  • 圓括號()是一種運算符,跟在函數名以後,表示調用該函數。    好比,isNaN() 就表示調用 isNaN 函數。
  • 當 function 出如今行首,爲了避免讓引擎將其理解成一個表達式。最簡單的處理,就是將其放在一個圓括號裏面。

 


 8. 回調函數

自定義的,不被本身調用的,達到某種條件後被某對象調用了。

異步加載通知機制: 

全部異步代碼,不能當即執行

只有等主進程執行完全部代碼後,纔會執行 異步代碼

  • setTimeout(function(){
        console.log("異步代碼");
    }, 0);
    
    console.log("主進程代碼");
    // 打印:
    // 主進程代碼
    // 異步代碼

 


 9. eval 命令

接受一個字符串做爲參數,並將這個字符串看成語句執行。


  • eval('var a = 1;');
    a    // 1
  • 若是參數字符串沒法看成語句運行,那麼就會報錯。    // Uncaught SyntaxError: Invalid or unexpected token
  • 若是eval的參數不是字符串,那麼會原樣返回。
  • eval 沒有本身的做用域都在當前做用域內執行,所以可能會修改當前做用域的變量的值,形成安全問題。
  • JavaScript 規定,若是使用嚴格模式eval內部聲明的變量,不會影響到外部做用域。
    • 不過,即便在嚴格模式下,eval依然能夠讀寫當前做用域的變量。
      (function f() {
        'use strict';
        var foo = 1;
        eval('foo = 2');
        console.log(foo);  // 2
      })()

      因此通常不推薦使用。一般狀況下,eval 最多見的場合是解析 JSON 數據的字符串,不過正確的作法應該是使用原生的JSON.parse方法

  • eval 的別名調用的形式五花八門,只要不是直接調用,都屬於別名調用,由於引擎只能分辨eval()這一種形式是直接調用。
    eval.call(null, '...');
    window.eval('...');
    (1, eval)('...');
    (eval, eval)('...');
    
    // 上面這些形式都是eval的別名調用,做用域都是全局做用域。

     


內存溢出

須要的內存,大於現有內存

內存泄漏

資源佔有一片空間,而不曾釋放,致使這片內存沒法被使用

及時 obj = null; 來釋放內存

  • 意外的全局變量

在函數中, 未使用 var 關鍵字定義變量, 致使直接聲明瞭一個全局變量

  • 沒有及時清理的 定時器 或者 回調函數

 

  • 閉包

 


 

10. 屬性描述符

相關文章
相關標籤/搜索