bind() 函数会建立一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具备相同的函数体(在 ECMAScript 5 规范中内置的call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用new操做符建立对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。
javascript
因为javascript
中做用域是由其运行时候所处的环境决定的,因此每每函数定义和实际运行的时候所处环境不同,那么做用域也会发生相应的变化。
例以下面这个状况:java
var id = 'window'; //定义一个函数,可是不当即执行 var test = function(){ console.log(this.id) } test() // window //把test做为参数传递 var obj = { id:'obj', hehe:test } //此时test函数运行环境发生了改变 obj.hehe() // 'obj' //为了不这种状况,javascript里面有一个bind方法能够在函数运行以前就绑定其做用域,修改以下 var id = 'window'; var test = function(){ console.log(this.id) }.bind(window) var obj = { id:'obj', hehe:test } test() // window obj.hehe() // window
上面介绍了bind
方法的一个重要做用就是为一个函数绑定做用域,可是bind
方法在低版本浏览器不兼容,这里咱们能够手动实现一下。数组
由于bind方法不会当即执行函数,须要返回一个待执行的函数(这里用到闭包,能够返回一个函数)return function(){}
浏览器
做用域绑定,这里可使用apply或者call方法来实现 xx.call(yy)/xx.apply(yy)
闭包
参数传递,因为参数的不肯定性,须要用apply传递数组(实例更明了
)xx.apply(yy,[...Array...]),若是用call就不太方便了,由于call后面的参数须要一个个列出来
app
有了上述的思路,大体的雏形已经明了了,代码应该也很容易实现ide
Function.prototype.testBind = function(that){ var _this = this, /* *因为参数的不肯定性,统一用arguments来处理,这里的arguments只是一个类数组对象,有length属性 *能够用数组的slice方法转化成标准格式数组,除了做用域对象that之外, *后面的全部参数都须要做为数组参数传递 *Array.prototype.slice.apply(arguments,[1])/Array.prototype.slice.call(arguments,1) */ slice = Array.prototype.slice, args = slice.apply(arguments,[1]); //返回函数 return function(){ //apply绑定做用域,进行参数传递 return _this.apply(that,args) } }
测试函数
var test = function(a,b){ console.log('做用域绑定 '+ this.value) console.log('testBind参数传递 '+ a.value2) console.log('调用参数传递 ' + b) } var obj = { value:'ok' } var fun_new = test.testBind(obj,{value2:'also ok'}) fun_new ('hello bind') // 做用域绑定 ok // testBind参数传递 also ok // 调用参数传递 undefined
上面已经实现了bind
方法的做用域绑定,可是美中不足的是,既然咱们返回的是一个函数,调用的时候应该支持传递参数,很显然,上面的 fun_new
调用的时候并不支持传参,只能在 testBind
绑定的时候传递参数,由于咱们最终调用的是这个返回函数测试
function(){ return _this.apply(that,args) } 这里面的args在绑定的时候就已经肯定了,调用的时候值已经固定, 咱们并无处理这个function传递的参数。
咱们对其进行改造this
return function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) }
这里的 Array.prototype.slice.apply(arguments,[0])
指的是这个返回函数执行的时候传递的一系列参数,因此是从第一个参数开始 [0]
,以前的args = slice.apply(arguments,[1])
指的是 testBind
方法执行时候传递的参数,因此从第二个开始 [1]
,两则有本质区别,不能搞混,只有二者合并了以后才是返回函数的完整参数
因此有以下实现
Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]); return function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } }
测试
var test = function(a,b){ console.log('做用域绑定 '+ this.value) console.log('testBind参数传递 '+ a.value2) console.log('调用参数传递 ' + b) } var obj = { value:'ok' } var fun_new = test.testBind(obj,{value2:'also ok'}) fun_new ('hello bind') // 做用域绑定 ok // testBind参数传递 also ok // 调用参数传递 hello bind
在以上2种传参方式中,bind
的优先级高,从 args.concat(Array.prototype.slice.apply(arguments,[0]))
也能够看出来,bind
的参数在数组前面。
官方文档上有一句话:
A bound function may also be constructed using the new operator: doing so acts as though the target function had instead been constructed. The provided this value is ignored, while prepended arguments are provided to the emulated function.
说明绑定事后的函数被new
实例化以后,须要继承原函数的原型链方法,且绑定过程当中提供的this被忽略(继承原函数的this对象),可是参数仍是会使用。
这里就须要一个中转函数把原型链传递下去
fNOP = function () {} //建立一个中转函数 fNOP.prototype = this.prototype; xx.prototype = new fNOP() 修改以下 Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]), fNOP = function () {}, //因此调用官方bind方法以后 有一个name属性值为 'bound ' bound = function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } fNOP.prototype = _this.prototype; bound.prototype = new fNOP(); return bound; }
并且bind
方法的第一个参数this
是能够不传的,须要分2种状况
直接调用bind以后的方法
var f = function () { console.log('不传默认为'+this) };f.bind()() // 不传默认为 Window
因此直接调用绑定方法时候
apply(that,
建议改成 apply(that||window,
,其实不改也能够,由于不传默认指向window
使用new
实例化被绑定的方法
容易糊涂,重点在于弄清楚标准的bind方法在new的时候作的事情,而后就能够清晰的实现
这里咱们须要看看 new
这个方法作了哪些操做 好比说 var a = new b()
建立一个空对象 a = {}
,而且this
变量引用指向到这个空对象a
继承被实例化函数的原型 :a.__proto__ = b.prototype
被实例化方法b
的this
对象的属性和方法将被加入到这个新的 this
引用的对象中: b
的属性和方法被加入的 a
里面
新建立的对象由 this
所引用 :b.call(a)
经过以上能够得知,若是是var after_new = new bindFun();
因为这种行为是把原函数当成构造器,那么那么最终实例化以后的对象 this
须要继承自原函数, 而这里的 bindFun
目前是
function(){ return _this.apply(that || window, args.concat(Array.prototype.slice.apply(arguments,[0])) ) }
这里apply
的做用域是绑定的that || window
,在执行 testBind()
的时候就已经固定,并无把原函数的this对象继承过来,不符合咱们的要求,咱们须要根据apply的特性解决这个问题:
在一个子构造函数中,你能够经过调用父构造函数的 `apply/call` 方法来实现继承 例如
function Product(name, price) { this.name = name; this.price = price; if (price < 0) { throw RangeError('Cannot create product ' + this.name + ' with a negative price'); } } function Food(name, price) { Product.call(this, name, price); this.category = 'food'; } //等同于(其实就是把Product放在Food内部执行了一次) function Food(name, price) { this.name = name; this.price = price; if (price < 0) { throw RangeError('Cannot create product ' + this.name + ' with a negative price'); } this.category = 'food'; }
因此在new
新的实例的时候实时将这个新的this
对象 进行 apply
继承原函数的 this
对象,就能够达到 new
方法里面的第 3 步的结果
apply(that||window, //修改成 若是是new的状况,须要绑定new以后的做用域,this指向新的实例对象 apply(isNew ? this : that||window, ==> Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]), fNOP = function () {}, //因此调用官方bind方法以后 有一个name属性值为 'bound ' bound = function(){ return _this.apply(isNew ? this : that||window, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } fNOP.prototype = _this.prototype; bound.prototype = new fNOP(); return bound; }
这里的 isNew
是区分 bindFun
是直接调用仍是被 new
以后再调用,经过原型链的继承关系能够知道,bindFun
属于 after_new
的父类,因此 after_new instanceof bindFun 为 true,
同时bindFun.prototype = new fNOP()
原型继承; 因此 fNOP
也是 after_new
的父类, after_new instanceof fNOP 为 true
Function.prototype.testBind = function(that){ var _this = this, slice = Array.prototype.slice, args = slice.apply(arguments,[1]), fNOP = function () {}, bound = function(){ //这里的this指的是调用时候的环境 return _this.apply(this instanceof fNOP ? this : that||window, args.concat(Array.prototype.slice.apply(arguments,[0])) ) } fNOP.prototype = _this.prototype; bound.prototype = new fNOP(); return bound; }
我看到有些地方写的是
this instanceof fNOP && that ? this : that || window,
我我的以为这里有点不正确,若是绑定时候不传参数,那么that
就为空,那不管怎样就只能绑定 window做用域了。
以上是我的看法,不对的地方望指导,谢谢!