官术网_书友最值得收藏!

4.1 定義

在使用函數(shù)之前,需要定義函數(shù),有多種定義方式可以使用。

通過function關(guān)鍵字聲明一個(gè)函數(shù),示例如下。

      function foo(a){
          console.log(a);
      }

在之前,我們已經(jīng)了解了變量的聲明提前(var hoisting),函數(shù)也是存在聲明提前的,上述代碼中,通過function定義的函數(shù)可以在聲明之前調(diào)用,示例如下。

      foo();

function foo(a){ console.log(a); }

函數(shù)聲明提前與變量聲明提前的不同點(diǎn)在于,函數(shù)聲明會(huì)在執(zhí)行環(huán)境創(chuàng)建時(shí)將函數(shù)的引用地址賦值給函數(shù)名,因此,通過函數(shù)聲明創(chuàng)建的函數(shù)可以在函數(shù)聲明前調(diào)用。

通過表達(dá)式定義一個(gè)函數(shù),示例如下。

      const foo = function(a){
          console.log(a);
      };

也可以在函數(shù)表達(dá)式中為函數(shù)創(chuàng)建一個(gè)名稱,不過這個(gè)名稱只能在函數(shù)內(nèi)部使用,在遞歸時(shí)會(huì)用到這種方式,示例如下。

通過構(gòu)造函數(shù)定義一個(gè)函數(shù),示例如下。

      const foo =  new Function("a","console.log(a);");

4.1.1 返回值

函數(shù)擁有返回值,默認(rèn)為undefined,可在函數(shù)內(nèi)部使用return關(guān)鍵字終止函數(shù)執(zhí)行并返回指定值。

示例代碼:

4.1.2 箭頭函數(shù)(Arrow Function)

箭頭函數(shù)是ES6中新增的一種函數(shù)定義方式,使用=>來快速定義一個(gè)函數(shù),箭頭函數(shù)要比普通函數(shù)/函數(shù)表達(dá)式更簡(jiǎn)潔,示例如下。

      const foo = x=>x*x;

foo(2); // -> 4

上述代碼中的foo函數(shù)等價(jià)于:

      const foo  = function(x){
          return x*x
      }
1. 箭頭函數(shù)的括號(hào)問題

如果函數(shù)的參數(shù)只有一個(gè),可以省略參數(shù)的圓括號(hào),但如果要定義的函數(shù)有多個(gè)參數(shù),則不能省略圓括號(hào),需要使用圓括號(hào)包裹這些參數(shù),示例如下。

沒有參數(shù)時(shí),圓括號(hào)也不能省略,示例如下。

      () => { ... }
2. 箭頭函數(shù)的花括號(hào)問題

你可能已經(jīng)注意到了,上面的箭頭函數(shù)中,有些有花括號(hào),有些則沒有。如果函數(shù)中只包含一條語句,可以省略花括號(hào),反之,則不能省略花括號(hào),示例如下。

3. 箭頭函數(shù)中的this

箭頭函數(shù)中的this是基于詞法作用域(Lexical scoping,又稱“靜態(tài)作用域”)的,放棄了所有普通this綁定的規(guī)則。在詞法作用域中,一切變量(包括this)都是根據(jù)作用域鏈來查找的,在創(chuàng)建箭頭函數(shù)時(shí),就保存好了上層上下文的作用域鏈,在箭頭函數(shù)中訪問this時(shí),就會(huì)去作用域鏈中查找,因此,箭頭函數(shù)看起來像是“繼承”了外層函數(shù)的this綁定。

示例代碼:

上述代碼中,箭頭函數(shù)的this指向的是函數(shù)定義時(shí)的作用域Counter,其內(nèi)部擁有num屬性,可以被訪問;而不使用箭頭函數(shù)的,其this按照普通this的綁定規(guī)則,指向執(zhí)行時(shí)的作用域window(setTimeout中的代碼是在全局作用域下執(zhí)行的,如果沒有綁定this,內(nèi)部代碼的this指向的就是window),而window下沒有num2屬性,因此,num2為undefined,對(duì)undefined進(jìn)行遞增時(shí)返回NaN。

4.1.3 關(guān)于this

上文中,我們提到了this的綁定規(guī)則,簡(jiǎn)單來說,函數(shù)執(zhí)行時(shí)的上下文是誰,this就指向誰,與函數(shù)在何處定義沒有任何關(guān)系。一般情況下,理解了這句話就夠了,但有時(shí)候,我們可能不太確定this的指向,示例如下。

為了徹底理解this值的問題,我們就需要從其源頭出發(fā)。

說到this,就不得不提到函數(shù),函數(shù)調(diào)用的表達(dá)式為:MemberExpression Arguments。

其中,MemberExpression表示成員表達(dá)式,包含以下部分。

  • PrimaryExpression
  • FunctionExpression
  • MemberExpression[ Expression ]
  • MemberExpression.IdentifierName
  • new MemberExpression Arguments

來看幾個(gè)例子。

簡(jiǎn)單地歸納一下,MemberExpression為括號(hào)前面的部分,MemberExpression后面的部分即為Arguments。

在前文我們介紹了JavaScript中的一種虛擬數(shù)據(jù)類型——Reference。之所以稱它虛擬,是因?yàn)槠渲淮嬖谟贓CMAscript規(guī)范中。

Reference由三部分構(gòu)成:

base的取值可以是undefined、對(duì)象、布爾值、字符串、Symbol值、數(shù)字、Environment Record(環(huán)境記錄,也可以看作是作用域)。

對(duì)于foo.bar(),其MemberExpression為foo.bar,其表達(dá)式為MemberExpression.Identifier Name,ES6規(guī)范當(dāng)中對(duì)該表達(dá)式的返回值進(jìn)行了描述:Return a value of type Reference whose base value is bv and whose referenced name is propertyNameString, and whose strict reference flag is strict。

