call 和bind

63 阅读5分钟

call

在this指向中我们知道,要想知道方法中的this是谁,就看该函数被调用时有没有 ‘点’ -> 有‘点’,this就是‘点’前面的对象;没有点,就是window/undefined

例如:

const fn = function fn(x,y) {
  console.log(this,x, y);
}
fn(10,20) // this -> window ,x -> 10,  y-> 20

Function.prototype
+ call
+ apply
+ bind
所有的函数都是 Function 实例,所以所有函数都可以调用这三个方法,而这三个方法都是用来改变函数中的 THIS 指向

const fn = function fn(x,y) {
  console.log(this,x, y);
}
let obj = {
  fn, // es6 中 等价于 => fn: fn 
  name: 'zcm',
}
// 如何让 fn 执行, this 指向 obj 呢?
fn(10, 20)
// 答案之一:就可以通过call,  fn.call(obj,10,20)

call VS apply

call VS apply

  • 都是把函数 立即执行 ,改变函数中的 this 指向的 [第一个参数是谁,就是把 this 改为谁]
  • 唯一区别: apply 要求把传递给函数的实参,以数组的形式管理起来,[最终效果和 call 一样,也是把数组中的每一项一个个传递给函数]
  • 真实项目中建议大家使用 call ,因为其性能好一些 [大佬们做过测试:三个及以上参数,call 的性能明显比 apply 好一些]
// 如何让 fn 执行, this 指向 obj 呢?
// 假设 obj 中没有fn 属性,我们直接obj.fn(10,20) ,会报错
const fn = function fn(x,y) {
  console.log(this,x, y);
}
let obj = {
  name: 'zcm',
}
obj.fn(10, 20);// obj.fn is not a function
fn.call(obj, 10, 20); // this -> obj, x -> 10, y-> 20
fn.apply(obj,[10,20]) // this -> obj, x -> 10, y-> 20
// fn.call()中,如果第一个改变this 参数不穿,直接传变量,默认把第一个变量给this,
// 不传参数[undefined]或者参数为null, this 都默认指向 window
fn.call(10,20); // this -> new Number(10); x -> 20 , y-> undefined
fn.call(); // window/undefined(严格模式)
fn.call(null); // window/undefined(严格模式)

call 的一个小应用

// 获取数组最大值
let arr = [23, 12, 32, 34, 43, 78, 21];
console.log(Math.max(arr)); // NaN, 函数 Math.max()需要一个个传参不能传数组
console.log(Math.max(23, 12, 32, 34, 43, 78, 21)); // 78
console.log(Math.max(...arr)); // 78
console.log(Math.max.apply(Math,arr)); // 78

重写call 方法思路

// 重写call方法
Function.prototype.call = function call(context,...params) {
  // this -> fn context-> obj, params -> [10,20]
}
const fn = function fn(x,y) {
  console.log(this, x, y);
  return x+y
}
let obj = {
  name: 'zcm',
}
let res = fn.call(obj, 10, 20);
// @1 fn 基于  __proto__ 找到 Function.prototype.call, 把 call 方法执行
// @2 在 call 方法执行的时候
// + context: obj 要改变的 THIS 指向  ,parmas: [10,20] 执行函数传递的实参信息   this: fn 要执行的函数
// + 它干的事情是: 立即把 fn (this)执行,并让fn (this) 中的 this 指向 obj(context),
//   把10,20(params)传递给 fn(this), 接收 fn(this) 执行的返回结果,作为最后的结果返回

重写call 基础版本

Function.prototype.call = function call(context,...params) {
  // this -> fn context-> obj, params -> [10,20]
  // 思路: 给 context 设置一个属性 [例如: xxx   新增的属性不要和原始 context 中的属性冲突(设置symbol唯一值属性)],
  // 让属性值等于要执行的函数(既: this [fn] );后面就可以基于 context.xxx() 执行,这样既把函数执行了,
  //  也让函数中的 this 改为 context 了
  let self = this,
    key = Symbol('KEY'),
    result;
  context[key] = self;
  result = context[key](...params)
   // 新增属性之后,函数执行结果之后,需要把新增的属性删除
  Reflect.deleteProperty(context, key);// ES6中可以基于 Reflect.deleteProperty(对象,键名) 
  // delete context[key]; // 新增的属性用完后记得移除,可以兼容性
  return result
}
const fn = function fn(x,y) {
  console.log(this, x, y);
  return x+y
}
let obj = {
  name: 'zcm',
}
let res = fn.call(obj, 10, 20);

重写call 升级版

初始化参数

Function.prototype.call = function call(context,...params) {
  // this -> fn context-> obj, params -> [10,20]
  // 两个  ==  包括undefined / null 
  if (context == null) context = window
  if(!/^(function| object)$/.test(typeof context)) Object(context)
  // 思路: 给 context 设置一个属性 [例如: xxx   新增的属性不要和原始 context 中的属性冲突(设置symbol唯一值属性)],
  // 让属性值等于要执行的函数(既: this [fn] );后面就可以基于 context.xxx() 执行,这样既把函数执行了,
  //  也让函数中的 this 改为 context 了
  let self = this,
    key = Symbol('KEY'),
    result;
  context[key] = self;
  result = context[key](...params)
  // 新增属性之后,函数执行结果之后,需要把新增的属性删除
  Reflect.deleteProperty(context, key);// ES6中可以基于 Reflect.deleteProperty(对象,键名) 
  // delete context[key]; // 新增的属性用完后记得移除,可以兼容性
  return result
}
const fn = function fn(x,y) {
  console.log(this, x, y);
  return x+y
}
let obj = {
  name: 'zcm',
}
let res = fn.call(obj, 10, 20);
console.log(res);
// @1 fn 基于  __proto__ 找到 Function.prototype.call, 把 call 方法执行
// @2 在 call 方法执行的时候
// + context: obj 要改变的 THIS 指向  ,parmas: [10,20] 执行函数传递的实参信息   this: fn 要执行的函数
// + 它干的事情是: 立即把 fn (this)执行,并让fn (this) 中的 this 指向 obj(context),
//   把10,20(params)传递给 fn(this), 接收 fn(this) 执行的返回结果,作为最后的结果返回

bind

应用场景

需求:点击按钮,fn 方法执行,我们想让其中的this变为 obj,ev 事件对象也存在,再传递 10,20

const submit = document.querySelector('#submit');
const obj = {
  name: 'zcm'
}
const fn = function fn(x, y, ev) {
  console.log(this,x,y,ev);
}

// submit.onclick = fn; // this -> submit    x-> PointerEvent
// 需求: 点击按钮,fn 方法执行,我们想让其中的this变为 obj,ev 事件对象也存在,再传递 10,20
submit.onclick = fn.call(obj, 10, 20)
// 这样处理是错误的,因为call 是把函数立即执行,
// 按钮还没有点击呢,fn 就执行了

// 修改方法
submit.onclick = function (ev) {
  // 先给时间行为绑定匿名函数,当点击的时候,先执行匿名函数[获取到ev 事件对象];
  // 在匿名函数执行的时候(this -> submit)我们再让真正要处理的 fn 函数执行,此时可以基于 call 去改变this了
  fn.call(obj,10,20,ev)
}

bind代码

手写bind,基于 call

submit.onclick = fn.bind(obj, 10, 20); // 这样处理就可以了

Function.prototype.bind = function bind(context, ...params) {
  // this -> fn, context-> obj, params -> [10,20]
  let self = this;
  return function operate(...args) {
    // args -> [ev],点击事件,默认传了一个ev 事件对象
    // this -> submit
    self.call(context,...params.concat(args))
  }
}