成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

從規(guī)范去看Function.prototype.call到底是怎么工作的?

DandJ / 2480人閱讀

摘要:抽象操作是在調(diào)用函數(shù)對象的內(nèi)部的方法。指的是調(diào)用函數(shù),指的是的值,然后是傳入到內(nèi)部方法相應(yīng)參數(shù)的值。切換上下文執(zhí)行,為函數(shù)調(diào)用棧在尾部調(diào)用函數(shù)做準備,切換運行中執(zhí)行上下文,實現(xiàn)上下文的動態(tài)改變。萬事具備,執(zhí)行,調(diào)用函數(shù)即可。

今天在學(xué)習(xí)前端工程化的過程中,遇到一個是實驗中的css屬性:fullscreen,有這樣一個例子:fullscreen偽元素官方demo

:fullscreen Demo

This text will become big and red when the browser is in fullscreen mode.

其中有一段代碼:

function enterFullscreen() {
    fullscreenFunc.call(fullscreenDiv);
}?

雖然結(jié)合上下文能看出來是為了兼容瀏覽器的fullscreen API,但是其中的Function.prototype.call()我自己其實沒有特別深究過。

為什么不直接fullscreenFunc(),這樣不能使得fullscreenDiv全屏嗎?

大家都說call與apply都是為了動態(tài)改變this的,僅僅是傳入?yún)?shù)的方式不同,call傳入(this,foo,bar,baz),而apply傳入(this,[foo,bar,baz])那么事實真如大家所說的那樣嗎?既然apply能動態(tài)改變this,那么為什么還要多此一舉開放一個call?
這其中肯定隱藏著一些秘密,那就是有些事情是apply做不到,而call可以勝任的。
繼續(xù)我們的啃規(guī)范之旅,去深入到Function.prototype.call()的內(nèi)部,徹底把它搞清楚。

19.2.3.4 Function.prototype.call (thisArg , ...args)
When the?call?method is called on an object?func with argument,?thisArg?and zero or more?args, the following steps are taken:

If?IsCallable(func) is?false, throw a?TypeError?exception.

Let?argList?be an empty?List.

If this method was called with more than one argument then in left to right order, starting with the second argument, append each argument as the last element of?argList.

Perform?PrepareForTailCall().

Return?Call(func,?thisArg,?argList).

The?length?property of the?call?method is?1.

當(dāng)call方法在帶參數(shù)的對象的方法上調(diào)用時,thisArg和零個或者對個參數(shù),會進行如下的步驟:

如果IsCallable(func)返回false,拋出TypeError異常。

定義argList為一個空的列表。

如果方法按照從左到右傳入的參數(shù)個數(shù)不止一個,從第二個參數(shù)開始,依次將每個參數(shù)從尾部添加到argList數(shù)組。

執(zhí)行PrepareForTailCall()

返回Call(func,thisArg,argList)

有3個點看不懂:

IsCallable(func)

PrepareForTailCall()

Call(func,thisArg,argList)

這些同樣在規(guī)范中有對應(yīng)描述:

7.2.3IsCallable ( argument )

The abstract operation IsCallable determines if?argument, which must be an?ECMAScript language valueor a?Completion Record, is a callable function with a [[Call]] internal method.

重點在于is a callable function with a [[Call]] internal method.,也就是說執(zhí)行isCallable(func)運算的func,如果函數(shù)內(nèi)部有一個內(nèi)在的[[Call]]方法,那么運算結(jié)果為true,也就是說這個函數(shù)是可調(diào)用的的。(callable)

14.6.3Runtime Semantics: PrepareForTailCall ( )

The abstract operation PrepareForTailCall performs the following steps:

Let?leafContext?be?the running execution context.

Suspend?leafContext.

Pop?leafContext?from?the execution context stack. The?execution context?now on the top of the stack becomes?the running execution context.

Assert:?leafContext?has no further use. It will never be activated as?the running execution context.

A tail position call must either release any transient internal resources associated with the currently executing function?execution context?before invoking the target function or reuse those resources in support of the target function.

ReturnIfAbrupt(argument).

If?Type(argument) is not Object, return?false.

If?argument?has a [[Call]] internal method, return?true.

Return?false.

雖然看不懂,但還是得硬著頭皮學(xué)習(xí)一波。
抽象操作PrepareForTailCall執(zhí)行以下幾個步驟:

