1. 程式人生 > >理解JavaScript的函式呼叫和this

理解JavaScript的函式呼叫和this

多年以來,我看到了許多人對於JavaScript函式呼叫有很多困惑。特別是許多人會抱怨,”this”在函式呼叫中的語義是令人疑惑的。

在我看來,通過理解核心的函式呼叫的原始模型,並且去看一下在此基礎之上的其他方式的函式呼叫(對原始呼叫的思想的抽取)可以消除這些困惑。實際上,ECMAScript 標準也是這麼考慮的。在某些地方來看,這篇文章是標準的簡化,但是二者的基本思想是一致的。

核心的原始函式呼叫方法

首先,讓我們來看一下核心的函式呼叫原始模型,一個Function的call方法[1]。call方法相對比較直接。
1、取引數的第一個到最後一個組成一個引數列表(argList);
2、第一個引數是thisValue;
3、把this設定為thisValue同時argList作為它的引數列表來呼叫函式。

比如:

JavaScript
12345 functionhello(thing){console.log(this+" says hello "+thing);}hello.call
("Yehuda","world")//=> Yehuda says hello world

正如你所見,我們呼叫了hello函式,把this設定為”Yehuda” 並傳入了一個引數”world”。這是JavaScript函式呼叫的主要原始形式。你可以把所有其他的函式呼叫作為這個原始模式的運用來考慮。(要“運用”原始模型來呼叫其他函式就要用更便利的語法並依據一個更基本的主要原始模型)
注:[1]在ES5標準中,call方法的描述基於其他的,更低水平的基元,但是它是在那個基元基礎上的非常簡單的包裹,因此我在這裡將其簡化了。想了解更多可以參考這篇文章後面的資訊。

簡單的函式呼叫

很明顯,總是用call來呼叫函式是令人難以忍受的。JavaScript允許我們用括號語法來直接呼叫函式(hello(“world”))。當我們這麼做的時候,呼叫是這樣的:

JavaScript
123456789 functionhello(thing){console.log("Hello "+thing);}// this:hello("world")// desugars to:hello.call(window,"world");

在ECMAScript 5 中,在嚴格模式下這個行為已經發生了變化[2]:

JavaScript
12345 // this:hello("world")// desugars to:hello.call(undefined,"world");

