深切明白JavaScirpt的函数挪用和"this"

过去许多年里,我看到过太多关于JavaScript函数挪用的殽杂。尤其是,许多人埋怨函数挪用中this的语义令人困惑。
在我看来,经由过程明白中心函数挪用原语,然后将其他一切挪用函数的要领视为在原语之上的语法糖,云云便可廓清许多这类迷惑。现实上,这正是ECMAScript范例对此的意见。在某些方面,这篇文章是范例的简化,但基础思路是一样的。

中心原语

起首,我们先看一下函数挪用的中心原语,Function对象的call要领[1]。挪用要领要领相对简朴。

  1. 从参数1到末端建立一个参数列表(argList)
  2. 第一个参数(参数0)是thisValue
  3. 经由过程将this的值设为thisValueargList作为其参数列表挪用函数

举例:

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水域中航行。
《深切明白JavaScirpt的函数挪用和

PS:我说谎的部份

在一般处所,我从范例的确实说话中稍微简化了现实。能够最严峻的诳骗是我称谓func.call为原语的说法。实际上,范例有一个func.call[obj.]func()都运用的原语(内部称为[[Call]])。
但是,照样看一下func.call的定义吧:

  1. 假如IsCallable(func)值为false,则抛出TypeError非常
  2. argList为一个空的List
  3. 假如运用多个参数挪用此要领,则从arg1最先,从左往右将每一个参数追加为argList的末了一个元素
  4. 供应thisArg作为this的值,并将argList作为参数列表,返回挪用func的内部要领[[Call]]的效果

如你所见,此定义本质上是一种很简朴的JavaScript语义绑定到原语[[Call]]操纵。
假如你看一下挪用函数的定义,前七个步骤设置thisValueargList,末了一步是:“供应thisArg作为this的值,并将列表argList作为参数值,返回挪用func的内部要领[[Call]]的效果。”
一旦肯定了argListthisValue,它基础上是雷同的说话。
我在称call是一个原语时作了一些诳骗,但其寄义基础上与我在文章开首提出的范例和援用的章节是一样的。
另有一些我没有在这里引见的其他案例(最值得注重的是with)。

原文地点

    原文作者:屯屯
    原文地址: https://segmentfault.com/a/1190000015829925
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