The
bind() function creates a new function (called a bound function) that has the same function body (the built-in call attribute in the ECMAScript 5 specification) as the called function (the target function of the bound function). When the target function is called the this value is bound to the first parameter of bind() and cannot be overridden. When the bound function is called, bind() also accepts preset parameters to provide to the original function. A bound function can also create objects using the new operator: this behaves like the original function as a constructor. The supplied this value is ignored, and the arguments to the call are supplied to the mock function.
Since the scope in JavaScript is determined by the environment in which it is run, often the function definition is different from the environment in which it is actually run, and the scope will also change accordingly.
For example, the following situation:
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
As introduced above, an important role of the bind method is to bind a scope to a function. However, the bind method is not compatible with lower version browsers. Here we can implement it manually.
Split the key ideas
Because the bind method will not execute the function immediately, it needs to return a function to be executed (closure is used here, which can return a function) return function(){}
Scope binding, Here you can use the apply or call method to implement xx.call(yy)/xx.apply(yy)
parameter transfer. Due to the uncertainty of parameters, you need to use apply to pass the array (the example is more clear) xx.apply(yy, [...Array...]), it is inconvenient to use call, because the parameters behind the call need to be listed one by one
implementation
With the above ideas, the rough prototype is already clear, the code should It is also easy to implement
binding scope and binding parameters
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) } }
test
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
dynamic parameters
The scope binding of the bind method has been implemented above, but the fly in the ointment is that since we return a function , it should support passing parameters when calling. Obviously, the above fun_new does not support passing parameters when calling, and can only pass parameters when testBind is bound, because what we end up calling is this return function
function(){ return _this.apply(that,args) } 这里面的args在绑定的时候就已经确定了,调用的时候值已经固定, 我们并没有处理这个function传递的参数。
We have Transformation
return function(){ return _this.apply(that, args.concat(Array.prototype.slice.apply(arguments,[0])) ) }
Array.prototype.slice.apply(arguments,[0]) here refers to a series of parameters passed when the return function is executed, so it starts from the first parameter [0], the previous args = slice.apply(arguments,[1]) refers to the parameters passed when the testBind method is executed, so starting from the second one [1], the two are essentially different and cannot be confused. Only after merging the two It is the complete parameter of the return function
So there is the following implementation
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])) ) } }
Test
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
Among the above two parameter passing methods, bind has the highest priority, from args.concat(Array.prototype.slice.apply(arguments,[ 0])) It can also be seen that the parameters of bind are in front of the array.
Prototype chain
There is a sentence in the official document:
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.
It means that after the bound function is instantiated by new, it needs to inherit the prototype chain method of the original function, and the this provided during the binding process is ignored (inheriting the original the this object of the function), but the parameters will still be used.
Here we need a transfer function to pass the prototype chain
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; }
And the first parameter this of the bind method does not need to be passed. There are two situations
Directly call the method after bind
var f = function () { console.log('不传默认为'+this) };f.bind()() // 不传默认为 Window
So directly call the bind method When defining the method, apply(that, it is recommended to change it to apply(that||window,. In fact, it is okay not to change it, because if it is not passed, the default point is to window
Using new to instantiate the bound method
It is easy to be confused, the key is to be clear What the standard bind method does when new can be clearly implemented
Here we need to see what operations the new method does. For example, var a = new b()
Create an empty object a = { }, and this variable reference points to this empty object a
inherits the prototype of the instantiated function: 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作用域了。