抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

最近准备面试,翻开之前写的实现,感觉有很多问题,再重新整理一下。

call

call的原理大概就是:

  1. 将函数设为对象的属性
  2. 执行该函数
  3. 删除该函数

贴上我之前写的代码

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)

评论