Function.prototype.bind : 原生bind函数,改变函数运行时的作用域。支持参数:第一个为作用域,后面的为传递给运行函数的参数(可为空)。
手写bind函数从最基本的函数开始:
1. 支持带参数的函数
var foo = {
age: 17
}
function getName(name) {
console.log(name)
return this.age
}
console.log(getName('aa')) //aa, undefined, 此时this指向window
Function.prototype.bind2 = function(context) {
var func = this // 这里的this指向getName函数
var bindArgs = Array.prototype.slice.call(arguments, 1) // 获取context后面传递的参数
return function() {
var args = Array.prototype.slice.call(arguments) // 获取实际调用时候的参数
return func.apply(context, bindArgs.concat(args))
}
}
var newFunc1 = getName.bind2(foo)
console.log(newFunc1('aa')) //aa, 17, 此时this指向window
var newFunc2 = getName.bind2(foo, 'aa')
console.log(newFunc2()) //aa, 17, 此时this指向window
2.假如上面getName函数在改变作用域后生成的函数,需要用作为构造函数,然后用new操作符来生成对象,先看原生的实现效果
var foo2 = {
value: 'test'
}
function GetName(name) {
console.log(this.value)
this.name = name
}
var Func3 = GetName.bind(foo2)
var newObj = new Func3('aa')
console.log( newObj.name, newObj instanceof GetName) // 'aa', true
3.再看用bind2来实现
var Func4 = GetName.bind2(foo2)
var newObj2 = new Func4('aa') // 此时new操作符生成的对象的this指针指向的是newObj2这个对象
console.log(newObj2) // {} 空对象,并没有name等属性,反而出现下面foo2的属性得到扩展
/**
* 原因:在new操作符的时候,bind2返回的函数体里面是func.apply(context, bindArgs.concat(args))这句
* 而context是foo2,所以是扩展的是foo2,并没有扩展newObj2
*/
console.log(foo2) // {name: 'aa',value:'test',newValue:'test'}
4. 对bind2进行优化
Function.prototype.bind3 = function(context) {
var func = this
var bindArgs = Array.prototype.slice.call(arguments, 1)
var bound = function() {
var args = Array.prototype.slice.call(arguments)
return func.apply(this instanceof bound ? this : context, bindArgs.concat(args))
}
return bound
}
var Func5 = GetName.bind3(foo2)
var newObj3 = new Func5('aa')
console.log(newObj3) // bound{name:'aa'}
5.如果GetName的prototype原型对象上有属性?
GetName.prototype.say = function() {
return this.name
}
var Func6 = GetName.bind3(foo2)
var newObj4 = new Func6('aa')
console.log(newObj4, newObj4.say) // bound{name:'aa'}, undefined 可以看出say方法并没有得到继承
6.再次对bind3进行优化
Function.prototype.bind4 = function(context) {
var func = this
var bindArgs = Array.prototype.slice.call(arguments, 1)
var bound = function() {
var args = Array.prototype.slice.call(arguments)
return func.apply(this instanceof bound ? this : context, bindArgs.concat(args))
}
bound.prototype = Object.create(this.prototype)
return bound
}
var Func7 = GetName.bind4(foo2)
var newObj5 = new Func7('aa')
console.log(newObj5, newObj5.say) // bound{name:'aa'}, function(){}
7.总结bind方法
;(function(global, factory){
return factory.call(global)
})(this, function(){
var selfBind = {}
selfBind.bind = function(funcName, context) {
var func = funcName
var bindArgs = Array.prototype.slice.call(arguments, 2)
var bound = function() {
var args = Array.prototype.slice.call(arguments)
return func.apply(this instanceof bound ? this : context, bindArgs.concat(args))
}
bound.prototype = Object.create(func.prototype)
return bound
}
this.selfBind = selfBind
})
// 使用
var person = {
name: 'rose',
age: 18
}
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function() {
return this.name
}
var bound = selfBind.bind(Person, person)
var obj = new bound('kobe', 22)
console.log(obj)