也就是說,表達(dá)式MemberExpression . IdentifierName返回的是一個(gè)Reference類型的數(shù)據(jù),那么,我們就能知道foo.bar返回的Reference類型的值如下。

了解了MemberExpression和Reference后,我們就可以接觸this了。

關(guān)于函數(shù)調(diào)用時(shí)的執(zhí)行過程,ECMAscript規(guī)范中對(duì)其進(jìn)行了描述:

EvaluateDirectCall函數(shù)內(nèi)部會(huì)嘗試調(diào)用內(nèi)部方法[[Call]],即調(diào)用函數(shù)實(shí)際上就是調(diào)用Object的內(nèi)部方法[[Call]],其中的thisValue即this,Arguments為參數(shù)。

說到底,我們關(guān)心的是this,而不是函數(shù)如何執(zhí)行,將上面的過程簡(jiǎn)化,剝離出與this有關(guān)的部分:

綜上,得出如下結(jié)論。

① this值取決于MemberExpression的返回值ref。

② 如果ref類型為Reference,且ref的base為Object,若ref已有this值,則this值為ref的this值,否則this值為GetBase(ref)。

③ 如果ref類型為Reference,且ref的base為Boolean、String、Number之一(簡(jiǎn)稱Environment Record),則this值為GetBase(ref).WithBaseObject()。

④ 如果ref類型不是Reference,則this值為undefined(非嚴(yán)格模式下,this的值為undefined時(shí),會(huì)被轉(zhuǎn)換為全局對(duì)象)。

有了這些結(jié)論,我們得到之前那幾行代碼的返回結(jié)果:

      foo.bar();
      (foo.bar)();
      (foo.bar = foo.bar)();
      (true && foo.bar)();
      (foo.bar, foo.bar)();

在前文中,我們已經(jīng)值得foo.bar返回值是Reference類型:

而其reference的base為Object,根據(jù)步驟②,因?yàn)閎ar不是一個(gè)箭頭函數(shù),因此,其this值即為foo,根據(jù)作用域的查找機(jī)制,foo.bar()的返回值就是2了。

現(xiàn)在,我們確定this值不再靠感覺了,那么,其他幾個(gè)例子的this值也就很容易可以確定了。

對(duì)于(foo.bar)(),其MemberExpression為(foo.bar),其表達(dá)式為(Expression),ES6規(guī)范當(dāng)中對(duì)該表達(dá)式的返回值進(jìn)行了描述:Return the result of evaluating Expression. This may be of type Reference。

也就是說,表達(dá)式(Expression)返回值取決于括號(hào)內(nèi)Expression的運(yùn)算結(jié)果,其結(jié)果可能是一個(gè)Reference類型的數(shù)據(jù),那么(foo.bar)返回的是foo.bar,(foo.bar)()的返回值自然就是2了。

同理,(foo.bar = foo.bar)、(true && foo.bar)、(foo.bar, foo.bar)即foo.bar = foo.bar、true &&foo.bar、foo.bar, foo.bar。

而賦值運(yùn)算符、邏輯運(yùn)算符、逗號(hào)運(yùn)算符的返回值均為:Return GetValue(rref)

GetValue(V)方法返回的是一個(gè)真實(shí)值,不是Reference類型,this值為undefined,非嚴(yán)格模式下會(huì)被轉(zhuǎn)換為全局對(duì)象。在瀏覽器中,全局對(duì)象即window,但const聲明的變量不會(huì)成為window的屬性,因此,后面3個(gè)例子的返回值為undefined。

1. bind

在箭頭函數(shù)之前,可以使用bind來創(chuàng)建一個(gè)指定了this值的新函數(shù),示例如下。

      // 注意,bind 返回的是一個(gè)函數(shù)
      getTitle.bind(foo)();  // > JavaScript

foo.getTitle.bind(window)(); // > undefined
2. apply與call

與bind創(chuàng)建一個(gè)函數(shù)的副本不同,apply與call直接修改函數(shù)執(zhí)行時(shí)的上下文,并執(zhí)行該函數(shù),示例如下。

      getTitle.apply(foo);  // > JavaScript
      getTitle.call(foo);   // > JavaScript

apply與call方法的作用相同,都是用來修改函數(shù)執(zhí)行時(shí)的上下文的,只在傳遞函數(shù)的參數(shù)時(shí)有區(qū)別,示例如下。

apply在傳遞參數(shù)時(shí),接收的是參數(shù)組成的數(shù)組,call則依次傳遞每個(gè)參數(shù),接收參數(shù)列表。另外,這兩個(gè)方法都支持arguments。

在上面的示例中,傳遞給apply和call的this值是window,如果指定的值為null/undefined,this值會(huì)自動(dòng)指向全局對(duì)象window,示例如下。

但在嚴(yán)格模式下,null和undefined分別指向null和undefined,示例如下。

練習(xí)

  • 使用多種方式定義一個(gè)函數(shù)。
主站蜘蛛池模板: 肥乡县| 大方县| 通山县| 长海县| 东阳市| 依兰县| 邯郸市| 昆明市| 大理市| 赫章县| 策勒县| 鲁山县| 若尔盖县| 泰兴市| 柳州市| 高雄市| 城固县| 融水| 武邑县| 广东省| 郧西县| 天门市| 内乡县| 宁陕县| 工布江达县| 洮南市| 福泉市| 涞水县| 延川县| 邳州市| 兴业县| 从化市| 五指山市| 长治市| 凤山市| 军事| 清涧县| 丽江市| 阳原县| 镇沅| 友谊县|