讓葉子上下文成為運行中的執(zhí)行上下文

暫停葉子上下文

頂葉子上下文來自執(zhí)行上下文的堆。當(dāng)前的在堆頂部的執(zhí)行上下文成為運行中的執(zhí)行上下文

斷言:葉子上下文沒有其他作用。它再也不會作為運行中執(zhí)行上下文被激活。

在調(diào)用目標(biāo)函數(shù)或者重用這些資源去支持目標(biāo)函數(shù)之前,尾部位置調(diào)用必須釋放與當(dāng)前執(zhí)行函數(shù)上下文相關(guān)的瞬態(tài)內(nèi)部資源。

ReturnIfAbrupt(argument).

如果Type(argument)不是對象,返回false。

如果argument含有[[call]]內(nèi)部方法,返回true。

返回 false

看懂一個大概,是為了在函數(shù)調(diào)用棧的尾部調(diào)用當(dāng)前函數(shù)做準備,其中的運行中執(zhí)行上下文,正是我們所說的this動態(tài)改變的原因,因為本質(zhì)上this改變并不僅僅是指向的對象發(fā)生變化,而是連帶著與其相關(guān)的上下文都發(fā)生了變化。

所以說,這一步是this動態(tài)改變的真正原因。

7.3.12Call(F, V, [argumentsList])

The abstract operation Call is used to call the [[Call]] internal method of a function object. The operation is called with arguments?F,?V?, and optionally?argumentsList?where?F?is the function object,?V?is an?ECMAScript language value?that is the?this?value of the [[Call]], and?argumentsList?is the value passed to the corresponding argument of the internal method. If argumentsList?is not present, an empty?List?is used as its value. This abstract operation performs the following steps:

ReturnIfAbrupt(F).

If?argumentsList?was not passed, let?argumentsList?be a new empty?List.

If?IsCallable(F) is?false, throw a?TypeError?exception.

Return?F.[[Call]](V,?argumentsList).

Call抽象操作是在調(diào)用函數(shù)對象的內(nèi)部的[[Call]]方法。這個操作參數(shù)類型包括F,V以及可選的argumentList。F指的是調(diào)用函數(shù),V指的是[[Call]]的this值,然后argumentsList是傳入到[[Call]]內(nèi)部方法相應(yīng)參數(shù)的值。如果argumentList不存在,那么argumentList將被置為一個空數(shù)組。這個方法按照下列幾步執(zhí)行:

ReturnIfAbrupt(F)

如果沒傳入argumentList,那么argumentList將會被置為一個空數(shù)組。

如果IsCallable(F)是false,返回TypeError異常。

返回 F.[[call]](V,argumentsList).

所以Function.prototype.call(this,...args)執(zhí)行過程現(xiàn)在很明了:

判斷傳入的func是否有[[call]]屬性,有[[call]]才意味著函數(shù)能被調(diào)用,否則拋出TypeError異常。

定義argList為一個空的列表。

傳參:如果方法按照從左到右傳入的參數(shù)個數(shù)不止一個,從第二個參數(shù)開始,依次將每個參數(shù)從尾部添加到argList數(shù)組。

切換this上下文:執(zhí)行PrepareForTailCall(),為函數(shù)調(diào)用棧在尾部調(diào)用函數(shù)做準備,切換運行中執(zhí)行上下文,實現(xiàn)this上下文的動態(tài)改變。

萬事具備,執(zhí)行Call(func,thisArg,argList),調(diào)用函數(shù)即可。

回到我們的例子:

fullscreenFunc.call(fullscreenDiv);

func為fullscreenDiv DOM 節(jié)點的方法:"requestFullscreen" || "mozRequestFullScreen" || "msRequestFullscreen"

|| "webkitRequestFullScreen",由于是fullscreen API,所以isCallable(func)返回true。

定義一個argList空數(shù)組用來傳參。

傳參:由于fullscreenFunc.call(fullscreenDiv);只有一個參數(shù),所以直接傳入argList空數(shù)組。

切換this上下文:停止當(dāng)前的this葉子上下文,也就是window,切換到fullscreenDiv的執(zhí)行上下文。

由于當(dāng)前瀏覽器為chrome,因此執(zhí)行 fullscreenDiv.webkitRequestFullscreen.[[call]](this,[])。

