javascript
export function函数传参_从底层看前端(七)—— JavaScript到底有多少种函数?
在上篇文章中我們了解到了執(zhí)行上下文是什么,也知道了任何語句的執(zhí)行都會依賴特定的上下文。
一旦上下文被切換,整個語句的效果可能都會發(fā)生變化。那么,切換上下文的時機(jī)就顯得非常重要。
在JavaScript中,切換上下文最主要的場景就是函數(shù)調(diào)用。在這篇文章中,我們就來講講函數(shù)調(diào)用切換上下文的事情。
在我們講函數(shù)調(diào)用之前,我們先來認(rèn)識一下函數(shù)家族。
函數(shù)
在ES2018中,函數(shù)已經(jīng)是一個很復(fù)雜的體系了,我在這里整理了一下。
第一種,普通函數(shù),用function關(guān)鍵字定義的函數(shù)。示例:
function foo(){//code } 第二種,箭頭函數(shù):用 => 運算符定義的函數(shù)。示例:
const foo = ()=>{//code } 第三種,在class中定義的函數(shù)。示例:
class C {foo(){//code} } 第四種,生成器函數(shù):用 function* 定義的函數(shù)。示例:
function* foo(){//code } 第五種:類。用class定義的類,實際上也是函數(shù)。
示例:
class Foo {constructor(){//code} } 第六,七,八種,異步函數(shù):普通函數(shù),箭頭函數(shù)和生成器函數(shù)前加上async關(guān)鍵字。示例:
async function foo(){// code } const foo = async () => {// code } async function foo*(){// code }ES6以來,大量加入的新語法極大地方便了我們編程的同時,也增加了很多我們理解的心智負(fù)擔(dān)。要想認(rèn)識這些函數(shù)的執(zhí)行上下文切換,我們必須要對他們行為上的區(qū)別有所了解。
對普通變量而言,這些函數(shù)并沒有本質(zhì)區(qū)別,都是遵循了'繼承定義時的環(huán)境'的規(guī)則,它們的一個行為差異在于this關(guān)鍵字。
那么,this 關(guān)鍵字是什么呢?
this 關(guān)鍵字行為
this 是JavaScript中的一個關(guān)鍵字,它的使用方法類似一個變量(但是this跟變量有很多不同)。
this是執(zhí)行上下文中很重要的一個組成部分。同一個函數(shù)調(diào)用方式不同,得到的this值也不同,我們看一個例子:
function showThis(){console.info(this) }var o = {showThis: showThis }showThis(); //global o.showThis(); //o在這個例子中,我們定義了函數(shù)showThis,我們把它賦值給了一個對象o的屬性,然后嘗試分別使用兩個引用來調(diào)用同一個函數(shù),結(jié)果得到了不同的this值。
普通函數(shù)的 this 值由'調(diào)用它所使用的的引用'來決定,其中奧秘就在于:我們獲取函數(shù)的表達(dá)式,它實際上返回的并非函數(shù)本身,而是一個 Reference 類型(其中標(biāo)準(zhǔn)類型之一)。
Reference 類型由兩部分組成,一個對象和一個屬性值。不難理解 o.showThis 產(chǎn)生的Reference類型,即由對象 o 和屬性'showThis'構(gòu)成。
當(dāng)做一些算術(shù)運算時,Reference類型會被解引用,即獲取真正的值來參與運算,而類似函數(shù)調(diào)用,delete等操作,都需要用到 Reference 類型中的對象。
在這個例子中,Reference類型中的對象被當(dāng)做this值,傳入了執(zhí)行函數(shù)的上下文中。
至此,我們對this的解釋已經(jīng)非常清晰了,調(diào)用函數(shù)時使用的引用,決定了函數(shù)執(zhí)行時刻的this值。
實際上從運行時的角度來看,this跟面向?qū)ο蠛翢o關(guān)聯(lián),它是與函數(shù)調(diào)用的表達(dá)式相關(guān)。
這個設(shè)計來自JavaScript早年,通過這樣的方式,巧妙地模擬了Java的語法,但是仍然保留了純粹的'無類'運行時設(shè)施。
如果,我們把這個例子稍作修改,換成箭頭函數(shù),結(jié)果就不一樣了。
const showThis = () => {console.log(this); }var o = {showThis: showThis }showThis(); // global o.showThis(); // global我們看到,改為箭頭函數(shù)后,無論用什么來調(diào)用它,都不影響它的this值。
接下來我們看看'方法',它的行為又不一樣了:
class C {showThis() {console.log(this);} } var o = new C(); var showThis = o.showThis;showThis(); // undefined o.showThis(); // o這里我們創(chuàng)建了一個類C,并且實例化出對象o,再把 o 的方法賦值給了變量 showThis。
這時候,我們使用 showThis 這個引用去調(diào)用方法時,得到了undefined。
所以,在方法中,我們看到this的行為也不大一樣,它得到了undefined的結(jié)果。
按照我們上面的方法,不難驗證出:生成器函數(shù),異步函數(shù)和異步普通函數(shù)跟普通函數(shù)行為是一致的,異步箭頭函數(shù)與箭頭函數(shù)的行為是一致的。
this關(guān)鍵字機(jī)制
說完了this行為,我們再來簡單談?wù)勗贘avaScript內(nèi)部,實現(xiàn)this這些行為的機(jī)制。
函數(shù)能夠引用定義時的變量,如上文分析,函數(shù)能記住定義時的this,因此,函數(shù)內(nèi)部必須有一個機(jī)制來保存這些信息。
在JavaScript標(biāo)準(zhǔn)中,為函數(shù)規(guī)定了用來保存定義是上下文的私有屬性[[Environment]]。
當(dāng)一個函數(shù)執(zhí)行時,會創(chuàng)建一條新的執(zhí)行環(huán)境記錄,記錄的外層詞法環(huán)境(outer lexical environment)會被設(shè)置成函數(shù)的[[Environment]]。
這個動作就是切換上下文了,我們假設(shè)有這樣的代碼:
var a = 1; foo();在別處定義了foo:
var b = 2; function foo(){console.log(b); // 2console.log(a); // error }這里的foo能夠訪問 b (定義時的詞法環(huán)境),卻不能訪問 a (執(zhí)行時的詞法環(huán)境),這就是執(zhí)行上下文的切換機(jī)制了。
JavaScript用一個棧來管理執(zhí)行上下文,這個棧的每一項又包含一個鏈表。如下圖所示:
當(dāng)函數(shù)調(diào)用時,會入棧一個新的執(zhí)行上下文,函數(shù)調(diào)用結(jié)束時,執(zhí)行上下文被出棧。
而this則是一個更為復(fù)雜的機(jī)制,JavaScript標(biāo)準(zhǔn)定義了[[thisMode]]私有屬性。
[[thisMode]]私有屬性有三個取值
lexical:表示從上下文中找this,這對應(yīng)了箭頭函數(shù)。global:表示this為undefined時,取全局對象,對應(yīng)了普通函數(shù)。
strict:當(dāng)嚴(yán)格模式時使用,this嚴(yán)格按照調(diào)用時傳入的值,可能為null或者undefined。
非常有意思的是,方法的行為跟普通函數(shù)有差異,恰恰是因為class設(shè)計成了默認(rèn)按照strict模式執(zhí)行。
我們可以用strict達(dá)成與上一節(jié)中方法的例子中一樣的效果。
"use strict" function showThis(){console.log(this); }var o = {showThis: showThis }showThis(); // undefined o.showThis(); // o函數(shù)創(chuàng)建新的執(zhí)行上下文中詞法環(huán)境記錄時,會根據(jù)[[thisMode]]來標(biāo)記新紀(jì)錄的[[ThisBindingStatus]]私有屬性。
代碼執(zhí)行遇到this時,會組成檢查當(dāng)前詞法環(huán)境記錄中的[[ThisBindingStatus]],當(dāng)我們找到有this的環(huán)境記錄時獲取this的值。
這樣的規(guī)則的實際效果時,嵌套的箭頭函數(shù)中的代碼都指向外層this,例如:
var o = {} o.foo = function foo(){console.log(this);return () => {console.log(this);return () => console.log(this);} }o.foo()()(); // o, o, o在上面的例子中,我們定義了三層嵌套的函數(shù),最外層的是普通函數(shù),兩層都是箭頭函數(shù)。
這里調(diào)用三個函數(shù),獲得的this值是一樣的,對象都是o。
JavaScript還提供了一系列函數(shù)的內(nèi)置方法來操作this值,下面我們來了解一下。
操作this的內(nèi)置函數(shù)
Function.prototype.call和Function.prototype.apply可以執(zhí)行函數(shù)調(diào)用時傳入的this值,示例如下:
function foo(a, b, c){console.log(this);console.log(a, b, c); } foo.call({}, 1, 2, 3); foo.apply({}, [1, 2, 3]);這里call和apply作用是一樣的,只是傳參方式有區(qū)別。
此外,還有Function.prototype.bind 它可以生成一個綁定的函數(shù),這個函數(shù)的this值固定了參數(shù)。
function foo(a, b, c){console.log(this);console.log(a, b, c); } foo.bind({}, 1, 2, 3)();有趣的是,call,bind和apply用于不接受this的函數(shù)類型如箭頭,class都不會報錯。
這時候,它們無法實現(xiàn)改變this的能力,但是可以實現(xiàn)傳參。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的export function函数传参_从底层看前端(七)—— JavaScript到底有多少种函数?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 西南科技大学OJ题 平衡二叉树的判
- 下一篇: 算法 --- [map的使用]求最大和