模拟实现bind

  • 不使用call apply
var people = {
  age: 100,
  show: function(name, sex) {
    console.info(this.age, name, sex);
  }
};

var other = {
  age: 20
};

var newShow = people.show.bind(other);

Function.prototype.bind2 = function() {
  var context = Array.prototype.shift.call(arguments);
  var that = this;
  var args = Array.prototype.slice.call(arguments); //讲伪数组转成数组

  return function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    context.fn = that;
    context.fn(...finalArgs);
    delete context.fn;
  };
};

var newShow1 = people.show.bind2(other, "geek", "male");
newShow1(); //20 'geek' 'male'

  • 使用apply
    上面使用了es6的扩展操作符去模拟es5语法有点牵强,使用apply做改进
var people = {
  age: 100,
  show: function(name, sex) {
    console.info(this.age, name, sex);
    return "hello";
  }
};

var other = {
  age: 20
};

var newShow = people.show.bind(other);

Function.prototype.bind2 = function() {
  var context = Array.prototype.shift.call(arguments);
  var that = this;
  var args = Array.prototype.slice.call(arguments); //讲伪数组转成数组

  return function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    return that.apply(context, finalArgs);// 直接使用apply绑定参数
  };
};

var newShow1 = people.show.bind2(other, "geek", "male");
const ret = newShow1(); //20 'geek' 'male'

console.info(ret); // hello

  • 使用eval继续改进
var people = {
  age: 100,
  show: function(name, sex) {
    console.info(this.age, name, sex);
  }
};

var other = {
  age: 20
};

var newShow = people.show.bind(other);

Function.prototype.bind2 = function() {
  var context = Array.prototype.shift.call(arguments);
  var that = this;
  var args = Array.prototype.slice.call(arguments); //讲伪数组转成数组

  return function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    context.fn = that;
    console.info(finalArgs);
    // 拼接函数字符串
    console.info("context.fn(" + finalArgs + ")");
    eval('context.fn(' + finalArgs + ')');

    delete context.fn;
  };
};

var newShow1 = people.show.bind2(other, "geek", "male");
newShow1(); //20 'geek' 'male'

// console.info(ret); // hello

本以为这段代码可以执行,没想到就报了个错

模拟实现bind_第1张图片
26行eval报错

为什么呢?
eval('context.fn(' + finalArgs + ')');直接这么拼接字符串调用了finalArgs 的tostring()方法将数据变成字符串,那么例子得到的结果就是:'context.fn(geek,male)',可以看到geek和male并没有被引号包裹,那么就被解析为变量,所以才报错。可以通过数组的map函数将字符串加上引号,但是不够优雅,然后参考了大佬的写法 JavaScript深入之call和apply的模拟实现,

var people = {
  age: 100,
  show: function(name, sex) {
    console.info(this.age, name, sex);
    return "hello";
  }
};

var other = {
  age: 20
};

var newShow = people.show.bind(other);

Function.prototype.bind2 = function() {
  var context = Array.prototype.shift.call(arguments);
  var that = this;
  var args = Array.prototype.slice.call(arguments); //讲伪数组转成数组

  return function() {
    var innerArgs = Array.prototype.slice.call(arguments);
    var finalArgs = args.concat(innerArgs);
    context.fn = that;

    var container = {}; //用该变量保存被eval执行的参数
    finalArgs = finalArgs.map((x, idx) => {
      container[idx] = x;
      return "container[" + idx + "]";
    });
    // 拼接函数字符串
    var ret = eval("context.fn(" + finalArgs + ")");

    delete context.fn;

    return ret;
  };
};

var newShow1 = people.show.bind2(other, "geek", "male");
var ret = newShow1(); //20 'geek' 'male'

console.info(ret); // hello

这边只是模拟操作,一般不建议在项目里使用eval,因为:
具体的原因是,js 引擎在解析包含有eval的函数时会保留该函数中的所有可以引用的变量保留在Clousre,因为根本不知道eval会插入什么样的语句,在该语句中会使用什么变量也是不确定的,所以函数中的所有可以引用的变量都会保留,那么在该函数中定义的其他函数也会保留该函数的所有可引用变量,容易造成内存浪费,同时导致内存泄漏的可能性又增大很多。
来自 let和闭包有啥关系

你可能感兴趣的:(模拟实现bind)