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