高级前端js【Q656】JS 中如何实现 call/apply

JS 中如何实现 call/apply

更多描述 在 JS 中如何实现 call/apply?

相关问题:

Issue 欢迎在 Gtihub Issue 中回答此问题: Issue 674

Author 回答者: shfshanyue

const call = (fn, thisObj, ...args) => {
  thisObj.fn = fn;
  const r = thisObj.fn(...args);
  delete thisObj.fn;
  return r;
};

Author 回答者: wussss

bind/softBind/apply/call 都是this显式绑定的方法

  • bind会返回一个硬绑定的新函数,新函数会使用指定的第一个thisCtx去调用原始函数,并将其它参数传给原始函数。 硬绑定会降低函数的灵活性,在绑定之后不能通过显式或硬绑定的方式改变this,只能通过new改变
  • softBind 会对指定的函数进行封装,首先检查调用时的 this,如果 this 绑定到全局对象或者 undefined,那就用指定的thisCtx 去调用函数,否则不会修改 this
  • apply和call功能相同,都是以指定的thisCtx和参数去执行方法,并返回原方法的返回值,只是apply中参数以数组传递
Function.prototype.myBind = function (ctx = globalThis) {
  const fn = this;
  const args = Array.from(arguments).slice(1);
  function bound() {
    if (this instanceof bound) {
      fn.apply(this, args);
    } else {
      fn.apply(ctx, args);
    }
  }
  bound.prototype = fn.prototype;
  return bound;
};
Function.prototype.mySoftBind = function (ctx = globalThis) {
  const fn = this;
  const args = Array.from(arguments).slice(1);
  function bound() {
    if (!this || this === globalThis) {
      fn.apply(ctx, args);
    } else {
      fn.apply(this, args);
    }
  }
  bound.prototype = fn.prototype;
  return bound;
};
Function.prototype.myCall = function (ctx = globalThis) {
  const args = Array.from(arguments).slice(1);
  const key = Symbol("key");
  ctx[key] = this;
  const res = ctx[key](...args);
  delete ctx[key];
  return res;
};
Function.prototype.myApply = function (ctx = globalThis) {
  const args = arguments[1];
  const key = Symbol("key");
  ctx[key] = this;
  const res = ctx[key](...args);
  delete ctx[key];
  return res;
};

Author 回答者: heretic-G

Function.prototype.call = function call(arm, ...args) {
  let fun = this;
  if (typeof fun !== "function") throw TypeError("must is function");
  let armObj = arm;
  if (typeof arm !== "object") {
    armObj = Object(arm);
  }
 
  let symbolKey = Symbol("tempKey");
  armObj[symbolKey] = fun;
  let result = armObj[symbolKey](...args);
  delete armObj[symbolKey];
  return result;
};
 
Function.prototype.apply = function call(arm, ...args) {
  let fun = this;
  if (typeof fun !== "function") throw TypeError("must is function");
  let armObj = arm;
  if (typeof arm !== "object") {
    armObj = Object(arm);
  }
 
  let symbolKey = Symbol("tempKey");
  armObj[symbolKey] = fun;
  let result = armObj[symbolKey](args);
  delete armObj[symbolKey];
  return result;
};
 
// TODO 完善bind 这里其实还有很多问题
Function.prototype.bind = function aBind(that, ...args) {
  let armFun = this;
  if (typeof armFun !== "function") throw TypeError("must a function");
  function BoundFun(...other) {
    if (new.target) {
      return new armFun(...args, ...other);
    } else {
      return armFun.call(that, ...args, ...other);
    }
  }
  BoundFun.__proto__ = armFun.__proto__;
  BoundFun.prototype = undefined;
 
  return BoundFun;
};

Author 回答者: iceycc

let person1 = {
  name: "Tom",
  sayHi(to, ...args) {
    console.log(
      `Hi,${to}, my name is ${this.name}。${args && args.toString()}`,
    );
  },
};
person1.sayHi();
 
