今天在学习JS设计模式的过程中,学习到代理模式 其中由一个Jquery的案例引发了一些深思,做些记录,以供加深印象:
1:首先jq中有一个$.proxy 的方法 可以实现绑定代理执行函数的对象 eg:
document.querySelector('button').addEventListener('click', function () {
setTimeout( $.proxy(function(){
$(this).css('color','red')
},this),3000 )
})
这里就实现了3000秒后$.proxy第一个参数函数 函数执行上下文为 点击的button
其中这个jq代码内部JS原理实际上就是一个bind方法,现在我们手写一下myBind实现这个JQ的proxy方法,核心代码为
if(!Function.prototype.myBind){
Function.prototype.myBind = function(){
if(typeof this != 'function'){// 判断调用者是不是函数
throw new TypeError('调用myBind 需要为一个函数来触发');
}
var self = this; //保存原函数 即调用myBind的函数 eg: fn.mybind(obj) this 就是fn
var context = [].shift.call(arguments) //参数中的第一个截取下来 这个为即将转变为的执行上下文对象 eg: fn.mybind(obj) context 就是obj
var args = [].slice.call(arguments) //剩余的参数保存为一个数组
return function(){
self.apply(context, [].concat.call(args,[].slice.call(arguments))) //绑定myBind的参数和执行myBind的参数也进行合并处理 下方有演示介绍
}
}
}
现在我们就自己实现了原生 js bind的方法,让我们测试一下他的效果:
var a = "window"
var obj = {
a: 'obj'
}
function fn(b,c,d){
console.log(this.a, arguments);
}
fn.myBind(obj,'b','c','d')('e') //输出 obj ["b", "c", "d", "e"]
fn.myBind(obj,'b','c')('d','e') //输出 obj ["b", "c", "d", "e"]
fn.myBind(obj)('b','c','d','e') //输出 obj ["b", "c", "d", "e"]
这里可以看出打印的this.a 已经指向obj了 而且绑定myBind的参数和执行myBind的参数也进行了合并展示
至此我们自己手动实现JS bind就已经实现了,这里衍生一个问题,上方代码中使用了大量的 [].shift.call(arguments) [].shift.slice(arguments)这种方法,为什么不直接使用 arguments.shift() arguments.slice呢? 难道代码少写一点,看着也更直观,它不香吗? 事实确实很残酷 直接使用是不被允许的 因为arguments 并不是一个真正的数组 只是一个有length的类数组 举个例子来证明:
var a = function(){
console.log(arguments);
console.log(arguments instanceof Array)
console.log(arguments.slice(1));
}
a(1,2,3,4);
执行结果会打印
1:Arguments(4) [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
2:false
3:报错:VM1767:3 Uncaught TypeError: arguments.slice is not a function
这里从第二个打印为false 可以得出原由 arguments 并不是一个真正的数组 无法使用数组的方法 但是可以使用js的借刀杀人方法 [].shift.call [].slice.call 通过call 或者 apply 拿来主义 借用一下是没有问题的 除了这种 还有第二种方法 Array.from(arguments) 把类数组转变成真正的数组 就可以直接使用 shift slice 这些数组的自带的方法了 也是可行的。快去试试你自己手写一个js原生bind的方法吧