簡短的一個版本說明是:一個函式呼叫比如:fn(…args)與fn.call(window [ES5-strict: undefined], …args)是一樣的。
注意,對於行內的函式宣告(function() {})() 與(function() {}).call(window [ES5-strict: undefined)也是一樣的。
注:[2] 實際上,我撒了點謊。ECMAScript 5 標準說undefined(幾乎)總是被傳入,當不在嚴格模式下時,被呼叫的函式應該改變this的值為全域性物件。這允許嚴格模式的呼叫者避免打破已經存在的非嚴格模式庫。

成員函式

下面一種非常常用的函式呼叫方式是函式作為一個物件的方法成員來呼叫(person.hello())。這種情況下函式呼叫像這樣:

JavaScript
123456789101112 varperson={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為一個獨立的函式。讓我們來看看動態的把函式附加到物件上發生了什麼:

JavaScript
12345678910 functionhello(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值:

JavaScript
12345678910 varperson={name:"Brendan Eich",hello:function(thing){console.log(this.name+" says hello "+thing);}}varboundHello=function(thing){returnperson.hello.call(person,thing);}boundHello("world");

儘管我們的boundHello 方法仍然可以改寫為boundHello.call(window, “world”) ,我們轉換了一個角度,應用我們的基元call方法來改變this為我們期望的值。
我們可以用自制體系來使得這個竅門有一般用途:

JavaScript
12345678 varbind=function(func,thisValue){returnfunction(){returnfunc.apply(thisValue,arguments);}}varboundHello=bind(person.hello,person);boundHello("world")// "Brendan Eich says hello world"

為了理解上面的程式碼,你只需要兩個額外的資訊。首先,arguments是一個類陣列物件,它擁有傳到函式裡的所有引數的引用。第二,apply方法的工作機制和基元call是完全一樣的,唯一的不同是它採用的一個類陣列的物件來作為引數,而不是用引數列表。
我們的 bind方法簡單的返回一個新函式。當它被呼叫的時候,我們的新函式簡單的呼叫傳進來的原始函式,設定原始值為this。它也遍歷引數。
因為this在某種程度上是一個常見的習語,ES5引入了一個新的bind方法給所有的Function物件來實現下面的行為:

JavaScript
12 varboundHello=person.hello.bind(person);boundHello("world")// "Brendan Eich says hello world"

當你需要一個未加工的函式作為回撥函式的時候這是非常有用的:

JavaScript
12345678 varperson={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裡面的bind

因為jQuery裡面大量的應用匿名回撥函式,它內部使用call方法來設定那些回撥函式的this值為更有用的值。比如,在所有的事件處理器函式中,jQuery沒有接收window作為this的值(如果你沒有特殊的干預),而是對元素呼叫call方法,並將事件處理器函式作為第一個引數。

這極其有用,因為在匿名函式內部的this的預設值並不是特別有用,但是它會給JavaScript初學者一個這樣的感覺:this一般是很奇怪的,並且是難以推測的經常變化的一個概念。

如果你理解了從一個有語法糖的函式呼叫到抽取出了“糖分”的函式呼叫func.call(thisValue, …args)的基本轉換規則,你應該就能操縱這個並不是十分“陰險”的 JavaScript this 值這一領域。

this-table.png

附:我有所‘欺騙’

在幾個地方,對於規範的措辭我有所簡化。或許最重要的‘欺騙’是我將func.call稱為一個基元(”primitive”)。實際上,這個規範有一個基元(在內部被稱為[[Call]])為func.call和obj.]func()所共有。

然而,讓我們來看一下func.call的定義:

  • 1、如果IsCallable(func) 結果為false,那麼就丟擲一個型別異常;
  • 2、讓 argList  為一個空列表;
  • 3、如果這個方法被呼叫的時候引數不止一個,那麼從左到右開始將arg1追加每一個引數作為 argList 的最新元素;
  • 4、返回呼叫func的內部方法[[Call]]的執行結果,提供thisArg作為this的值,argList作為引數的列表。

正如你所見,這個定義本質上是一個很簡單的JavaScript的語言繫結到基元[[Call]]操作符。

如果你看一下函式呼叫的定義,前七步是設定thisValue和argList,最後一步是:“返回 呼叫func的內部方法 [[Call]]的結果值,提供thisArg作為this的值,argList作為引數的列表”。

一旦thisValue和argList的值被確定,func.call的定義和函式呼叫的定義本質上是相同的字眼。

我在稱call為一個基元上做了一點欺騙,但是在本質上他們意思還是一樣的,我在文章開頭拿出規範且做了引用。

還有很多案例(大多數文章會明顯的包含with)我沒有在文章中進行討論。

打賞支援我翻譯更多好文章,謝謝!

打賞譯者

打賞支援我翻譯更多好文章,謝謝!

任選一種支付方式

相關推薦

理解JavaScript函式呼叫this

多年以來,我看到了許多人對於JavaScript函式呼叫有很多困惑。特別是許多人會抱怨,”this”在函式呼叫中的語義是令人疑惑的。 在我看來,通過理解核心的函式呼叫的原始模型,並且去看一下在此基礎之上的其他方式的函式呼叫(對原始呼叫的思想的抽取)可以消除這些困惑。

函式呼叫this

1.函式的呼叫 (1)事件呼叫 (a)aLi[0].onclick=function(){}; (b)aLi[i].onclick=fn1; (2)直接呼叫 函式名+();就會直接呼叫,不管在哪個位置,只要函式名加()就會執行 2.this (1)事件呼叫時,this指的就是事件前

深入理解Solidity——函式呼叫賦值

函式呼叫(Function Calls) 內部函式呼叫(Internal Function Calls) 當前合約的函式可以直接內部(Internal)呼叫,也可以遞迴地呼叫,比如這個古怪的例子: pragma solidity ^0.4.16;

徹底理解JavaScript函式呼叫方式傳參方式——結合經典面試題

JavaScript函式的呼叫方式和傳參方式 瞭解函式的呼叫過程有助於深入學習與分析JavaScript程式碼。 在JavaScript中,函式是一等公民,函式在JavaScript中是一個數據型別,而非像C#或其他描述性語言那樣僅僅作為一個模組來使用。函式有四種呼叫模式,分別是:函式呼叫形式、

【譯】理解Javascript函式執行—呼叫棧、事件迴圈、任務等

原文作者:Gaurav Pandvia 原文連結:medium.com/@gaurav.pan… 文中部分連結可能需要梯子。 歡迎批評指正。 現如今,web開發者(我們更喜歡被叫做前端工程師)用一門指令碼語言就能做任何事情,從提供瀏覽器中的互動,到開發電腦遊戲、桌面工具、跨平臺移動

深入理解javascript函式進階系列第三篇——函式節流函式防抖

前面的話   javascript中的函式大多數情況下都是由使用者主動呼叫觸發的,除非是函式本身的實現不合理,否則一般不會遇到跟效能相關的問題。但在一些少數情況下,函式的觸發不是由使用者直接控制的。在這些場景下,函式有可能被非常頻繁地呼叫,而造成大的效能問題。解決效能問題的處理辦法就是函式節流和函式防抖。本

JavaScript的selfthis使用小結

js中this用法一、self  這個非常簡單。我們知道,打開任何一個網頁,瀏覽器會首先創建一個窗口,這個窗口就是一個window對象,也是js運行所依附的全局環境對象和全局作用域對象。self 指窗口本身,它返回的對象跟window對象是一模一樣的。也正因為如此,window對象的常用方法和函數都可以用se

【譯】理解 Javascript 執行上下文執行棧

原文地址:Understanding Execution Context and Execution Stack in Javascript 譯文地址:理解 Javascript 執行上下文和執行棧 譯者:夏佳昊 校對

理解 Javascript 執行上下文執行棧

必須 最簡 存在 模式 裏的 execution mas bject variables 如果你是一名 JavaScript 開發者,或者想要成為一名 JavaScript 開發者,那麽你必須知道 JavaScript 程序內部的執行機制。理解執行上下文和執行棧同樣有助於理

Javascript函式中的this的指向規則

用new呼叫函式, this指向新建立的物件。 使用call,apply(bind)呼叫函式, this指向繫結的的物件。 函式作為某物件的屬性呼叫, this指向這個物件。 其他情況下, this指向全域性物件(嚴格模式下指向undefined)。

如何理解JavaScript的原型原型鏈?

之前有朋友在公眾號給我留言,問問怎麼去理解原型和原型鏈的問題。這個問題,在面試中,很多同學經常都會遇到。 回覆多了,覺得大家對這塊知識點理解還是不夠深。於是決定今天來給大家講講,方便大家記憶。 JavaScript的特點 JavaScript是一門直譯式指令碼語言,是一種動態型別、基於原型的

全面理解Javascript閉包閉包的幾種寫法及用途【轉】

內部 ron 除了 因此 擁有 否則 這一 sage ssa 一、什麽是閉包和閉包的幾種寫法和用法 1、什麽是閉包 閉包,官方對閉包的解釋是:一個擁有許多變量和綁定了這些變量的環境的表達式(通常是一個函數),因而這些變量也是該表達式的一部分。閉包的特點:  1. 作為一個函

JavaScript 函式呼叫

JavaScript 函式呼叫 JavaScript 函式有 4 種呼叫方式。 每種方式的不同在於 this 的初始化。 this 關鍵字 一般而言,在Javascript中,this指向函式執行時的當前物件。 呼叫 JavaScript 函式 在之前的章節中我們

一個關於javascript閉包this的題目

##1. var name = "The Window"; var object = { name: "My Object", getNameFunc: function(){ return this.name; } } 問:object.getNam

全面理解Javascript閉包閉包的幾種寫法及用途

  好久沒有寫部落格了,過了一個十一長假都變懶了,今天總算是恢復狀態了。好了,進入正題,今天來說一說javascript裡面的閉包吧!本篇部落格主要講一些實用的東西,主要將閉包的寫法、用法和用途。  一、什麼是閉包和閉包的幾種寫法和用法 1、什麼是閉包 閉包,官方對

深入理解JavaScript函式、建構函式、物件、prototype(原型物件)的關係。

學了這麼久前端,一直搞不明白函式與物件之間的關係,實在慚愧,是時候總結一下了。。以下是學習時的記錄,若有錯誤之處,請指出。。 首先我們要知道js中的資料型別有以下幾種: Number   Boolean   undefined     Object &

詳解Javascript 函式宣告函式表示式的區別

Javascript Function無處不在,而且功能強大!通過Javascript函式可以讓JS具有面向物件的一些特徵,實現封裝、繼承等,也可以讓程式碼得到複用。但事物都有兩面性,Javascript函式有的時候也比較“任性”,你如果不瞭解它的“性情”,它很

JavaScript函式節流函式去抖知識點學習

概念 節流 (throttle) 讓一個函式不要執行的太頻繁,減少執行過快的呼叫,叫節流 去抖 (debounce) 去抖就是對於一定時間段的連續的函式呼叫,只讓其執行一次 throttle 應用場景 DOM 元素的拖拽功能實現(mousemove) 射擊遊戲的

JavaScript函式宣告函式表示式區別

在定義函式時,我們一般使用下面這兩種方法: 用函式宣告建立的函式:    function a() {    }用函式表示式建立的函式:    var b = function() {    }當我們執

閉包中this的指代,以及四種函式呼叫this的指代

    Q:// (1)這裡this指代什麼?function a(){    // (2)這裡this指代什麼?    var c = 2;    function b(){        // (3)這裡呢?this又指代什麼?        return c;    }    return b;}a()