理解JavaScript函数调用和this

更新日期: 2019-08-28阅读: 1.6k标签: this

前言

我看到很多人都有关于JavaScript函数调用的困惑。特别是,很多人抱怨函数调用中的语义令人困惑。

在我看来,通过理解核心函数调用原语,可以清除很多这种混淆,然后查看在该原语之上调用函数作为语法糖的所有其他方法。

事实上,这正是ECMAScript规范对此的看法。

在某些地方,这篇文章对ECMAScript规范做了一些简化,但基本思路是一样的。


核心原语

首先,让我们看一下核心函数调用原语,一个函数的调用方法[1]。调用方法相对简单。

  1. 从第一个参数到最后一个参数创建一个参数列表(argList)
  2. 第一个参数是thisValue
  3. 通过调用指向thisValue的this和表示参数列表的argList调用该函数

举例:

function hello(thing) {
  console.log(this + " says hello " + thing);
}

hello.call("Cloudy", "world") //=> Cloudy says hello world

正如你所看到的,我们在调用hello时通过call使this指向"Cloudy",同时为它传递一个"world"参数。这是JavaScript函数调用的核心原语。你可以将所有其他函数调用视为对该原语的“脱糖(desugars)”。 (所谓“脱糖(desugars)”就是采用更为简的语法并用更基本的核心原语来描述它)。

[1]在ES5规范中,调用方法是用另一个更低级的原语来描述的,但它在该原语之上是一个非常薄的包装器,所以我在这里简化了一下。有关更多信息,请参阅本文末尾。


简单的函数调用

显然,一直调用函数会非常烦人。JavaScript允许我们使用parens语法(hello(“world”)直接调用函数。当我们这样做时,调用脱糖:

function hello(thing) {
  console.log("Hello " + thing);
}

// this:
hello("world")

// desugars to:
hello.call(window, "world");

但在ECMAScript 5 严格模式[2]时中会有不一样的结果:

// 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(几乎)总是被传递,但被调用的函数应该在不处于严格模式时将其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的函数:

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"),现在我们重新使用我们的原始调用方法将此值更改回我们想要的值。

我们可以通过一些调整使这个技巧达到通用目的:

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方法只返回一个新函数。调用它时,我们的新函数只调用传入的原始函数,将原始值设置为此值。它也通过参数传递。

因为这是一个有点常见的习惯用法,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下一版本的委员会)继续研究更优雅,仍然向后兼容的解决方案。


PS: 一些cheat

在一些地方,我从规范的确切措辞中略微简化了现实。可能最重要的cheat是我称func.call为“原始”的方式。实际上,规范有一个原语(内部称为[[Call]])func.call和[obj.func()都使用。但是,看一下func.call的定义:

  1. 如果IsCallable(func)为false,则抛出TypeError异常。
  2. 让argList为空List。
  3. 如果使用多个参数调用此方法,则从arg1开始以从左到右的顺序将每个参数附加为argList的最后一个元素
  4. 返回调用func的[[Call]]内部方法的结果,提供thisArg作为此值,并将argList作为参数列表。

如您所见,此定义本质上是一种非常简单的JavaScript语言绑定到原始[[Call]]操作。如果你看一下调用函数的定义,前七个步骤设置thisValue和argList,最后一步是:“返回在func上调用[[Call]]内部方法的结果,将thisValue作为此值并提供列表argList作为参数值。”一旦确定了argList和thisValue,它基本上是相同的措辞。

我在调用call一个原语时作了一些cheat,但其含义基本上与我在本文开头提取规范并引用章节和诗句时的含义相同。还有一些我没有在这里介绍的其他案例。

原文请参考:《Understanding JavaScript Function Invocation and "this"》 -- 作者Yehuda Katz

链接: https://www.fly63.com/article/detial/5006

解读Javascript中的this机制,别再为了 this 发愁了

JavaScript中有很多令人困惑的地方,或者叫做机制。但是,就是这些东西让JavaScript显得那么美好而与众不同。比方说函数也是对象、闭包、原型链继承等等,而这其中就包括颇让人费解的this机制。

js中this的详细解释,JavaScript中this绑定规则【你不知道的JavaScript】

this的绑定过程之前的调用栈 和 调用位置,this绑定规则:1、默认模式,2、隐式绑定,3、显式绑定,4、new绑定

JavaScript中this的运行机制及爬坑指南

在 JavaScript 中,this 这个特殊的变量是相对比较复杂的,因为 this 不仅仅用在面向对象环境中,在其他任何地方也是可用的。 本篇博文中会解释 this 是如何工作的以及使用中可能导致问题的地方,最后奉上最佳实践。

JS中this的指向问题(全)

this关键字在js中的指向问题不管是工作还是面试中都会经常遇到,所以在此对它进行一下总结:全局作用域中、闭包中指window、函数调用模式:谁调用就指谁、构造函数中,this指实例对象、apply/call改变this的指向、bind改变this指向等

彻底淘汰并消除JavaScript中的this

如果这很难明白,为什么我们不停止使用它呢?认真的思考一下?如果你读过 将90%的垃圾扔进垃圾桶后,我如何重新发现对JavaScript的爱, 当我说扔掉它时,你不会感到惊讶,this被丢弃了

JavaScript this常见指向问题

this的用法:直接在函数中使用 谁调用这个函数this就指向谁 ,对象中使用, 一般情况下指向该对象 ,在构造函数中使用

JavaScript函数中this的四种绑定策略

this的四种绑定策略:默认绑定、隐式绑定、显示绑定、new绑定。首先要明确的是一般情况下,this不是函数被定义时绑定,而是函数被调用时被绑定 ,那么函数中的this有四种绑定方式:

改变this的指向

this是Javascript语言的一个关键字。它代表函数运行时,自动生成的一个内部对象.this永远指向函数的调用者。随着函数使用场合的不同,this的值会发生变化。但是有一个总的原则,那就是this指的是,调用函数的那个对象。

js手写实现apply,call,bind

apply和call的区别就是传的参数形式不一样。call是一个一个的传,apply可以将参数以数组的形式传进去。而bind是传入第二个和后面的参数,且绑定this,返回一个转化后的函数。

this指向问题的经典场景

以函数形式调用,this指向window;以方法形式调用,this指向调用方法的那个对象;构造函数调用,this指向实例的对象;使用window对象的方法使,指向window;多重场景改变this指向

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!