let person2 = {
  name: "Jerry",
};
// call
person1.sayHi.call(person2, "Heydi");
// apply
person1.sayHi.apply(person2, ["Heydi"]);
// bind
let sayHiToJark = person1.sayHi.bind(person2, "Heydi"); // 柯里化
sayHiToJark("Wellcom to you");
// my call
Function.prototype.myCall = function (ctx, ...args) {
  let fn = this;
  if (typeof fn !== "function") throw TypeError("must is fucntion");
  let thisObj = ctx;
  if (typeof ctx !== "object") {
    thisObj = Object(ctx);
  }
  const key = Symbol("key");
  thisObj[key] = fn;
  const res = thisObj[key](...args);
  delete thisObj[key];
  return res;
};
person1.sayHi.myCall(person2, "Tim");
// my apply
Function.prototype.myApply = function (ctx, args) {
  let fn = this;
  if (typeof fn !== "function") throw TypeError("must is fucntion");
  let thisObj = ctx;
  if (typeof ctx !== "object") {
    thisObj = Object(ctx);
  }
  if (!Array.isArray(args)) throw TypeError("must is array");
  const key = Symbol("key");
  thisObj[key] = fn;
  const res = thisObj[key](...args);
  delete thisObj[key];
  return res;
};
person1.sayHi.myApply(person2, ["Tim"]);
// my bind
Function.prototype.myBind = function (ctx, ...args) {
  const fn = this;
  return function (...args2) {
    const key = Symbol("key");
    ctx[key] = fn;
    const res = ctx[key](...args, ...args2);
    delete ctx[key];
    return res;
  };
};
let sayHiToMary = person1.sayHi.bind(person2, "Mary");
sayHiToMary("Wellcom to you");

Author 回答者: Vi-jay

Function.prototype.myCall = function (target) {
  const args = [].slice.apply(arguments, [1]);
  const fnName = Symbol("fn");
  target[fnName] = this;
  Object.defineProperty(target, fnName, { enumerable: false });
  let res;
  eval(`res  = target[fnName](${args.join(",")})`);
  delete target[fnName];
  return res;
};

Author 回答者: yxw007

Function.prototype.myCall = function (context) {
  //! 说明:node环境根作用域this 就是globalthis, browser 环境就是window
  if (context) {
    //! 参数:可能不为对象,所以需要利用Object包裹一层
    if (typeof context !== "object") {
      context = Object(context);
    }
  } else {
    context = globalThis;
  }
 
  //! 说明:由于第一个参数为context,后面的才为调用函数参数,所以需要slice(1)
  const args = Array.from(arguments).slice(1);
 
  //! f1 调用的myCall方法, 此时this就是调用的函数本身
  context.fn = this;
  let ret = context.fn(...args);
 
  //! 说明:不应该改变了this指向,就给调用方法的对象添加一个方法属性,所以调用完后需要删除
  delete context.fn;
 
  return ret;
};
 
function f1() {
  console.log("f1, this:", this, ",arguments:", arguments);
}
 
f1.myCall("hello", "123");

Author 回答者: zhangtuo1999

Function.prototype.myCall = function (ctx) {
  ctx ??= globalThis;
  ctx = Object(ctx);
 
  const args = [...arguments].slice(1);
 
  const key = Symbol("key");
  ctx[key] = this;
  const res = ctx[key](...args);
  delete ctx[key];
 
  return res;
};
 
Function.prototype.myApply = function (ctx) {
  ctx ??= globalThis;
  ctx = Object(ctx);
 
  const args = arguments[1];
 
  const key = Symbol("key");
  ctx[key] = this;
  const res = ctx[key](...args);
  delete ctx[key];
 
  return res;
};

Author 回答者: Yeti-xxx

实现一个call:

function person(a, b) {
    return {
        name: this.name,
        sum: a + b
    }
}

const yeti = {
    name: 'yeti'
}

// 实现一个call
Function.prototype.newCall = function (obj, ...args) {
    const window = { windwo: 'window' }
    if (!obj) {
        obj = window
    }
    obj.p = this    //此处的this是person函数 相当于在obj中添加了一个person方法
    const resCall = obj.p(...args)  //由于函数可能会返回值,所以将执行的结果保存并返回
    delete obj.p
    return resCall

}

const res = person.newCall(yeti, 1, 2)
console.log(res);

Author 回答者: Yeti-xxx

实现一个apply:

function person(a, b, c) {
    console.log(Math.max.apply(null, [...arguments]));
    console.log(Math.max.newApplay(null, [...arguments]));
    return {
        name: this.name,
        sum: a + b + c
    }
}

const yeti = {
    name: 'yeti'
}
// 实现一个apply
Function.prototype.newApplay = function (obj, arr) {
    const window = { windwo: 'window' }
    if (!obj) {
        obj = window
    }
    obj.p = this
    if (!arr) { //如果为传入参数数组,直接执行
        const resnewApply = obj.p()
        delete obj.p
        return resnewApply
    } else {
        const resnewApply = obj.p(...arr)
        delete obj.p
        return resnewApply
    }

}

const resApplay = person.newApplay(yeti, [1, 2, 6])
console.log(resApplay);

Author 回答者: algok-876

如果contxt中原本就有同名的fn属性呢,调用完你这个call之后,原本的fn属性就消失了 @yxw007