最近准备面试,翻开之前写的实现,感觉有很多问题,再重新整理一下。
call
call的原理大概就是:
- 将函数设为对象的属性
- 执行该函数
- 删除该函数
贴上我之前写的代码
Function.prototype.myCall = function (context,...args){
if(typeof context === 'object'){
// 如果参数正确就挂载到该对象,否则就就挂载到window
context = context || window
}
let Fn = Symbol('anyFunction');
// 将被改变this指向的函数挂载到传入参数的对象上
context[Fn] = this
const res = context[Fn](...args);
delete context[Fn];
return res
}
点评:
函数名使用Symbol,太过高级,实现ES3的函数却要使用ES6的特性。
还有剩余参数rest
重新写一份:
Function.prototype._call = function(){
// 拿到要绑定到的对象
var context = arguments[0] || window;
var args = [];
var timestamp = +new Date();
for(var i = 1, len = arguments.length; i < len; i++) {
args.push('arguments[' + i + ']');
}
context[timestamp] = this;
// args 会自动toString
var result = eval('context[timestamp]('+ args +')')
delete context[timestamp]
return result;
}
apply
和call类似,只不过传入的参数是个数组,处理一下就好了
Function.prototype.apply = function (context, arr) {
context = Object(context) || window;
context.fn = this;
var result;
if (!arr) {
result = context.fn();
}
else {
var args = [];
for (var i = 0, len = arr.length; i < len; i++) {
args.push('arr[' + i + ']');
}
result = eval('context.fn(' + args + ')')
}
delete context.fn
return result;
}
bind
按年龄来说,前两个都是ES3的东西,而bind是ES5新增的函数,所以可以在bind实现中用到前两个函数。
bind返回被改变this指向的函数,传参的时候可以在bind传,并且可以在执行的时候传。
实现思路就是返回一个函数,函数里执行被改变this指向的函数,并且把(bind和返回的函数的)实参带上。
Function.prototype._bind = function(){
var context = arguments[0]
var args = Array.prototype.slice.call(arguments,1)
return function(){
var params = args.concat(Array.prototype.slice.call(arguments))
return this.apply(context,params);
}
}
// 测试
const foo = {
say(a,b){
console.log(this.value);
console.log(a,b);
}
}
const f = foo.say.bind({value:1},2)
f(3)
// 1
// 2 3
然而事情并没有这么简单,当被绑定的函数作为构造函数时,因为 new 的缘故,this 会被修改指向为新创建的对象(普通函数指向window)
演示:
var foo = {
value: 1
};
function bar(name, age) {
console.log(this.value); // undefined
}
var bindFoo = bar.bind(foo, 'daisy');
// new 操作会把构造函数的this指向新创建的对象
var obj = new bindFoo('18');
所以修改为:
Function.prototype._bind = function(){
var context = arguments[0]
var args = Array.prototype.slice.call(arguments,1)
const bound = function(){
var params = args.concat(Array.prototype.slice.call(arguments))
// 当作为构造函数时,this 指向实例,此时结果为 true,将绑定函数的 this 指向该实例,可以让实例获得来自绑定函数的值
const result = (this instanceof bound ? this : context)
return result;
}
// 让实例继承来自绑定函数的原型的值
bound.prototype = Object.create(this.prototype);
return bound;
}
参考:
JavaScript深入之call和apply的模拟实现 · Issue #11 · mqyqingfeng/Blog (github.com)