手动封装es5中 bind apply call
1.call(不传参版本)
// 函数原型上添加 myCall方法 来模拟call Function.prototype.myCall = function(obj){
if(obj === null || obj === undefined){
obj = window;
}else{
obj = object(obj);
} //我们要让传入的obj成为, 函数调用时的this值. obj._fn_ = this; //在obj上添加_fn_属性,值是this(要调用此方法的那个函数对象)。 val = obj._fn_(...arg); //不能直接return obj._fn_(...arg) 这样就不delete属性了 obj._fn_(); //在obj上调用函数,那函数的this值就是obj. delete obj._fn_; // 再删除obj的_fn_属性,去除影响. //_fn_ 只是个属性名 你可以随意起名,但是要注意可能会覆盖obj上本来就有的属性 return val }
如果要传参的话可以借用es6里面的拓展运算符 (...)(省略了其他的判断)
Function.prototype.myCall = function(obj,...arg){ obj._fn_ = this; obj._fn_(...arg); delete obj._fn_; }
测试一下
let test = { name:'test' } let o = { name:'o', fn:function(){ console.log(this.name, ...arguments); //这里把参数显示一下 } } o.fn(1,2,3) // "o" 1 2 3 o.fn.call(test,1,2,3) // "test" 1 2 3 o.fn.myCall(test,1,2,3) // "test" 1 2 3
不用es6可以用eval
Function.prototype.myCall = function(obj){ let arg = [];
let val; for(let i = 1 ; i<arguments.length ; i++){ arg.push( 'arguments[' + i + ']' ) ; // 这里要push 这行字符串 而不是直接push 值 // 因为直接push值会导致一些问题 // 例如: push一个数组 [1,2,3] // 在下面👇 eval调用时,进行字符串拼接,JS为了将数组转换为字符串 , // 会去调用数组的toString()方法,变为 '1,2,3' 就不是一个数组了,相当于是3个参数. // 而push这行字符串,eval方法,运行代码会自动去arguments里获取值 } obj._fn_ = this; val = eval( 'obj._fn_(' + arg + ')' ) // 字符串拼接,JS会调用arg数组的toString()方法,这样就传入了所有参数 delete obj._fn_;
return val } //测试 let test = { name:'test' } let o = { name:'o', fn:function(){ console.log(this.name, ...arguments); //这里把参数显示一下 } } o.fn(1,['a','b'],3) // "o" 1 ["a","b"] 3 o.fn.call(test,1,['a','b'],3) // "test" 1 ["a","b"] 3 o.fn.myCall(test,1,['a','b'],3) // "test" 1 ["a","b"] 3
2.apply(用上面的mycall版本)
// ES6 Function.prototype.myApply = function(obj,arr){ let args = []; for(let i = 0 ; i<arr.length; i++){ args.push( arr[i] ); } // 其实直接 ...arr 传参也可以 但是效果就和aplly有微小差别了 return this.myCall(obj, ...args); } // eval Function.prototype.myApply = function(obj,arr){ let args = []; for(let i = 0 ; i<arr.length; i++){ args.push( 'arr[' + i + ']' ); // 这里也是push 字符串 } return eval( 'this.myCall(obj,' + args + ')' ); }
不用mycall版本
Function.prototype.myApply = function(obj,arr){ let args = []; let val ; for(let i = 0 ; i<arr.length ; i++){ args.push( 'arr[' + i + ']' ) ; } obj._fn_ = this; val = eval( 'obj._fn_(' + args + ')' ) delete obj._fn_; return val }
3.bind(借用apply,call)
Function.prototype.bind = function(son){ var that = this; var parent = Array.prototype.slice.call(arguments);//具有length属性的对象转成数组 return function() { return that.apply(son, parent.slice(1)); } }
es5
Function.prototype.myBind = function(obj){ let _this = this; let argArr = []; let arg1 = []; for(let i = 1 ; i<arguments.length ; i++){ // 从1开始 arg1.push( arguments[i] ); // 这里用arg1数组收集下参数 // 获取arguments是从1开始, 但arg1要从 0(i-1)开始 // 若是用Array.prototype.slice.call(argument)就方便多了 argArr.push( 'arg1[' + (i - 1) + ']' ) ; // 如果用arguments在返回的函数里运行 会获取不到这个函数里的参数了 } return function(){ let val ; for(let i = 0 ; i<arguments.length ; i++){ // 从0开始 argArr.push( 'arguments[' + i + ']' ) ; } obj._fn_ = _this; val = eval( 'obj._fn_(' + argArr + ')' ) ; delete obj._fn_; return val }; }
es6
Function.prototype.myBind = function(obj,...arg1){ return (...arg2) => { let args = arg1.concat(arg2); let val ; obj._fn_ = this; val = obj._fn_( ...args ); delete obj._fn_; return val } }