因此我們之前提的那個為什么不直接fullscreenFunc(),這樣不能使得fullscreenDiv全屏嗎?,答案就很清楚了?不能。
為什么呢?

var fullscreenFunc   = fullscreenDiv.requestFullscreen;
if (!fullscreenFunc) {
     ["mozRequestFullScreen", "msRequestFullscreen","webkitRequestFullScreen"].forEach(function (req) {
        fullscreenFunc = fullscreenFunc || fullscreenDiv[req];
     });
}

下面的代碼,僅僅是獲得了fullscreenDiv對象的fullscreen request API的引用,而fullscreenFunc的作用域是全局的window對象,也就是this的當(dāng)前指向為window。

而我們是想觸發(fā)window的子對象fullscreenDiv的全屏方法,所以需要將this上下文切換為fullscreenDiv,這就是不直接調(diào)用fullscreenFunc(),需要fullscreenFunc.call(fullscreenDiv)的原因。

最近在看龍書,第一章講到動態(tài)語言與靜態(tài)語言的區(qū)別,龍書中講到"運行時決定作用域的語言是動態(tài)語言,在編譯時指定作用域的預(yù)言是靜態(tài)語言"。例子中的以function關(guān)鍵字定義的類,this運行中執(zhí)行上下文的切換,恰恰證明了javascript是一門動態(tài)語言;再舉個形象的靜態(tài)語言的例子,java會使用class關(guān)鍵字構(gòu)建類,在類內(nèi)部使用private,public等關(guān)鍵字去指定作用域,編譯時就會去約束其作用域,具有非常強的約束性,this始終指向當(dāng)前類。

剛才和一個java后端同事確認,java也有this關(guān)鍵字,但是僅能使用當(dāng)前類中的方法,B類可以調(diào)用A類中的方法,比如通過super實現(xiàn)對父類的繼承,但是當(dāng)前類中的this指向是不會變的。

js中的this,是可以通過call或者apply進行動態(tài)切換從而去調(diào)用其他類中的方法的,B類不能調(diào)用A類中的方法。(注意:我們這里的類指的是以function關(guān)鍵字進行定義的類,暫時不考慮es6的class關(guān)鍵字構(gòu)造類的方式。)

說了這么多,我們再來強調(diào)下重點:

加粗的部分是重點!
加粗的部分是重點!
加粗的部分是重點!

拋開V8引擎內(nèi)部執(zhí)行call和apply的原理不說,二者最終實現(xiàn)的都是this上下文的動態(tài)切換,所以就像大家所說的那樣,都是動態(tài)改變this。我們只要心里知道,其實二者在背后實現(xiàn)動態(tài)切換this的操作部分有很大的不同就可以了,當(dāng)出現(xiàn)由于內(nèi)部實現(xiàn)細節(jié)引起的問題時,我們可以快速定位。

That"s it !

2019.8.20更新

js忍者秘籍給出的精簡解釋是:“js可以通過apply和call顯示指定任意對象作為其函數(shù)上下文?!睆娏医ㄗh閱讀P52~P55。言簡意賅,通俗易懂。

主要有兩個用途:

普通函數(shù)中指定函數(shù)上下文

回調(diào)函數(shù)中強制指定函數(shù)上下文

回調(diào)函數(shù)強制指定函數(shù)上下文很好地體現(xiàn)了函數(shù)式編程的思想,創(chuàng)建一個函數(shù)接收每個元素,并且對每個元素做處理。

本質(zhì)上,apply和call都是為了增強代碼的可擴展性,提升編程的效率。

我想這也是js中每一個方法或者api的初衷,提供更加便利的操作,解放初更多的生產(chǎn)力。不斷加入新方法的es規(guī)范也是這個初衷。

由于我使用vue比較多,所以根據(jù)以上的應(yīng)用場景出1個單文件組件示例和1個普通示例供參考:

// 普通函數(shù)中指定函數(shù)上下文
// 通過Math.max()獲得數(shù)組中的最大項
// 回調(diào)函數(shù)中強制指定函數(shù)上下文
// 手動實現(xiàn)一個Array.prototype.filter
const numbers = [1, 2, 3, 4];
function arrayFilter(array, callback) {
  const result = [];
  for (let i = 0; i < array.length; i++) {
    const validate = callback.call(array[i], array[i]);
    if (validate) {
      result.push(array[i]);
    }
  }
  return result;
}

