把玩 JavaScript 中的 bind

媒介

本日闲着无聊随意逛了逛MDN,忽而看到一个要领Function.prototype.bind(),倏忽发明除了运用这个要领以外都没有细致琢磨过这个要领。因而乎,找到了kill time的事变-写博客。

基础学问简介

随意看看材料发明这玩意实在不简朴,明白起来须要不少基础学问,在这里排列一些,也算是一个总结和温习。

函数

下面这段话来自《JavaScript言语精炼》,名不虚传地形貌了函数的精华。

挪用一个函数会停息当前函数的实行,通报控制权和参数给新函数。除了声明时定义的形式参数,每一个函数还吸收两个附加的参数:this和arguments。参数this在面向对象编程中异常重要,他的值取决于挪用的形式。在JavaScript内里一共有四种挪用形式:要领挪用形式、函数挪用形式、组织器挪用形式和apply挪用形式。这些形式在怎样初始化症结参数this上面存在差异。

要领挪用形式

当一个函数被保存为对象的一个属性时,我们称它为一个要领。当一个要领被挪用的时刻,this被绑定到该对象。

var info = {
    name: 'yuanzm',
    sayName: function() {
        console.log(this.name);
    }
}
info.sayName();    //yuanzm

函数挪用形式

当一个函数并不是为一个对象的属性的时刻,他就是被当作一个函数来挪用的。此形式挪用函数的时刻,this被绑定到全局对象。这是言语设想上的一个毛病。倘使言语设想准确,this应该是绑定到外部函数的this变量。

var name = "yuanzm"
var sayName = function() {
    console.log(this.name);
}
sayName();// yuanzm

组织器挪用形式

假如在一个函数前面带上new来挪用,那末背地里将会建立一个连接到该函数的prototype成员的新对象,同时this会被绑定到新对象上。(JavaScript原型相干学问这里不做赘述)

function Info(name) {
    this.name = name;
}
Info.prototype.sayName = function() {
    console.log(this.name);
}
var info = new Info('yuanzm');
info.sayName();//yuanzm

Apply挪用形式

依据MDN的定义

The apply() method calls a function with a given this value and arguments provided as an array (or an array-like object).

var numbers = [5, 6, 2, 3, 7];
var max = Math.max.apply(null, numbers);

类数组

一个类数组被定义为:

  • 具有:指向对象元素的数字索引下标以及 length 属性通知我们对象的元素个数
  • 不具有:诸如 push 、 forEach 以及 indexOf 等数组对象具有的要领

相符上述定义的类数组是长下面这模样的:

var my_object = {
    '0': 'zero',
    '1': 'one',
    '2': 'two',
    '3': 'three',
    '4': 'four',
    length: 5
};

前面提到的arguments也是类数组。许多时刻,处置惩罚类数组最费事的要领就是将它转化成数组。那末就涉及到一个异常有意思的话题:将类数组转换成数组。
将类数组转换成数组异常简朴,挪用Array自带的要领即可:

Array.prototype.slice.call(arguments);

个中call换成apply也是一样的。
简朴解释一下,slice要领通例的挪用体式格局为array.slice(start, end),会对array中的一段做浅复制,起首复制array[start], 一向复制到array[end]。前面提到过apply(或call)会切换一个函数挪用的上下文,也就是Array.prototype.slice.call(arguments);手动绑定了须要操纵的array为arguments,因为arguments和数组一样具有下标,所以这个要领也是可行的,因此发生了一个新的数组,这个数组具有一般数组的一切要领,同时具有arguments每一个下标对应的值。

bind

简介

前面说了这么多,主角终究上台了!不过为了凸显它的作用,照样须要抛出一段达不到我们需求的代码。

