JS基础篇:10、call、apply、bind

270 阅读1分钟

引言

上一篇学习了在不同情况下this的指向,那这个指向能否人为修改呢,答案是肯定的,今天学习改变this指向的方法。

call

格式 fn.call(obj, params, ...); 调用call立即执行fn方法

let obj = {
  name: '前端小菜鸟',
  getName: function (age, sex) { console.log(`${this.name} 性别 ${sex} 从事前端工作 ${age} 年`)} 
}
let newObj = {
  name: '大菜鸟'
}
obj.getName(1, '男') // 前端小菜鸟 性别 男 从事前端工作 1 年
obj.getName.call(newObj, 3, '男'); // 大菜鸟 性别 男 从事前端工作 3 年

apply

格式 fn.apply(obj, [params1, params2, ...]) 用法和call一样,调用apply立即执行fn方法,区别在于apply接受的第二个参数是数组

let obj = {
  name: '前端小菜鸟',
  getName: function (age, sex) { console.log(`${this.name} 性别 ${sex} 从事前端工作 ${age} 年`)} 
}
let newObj = {
  name: '大菜鸟'
}
obj.getName(1, '男') // 前端小菜鸟 性别 男 从事前端工作 1 年
obj.getName.apply(newObj, [3, '男']); // 第二个参数是数组 大菜鸟 性别 男 从事前端工作 3 年

bind

格式 fn.bind(obj, params, ...) 接受参数和call一样 ,区别在于 bind返回一个新的函数,需要主动触发执行

let obj = {
  name: '前端小菜鸟',
  getName: function (age, sex) { console.log(`${this.name} 性别 ${sex} 从事前端工作 ${age} 年`)} 
}
let newObj = {
  name: '大菜鸟'
}
// 返回的是一个新的函数
console.log(obj.getName.bind(newObj, [3, '男'])); // [Function: bound getName]
obj.getName.bind(newObj, 3, '男')(); // 大菜鸟 性别 男 从事前端工作 3 年

特殊情况

非严格模式下 call、apply、bind如果不传参数时,或者第一个参数是null/undefined this都指向 Window

let obj = {
  name: '前端小菜鸟',
  getName: function () { console.log(this)} 
}
obj.getName.call(); // Window
obj.getName.apply(); // Window
obj.getName.bind()(); // Window 
obj.getName.call(null); // Window
obj.getName.apply(null); // Window
obj.getName.bind(null)(); // Window
obj.getName.call(undefined); // Window
obj.getName.apply(undefined); // Window
obj.getName.bind(undefined)(); // Window

严格模式下 第一个参数传谁就指向谁 包含null、undefined,如果不传指向undefined

"use strict"
let obj = {
  name: '前端小菜鸟',
  getName: function () { console.log(this)} 
}
obj.getName.call(); // undefined
obj.getName.apply(); // undefined
obj.getName.bind()(); // undefined 
obj.getName.call(null); // null
obj.getName.apply(null); // null
obj.getName.bind(null)(); // null
obj.getName.call(undefined); // undefined
obj.getName.apply(undefined); // undefined
obj.getName.bind(undefined)(); // undefined

that

开发过程中我们还可以用that来改变this指向

var name = '我是大菜鸟'

let obj = {
  name: '前端小菜鸟',
  getName: function () { 
    let that = this; // 用that保存this
    setTimeout(function () { 
      console.log(this.name , that.name); // 定时器里this指向Window 我是大菜鸟 that指向当前对象 前端小菜鸟
    }, 10);
  } 
}
obj.getName();

箭头函数

箭头函数无法用call、apply、bind改变this指向

var name = '大菜鸟'
let obj = {
  name: "前端小菜鸟",
  getName: (a,b) => {
      console.log(this.name,a,b);
  }
};
let fn = obj.getName;
fn(1,2); // 大菜鸟 1 2
fn.bind(obj, 3,4)(); // 大菜鸟 3 4
fn.call(obj,5,6);// 大菜鸟 5 6
fn.apply(obj,[1,2]);// 大菜鸟 7 8

call、apply、bind原理

call、apply、bind实际上都是function原型上的方法,所有实例都可以调用

Function.png

call实现

let obj = {
  name: '前端小菜鸟',
  getName: function (age, sex) { console.log(this.name + "性别" + sex +'工作' + age + '年')} 
}
let newObj = {
  name: '大菜鸟'
}

function myCall() {
  // 解构剩余参数
  let [thisArg, ...args] = arguments;
  thisArg = Object(thisArg) || window;
  thisArg.fn = this; // 随便定义一个属性用来保存当前方法
  let result = thisArg.fn(...args); // 执行方法得到结果
  delete thisArg.fn; // 当前对象没有这个属性 所以要删除
  return result; // 返回结果
}
Function.prototype._call = myCall; // Function原型上添加方法

obj.getName._call(newObj, 3, '男') // 大菜鸟性别男工作3年

// 极简版本
Function.prototype.Call = function (that, ...args) {
  that.fn = this;
  let res = that.fn(...args);
  delete that.fn;
  return res
}

apply实现

call都实现了 apply那不是手到擒来

let obj = {
  name: '前端小菜鸟',
  getName: function (age,sex) { console.log(`${this.name} 性别${sex}工作${age}年`)} 
}
let newObj = {
  name: '大菜鸟'
}
Function.prototype._apply = myApply;
function myApply() {
  let [thisArg, args] = arguments; // 跟call不同的其实就在这里 获取参数的方式不同
  thisArg = Object(thisArg) || window;
  thisArg.fn = this;
  let result = thisArg.fn(...args);
  delete thisArg.fn;
  return result;
}

obj.getName._apply(newObj,[3, '男']); // 大菜鸟 性别男工作3年

// 极简版本
Function.prototype.Apply = function (that, args) {
  that.fn = this;
  let res = that.fn(...args);
  delete that.fn;
  return res
}

bind实现

let obj = {
  name: '前端小菜鸟',
  getName: function (age,sex) { console.log(`${this.name} 性别${sex}工作${age}年`)} 
}
let newObj = {
  name: '大菜鸟'
}

Function.prototype._bind = function (that, ...args) {
  let fn = this;
  return function () {
      return fn.call(that, ...args)
  }
}
obj.getName._bind(newObj, 3, '男')();

总结

callapplybind
调用后立即执行x
第二个参数是数组xx
返回值是一个函数xx