frontend-interview icon indicating copy to clipboard operation
frontend-interview copied to clipboard

如何实现call和apply、bind?

Open su37josephxia opened this issue 3 years ago • 26 comments

su37josephxia avatar Jan 18 '22 02:01 su37josephxia

  1. call方法实现 Function.prototype.myCall = function(obj,...args) { let ctx = obj || window;//没有指定指向对象则指向对象设为window ctx.fn = this;//这个this指的是调用myCall的函数,将该函数绑定到ctx的fn属性上 let result = ctx.fn(...args); delete ctx.fn; }

  2. apply方法实现 Function.prototype.myApply = function (obj,args) { let ctx = obj || window;//没有指定指向对象则指向对象设为window ctx.fn = this;//这个this指的是调用myApply的函数,将该函数绑定到ctx的fn属性上 let result = args? ctx.fn(...args): ctx.fn(); delete ctx.fn; }

  3. bind方法实现 Function.prototype.myBind = function (obj) { if (typeof this !== "function") { throw new Error("Function.prototype.bind - what is trying to be bound is not callable"); }; var args = Array.prototype.slice.call(arguments,1)[0];//取出绑定时的参数 var self = this; var middleFun = function() {};//设置中间函数 var result = function() { var bindArgs = Array.prototype.slice.call(arguments)[0];//取出调用时的总参数 /**

    • 当绑定函数作为构造器时,this指向实例,可以让实例获得绑定函数的值
    • 当绑定函数为普通函数时,this指向window,将绑定函数的this指向obj */ self.apply(this instanceof middleFun? this: obj,args.concat(bindArgs)); } //中间函数继承调用绑定函数的函数 middleFun.prototype = self.prototype; //生成的函数再继承中间函数,这样当我们改变生成函数的原型中的某个属性时, //就不会影响到调用绑定函数的函数的属性,因为此时生成的函数与调用函数之间多了一层原型, //就是中间函数的原型。 result.prototype = new middleFun();//使用new继承,可以使middleFun的this丢失,重新指向new的新对象 return result; }
  4. 测试 var name = "window"; let ooo = { name:'haha' } function showname(show,more) { let newName = this.name; console.log(newName) console.log(show,more) } showname.myCall(ooo,"show","more");//haha show more showname.myApply(ooo,["show","more"]);//haha show more var aaa = showname.myBind(ooo,["show"]); aaa(["more"])//haha show more var bbb = new aaa(111);//undefined show 111

wzl624 avatar Jan 20 '22 10:01 wzl624

fn.call(xx, ...args) call 实现过程: 1. 获取第一个参数,若第一个参数为 null 或 undefined 时,this 指向 window,构建对象 2. 将对应函数传入构建的对象 3. 获取参数并执行相应函数 4. 删除该对象中函数 5. 返回计算结果 Function.prototype.myCall = function (context, ...args) { context = context ? Object(context) : window; context.fn = this; let result = context.fn(...args); delete context.fn; return result; }

fn.apply(xx, [...args]) apply 实现过程:过程call,只是参数处理部分略微不同,call处理的是参数队列,apply处理的是参数数组 Function.prototype.myApply = function (context, arr) { context = context ? Object(context) : window; context.fn = this; let result = arr ? context.fn(...arr) : context.fun(); // 处理的是参数数组 delete context.fn; return result; }

fn.bind(xx,[, arg1[, arg2[, ...]]]) 1. bind 返回的是一个函数 2. 能够接受多个参数,也能够接受柯里化形式的传参 3. bind 绑定 this 只会生效一次 4. 获取到调用bind() 返回值后,若当作构造函数调用的话,bind() 传入的上下文失效 Function.prototype.myBind = function (context, ...args) { if (typeof(this) !== 'function') { throw new TypeError('The bound object needs to be a function'); } const self = this; const fNOP = function() {}; const fBound = function(...fBoundArgs) { return self.apply(this instanceof fNOP ? this : context, [...args, ...fBoundArgs]); } if (this.prototype) { fNOP.prototype = this.prototype; } fBound.prototype = new fNOP(); return fBound; }

ruixue0702 avatar Jan 20 '22 11:01 ruixue0702

实现call apply的主要思路

  • 第一个参数为undefinednull的时候,将this指向window
  • 改变了this指向,让新的对象执行该函数。

实现bind的主要思路

  • bind方法不会立即执行,需要返回一个待执行的函数
  • 实现作用域绑定
  • 参数传递

相关代码

  • apply
Function.prototype.myApply = function(context){
    if(typeof context === 'undefined' || context === null){
        context = window
    }
    context.fn = this
    const args = arguments[1]||[]
    const result =context.fn(...args)
    delete context.fn
    return result
}
  • call
Function.prototype.myCall = function(context){
    if(typeof context === 'undefined' || context === null){
        context = window
    }
    context.fn = this
    const args = [...arguments].slice(1)
    const result = context.fn(...args)
    delete context.fn
    return result
}
  • bind
Function.prototype.myBind = function () {
    let that = this
    const args = [...arguments]
    return function () {
        that.call(...args)
    }
}

qytayh avatar Jan 20 '22 13:01 qytayh

call

function call(fn, ctx, ...args) {
  if (ctx == undefined) {
      return fn(...args)
  }

  let type = typeof ctx
  type = type.replace(type[0], p => p.toUpperCase())
      
  let res
  const b = Symbol()
  window[type].prototype[b] = fn
  res = ctx[b](...args)
  delete window[type].prototype[b]

  return res
}

例子: console.log(call(Array.prototype.slice, '123', 2)) // '3' console.log(call(Object.prototype.toString, Symbol()) // '[object Symbol]' console.log(call(Object.prototype.toString, 1)) // '[object Number]'

apply

function apply(fn, ctx, args) {
    return call(fn, ctx, ...args)
}

例子: function ko(a, b) { return this.a + a + b } console.log(apply(ko, { a: '2' }, [1, 2])) // '212'

bind

function bind(fn, ctx) {
    return (...args) => apply(fn, ctx, args)
}

例子: console.log(bind(ko, { a: 2 })(1, 2)) // 5

遇到问题

Object.prototype.toString.call(undefined) 无法实现

zcma11 avatar Jan 20 '22 13:01 zcma11

call、aplly是立即执行,而bind却不是,call方法的第二个参数是展开数组,而apply是数组。

  • call
Function.property.call = function call(context, ...params) {
    // 1. 判断content是否传递
    context == null ? context = window : null
    // 2. 如果传递了,则context必须是对象
    !/^(object|function)/.test(context) ? context = Object(context) : null
   
    let self = this, result = null,
         key = Symbol('key') // 新增的属性名称保持唯一性,防止污染了原始对象中的成员
    context[key] = self
    result = context[key](...params)
    delete context[key] // 用完移除自己新增的属性
    return result
}
  • apply
Function.property.apply = function apply(context) {
    context == null ? context = window : null
    !/^(object|function)/.test(context) ? context = Object(context) : null

    let self = this, result = null,
         key = Symbol('key');
    context[key] = self
    const args = [...arguments].slice(1)
    result = context[key](...args)
    delete context[key]
    return result
}
  • bind
Function.property.bind = function bind(context, ...outerArgs) {
    let self = this
    return function (...innerArgs) {
        self.call(context, ...outerArgs.contact(innerArgs))
    }
}

QbjGKNick avatar Jan 20 '22 13:01 QbjGKNick

call

Function.prototype.myCall = function (context, ...args) {

// 参数1,将要被指向的this
// apply中的this是调用apply函数函数

    // context 表示Man的this,如果为空,将 context 指向全局对象,浏览器下windos,node环境global
  if (typeof context === 'undefined' || context === null) {
    context = window;
  }
// 
  // 此处使用Symbol,防止context当前命名的属性值key与context已有的属性名冲突
  let key = Symbol();
  // 在man对象上挂载this,这里的this应为被Person调用,所以此处的this指向的是Person

// 此处用不可枚举属性
 Object.defineProperty(context, key, {
    value: this,
    //是否为枚举属性
    enumerable: false,
  })


  // context[key] = this;
  // 执行 Person 函数,因为此处是context调用,context又是Man的this;
  // 所以 Person 函数执行的时候,Person中this就指向了context,指向了Man
  let fn = context[key](...args);
    // 删除对象上的函数
  delete context[key];
  // Person函数的返回值。若没有返回值,则返回 `undefined`
  return fn;
};

function Person(name, price) {
  this.name = name;
  this.price = price;
}

function Man(name, price) {
    // 将Person中属性挂载到Man函数
  Person.myCall(this, name, price);
  this.category = 'food';
}
const obj = new Man('张三', '男');
console.log('obj', obj);

apply

Function.prototype.myApply = function (context, args) {
    // 此处args是数组,已经是数组,不需要使用扩展符展开;此处可以增加args是否为数组的判断

    // context 表示Man的this,如果为空,将 context 指向全局对象,浏览器下windos,node环境global
  if (typeof context === 'undefined' || context === null) {
    context = window;
  }
  // 此处使用Symbol,防止context为window是,命名的key与window已有的属性名冲突
  let key = Symbol();
  // 在man对象上挂载this,这里的this应为被Person调用,所以此处的this指向的是Person
  context[key] = this;
  // 执行 Person 函数,因为此处是context调用,context又是Man的this;
  // 所以 Person 函数执行的时候,Person中this就指向了context,指向了Man
  let fn = context[key](...args);
    // 删除对象上的函数
  delete context[key];
  // Person函数的返回值。若没有返回值,则返回 `undefined`
  return fn;
};

bind


Function.prototype.myBind = function (context) {
    // 判空
  if (typeof context === 'undefined' || context === null) {
    context = window;
  }
  // 此时 context 是 Man 的this
  
  self = this;
   // 此时 self 是 Person 的this
  return function (...args) {
     // 此处可以理解为 Person 使用 apply 修改 Man的 this 值
    return self.apply(context, args);
  };
};

function Person(name, price) {
  this.name = name;
  this.price = price;
}

function Man(name, price) {
  Person.call(this, name, price);
  console.log(this);
}

function Man2(name, price) {
  Person.myBind(this)(name, price);
}
const obj2 = new Man2('张三2', '男2');
console.log(obj2.name);

chunhuigao avatar Jan 20 '22 14:01 chunhuigao

call和apply的实现就是利用js中this的动态作用域,改变函数的调用者,具体就是使用给传进来的参数添加一个独一无二的属性,这里可以使用symbol,将此函数添加到这个对象上,然后用这个对象调用这个方法,完事之后删除此属性即可 call,和apply实现过程基本相似,只有参数格式不同, bind可以使用call和apply将函数绑定到传入的对象当中,返回一个新的函数,这个函数中就是使用对象来调用函数

yaoqq632319345 avatar Jan 20 '22 14:01 yaoqq632319345

使用函数的方法调用模式改变this的指向,将被执行的函数作为传入上下文的属性被调用

// call
Function.prototype.myCall = function(content, ...args) {
  // 判断如果执行上下文为空,则置为全局环境变量,浏览器模式是window,node环境是global
	if(content === undefined || content === null) {
  	content = window;	
  }
  // 设置传入上下文的属性key,使用symbol防止重名
  const key = Symbol();
  // 属性为当前函数的上下文
  content[key] = this;
	// 使用传入上下文执行当前函数,这里就使用了函数的方法调用模式
  const res = content[key](...args);
  delete content[key];
  return res;
}

//demo
this.c = 333;
var Func = function(a, b) {
	console.log(a+b+this.c)
}
var D = function(){
	this.c = 222;
}
Func(1,2)   // 336
Func.myCall(new D(), 1, 2) // 225


// apply
Function.prototype.myApply = function(content, args) {
	// 判断如果执行上下文为空,则置为全局环境变量,浏览器模式是window,node环境是global
	if(content === undefined || content === null) {
  	content = window;	
  }
  // 设置传入上下文的属性key,使用symbol防止重名
  const key = Symbol();
  // 属性为当前函数的上下文
  content[key] = this;
	// 使用传入上下文执行当前函数,这里就使用了函数的方法调用模式
  const res = content[key](...args);
  delete content[key];
  return res;
}

//demo
this.c = 333;
var Func = function(a, b) {
	console.log(a+b+this.c)
}
var D = function(){
	this.c = 222;
}
Func(1,2)   // 336
Func.myApply(new D(), [1, 2]) // 225


// bind
Function.prototype.myBind = function(content) {
	// 判断如果执行上下文为空,则置为全局环境变量,浏览器模式是window,node环境是global
	if(content === undefined || content === null) {
  	content = window;	
  }
  
  // this是当前函数的上下文,这里返回使用当前函数去apply的方法
  const self = this;
  return function(...args) {
  	self.apply(content, args)
  }  
}

//demo
this.c = 333;
var Func = function(a, b) {
	console.log(a+b+this.c)
}
var D = function(){
	this.c = 222;
}
Func(1,2)   // 336
Func.myBind(new D())(1,2) // 225

aiuluna avatar Jan 20 '22 15:01 aiuluna

call与apply的实现有些类似,拿apply来说,首先如果没有传this值,默认window; 然后给传入的context对象新填一个属性,让这个属性指向当前调用函数,然后根据传入的参数调用函数,返回结果; bind他是返回一个新函数,这就形成了一个闭包;通过arguments对象可以取得传入的参数,这个参数有两部分,一部分是函数bind是预备转入的参数,一个是这个闭包函数调用时传入的参数,最终接种apply方法处理当前函数想要绑定的this值与参数。

具体实现在我的掘金

Limeijuan avatar Jan 20 '22 15:01 Limeijuan

call apply bind利用将函数挂载在第一个参数context上执行来实现函数的内部this指向context, 其实就是利用了对象调用函数,函数默认this指向对象。再说一说三个方法的不同,call和apply一个都是上下文参数,第二个则有所不同,call是多个实参传值,apply则是是一个数组存放所有参数,bind其实与call apply不同的是返回一个被call/apply后函数,并不执行这个函数 模拟实现代码

jiafei-cat avatar Jan 20 '22 15:01 jiafei-cat

image

Cassieqian avatar Jan 20 '22 15:01 Cassieqian

https://juejin.cn/post/7055308553938010120/

863379105 avatar Jan 20 '22 15:01 863379105

  • 模拟call

    1. 将函数设为对象的属性
    2. 执行该函数
    3. 删除该函数
    Function.prototype.call2 = function(context) {
        // 首先要获取调用call的函数,用this可以获取
        context.fn = this;
        context.fn();
        delete context.fn;
    }
    
    var foo = {
        value: 1
    };
    
    function bar() {
        console.log(this.value);
    }
    
    
  • 模拟apply

    Function.prototype.apply = function (context, arr) {
        var context = Object(context) || window;
        context.fn = this;
    
        var result;
        if (!arr) {
            result = context.fn();
        }
        else {
            var args = [];
            for (var i = 0, len = arr.length; i < len; i++) {
                args.push('arr[' + i + ']');
            }
            result = eval('context.fn(' + args + ')')
        }
    
        delete context.fn
        return result;
    
    
  • 模拟bind

    • bind() 方法会创建一个新函数
    • 模拟bind
      • 返回一个新函数
      • 可以传入参数,arguments
    Function.prototype.bind2 = function (context) {
    
        var self = this;
        // 获取bind2函数从第二个参数到最后一个参数
        var args = Array.prototype.slice.call(arguments, 1);
    
        return function () {
            // 这个时候的arguments是指bind返回的函数传入的参数
            var bindArgs = Array.prototype.slice.call(arguments);
            self.apply(context, args.concat(bindArgs));
        }
    
    }
    

superjunjin avatar Jan 20 '22 15:01 superjunjin

  • 三种方法都是改变函数this的指向,接收参数的形式不同,bind额外返回一个函数
  • 由于函数都可以使用这三种方法,因此在Function的原型上进行修改或者扩充
  • 为了不作弊,实现也不使用call,apply,用到es6解构赋值

call

Function.prototype.myCall = function(ctx, ...args) {
    ctx = ctx || window
    ctx.fn = this
    const result = ctx.fn(...args)
    delete ctx.fun
    return result
}

apply

Function.prototype.myApply = function(ctx, args) {
    ctx = ctx || window
    ctx.fn = this
    const result = ctx.fn(...args)
    delete ctx.fun
    return result
}

bind

Function.prototype.myBind = function(ctx, ...args) {
    ctx = ctx || window
    ctx.fn = this
    return function(...returnArgs) {
        return ctx.fn(...args, ...returnArgs)
    }
}

jj56313751 avatar Jan 20 '22 15:01 jj56313751

  • call
    • 改变 this 指向
      • 先看看怎么改变 this 的指向问题。call 这个方法是属于函数的,所以我们要实现的类似功能方法的话,需要给在构造函数上的原型上绑定自定义方法
    • 处理传入的参数
      • 把绑定上下文和arguments结合在一起
  • apply
    • call 和 apply 的在使用的时候,有个区别就是 apply 的参数必须是通过数组的形式
  • bind
    • bind 和 call 差不多,有点不同的就是 bind 终生绑定不立即执行返回一个新函数,传参和 call 是一样的
    • 思路和 call 是一样的,唯一的不同是函数不能立即执行,可以返回一个匿名函数,当它执行的时候在去改变 this 指向和处理参数

zzzz-bang avatar Jan 20 '22 15:01 zzzz-bang

call实现,使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。 该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。

Function.prototype.mycall = function (context,...args) {
 let context = context ? context : window;
 let args = args ? args : [];
 let key = Symbol();
 context[key] = this;
 let res = context[key](...args);
 delete context[key];
 return res;
}

apply实现

Function.prototype.myapply = function (context,args) {
 let context = context ? context : window;
 let args = args ? args : [];
 let key = Symbol();
 context[key] = this;
 let res = context[key](...args);
 delete context[key];
 return res;
}

bind实现,bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。因为返回是一个函数,所以使用new调用的时候应用是返回传入函数的 实例化后的对象

Function.prototype.mybind = function (context,...args) {
  if(typeof this !== 'function'){
  	return new TypeError('must be function');
  }
  let _this=this;
  return function F(...newArgs){
  	if(this instanceof F){
		return new _this(...args,...newArgs);
	}
	return _this.apply(context,...args,...newArgs);
  }
}

yanzefeng avatar Jan 20 '22 15:01 yanzefeng

call实现

Function.prototype.myCall = function (context) {
  if (typeof this !== 'function') {
    throw new TypeError('not a function')
  }
  const symbolFn = Symbol()
  const args = [...arguments].slice(1)
  context = context || window
  context[symbolFn] = this
  const result = context[symbolFn](...args)
  delete context[symbolFn]
  return result
}
const obj = {
  name: 'obj'
}
function foo () {
  console.log(this.name)
}
foo.myCall(obj) // obj

apply

Function.prototype.myApply = function(context) {
  if(typeof this !== 'function') {
    throw new TypeError('error');
  }
  context = context || window;
  context.fn = this;
  var result = arguments[1] ? context.fn(...arguments[1]) : context.fn();
  delete context.fn;
  return result;
}
function foo(){
  console.log(this.age);
}
var obj = {
  age: 101
}
foo.myApply(obj); // 输出101

bind

Function.prototype.myBind = function(context) {
  if(typeof this !== 'function') {
    throw TypeError('error');
  }
  const self = this;
  const args = [...arguments].slice(1);
  return function F() {
    if(this instanceof F) {
      return new self(...args, ...arguments);
    }
    return self.apply(context, args.concat(...arguments));
  }
}
function foo() {
  console.log(this.age);
}
var obj = {
  age: 121
}
var newFunc = foo.myBind(obj);
newFunc(); // 输出121

792472461 avatar Jan 20 '22 15:01 792472461

exports.call = function (context, ...args) { context.fn = this; const result = context.fn(...args); delete context.fn; return result; };

exports.apply = function (context, args) { context.fn = this; const result = context.fn(...args); delete context.fn; return result; };

exports.bind = function (context, ...args) { const f = this; return function F() { return f.apply(context, [...args, ...arguments]); }; };

alec1815 avatar Jan 20 '22 16:01 alec1815

// 此方法为某函数调用,所以this指向调用的函数 // 我们的目标是改变函数内部的this调用,然后调用函数并把参数原样传递 // 由this绑定四条规则: // 默认绑定是绑定为全局,不可行 // 显示绑定就是aply,call我们目前就是要实现这个,不可行 // new绑定是改变this为创建的新对象,不能达到定向绑定为obj的需求 // 所以只剩隐式绑定了, // 隐式绑定就是函数作为对象的属性引用被调用时,this就指向这个对象,那我们想让函数this指向obj,就得让函数变成obj的属性引用并调用

// 实现call,call第二个及以后的参数为一个一个传递
 Function.prototype.myCall = function (obj, ...args) {

     let context = obj ? Object(obj) : window; //obj不存在就绑定window对象
     context.fn = this; //给obj添加fn属性,并指向函数

     let result = context.fn(...args); //执行函数
     delete context.fn; //删除fn属性
     return result; //返回函数执行结果
 }

 //aply和call相似,只是传参方式不同,只有两个参数,第一个为绑定this的对象,第二个为所有需要传递给函数的参数数组
 Function.prototype.myAplay = function (obj, args) {
     let context = obj ? Object(obj) : window; //obj不存在就绑定window对象
     context.fn = this; //给obj添加fn属性,并指向函数
     let result = args ? context.fn(...args) : context.fn(); //执行函数
     delete context.fn; //删除fn属性
     return result; //返回函数执行结果
 }

 //bind是硬绑定,bind() 依然用来改变函数 this 指向,但它不会像 call() 与 apply() 方法会立即执行这个函数,而是返回一个 新函数 给外部,外部用一个变量去接收这个新函数并执行。
 Function.prototype.myBind = function (context) {
  // 判断调用 myBind 方法的是否为函数
  if (typeof (this) !== "function") {
      throw Error("调用_bind方法的必须为函数")
  }
  // 截取传给函数的参数
  let args = Array.prototype.slice.call(arguments, 1)

  // 保存这个函数,以便后续使用
  let _fn = this

  // 创建一个待会儿返回出去的函数,这个函数会赋到外部变量中
  let bindFn = function () {
      // 获取_bind方法返回的函数的参数
      let newArgs = Array.prototype.slice.call(arguments)
      // 通过apply去改变this指向,实现函数柯里化
      let _obj = this.constructor === _fn ? this : context
      _fn.apply(_obj, newArgs.concat(args))
  }

  // 创建一个中介函数,以便实现原型继承
  let ProtoFn = function () {}
  ProtoFn.prototype = _fn.prototype
  bindFn.prototype = new ProtoFn()

  // 返回bindFn的函数给外部
  return bindFn;
}
 let obj = {
     a: 1
 };

 function b(b,c) {
     console.log(this.a,b,c);
 }

 b.myCall(obj,2,3);//123
 b.myAplay(obj,[2,3]);//123

 var bar=b.myBind(obj);

 bar(2,3);//123

JanusJiang1 avatar Jan 20 '22 16:01 JanusJiang1

过程

call 的作用是将函数的this指向第一个参数,并用剩余的参数作为函数的参数执行该函数 所以实现过程首先可以将当前函数绑定到传入 this 的 fn 属性上,然后将后续的参数作为函数的参数执行 this.fn,最后删除对象的这个属性 但是这里没有考虑到 this 对象已经有 fn 属性的情况,所以要判断一下,如果fn 属性存在,可以拼接一个随机数 接下来还没有考虑到有返回值的情况,所以还要处理一下返回值 调用 call 如果没有传入参数或者第一个参数为 null 或者 undefined 的话,会将函数 this 绑定到 window,如果传入参数为基本数据类型,会将其转为对象 所以要处理一下这里的逻辑 有了实现 call 的过程,aplly 和 bind 就大同小异了

call

Function.prototype.call2 = function(obj){
  obj = obj?Object(obj):window;
  let _fn = "fn",result;
  while (obj.hasOwnProperty(_fn)) {
    _fn = "fn" + Math.random(); // 循环判断并重新赋值
  }
  obj[_fn] = this;
  if(arguments.length>1){
    result = obj[_fn](...([...arguments].slice(1)));
  }else{
    result = obj[_fn]();
  }
  delete obj[_fn];
  return result;
}

apply

Function.prototype.apply2 = function(obj,arr){
  obj = obj?Object(obj):window;
  let _fn = "fn",result;
  while (obj.hasOwnProperty(_fn)) {
    _fn = "fn" + Math.random(); // 循环判断并重新赋值
  }
  obj[_fn] = this;
  if(arr){
    result = obj[_fn](...arr);
  }else{
    result = obj[_fn]();
  }
  delete obj[_fn];
  return result;
}

bind

Function.prototype.bind2 = function(obj){
  obj = obj?Object(obj):window;
  var myArguments = arguments,self = this;
  if(arguments.length>1){
    return function(){
      self.apply(obj,[...myArguments].slice(1))
    };
  }else{
    return function(){
      self.apply(obj);
    };
  }
}

zhenyuWang avatar Jan 20 '22 16:01 zhenyuWang

call call() 方法在使用一个指定的 this 值和若干个指定的参数值的前提下调用某个函数或方法。 call 改变了 this 的指向; 调用函数执行 从 Arguments 对象中取值,取出第二个到最后一个参数,然后放到一个数组里 将函数设为对象的属性 执行该函数 删除该函数 this 参数可以传 null,当为 null 的时候,视为指向 window apply apply 的实现跟 call 类似 apply的第一个参数是普通函数,第二个则为该函数的参数祖成的数组 bind bind() 方法会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。 bind 函数的两个特点: 返回一个函数 可以传入参数

partiallove avatar Jan 20 '22 16:01 partiallove

bind、call、apply 异同点 相同点:

调用者都是且必须是函数 改变函数执行时的上下文,即改变this指向

区别: call: fun.call(thisArg, param1, param2, ...)

使用一个指定的this值和单独给出的一个或多个参数来调用一个函数,返回调用结果

apply: fun.apply(thisArg, [param1,param2,...])

使用一个指定的this值和单独给出的 一个或多个参数的数组 来调用一个函数,返回调用结果

bind:fun.bind(thisArg, param1, param2, ...)

改变调用功函数的this指向,返回一个新的函数,不需要调用

实现思路: call、apply

  • 设置this指向
  • 给ctx设置临时函数,将函数的this隐式绑定指向到ctx上(此处为了防止绑定的对象中本身就存在与临时函数相同的字段,可以使用Symbol规避)
  • 执行临时函数并传递单数
  • 删除临时函数
  • 返回结果

bind:

  • 区别于call、apply,bind只需要返回绑定后的函数,不需要执行
  • bind接收的参数都需要传递到绑定后的函数

alienRidingCat avatar Jan 20 '22 17:01 alienRidingCat

实现call

// 重要思想有以下两点

// 1. 利用this隐式绑定指向调用者的原理,在obj下动态的加上foo方法,执行完之后再删除
// 2. 我们可以将mycall方法挂载到Function的原型对象上,这里每一个函数实例都能够调用mycall方法
// 3. 在mycall方法里可以通过当前的this获取到调用mycall的函数

Function.prototype.mycall = function (obj){
    let context = obj || window; // 传入进来的,要将this指向的obj对象
    let args = Array.from(arguments).slice(1);// 传进来的参数
    let fn = Symbol('fn');// 定义一个方法名,只是用这个名字挂到obj下面而已,名字是什么不重要,所以我们用Symbol定义以免跟obj下的原有属性冲突
    context[fn] = this;// 在传进来的对象下面把我们实际要调用的方法挂上
    let result = context[fn](...args) // 调用context下的fn方法,这时实际就是执行了真正的函数方法,内部的this已经隐式的指向了context
    delete context[fn];// 那到结果后,我们将这个方法删除
    return result;
}


function foo(b,c){
    console.log(this.a+b+c);
}

obj = {
    a:1
}
foo.mycall(obj,2,3)

实现apply,和call相比只是传参不一样而已

Function.prototype.myapply = function (obj,args){
    let context = obj || window;
    let fn = Symbol('fn');
    context[fn] = this;
    let result = context[fn](...args);
    delete context[fn]
    return result;
}

function foo(b,c){
    console.log(this.a + b + c)
}

let obj = {a:1}

foo.myapply(obj,[2,3])

实现bind,需要借用apply或者call,如果不让使用,那就先自己实现一个


// 和apply和call相比,bind是返回了一个函数,而且还需要保留传进来的obj和参数,
// 那么我们一定要用到闭包,去将这些内容存在闭包内
// 这里bind方法的实现,需要借用以下apply或者call,如果不准使用apply和call,那我们就自己先实现myapply和mycall




Function.prototype.mybind = function (obj) {
    // 这里很多人喜欢加上this判断,判断this如果不是function那么要报错
    // 我其实是很有疑问的,如果this不是function,那这个mybind什么情况下会被执行呢?
    let context = obj || window; 
    var _this = this
    var args = Array.from(arguments).slice(1)
    // 这里返回一个函数
    return function F() {
      // 由于我们这里返回了一个函数,那么要考虑到外部会把我们这个函数当前做构造函数去执行new操作了
      // 如果外部new了我们这个函数,按照new一个函数的步骤,这个this目前应该指向这个新的实例
      // 那么这个this一定是F的实例,即满足下列条件
      if (this instanceof F) {
        // 那我们就用闭包内存储的_this当做构造函数,new一个实例出来返回出去
        return new _this(...args, ...arguments)
      }
      // 如果不是上面那种情况,那就是正常调用了这个函数,那么我们就执行闭包内的存储的_this方法,并改变函数内部this,指向调用mybind传进来的上下文
      return _this.apply(context, args.concat(...arguments))
    }
  }


function foo(b,c){
    console.log(this.a+b+c);
}

obj = {
    a:1
}

let newFoo = foo.mybind(obj,2,3) // 这一步执行完,其实就是得到了一个闭包F函数,并保存了传进来的obj和参数
newFoo();
let aaa = new newFoo();// 这里是刚才分析的new的那种场景,需要判断instanceof
// newFoo()

crazyyoung1020 avatar Jan 20 '22 17:01 crazyyoung1020

call

Function.prototype.mycall = function (context, ...args) {
  context.fn = this;
  const result = context.fn(...args);
  delete context.fn;
  return result;
};

apply

Function.prototype.myApply = function (context, args) {
  context.fn = this;
  const result = context.fn(...args);
  delete context.fn;
  return result;
};

bind

Function.prototype.myBind = function (context, ...args) {
  const f = this;
  return function () {
    return f.apply(context, [...args, ...arguments]);
  };
};

rachern avatar Jan 20 '22 17:01 rachern

1、实现call、apply、bind这个三个函数我们首先要先实现call,后面两个基于它来实现;

2、要实现他们肯定要知道这三个函数的特点: 1)call/apply是立即执行的函数,直接返回函数执行结果, 2)bind是返回一个调用函数的克隆版并改变了this指向的函数,非立即调用 3)call接受的是多个参数,apply接受的是数组参数

3、开始实现 a. call的实现: 1)我们利用给指定上下文环境创建一个临时对象,添加调用函数作为它的一个唯一key作为键的函数值,这样通过对象调用该函数就实现了用指定的上下文环境的this来指向这个调用函数了 2)用完临时属性再删除返回调用结果 Function.prototype.myCall = function(ctx,...args) { const obj = ctx == undefined ? window : Object(ctx); const key = Symbol(); obj[key] = this; const result = obj[key](...args); delete obj[key]; return result; }

