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