ES5新增函数之二: Function.prototype.bind();

在上一篇文章里我们分析了ES5对几个常用类新增的函数,今天就重点来讲解一下Function中的bind函数。

简单来说,bind函数用于将当前函数和指定对象绑定,返回一个新的函数,当新函数被调用时,代码会在指定对象的上下文中执行。

这就涉及到JavaScript程序执行上下文Context的知识了,在JavaScript中函数内部如果存在与Context有关的代码,如果我们在调用之前改变其Context,那么执行结果就不同,这一点我们可以用一个最基本的例子来说明:

var name = 'Global';

var student = {
  name: 'John'
};

var person = {
  name: 'Scott',
  getName: function() {
    return this.name;
  }
};

console.log(person.getName());      // Scott

var getName = person.getName;
console.log(getName());             // Global
console.log(getName.call(student)); // John

如上所示,我们在全局声明一个name变量,然后声明student和person对象,分别都有name属性,其中person包含一个getName函数,用于返回所在对象的name属性。第一步我们直接调用person的getName函数,返回Scott;然后我们先去到person的getName函数引用,之后直接调用,注意,这次调用跟第一步是不同的,它的执行环境是全局,所以函数内部的this.name会指向我们全局声明的name,所以结果会返回Global;最后我们使用call方法将getName函数在student对象的上下文中执行,结果会返回student的name属性,即John。

针对这个问题,我们可以使用bind函数将getName绑定person对象,返回一个带有固定作用域的新函数,以后不管在哪调用这个新函数,都不用担心作用域的问题:

var getName = person.getName.bind(person);  //使用bind函数绑定person对象
console.log(getName());               //Scott
console.log(getName.call(student));   //Scott

注意最后一个调用虽然使用了call函数,但因为getName使用bind绑定了person对象,所以不会再被call函数更改作用域了,打印结果仍然会是person对象的name。

另外一个例子是调用setTimeout或setInterval,当我们在函数内部调用setTimeout时需要特别小心,因为setTimeout函数是在全局Context中执行的,我们来看下面这段代码及运行的结果:

var name = 'John';

var person = {
  name: 'Scott',
  showMyName: function() {
    setTimeout(this.printName, 1000);
  },
  printName: function() {
    console.log('in person object, there is a name: ', this.name);
  }
};

person.showMyName();   //in person object, there is a name: John

ES5新增函数之二: Function.prototype.bind();_第1张图片

我们本希望在调用showMyName后延时1秒然后打印person的name属性,可是结果并不是如我们期望的那样,而是打印出了全局变量的值John,这是因为setTimeout把person的printName函数放到了全局执行了,那printName里面的this自然也指向了全局中的name变量。对于这个问题我们同样可以使用bind函数将作用域绑定为person对象,进而将printName的this总是指向person对象:

setTimeout(this.printName.bind(this), 1000);   //使用bind绑定当前对象person

这样一来结果就会如我们期望打印出person的name属性,即Scott了:

ES5新增函数之二: Function.prototype.bind();_第2张图片

上面介绍了这么多,想必大家已经对bind的作用有所了解了,下面来详细介绍一下bind函数的签名:

func.bind(context, [arg1, [arg2, [...]]]);

函数的第一个参数是执行环境的上下文对象,后面的参数列表是预设参数,我们知道,调用bind函数会返回一个新函数,当这个新函数调用时,上面参数列表里的预设函数也会附带作为实参传入。在上面两个例子中我们使用bind函数时并没有预设参数,下面我们举个例子来说明一下如何使用预设参数:

var listDrinks = function() {
  var drinks = Array.prototype.slice.call(arguments);
  console.log(drinks.join(', '));
};

listDrinks('tea', 'coffee');  //tea, coffee

var listDrinksOfRestaurant = listDrinks.bind(null, 'this restaurant serves: water');

listDrinksOfRestaurant('tea', 'coffee', 'milk');  //this restaurant serves: water, tea, coffee, milk

打印结果如下:

ES5新增函数之二: Function.prototype.bind();_第3张图片

上面的例子很简单,listDrinks函数用于列出所有的饮料,然后我们使用bind函数返回了一个新的函数listDrinksOfRestaurant,它专门用于列出一个餐馆提供的所有饮料,因为每个餐馆都得提供水,所以这是一个基本的要求,我们就把它作为预设参数在bind时传递进去,这样不管listDrinksOfRestaurant如何调用,water会一直存在的。另外,我们注意到上面使用bind函数时第一个参数是null,这是因为我们的listDrinks函数内部没有任何与上下文有关的代码,所以不需要传递上下文对象即可。

bind函数在ES5规范中是一个很重要的特性,给开发者带来了极大的便利,但它在低版本的浏览器中是无法使用的,所以我们很有必要实现我们自己的bind函数:

if (!Function.prototpe.bind) {
    Function.prototype.bind = function(context) {
        var self = this, 
        	args = Array.prototype.slice.call(arguments);
            
        return function() {
            return self.apply(context, args.slice(1).concat(arguments));    
        }
    };
}
以上就是bind函数的全部内容,因为它涉及到一些作用域的概念及运行机制,所以需要细细体会,了解其中的奥妙之处,谢谢大家。

你可能感兴趣的:(Front,End)