乐趣区

理解JavaScript函数调用和this

原文链接:原文链接

多年来,我已经看到许多关于 JavaScript 函数调用的困惑。特别是,许多人抱怨函数调用中的 this 语义令人困惑。

在我看来,通过理解核心函数调用语句,然后在该原语之上查看以糖为例调用功能的所有其他方式,可以消除许多此类混淆。

核心原函数

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

  1. 从参数 1 到结尾创建参数列表(argList
  2. 第一个参数是thisValue
  3. 将此函数设置为此 thisValue 并将 argList 作为其参数列表来调用该函数.

例如:

function hello(thing){console.log(this + "says hello" + thing)
}
hello.call("Mandy", "world") // Mandy says hello world

我们调用 hello 方法,他
它的 this 被设置成 Mandy,还有一个参数world 这是 JavaScript 函数调用的核心原语。您可以将所有其他函数调用视为对该核心原语的替代(“替代”是采用一种方便的语法,并以更基本的核心原语进行描述)。

简单函数调用

显然,一直使用 call 调用函数会很烦人。JavaScript使我们可以使用语法 (hello("world")) 直接调用函数。当我们这样做时,调用将会被替代。

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

// this: 
hello("world")

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

当使用严格模式 (use strict) 时,相当于

// 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)

成员方法

方法调用中另一种个非常常见的调用方式是:方法作为对象的成员调用(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 定义为独立函数。让我们看看如果我们动态地将 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")
// [object Object] says hello world

hello("world") // "[object DOMWindow]world"
// [object Window] says hello 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"); // Brendan Eich says hello world

即使我们的 boundHello("world") 调用仍然相当于 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 下一版本的委员会)继续致力于开发一种更加优雅,仍然向后兼容的解决方案。

退出移动版