- JS全書:JavaScript Web前端開發(fā)指南
- 高鵬
- 2294字
- 2020-09-18 10:29:18
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ù)。
- C語言程序設(shè)計(jì)案例教程
- Python Deep Learning
- 機(jī)械工程師Python編程:入門、實(shí)戰(zhàn)與進(jìn)階
- Microsoft Dynamics GP 2013 Reporting, Second Edition
- 軟件品質(zhì)之完美管理:實(shí)戰(zhàn)經(jīng)典
- Building Android UIs with Custom Views
- 一步一步跟我學(xué)Scratch3.0案例
- 基于GPU加速的計(jì)算機(jī)視覺編程:使用OpenCV和CUDA實(shí)時(shí)處理復(fù)雜圖像數(shù)據(jù)
- Modular Programming with JavaScript
- SQL Server 2012 數(shù)據(jù)庫應(yīng)用教程(第3版)
- Java RESTful Web Service實(shí)戰(zhàn)
- PHP 7 Programming Blueprints
- 生成藝術(shù):Processing視覺創(chuàng)意入門
- Getting Started with SQL Server 2014 Administration
- C#編程魔法書