js实现call apply bind函数

196 阅读4分钟

实现call apply bind函数

在JavaScript中,call、apply和bind是用来改变函数上下文的三个方法,在实现之前先了解下三个函数的区别:

三个函数的使用区别

  1. 调用效果不同: call与apply改变函数的this指向后调用了该函数,而bind只会返回一个被绑定好this的函数,不会主动执行
  2. 传入参数不同 call方法接受一个参数列表,apply方法接受一个参数数组

需要留意的情况

  1. callapplybind的第一个参数thisArg传入的是函数需要绑定this的对象, thisArgundefinnd或者null时,this会被绑定到全局上,在浏览器环境下也就是window,所以需要对thisArg做边界判断
  2. 如果传入thisArg的是基本数据类型,需要对基本数据类型转化成对象, 这里推荐使用Object()进行转换,该方法不会改变数据的类型,且如果给定值是一个已经存在的对象,则会返回这个已经存在的值(相同地址),,需要注意的是如果设定Symbol()值为对象属性,Symbol属性不可被delete运算符删除,需要使用Reflect.deleteProperty()方法删除
  3. 考虑到绑定this的函数有可能为箭头函数:

箭头函数中this指向不能被callapplybind方法改变,调用这三个函数后箭头函数中的this依旧指向被定义时的执行上下文

const foo = () => {
  console.log(this);
};
const obj = { name: "obj" };
foo.call(obj); // window (this依旧指向该foo定义时的执行上下文)

箭头函数中不存在arguments,当在函数内没有arguments会通过作用域链去上层作用域找, 一直找到全局作用域,而浏览器环境下全局作用域没有arguments,可能会报错,所以实现时建议不要使用arguments来获取函数参数

  1. 对于this绑定,这里采用也就是隐式绑定,给对象添加一个方法func的形式来绑定this,考虑对象自身可能存在同名属性,为避免影响对象,使用ES6Symbol()方法生成的唯一值代替func属性。每个从 Symbol() 返回的 symbol 值都是唯一的。一个 symbol 值能作为对象属性的标识符;这是该数据类型仅有的目的

  2. 如果绑定this的函数执行后需要返回内容,需要将执行结果拿到并返回出去

call

//fn.call(obj,'1','2')
Function.prototype.myCall = function (thisArg, ...args) {
  // 获取函数
  const fn = this;
  // 获取要绑定this的对象
  thisArg =
    thisArg !== undefined && thisArg !== null ? Object(thisArg) : window;
  // 获取参数
  args = Array.isArray(args) ? args : [];
  //这里采用Symbol()保证新加的属性不同名,不会污染对象
  const func = Symbol("fn");
  // 给对象添加要绑定的函数
  thisArg[func] = fn;
  // 通过(对象.方法)的形式隐式绑定函数的this,并获取函数执行后的返回结果
  const result = thisArg[func](...args);
  // 调用完成之后删除对象上的该函数
  Reflect.deleteProperty(thisArg, func);
  // 将函数调用结果返回
  return result;
};

apply

// fn.apply(obj,['name','age'])
Function.prototype.myApply = function (thisArg, argArray) {
  // 获取函数
  const fn = this;
  // 获取要绑定this的对象
  thisArg =
    thisArg !== undefined && thisArg !== null ? Object(thisArg) : window;
  // 获取参数
  argArray = Array.isArray(argArray) ? argArray : [];
  // 这里采用Symbol()保证新加的属性不同名,不会污染对象  
  const func = Symbol("fn"); 
  // 给对象添加要绑定的函数 
  thisArg[func] = fn;
  // 隐式绑定函数的this,获取函数执行后的返回结果
  const result = thisArg[func](...argArray);
  // 调用完成之后删除对象上的该函数
  Reflect.deleteProperty(thisArg, func);
  // 将函数调用结果返回
  return result;
};

bind

//fn.bind(obj,1,2)(5,6)
Function.prototype.myBind = function (thisArg, ...argArray) {
  // 获取函数
  const fn = this;
  // 获取要绑定this的对象
  thisArg =
    thisArg !== undefined && thisArg !== null ? Object(thisArg) : window;
  // 获取绑定时的参数
  argArray = Array.isArray(argArray) ? argArray : [];
  // 定义需要返回的代理函数
  function proxyFn(...args) {
    // 获取两次传参的总参数
    const arg = [...argArray, ...args];
    // 这里采用Symbol()保证新加的属性不同名,不会污染对象 
    const func = Symbol("fn"); 
    // 给对象添加要绑定的函数 
    thisArg[func] = fn;
    // 隐式绑定函数的this,获取函数执行后的返回结果
    const result = thisArg[func](...arg);
    // 删除绑定的函数属性
    Reflect.deleteProperty(thisArg, func);
    // 返回函数的执行结果
    return result;
  }
  // 最后返回未执行的函数
  return proxyFn;
};

检验

const obj = { name: "obj" };
function foo() {
  console.log(this, this.name);
}
foo.myCall(obj)
console.log(obj, "输出");

可以看到this已被绑定到obj对象上,执行时可拿到obj上绑定的函数,执行结束后obj依旧保持原样