JavaScript:手写 bind,call,apply

65 阅读2分钟

手写源码并分析逻辑

/** @format
 * 函数
 * call,apply,bind 用于改变函数中执行时的this
 * apply,call 参数不一致 apply 是一个数组,call是一个参数列表
 * bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
 */
// 根据如上述求定义编写自定义改变this函数
/**
 * call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行
 */
/**
 * ES6写法
 * Function.prototype.$call=function(context,...args){}
 * @returns any
 */
Function.prototype.$call = function () {
 /**
  * this 用于引用执行上下文中的对象
  * this 谁调用指向谁,动态调整
  * 当访问对象的属性成员时
  * 如果本身没有,就通过 __proto__ 向上寻找
  * __proto__ 指向的是 对象的构造函数原型对象 fun.prototype
  * 由于只要是对象类型的数据就拥有 __proto__ 属性
  * fun.prototype 不存在的话接着寻找 fun.prototype.__proto__
  * 最后也找不到,就返回 null
  *  */
 console.log('我要打印的值:', this);
 // 获取参数
 const context = arguments[0] || window;
 // es5 语法 可以遍历参数
 /*  const _arguments = [];
 for (let index = 1; index < arguments.length; index++) {
  _arguments.push(arguments[index]);
 } */
 //  es6语法
 const _arguments = [...arguments].slice(1);
 context.fn = this;
 const result = context.fn(..._arguments);
 delete context.fn;
 return result;
};
/**
 * apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变thi指向一次。
 */
Function.prototype.$apply = function () {
 console.log('我要打印的值:', this);
 // 获取参数
 const context = arguments[0] || window;
 context.fn = this;
 let result = null;
 if (!arguments[1] || !Array.isArray(arguments[1]) || arguments[1].length === 0) {
  result = context.fn();
 } else {
  result = context.fn(...arguments[1]);
 }
 delete context.fn;
 return result;
};
/**
 * bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
 */
Function.prototype.$bind = function () {
 const context = arguments[0] || window;
 const _arguments1 = [...arguments].slice(1);
 context.fn = this;
 const fn = this;
 return function () {
  const _arguments2 = [...arguments].slice();
  /**
   * apply 方法允许我们在调用函数时,将一个数组或类数组对象展开为函数的参数列表。这正是我们需要的功能,因为我们想要将预设的参数与新函数调用时传入的参数合并。
另外,使用 apply 方法还能使得代码更加简洁,因为我们不需要手动构建一个参数数组,而是直接使用 apply 方法来将预设的参数与新函数调用时传入的参数合并。这样可以减少代码的复杂度和出错的可能性。
   */
  return fn.$apply(context, [].concat[(_arguments1, _arguments2)]);
  // 直接调用
  // return context.fn(...([].concat(_arguments1, _arguments2)));
 };
};
/**
 * 等同于
 * var a= new Function();
 */
const obj = {
 name: 'tongbinzuo',
};
function a() {
 console.log('我要打印的值:', this.name);
}
const b = a.$bind(obj, 1, 2, 3, 4);
b(5, 6, 7, 8);