Daily-Question icon indicating copy to clipboard operation
Daily-Question copied to clipboard

【Q656】JS 中如何实现 call/apply

Open shfshanyue opened this issue 2 years ago • 10 comments

在 JS 中如何实现 call/apply?

相关问题:

shfshanyue avatar Jul 18 '21 15:07 shfshanyue

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

shfshanyue avatar Jul 19 '21 05:07 shfshanyue

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
};

wussss avatar Jul 30 '21 08:07 wussss


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
}

heretic-G avatar Jul 30 '21 08:07 heretic-G

  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')

iceycc avatar Aug 21 '21 03:08 iceycc

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;
};

Vi-jay avatar May 15 '22 08:05 Vi-jay


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");

yxw007 avatar Jun 13 '22 03:06 yxw007

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
}

zhangtuo1999 avatar Jan 27 '23 06:01 zhangtuo1999

实现一个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);

Yeti-xxx avatar Mar 25 '23 03:03 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);

Yeti-xxx avatar Mar 25 '23 03:03 Yeti-xxx

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

algok-876 avatar Aug 23 '23 07:08 algok-876