b. apply的实现就简单多了,直接调用call,把数组参数展开传进去就行了: Function.prototype.myApply = function(ctx,arr=[]) { return this.myCall(ctx,...arr); }

c. bind的实现:我们需要把调用函数克隆一下,首先用个指针保存调用函数的this,创建个闭包函数返回用myCall改变this指向后执行的结果,最后判断下传入的上下文环境有没有prototype,如果有就给新函数加上; Function.prototype.myBind = function(ctx,...args) { let _self = this; const newFn = function(...rest) { return _self.myCall(ctx,...args,...rest); } if(_self.prototype) { newFn.prototype = Object.create(_self.prototype); } return newFn; }

guoshukun1994 avatar Jan 20 '22 18:01 guoshukun1994

我们要去实现call apply by这三个函数,就首先是要确定他们的核心职责是什么。他们的核心职责就是要去调用函数,并且是以指定的 this 对象。

那么调用这个函数的时候,肯定会传入一个函数,传入一个指定的对象。那么调用函数是很简单的,实现的难度主要是围绕在怎么去指定 this 上面。那么我们知道指定 this 的话是有三种方式的啊。一种是以构造函数的方式去调用函数,以普通函数的方式去调用函数,还有就是以方法的方式去调用函数,那么,其中只有以方法的方式去调用函数,才可能让我们有机会去让 this 为任意值。

那么我们知道了可以通过以方法调用函数的方式去指定 this 指向。那么具体操作的话,具体实现的话,我们就需要把所调用的函数挂到所指定的对象上,然后以方法的形式去调用,就能实现call apply bind的核心逻辑了。

Function.prototype.mycall = function (context = window,...args) {
 let key = Symbol();
 context[key] = this;
 let res = context[key](...args);
 delete context[key];
 return res;
}
Function.prototype.myapply = function (context = window,args = []) {
 let key = Symbol();
 context[key] = this;
 let res = context[key](...args);
 delete context[key];
 return res;
}
Function.prototype.mybind = function (context,...args) {
  let _this=this;
  return function F(...newArgs){
  	if(this instanceof F){
		return new _this(...args,...newArgs);
	}
	return _this.apply(context,...args,...newArgs);
  }
}

rhythm022 avatar Jan 22 '22 15:01 rhythm022