在讨论bind方法前,我们可以先看一个例子:var getElementsByTagName = document.getElementsByTagName;getElementsByTagName(‘body’);这样在浏览器(这里使用的是chrome)执行会报错:原因也显而易见:上面的getElementsByTagName方法是document.getElementsByTagName的引用,但是在执行时this指向了global或window对象,而不是document对象。解决办法也很简单,使用call或bind方法来改变this:var getElementsByTagName = document.getElementsByTagName;getElementsByTagName.call(document, ‘body’);或var getElementsByTagName = document.getElementsByTagName;getElementsByTagName.bind(document)(‘body’);上述两种解决办法也可以看出call和bind的区别:call方法是直接执行,而bind方法是返回一个新函数。实现由于bind方法是从ES5才开始引入的,不是所有浏览器都支持,为了实现兼容,需要自己实现bind方法。我们先来看看bind方法的定义:bind方法会创建一个新函数。当这个新函数被调用时,bind的第一个参数将作为它运行时的this(该参数不能被重写), 之后的一序列参数将会在传递的实参前传入作为它的参数。新函数也能使用new操作符创建对象:这种行为就像把原函数当成构造器,提供的this值被忽略。初步思路因为bind方法不是立即执行函数,需要返回一个待执行的函数,这里可以利用闭包:return function(){};作用域绑定:可以使用apply或call方法来实现;参数传递:由于参数的不确定性,需要用apply传递数组;根据上述思路,我们先来实现一个简单的customBind方法;Function.prototype.customBind = function (context) { var self = this, /** * 由于参数的不确定性,我们用 arguments 来处理 * 这里的 arguments 只是一个类数组对象,可以用数组的 slice 方法转化成标准格式数组 * 除了作用域对象 self 以外,后面的所有参数都需要作为数组进行参数传递 / args = Array.prototype.slice.call(arguments, 1); // 返回新函数 return function() { // 作用域绑定 return self.apply(context, args); }};测试初版var testFn = function(obj, arg) { console.log(‘作用域对象属性值:’ + this.value); console.log(‘绑定函数时参数对象属性值:’ + obj.value); console.log(‘调用新函数参数值:’ + arg);}var testObj = { value: 1};var newFn = testFn.customBind(testObj, {value: 2});newFn(‘hello world’);// 执行结果:// 作用域对象属性值:1// 绑定函数时参数对象属性值:2// 调用新函数参数值:undefined从测试执行结果可以看出,上面已经实现了作用域绑定,但是返回新函数newFn不支持传参,只能在testFn绑定时传参。因为我们最终需要使用的是newFn,所以我们需要让newFn支持传参。动态参数我们来继续改造Function.prototype.customBind = function (context) { var fn = this, args = Array.prototype.slice.call(arguments, 1); return function() { // 将新函数执行时的参数 arguments 全部数组化,然后与绑定时传参 arg 合并 var newArgs = Array.prototype.slice.call(arguments); return fn.apply(context, args.concat(newArgs)); }};测试动态参数var testFn = function(obj, arg) { console.log(‘作用域对象属性值:’ + this.value); console.log(‘绑定函数时参数对象属性值:’ + obj.value); console.log(‘调用新函数参数值:’ + arg);}var testObj = { value: 1};var newFn = testFn.customBind(testObj, {value: 2});newFn(‘hello world’);// 执行结果:// 作用域对象属性值:1// 绑定函数时参数对象属性值:2// 调用新函数参数值:hello world可以看出,绑定时传的参数和新函数执行时传的参数是合并在一起形成完整参数的。原型链我们再回到bind方法的定义第二条:新函数也能使用new操作符创建对象。说明绑定后的新函数被new实例化之后,需要继承原函数的原型链方法,且绑定过程中提供的this被忽略(继承原函数的this对象),但是参数还是会使用。所以我们需要一个中转的函数将原型链传递下去。首先我们需要明确new实例化过程,比如说var a = new b():创建一个空对象a = {},并且this变量引用指向到这个空对象a;继承被实例化函数的原型:a.proto = b.prototype;被实例化方法b的this对象的属性和方法将被加入到这个新的this引用的对象中:b的属性和方法被加入的a里面;新创建的对象由this所引用:b.call(a);接下来我们实现原型链。Function.prototype.customBind = function (context) { var self = this, args = Array.prototype.slice.call(arguments, 1); // 创建中转函数 var cacheFn = function() {}; var newFn = function() { var newArgs = Array.prototype.slice.call(arguments); /* * 这里的 this 是指调用时的执行上下文 * 如果是 new 操作,需要绑定 new 之后作用域,this 指向新的实例对象 */ return self.apply(this instanceof cacheFn ? this : context, args.concat(newArgs)); }; // 中转原型链 cacheFn.prototype = self.prototype; newFn.prototype = new cacheFn(); return newFn;};测试原型链function Point(x, y) { this.x = x; this.y = y;}Point.prototype.toString = function() { return this.x + ‘,’ + this.y;};var YAxisPoint = Point.customBind({}, 0);var axisPoint = new YAxisPoint(5);axisPoint.toString(); // “0,5"axisPoint instanceof Point; // trueaxisPoint instanceof YAxisPoint; // truenew Point(1, 2) instanceof YAxisPoint; // true