聊聊new Function這個陌生面孔及函數作用域和函數執行順序

咱們平時定義一個函數是怎麼個定義法呢, 咱們以計算3和5的和爲例:
你首先想到的可能就是,這還不簡單,so easy,你看:

  function sum(a, b) {
    return a + b;
  }

或者用咱們的ES6

  let sum = (a, b) => a + b;

恭喜你答對了,其實啊,函數的定義常見的也就是上面的這種方式,沒有什麼特別的花樣,但是今天咱們聊聊new Function這個新朋友,其實算是大佬,叫做:運籌於帷幄之中,決勝千里之外,看看它是怎麼創建函數的:

  let sum = new Function('a', 'b', 'return a + b');

你的第一反應可能是 我擦,這是什麼變態的寫法,怪不得沒人用,哈哈,說的還是挺有道理的,接下來呢,咱們就來把一把這個函數到底是怎麼回事

什麼是函數

一個通俗的說法就是:函數簡單的說就是重複執行的代碼塊。函數是這樣的一段JavaScript 代碼,它只定義一次,但可能被執行或調用任意次。

函數的定義

  • 函數聲明式定義 function student(){}
    這種定義方式,會將函數聲明提升到該函數所在作用域的最開頭,也是就無論你在這個函數的最小作用域的那兒使用這種方式聲明的函數,在這個作用域內,你都可以調用這個函數爲你所用

  • 函數表達式 var student = function(){}
    此方式定義的函數,只能在該作用域中,這段賦值代碼執行之後才能通過fun()調用函數,否則,由於變量聲明提升,fun === undefined

  • new Function 形式 var student = new Function(arg1 , arg2 ,arg3 ,…, argN , body)
    Function構造函數所有的參數都是字符串類型。除了最後一個參數, 其餘的參數都作爲生成函數的參數即形參。這裏可以沒有參數。最後一個參數, 表示的是要創建函數的函數體。

首先,看上面的解釋啊,說的是啥意思啊,函數聲明,變量聲明的,最開頭的,是不是挺頭大的,來,聽君一番解釋,你就明白了:

變量提升

首先,拋開函數,我用一個很簡單的例子來說明這個問題啊:

  console.log(foo); // undefined
  var foo = '秦時明月';
  console.log(foo)  // 秦時明月

其實上面的代碼就是

  var foo;
  console.log(foo);
  foo = '秦時明月';
  console.log(foo);

此時,首先foo提前聲明,直至到賦值,之前foo的值均爲undefined,函數聲明也是同樣的道理,也就是說,你直接聲明個函數

  function foo() {}

函數提升,相當於

  var foo;
  foo = function() {};

來看個例子吧:

  console.log(foo);  // foo() {console.log(123);}
  console.log(foo());//undefined  爲啥是undefined下面我會說
  var foo = 456;
  function foo() {
    console.log(123);//123
  }
  console.log(foo); //undefined
  foo = 789;
  console.log(foo);//456
  console.log(foo());// 789

來,咱們分析一下執行結果啊,第一句打印的結果是foo這個函數體,咱們可以看出
梳理一下js的執行順序,上述代碼等同於等於:

  var foo;                           // (0)   聲明foo,並賦值爲undefined
  foo = function() {                 // (1-1) 將一個函數賦值給foo
    console.log(123);                //(2-1) 執行函數,打印123
  }
  console.log(foo);                  //(1-2) foo此時爲函數體的引用
  console.log(foo());                //(2-2)首先執行foo()函數,進入(2-1),執行完畢後,函數未有返回體即return,故打印undefined
  foo = 456;                         //(3)再次給foo重新複製一個基本數據類型的456
  console.log(foo);                  // (4)打印出456
  // 再次賦值
  foo = 789                          // (5)又再一次給foo重新複製一個基本數據類型的789
  console.log(foo);                  // (6)打印出789
  console.log(foo());                // (7)此時foo非函數體引用,故不能使用函數調用方法,故報錯 foo is not a function

來在分析一個函數的執行順序:

  console.log(person)
  console.log(fun)
  var person = '樂鳴'
  console.log(person)

  function fun () {
      console.log(person)
      var person = '秦時明月'
      console.log(person)
  }
  fun()
  console.log(person)

等同於

  var fun;
  fun = function() {
    console.log(person)
    var person = '秦時明月'
    console.log(person)
  };
  console.log(person);   // undefined
  console.log(fun);     // 函數體function(){...}
  var person = '清風';
  console.log(person);  // 清風
  fun();               // 執行fun函數   // undefined // 秦時明月
  console.log(person); // 清風

咱們可以看到,爲啥類似於這樣一個函數

  var person = '清風明月';
  function fun(){
    console.log(person);
    var person = '秦時明月';
    console.log(person);
  }

打印出來第一個person是個undefined呢,其實這有牽涉到函數的作用域了,根據犀牛書中說道:
代碼在執行時,首先會檢查person是否爲函數私有變量,如果是,則停止查找全局變量中的person,並且該函數中的person僅僅在函數體中生效,所以當檢測到person爲fun的私有變量是,如果前置,則直接返回undefined也就理所當然了。

總結

  • 第一種和第二種函數的定義的方式其實是第三種new Function 的語法糖,當我們定義函數時候都會通過 new Function 來創建一個函數,只是前兩種爲我們進行了封裝,我們看不見了而已,js 中任意函數都是Function 的實例。
  • ECMAScript 定義的 函數實際上是功能完整的對象。
  • 函數提升優先級高於變量提升,且不會被同名變量聲明時覆蓋,但是會被變量賦值後覆蓋,即函數聲明變量init時,優先級高於其他變量,並置於作用域最頂
  • 函數私有變量優先級高於全局變量,並且函數私有變量僅在該函數體中有效,一般不影響全局作用域
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章