bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。
var result = fun.bind(thisArg[, arg1[, arg2[, ...]]]) result(newArg1, newArg2...)
没看懂没事接着往下看。
bind到底做了什么
从上面的介绍中可以看出三点。首先调用bind方法会返回一个新的函数(这个新的函数的函数体应该和fun是一样的)。同时bind中传递两个参数,第一个是this指向,即传入了什么this就等于什么。如下代码所示:
this.value = 2 var foo = { value: 1 } var bar = function() { console.log(this.value) } var result = bar.bind(foo) bar() // 2 result() // 1,即this === foo
第二个参数为一个序列,你可以传递任意数量的参数到其中。并且会预置到新函数参数之前。
this.value = 2 var foo = { value: 1 }; var bar = function(name, age, school) { console.log(name) // 'An' console.log(age) // 22 console.log(school) // '家里蹲大学' } var result = bar.bind(foo, 'An') //预置了部分参数'An' result(22, '家里蹲大学') //这个参数会和预置的参数合并到一起放入bar中
我们可以看出在最后调用 result(22, '家里蹲大学') 的时候,其内部已经包含了在调用bind的时候传入的 'An'。
一句话总结:调用bind,就会返回一个新的函数。这个函数里面的this就指向bind的第一个参数,同时this后面的参数会提前传给这个新的函数。调用该新的函数时,再传递的参数会放到预置的参数后一起传递进新函数。
自己实现一个bind
实现一个bind需要实现以下两个功能
返回一个函数,绑定this,传递预置参数
bind返回的函数可以作为构造函数使用。故作为构造函数时应使得this失效,但是传入的参数依然有效
1、返回一个函数,绑定this,传递预置参数
this.value = 2 var foo = { value: 1 }; var bar = function(name, age, school) { console.log(name) // 'An' console.log(age) // 22 console.log(school) // '家里蹲大学' console.log(this.value) // 1 } Function.prototype.bind = function(newThis) { var aArgs = Array.prototype.slice.call(arguments, 1) //拿到除了newThis之外的预置参数序列 var that = this return function() { return that.apply(newThis, aArgs.concat(Array.prototype.slice.call(arguments))) //绑定this同时将调用时传递的序列和预置序列进行合并 } } var result = bar.bind(foo, 'An') result(22, '家里蹲大学')
这里面有一个细节就是Array.prototype.slice.call(arguments, 1) 这句话,我们知道arguments这个变量可以拿到函数调用时传递的参数,但不是一个数组,但是其具有一个length属性。为什么如此调用就可以将其变为纯数组了呢。那么我们就需要回到V8的源码来进行分析。#这个版本的源码为早期版本,内容相对少一些。
function ArraySlice(start, end) { var len = ToUint32(this.length); //需要传递this指向对象,那么call(arguments), //便可将this绑定到arguments,拿到其length属性。 var start_i = TO_INTEGER(start); var end_i = len; if (end !== void 0) end_i = TO_INTEGER(end); if (start_i < 0) { start_i += len; if (start_i < 0) start_i = 0; } else { if (start_i > len) start_i = len; } if (end_i < 0) { end_i += len; if (end_i < 0) end_i = 0; } else { if (end_i > len) end_i = len; } var result = []; if (end_i < start_i) return result; if (IS_ARRAY(this)) SmartSlice(this, start_i, end_i - start_i, len, result); else SimpleSlice(this, start_i, end_i - start_i, len, result); result.length = end_i - start_i; return result; };
从源码中可以看到通过call将arguments下的length属性赋给slice后,便可通过 start_i & end_i来获得最后的数组,所以不需要传递进slice时就是一个纯数组最后也可以得到一个数组变量。
2、bind返回的函数可以作为构造函数使用
被用作构造函数时,this应指向new出来的实例,同时有prototype属性,其指向实例的原型。
this.value = 2 var foo = { value: 1 }; var bar = function(name, age, school) { ... console.log('this.value', this.value) } Function.prototype.bind = function(newThis) { var aArgs = Array.prototype.slice.call(arguments, 1) var that = this //that始终指向bar var NoFunc = function() {} var resultFunc = function() { return that.apply(this instanceof that ? this : newThis, aArgs.concat(Array.prototype.slice.call(arguments))) } NoFunc.prototype = that.prototype //that指向bar resultFunc.prototype = new NoFunc() return resultFunc } var result = bar.bind(foo, 'An') result.prototype.name = 'Lsc' // 有prototype属性 var person = new result(22, '家里蹲大学') console.log('person', person.name) //'Lsc'
上面这段模拟代码做了两件重要的事。
1.给返回的函数模拟一个prototype属性。,因为通过构造函数new出来的实例可以查询到原型上定义的属性和方法
var NoFunc = function() {} ... NoFunc.prototype = that.prototype //that指向bar resultFunc.prototype = new NoFunc() return resultFunc