原文链接: 原文链接

多年来,我已经看到许多关于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下一版本的委员会)继续致力于开发一种更加优雅,仍然向后兼容的解决方案。


MandyShen
166 声望21 粉丝