const evenArrays = arrayFilter(numbers, (n) => n % 2 === 0);
console.log(evenArrays);// [2, 4]

期待和大家交流,共同進步,歡迎大家加入我創(chuàng)建的與前端開發(fā)密切相關(guān)的技術(shù)討論小組:

SegmentFault專欄:趁你還年輕,做個優(yōu)秀的前端工程師

Github博客: 趁你還年輕233的個人博客

掘金主頁:趁你還年輕233

SegmentFault技術(shù)圈: ES新規(guī)范語法糖

知乎專欄:趁你還年輕,做個優(yōu)秀的前端工程師

前端開發(fā)交流群:660634678

努力成為優(yōu)秀前端工程師!

加油,前端同學(xué)們!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/94785.html

相關(guān)文章

  • 規(guī)范去看Function.prototype.apply到底怎么工作?

    摘要:如果是或者,會將作為值。否則,被調(diào)用的函數(shù),進行轉(zhuǎn)換后,作為值。又怎么操作這個很神奇。能轉(zhuǎn)換它的參數(shù)為到總共個整數(shù)中的一個,這個函數(shù)遵循以下規(guī)則。不斷加入新方法的規(guī)范也是這個初衷。 showImg(https://segmentfault.com/img/remote/1460000012563719); 今天看element-react源碼的時候,又看到了這張似曾相識卻又異常陌生的老...

    tanglijun 評論0 收藏0
  • 【2】this

    摘要:否則如果是或,則設(shè)綁定為全局對象。令為解釋執(zhí)行的結(jié)果。返回一個值類型的引用,其基值為且其引用名為,嚴格模式標(biāo)記為。進入函數(shù)代碼,為,非嚴格模式下將賦值為全局對象。內(nèi)置函數(shù)如何使用的內(nèi)置函數(shù)修改是通過給的內(nèi)置方法傳遞來實現(xiàn)的。 this 說到this,需要明確三方面內(nèi)容: this何時被賦值 this被賦了什么值 內(nèi)置函數(shù)如何使用this的 this何時被賦值 進入函數(shù)代碼 當(dāng)控制流...

    Alex 評論0 收藏0
  • 搞懂JavaScriptFunction.prototype.bind[譯]

    摘要:搞懂的譯可能是初學(xué)的人最不關(guān)心的函數(shù),當(dāng)你意識到需要保持在其他函數(shù)中的上下文,實際上你需要的是。這就是問題所在。整合事件綁定和一個重大提高就是,和等等。然而,并沒有原生添加事件到多個節(jié)點的方式。能力有限,如有疑問,紕漏,速指出,感謝你 搞懂JavaScript的Function.prototype.bind[譯] Ben Howdle binding可能是初學(xué)Javascript的人最...

    Pandaaa 評論0 收藏0
  • javascript中Function、ArrowFunction和GeneratorFunctio

    摘要:等價與注意如果構(gòu)造函數(shù)有自己的返回,那么情況有所不同。,定義了的屬性,默認是聲明的函數(shù)名,匿名函數(shù)是。匿名函數(shù)表達式和函數(shù)聲明都不會創(chuàng)建匿名作用域。 ECMAScript規(guī)范中對Function的文檔描述,我認為是ECMAScript規(guī)范中最復(fù)雜也是最不好理解的一部分,它涉及到了各方面。光對Function就分了Function Definitions、Arrow Function D...

    cyixlq 評論0 收藏0
  • 深入call apply bind

    摘要:眾所周知,這三個函數(shù)都是改變執(zhí)行上下文的,那么我們來捋一捋,這些函數(shù)內(nèi)部到底做了什么。 前言 稍微翻了一下call,apply, bind 的各種論壇上的文章, 發(fā)現(xiàn)講的都太淺了,大部分都只講了個用法, 對于實現(xiàn)的原理卻都沒有提,因此,在這里,我寫下這篇文章, 希望能讓大家認識到原理所在。 眾所周知, 這三個函數(shù)都是改變執(zhí)行上下文的 , 那么我們來捋一捋,這些函數(shù)內(nèi)部到底做了什么。 c...

    Alex 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<