call,apply和bind方法的实现
在JavaScript中,call,apply和bind方法用于完成this的显式绑定。call和apply将某个函数的this绑定到通过它们的第一个参数指定的对象上并执行这个函数,而bind将返回一个函数,这个函数的this被绑定到了通过bind的第一个参数指定的对象上,且无法通过显式绑定或者隐式绑定修改这个函数的this,只能通过new绑定改变this的指向。
call和apply非常类似,它们的区别在于apply需要以数组的形式提供除第一个参数以外的剩余参数,而call只需要按照正常方式传参。call和apply会把这些“剩余参数”传递给调用它们的函数。
1 // call方法的实现 2 Function.prototype.myCall = function (context) { 3 if (typeof this !== 'function') { 4 throw new TypeError("The caller of call must be a function"); 5 } 6 7 // 如果没有提供上下文对象,则将上下文对象设置为全局对象 8 context = context || global; 9 10 // 使用Symbol设置属性名,避免属性污染 11 const fn = Symbol(); 12 13 // 将调用call方法的函数设置为上下文对象的属性 14 context[fn] = this; 15 16 // 获得参数 17 const args = [...arguments].slice(1); 18 19 // 执行函数 20 const result = context[fn](...args); 21 22 // 删除属性 23 delete context[fn]; 24 25 return result; 26 }
1 // apply方法的实现 2 Function.prototype.myApply = function (context) { 3 if (typeof this !== "function") { 4 throw new TypeError("The caller of apply must be a function"); 5 } 6 7 // 如果没有提供上下文对象,则将上下文对象设置为全局对象 8 context = context || global; 9 10 // 使用Symbol设置属性名,避免属性污染 11 const fn = Symbol(); 12 13 // 将调用apply方法的函数设置为上下文对象的属性 14 context[fn] = this; 15 16 // 判断apply方法的第二个参数是否存在,并根据相应执行函数 17 let result; 18 if (arguments[1]) { 19 result = context[fn](...arguments[1]); 20 } else { 21 result = context[fn](); 22 } 23 24 // 删除属性 25 delete context[fn]; 26 27 return result; 28 }
bind的实现更加复杂。首先,我们需要获取bind的this值并保存到一个变量中,这是因为需要返回的函数bound无法访问bind的this值(在内部函数的作用域中无法访问外部函数的this)。其次,我们需要获得curried参数并将其与传递给bound的参数拼接起来。另外还需要注意的是new绑定是唯一可以改变bound函数的this值的方法,因此必须判断何时通过new操作符调用bound,此时我们需要将bound的this值设置为new创建的对象。最后需要注意bound的prototype属性应与调用bind的函数的prototype属性保持一致。
1 // bind方法的实现 2 Function.prototype.myBind = function (context) { 3 if (typeof this !== "function") { 4 throw new TypeError("The caller of bind must be a function"); 5 } 6 7 // 如果没有提供上下文对象,则将上下文对象设置为全局对象 8 context = context || global; 9 10 // 由于内部函数的作用域无法访问外部函数作用域中的this,因此需要把this用另一个变量保存 11 let func = this; 12 13 // 获得curried参数 14 let curried = [].slice.call(arguments, 1); 15 16 // 判断是否通过new调用bind返回的函数 17 let bound = function () { 18 let obj; 19 if (this instanceof bound) { 20 obj = this; // 通过new调用bind返回的函数,需要将obj绑定到new创建的对象上 21 } else { 22 obj = context; // 否则将obj绑定到bind指定的上下文对象上 23 } 24 25 // 拼接参数,并执行函数 26 return func.apply(obj, curried.concat([].slice.call(arguments))); 27 } 28 29 // 将bind返回的函数的prototype属性设置为func的prototype 30 bound.prototype = Object.create(func.prototype); 31 32 return bound; 33 }