摘要:簡單的函數(shù)調(diào)用顯而易見,一直用調(diào)用函數(shù)將會非常煩人。規(guī)范說幾乎總是被傳遞,但不在嚴格模式下時被調(diào)用函數(shù)應(yīng)該將其更改為全局對象。實際上,規(guī)范有一個和都使用的原語內(nèi)部稱為。
過去很多年里,我看到過太多關(guān)于JavaScript函數(shù)調(diào)用的混淆。尤其是,很多人抱怨函數(shù)調(diào)用中this的語義令人困惑。
在我看來,通過理解核心函數(shù)調(diào)用原語,然后將其他所有調(diào)用函數(shù)的方法視為在原語之上的語法糖,如此便可澄清很多這類疑惑。事實上,這正是ECMAScript規(guī)范對此的看法。在某些方面,這篇文章是規(guī)范的簡化,但基本思路是一樣的。
首先,我們先看一下函數(shù)調(diào)用的核心原語,F(xiàn)unction對象的call方法[1]。調(diào)用方法方法相對簡單。
從參數(shù)1到末尾創(chuàng)建一個參數(shù)列表(argList)
第一個參數(shù)(參數(shù)0)是thisValue
通過將this的值設(shè)為thisValue和argList作為其參數(shù)列表調(diào)用函數(shù)
舉例:
function hello(thing) { console.log(this + " says hello " + thing); } hello.call("Yehuda", "world") //=> Yehuda says hello world
如你所見,我們通過將this設(shè)置為“Yehuda”和單個參數(shù)“world”來調(diào)用hello方法。這正是JavaScript中函數(shù)調(diào)用的核心原語。你可以認為所有其他方式的函數(shù)調(diào)用都可”去糖“得到這個原語。(“去糖”是指采用一種方便的語法并用更基本的核心原語來描述它)。
[1]在ES5規(guī)范中,call方法是用另一個更底層的原語來描述的,但它是在那個原語之上的簡單封裝,所以我在這里簡化了一下。有關(guān)更多信息,請參閱本文末尾。
簡單的函數(shù)調(diào)用顯而易見,一直用call調(diào)用函數(shù)將會非常煩人。JavaScript允許我們直接使用括號語法hello("world")來調(diào)用函數(shù)。當我們這樣做時,調(diào)用“去糖”如下:
function hello(thing) { console.log("Hello " + thing); } // this: hello("world") // desugars to: hello.call(window, "world");
僅在使用嚴格模式[2]的ECMAScript 5中,此行為將改變:
// this: hello("world") // desugars to: hello.call(undefined, "world");
簡短版本的說法是:像fn(...args)這樣的函數(shù)調(diào)用和fn.call(window [ES5-strict: undefined], ...args)是一模一樣的。
注意,對于行內(nèi)聲明的函數(shù)(function() {})()也是成立的:(function() {})()和(function() {}).call(window [ES5-strict: undefined)是一模一樣的。
[2]事實上,我撒了一點小謊。ECMAScript 5規(guī)范說undefined(幾乎)總是被傳遞,但不在嚴格模式下時被調(diào)用函數(shù)應(yīng)該將其thisValue更改為全局對象。這允許嚴格模式下調(diào)用者避免破壞現(xiàn)有的非嚴格模式庫。
成員函數(shù)調(diào)用方法的下一個非常普遍的方式是作為一個對象的一個成員 (person.hello())。在這種情況下,調(diào)用“去糖”如下:
var person = { name: "Brendan Eich", hello: function(thing) { console.log(this + " says hello " + thing); } } // this: person.hello("world") // desugars to this: person.hello.call(person, "world");
注意,hello方法在這種形式下是如何附加到對象上是無關(guān)緊要的。請記住,我們之前將hello定義為一個獨立函數(shù)。接下來我們看看如果動態(tài)地將其附加到對象上會發(fā)生什么:
function hello(thing) { console.log(this + " says hello " + thing); } person = { name: "Brendan Eich" } person.hello = hello; person.hello("world") // still desugars to person.hello.call(person, "world") hello("world") // "[object DOMWindow]world"
注意,函數(shù)對其this值沒有一貫的定義,它總是在調(diào)用時根據(jù)調(diào)用者調(diào)用的方式進行設(shè)置。
使用Function.prototype.bind因為引用this值一貫不變的函數(shù)有時是很方便的,人們歷來使用一個簡單的閉包技巧將函數(shù)轉(zhuǎn)換為this值一貫不變的對應(yīng)函數(shù):
var person = { name: "Brendan Eich", hello: function(thing) { console.log(this.name + " says hello " + thing); } } var boundHello = function(thing) { return person.hello.call(person, thing); } boundHello("world");
盡管我們的boundHello調(diào)用仍然“去糖”為boundHello.call(window, "world"),但我們改變方向并使用我們的原語call方法將this值更改回我們想要的值。
我們做些調(diào)整可以把這個技巧變?yōu)橥ㄓ媒夥ǎ?/p>
var bind = function(func, thisValue) { return function() { return func.apply(thisValue, arguments); } } var boundHello = bind(person.hello, person); boundHello("world") // "Brendan Eich says hello world"
為了理解這一點,您只需要兩個額外的知識。首先,arguments是一個類Array對象,它表示傳遞給函數(shù)的所有參數(shù)。其次,apply方法的工作原理和call原語除了它采用類Array對象而不是一次列出一個參數(shù)之外完全一樣。
我們的bind方法簡單地返回一個新函數(shù)。當它被調(diào)用時,我們的新函數(shù)只是調(diào)用傳入的原始函數(shù),并將原始值設(shè)置為其this值,當然它也傳遞參數(shù)。
因為這是一個有點常見的習(xí)慣用法,ES5在所有Function對象上引入了一個新方法bind,實現(xiàn)了此行為:
var boundHello = person.hello.bind(person); boundHello("world") // "Brendan Eich says hello world"
當您需要將原始函數(shù)作為回調(diào)傳遞時,此方法將非常有用:
var person = { name: "Alex Russell", hello: function() { console.log(this.name + " says hello world"); } } $("#some-div").click(person.hello.bind(person)); // when the div is clicked, "Alex Russell says hello world" is printed
確實,這有點笨,TC39(負責ECMAScript下一版本的委員會)將繼續(xù)致力于一個更優(yōu)雅、向后兼容的解決方案。
面向jQuery由于jQuery中大量使用匿名回調(diào)函數(shù),因此它在內(nèi)部使用call方法將這些回調(diào)的this值設(shè)置為更有用的值。舉個例子,在所有事件處理程序中(如不進行特殊干預(yù)),jQuery不接收window作為其this值,而是通過把設(shè)置事件處理程序的元素作為它第一個參數(shù)在回調(diào)函數(shù)上調(diào)用call。
這非常有用,因為匿名回調(diào)函數(shù)中的默認this的值并不是特別有用,除了它給初學(xué)者對javascript的一種印象,this通常是一個奇怪的,經(jīng)常變動至于難以解釋的概念。
如果你理解了將“含糖”函數(shù)調(diào)用轉(zhuǎn)換為“已去糖”的func.call(thisValue, ...args)的基本規(guī)則,那么你應(yīng)該能夠在并不是那么危險的JavaScriptthis水域中航行。
在個別地方,我從規(guī)范的確切措辭中略微簡化了事實。可能最嚴重的欺騙是我稱呼func.call為原語的說法。實際上,規(guī)范有一個func.call和[obj.]func()都使用的原語(內(nèi)部稱為[[Call]])。
然而,還是看一下func.call的定義吧:
如果IsCallable(func)值為false,則拋出TypeError異常
讓argList為一個空的List
如果使用多個參數(shù)調(diào)用此方法,則從arg1開始,從左往右將每個參數(shù)追加為argList的最后一個元素
提供thisArg作為this的值,并將argList作為參數(shù)列表,返回調(diào)用func的內(nèi)部方法[[Call]]的結(jié)果
如你所見,此定義本質(zhì)上是一種很簡單的JavaScript語義綁定到原語[[Call]]操作。
如果你看一下調(diào)用函數(shù)的定義,前七個步驟設(shè)置thisValue和argList,最后一步是:“提供thisArg作為this的值,并將列表argList作為參數(shù)值,返回調(diào)用func的內(nèi)部方法[[Call]]的結(jié)果。”
一旦確定了argList和thisValue,它基本上是相同的措辭。
我在稱call是一個原語時作了一些欺騙,但其含義基本上與我在文章開頭提出的規(guī)范和引用的章節(jié)是一樣的。
還有一些我沒有在這里介紹的其他案例(最值得注意的是with)。
原文地址
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/96465.html
在JavaScript中‘this’關(guān)鍵字是一個非常重要的概念,我們雖然知道它重要,但它也十分的晦澀難懂,也給我們學(xué)習(xí)造成不小的困擾。 什么是'this'關(guān)鍵字 'this'關(guān)鍵字是為每個執(zhí)行上下文(每個函數(shù))創(chuàng)建的一個特殊變量;所以一般來說,在使用'this'關(guān)鍵字的函數(shù)中,'this'永遠是取其所有者的值。總結(jié)一句話是該函...
摘要:一看這二逼就是周杰倫的死忠粉看看控制臺輸出,確實沒錯就是對象。從根本上來說,作用域是基于函數(shù)的,而執(zhí)行環(huán)境是基于對象的例如全局執(zhí)行環(huán)境即全局對象。全局對象全局屬性和函數(shù)可用于所有內(nèi)建的對象。全局對象只是一個對象,而不是類。 覺得本人寫的不算很爛的話,可以登錄關(guān)注一下我的GitHub博客,博客會堅持寫下去。 今天同學(xué)去面試,做了兩道面試題,全部做錯了,發(fā)過來給我看,我一眼就看出來了,因為...
大家會發(fā)現(xiàn),自從 React v16.8 推出了 Hooks API,前端框架圈并開啟了新的邏輯復(fù)用的時代,從此無需在意 HOC 的無限套娃導(dǎo)致性能差的問題,同時也解決了 mixin 的可閱讀性差的問題。這里也有對于 React 最大的變化是函數(shù)式組件可以有自己的狀態(tài),扁平化的邏輯組織方式,更加友好地支持 TS 類型聲明。 在運用Hooks的時候,除了 React 官方提供的,同時也支持我們...
摘要:本文主要介紹的數(shù)據(jù)結(jié)構(gòu)簡單動態(tài)字符串簡稱。遵守字符串以空字符串結(jié)尾的慣例,保存的空字符串一個字節(jié)空間不計算在的屬性里面。添加空字符串到字符串末尾等操作,都是由函數(shù)自動完成的,所以這個空字符對于使用者來說完全是透明的。Redis是用ANSI C語言編寫的,它是一個高性能的key-value數(shù)據(jù)庫,它可以作用在數(shù)據(jù)庫、緩存和消息中間件。其中 Redis 鍵值對中的鍵都是 string 類型,而鍵...
摘要:是提出并積極開發(fā)的一種新的在線格式,旨在加快解析速度,同時保持原始的語義不變。它的實現(xiàn)方式是使用有效的二進制來表示代碼和數(shù)據(jù)結(jié)構(gòu),并且存儲和提供額外的信息來提前指導(dǎo)解析器工作。提升依賴于提升所有聲明變量函數(shù)類。 原文:Faster script loading with BinaryAST?本文首發(fā)于公眾號:符合預(yù)期的CoyPan JavaScirpt的冷啟動 web應(yīng)用的表現(xiàn),越來...
閱讀 2892·2019-08-30 15:55
閱讀 1994·2019-08-30 14:02
閱讀 1232·2019-08-29 15:23
閱讀 1001·2019-08-29 11:27
閱讀 457·2019-08-26 11:43
閱讀 3184·2019-08-26 10:32
閱讀 1249·2019-08-23 14:41
閱讀 3296·2019-08-23 14:41