JavaScript 函数中的apply()、call()和bind()

date
May 9, 2017
slug
apply-call-bind
status
Published
tags
JavaScript
summary
type
Post
第一次接触这三个方法时,我还没怎么写过 js,一直无法理解这三个方法的作用,MDN 上的资料也看不懂。现在回过头来继续啃这些知识点,顿时就有豁然开朗之感了。

函数属性和方法

ECMAScript 中的函数是对象,因此函数也有属性和方法。每个函数包含两个属性:length 和 prototype。length 表示函数的参数个数:
function sum(num1,num2) {
  return num1 + num2;
}

console.log(sum.length); // 2
而 prototype 所对应的就是该对象传说中的原型了。对于 ECMAScript 中的引用类型而言,prototype 保存着它们所有实例方法,对于函数来讲,其 prototype 中就包括 apply()、call() 和 bind()。

apply() 与 call()

这两个方法的用途都是在特定的作用域中调用函数。换句话说就是设置函数体中的 this 的值。

apply()

apply() 接受两个参数,第一个是要设定的作用域,另一个是参数数组(arguments 对象亦可)
function sum(num1,num2) {
  return num1 + num2;
}

function callSum1(num1,num2) {
  return sum.apply(this,arguments);
 // or: return sum.apply(this,[num1,num2]);
}

console.log(callSum1(10,10)); // 20
在此处是在全局作用域环境下调用的 callSum1,则 apply() 传入的 this 便是 window 对象(浏览器环境)。
另一个例子,扩充函数赖以运行的作用域:
window.word = "Hello";
const o = {word:"World"};

function saySomething() {
  console.log(this.color);
}

saySomething(); // Hello
saySomething.apply(window); // Hello
saySomething.apply(o); // World
在第三次调用 saySomething() 时,该函数的执行环境变了,this 指向了 o,所以打印出来的是 “World”。
使用 apply() 和 call() 来扩充函数赖以运行的作用域的好处是,对象不需要与方法有任何耦合关系,这样能大大增加代码的复用性。

call()

call() 与 apply() 大同小异,它的作用与 apply() 是相同的。只是传递给 call() 的参数必须逐个列举出来:
function callSum1(num1,num2) {
  return sum.call(this,num1,num2);
}

bind()

bind() 方法会新建一个函数的实例,这个函数实例的 this 值会被绑定到传给 bind() 函数的值。
const obj = {};

function test() {
  console.log(this === obj);
}

const newTest = test.bind(obj);
test(); // false
newTest(); // true
newTest() 函数中的 this 始终指向 obj,无论在哪里调用,其输出结果始终是 true。

用途

JS 中 this 是动态指向的,而 bind() 便可以把 this 固定住。我们来看一个具体例子:

click 事件处理

定义一个日志对象来记录 click 的次数:
let logger = {
  count:0,
  increment: function() {
    this.count++;
    console.log(count);
  }
}
然后在点击事件的回调函数中调用该对象的 increment() 方法:
document.getElementById('btn').addEventListener('click', logger.increment);
这里要注意的是,如果直接像上述代码这么写,那么 increment() 函数中的 this 指向的是 id 为 ‘btn’ 的 DOM 对象,而非 logger。这时就需要靠 bind() 出场了:
document.getElementById('btn').addEventListener('click', logger.increment.bind(logger));
如果你用 ES6 写 React 的话,上述场景你一定不会陌生。在写事件函数时,我们经常需要 bind() 一下来保证 this 一直指向组件实例:
constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this)
}
handleClick(e) {
    console.log(this);
}
render() {
    return (
        <div>
            <button onClick={this.handleClick}>点击</button>
        </div>
    );
}
而用 ES5 写时, 使用 React.createClass() 来建立组件时,React 会帮你自动绑定函数的 this。

© Sytone 2021 - 2024