var name = 'yuan'
var info = {
    name: "yuanzm",
    sayName: function() {
        console.log(this.name);
    }
}
var sayName = info.sayName;
// 我们自身是愿望获得yuanzm的,然则确获得了yuan这个效果。为何会获得这个效果,前面的长篇大论起作用了。
// 假如是采纳info.sayName()这类挪用体式格局,相符函数的要领挪用形式,函数内部的this是info对象;
// 假如令一个变量sayName为info.sayName,这个时刻再挪用函数,就是一般的函数挪用形式了,效果天然为yuan。
sayName();    // yuan 

那末我们运用bind就是愿望末了获得的效果为yuanzm

如今我们可以好好引见bind了。依据MDN的定义:

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

翻译过来就是,bind()要领会建立一个新函数,称为绑定函数.当挪用这个绑定函数时,绑定函数会以建立它时传入 bind()要领的第一个参数作为 this,传入 bind() 要领的第二个以及今后的参数加上绑定函数运行时自身的参数根据递次作为原函数的参数来挪用原函数。
他的语法是:

fun.bind(thisArg[, arg1[, arg2[, ...]]]);

解决问题

有了bind,上述问题我们就可以获得想要的效果了:

var name = 'yuan'
var info = {
    name: "yuanzm",
    sayName: function() {
        console.log(this.name);
    }
}
var sayName = info.sayName.bind(info);
sayName();    // yuanzm

bind的用法在MDN上面形貌得很细致,本文的目标也不是为了照搬MDN,所以这里有关bind运用的部份就到这儿,接下来我们去看看Javascript库中bind的完成

Prototype中的bind

良久之前Prototype的bind写法是下面如许的(解释为本人所加):

Function.prototype.bind = function(){ 
    // bind作为Function的prototype属性,每一个函数都具有bind要领,个中的this指向挪用该要领的函数(也即一个函数对象实例)
    var fn = this;
    // 前面说过,这个要领是为了将类数组转换成数组
    var args = Array.prototype.slice.call(arguments);
    // 数组的shift要领会移除数组的第一个元素,在bind要领的参数内里,第一个参数就是须要绑定this的对象
    var object = args.shift();
    return function(){ 
        return fn.apply(object,
        // 如今的args是移除了最初参数中第一个参数后的数组,也就是新函数的预设的初始参数。
        // array.concat(items...)要领发生一个新数组,它包括一份array的浅复制,并把一个或许多个参数item附加在厥后。
        // 假如参数item是一个数组,那末它的元素会被离别增加。
        // 因而这一行代码发生了一个参数数组,这个数组由预设的初始参数(假如存在)和新通报的参数构成。
        args.concat(Array.prototype.slice.call(arguments))); 
    }; 
};

在最新的版本1.7.2(2015.06.23查阅官网)中https://github.com/sstephenson/prototype/blob/master/src/prototype/lan…,bind是下面这模样的:

function bind(context) {
    if (arguments.length < 2 && Object.isUndefined(arguments[0]))
        return this;

    if (!Object.isFunction(this))
        throw new TypeError("The object is not callable.");

    var nop = function() {};
    var __method = this, args = slice.call(arguments, 1);

    var bound = function() {
        var a = merge(args, arguments);
        // Ignore the supplied context when the bound function is called with
        // the "new" keyword.
        var c = this instanceof bound ? this : context;
        return __method.apply(c, a);
    };

    nop.prototype   = this.prototype;
    bound.prototype = new nop();

    return bound;
}

可见,除了加了一些异常情况推断,中心代码和之前并没有大差异。

结语

总得来说apply、call和bind都是为了手动绑定this对象,目标上没有什么区别。然则bind和别的二者的显著的区别是,bind会发生一个新的函数,这个函数还可以有预设的参数,这在许多时刻会比apply和call越发好用。合理应用apply、call和bind会使得javaScript代码越发文雅。

参考材料

[1] Function.prototype.apply()
[2] JavaScript 的怪癖 8:“类数组对象”
[3] how does Array.prototype.slice.call() work?
[4] JavaScript’s Apply, Call, and Bind Methods are Essential for JavaScript Professionals

    原文作者:yuanzm
    原文地址: https://segmentfault.com/a/1190000002929289
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