最近在看《深入了解JavaScript》这本书,在看到了关于Function构造函数和bind函数后,让我突然有股冲动想自己来实现一下bind函数,于是乎,我就开始尝试编写bind函数。
尝试编写的第一个版本,写的实在是太累赘了,并且还存在一些问题。
Function.prototype.binds = function(obj){ var This = this; //var args = Array.prototype.slice.call(arguments, 1); if(typeof obj != 'object'){ throw new Error('The first argument must be Object!'); } var args = []; if(arguments.length > 0){ for(var i = 1; i < arguments.length; i++){ args.push(arguments[i]); } } function fun(){ var arr = clone(args); for(var i=0; i < arguments.length; i++){ arr.push(arguments[i]); } var name = 'fun' + String(Math.random()).slice(2,-1); obj[name] = This; var params = []; for(i = 0; i < arr.length; i++){ params.push('arr['+i+']'); } params = params.join(); var newFun = new Function('arr','obj["'+name+'"]('+ params +')'); newFun(arr); delete obj[name]; } return fun; }; function clone(o){ var obj; switch(Object.prototype.toString.call(o).slice(8, -1)){ case 'Array': obj = []; for(var i = 0; i < o.length; i++){ obj.push(clone(o[i])); } return obj; break; case 'Object': obj = {}; for(var i in o){ if(o.hasOwnProperty(i)){ obj[i] = clone(o[i]); } } return obj; break; default: return o; break; } } function say(){ console.log(this.name, arguments); } var obj = { name:'tom' }; var fun = say.binds(obj,1,2,3); fun(4,5); fun(4,5,7,8); fun(4);
……
……
……
……
时隔多日之后,我看到了W3C高级文档中的ECMAScript继承机制实现中的对象冒充,让我茅塞顿开,其实bind函数并不需要写得那么复杂。其原理无外乎就是在要依赖的对象上面新建一个属性用以承载该函数,在执行之后,再删除掉这个属性而已。而使用Function构造函数只是想要把数组内的成员一个一个地传入函数中而已。
以下是第二个版本,基本没有问题了,算是完美实现了bind函数。
Function.prototype.newBind = function(){ var name = 'random' + String(Math.random()).slice(2,-1);//随机生成一个属性名,防止冲突。 var args = [];//用处存储传入参数 var This = this;//保存this指针 var obj = arguments[0];//获取要绑定的对象 //将参数存入args for(var i = 1; i < arguments.length; i++){ args.push(arguments[i]); } //返回一个新的函数 return function(){ //循环获取传入的参数,并存入args中 for(var i = 0; i < arguments.length; i++){ args.push(arguments[i]); } //根据参数个数生成调用的字符串 var params = []; for(var i = 0; i < args.length; i ++){ params.push('args['+ i +']'); } //使用Function构造函数构造一个函数用以将参数分个传入,并调用 var newFun = new Function('obj','name', 'This','args','obj[name] = This;obj[name]('+ params.join() +');delete obj[name]'); newFun(obj, name, This, args); } }; var b = { 0:1, 1:2, 2:3, length:3 } var c = Array.prototype.forEach.bind(b, function(){ console.log(arguments) }) c();
而如call和apply的编写只是在bind的基础上进行一些修改。
如call,这里就不用返回一个新的函数,只需要将bind返回的函数该为立即执行函数即可
Function.prototype.newCall = function(){ var name = 'random' + String(Math.random()).slice(2,-1);//随机生成一个属性名,防止冲突。 var args = [];//用处存储传入参数 var This = this;//保存this指针 var obj = arguments[0];//获取要绑定的对象 //将参数存入args for(var i = 1; i < arguments.length; i++){ args.push(arguments[i]); } (function(){ //循环获取传入的参数,并存入args中 for(var i = 0; i < arguments.length; i++){ args.push(arguments[i]); } //根据参数个数生成调用的字符串 var params = []; for(var i = 0; i < args.length; i ++){ params.push('args['+ i +']'); } //使用Function构造函数构造一个函数用以将参数分个传入,并调用 var newFun = new Function('obj','name', 'This','args','obj[name] = This;obj[name]('+ params.join() +');delete obj[name]'); newFun(obj, name, This, args); })() }; var b = { 0:1, 1:2, 2:3, length:3 } var c = Array.prototype.forEach.newCall(b, function(){ console.log(arguments) })
而apply则是在call的基础上再修改一点,这时候就不需要去截取arguments,只需要直接使用arguments[1]就可以了。
Function.prototype.newApply = function(){ var name = 'random' + String(Math.random()).slice(2,-1);//随机生成一个属性名,防止冲突。 var args = arguments[1] || [];//用处存储传入参数 var This = this;//保存this指针 var obj = arguments[0];//获取要绑定的对象 //立刻执行函数 (function(){ //循环获取传入的参数,并存入args中 for(var i = 0; i < arguments.length; i++){ args.push(arguments[i]); } //根据参数个数生成调用的字符串 var params = []; for(var i = 0; i < args.length; i ++){ params.push('args['+ i +']'); } //使用Function构造函数构造一个函数用以将参数分个传入,并调用 var newFun = new Function('obj','name', 'This','args','obj[name] = This;obj[name]('+ params.join() +');delete obj[name]'); newFun(obj, name, This, args); })() }; var b = { 0:1, 1:2, 2:3, length:3 } var c = Array.prototype.forEach.newApply(b, [function(){ console.log(arguments) }])
到这里bind、call和apply的实现就结束了。果然随着知识量的提升,一些本来很困扰人的问题便会迎刃而解。