登录
原创

详细的谈一谈apply, call, bind

发布于 2020-10-15 阅读 105
  • 前端
  • JavaScript
原创

01 前言

这又是一个面试经典问题,也是 ES5 中众多坑中的一个。虽然在 ES6 中可能会极大避免 this 产生的错误,但是为了一些老代码的维护,详细的了解一下 this 的指向和 call、apply、bind 三者的区别还是很有必要的。

02 this的指向

了解call,apply,bind之前我们先来了解下js中 this 的指向。其实 this 的指向,始终坚持一个原理: this 永远指向最后调用它的那个对象。

下面我们就来讨论下这些场隐式绑定的场景this指向:

  • 全局上下文
  • 直接调用函数
  • 对象.方法的形式调用
  • DOM事件绑定(特殊)
  • new构造函数绑定
  • 箭头函数

全局上下文

全局上下文默认this指向window, 严格模式下指向undefined。

直接调用函数

let obj = {
  a: function() {
    console.log(this);
  }
}
let func = obj.a;
func();

这种直接调用函数的情况。this相当于全局上下文的情况,默认指向window。

对象.方法 调用

let obj = {
  a: function() {
    console.log(this);
  }
}
obj.a();

这种对象.方法的调用情况。this指向的是这个对象。

DOM事件绑定

<div></div>
<button onclick="myClick()">123</button>
let dom = document.querySelector('div');
dom.addEventerListener('click',function(){
  console.log(this);
});

function myClick(){
  console.log(this);
}
  • addEventerListener中 this 默认指向绑定事件的元素。
  • onclick等系列事件和IE中使用的attachEvent默认this指向window。

new构造函数

function Person(name) {
  this.name = name;
  this.age = 6;
}
let tz = new Person('Tz');

此时构造函数中的this指向实例对象(tz)。

箭头函数

箭头函数没有this, 因此也不能绑定。里面的this会指向当前最近的非箭头函数的this,找不到就是window(严格模式是undefined)。

let obj = {
  a: function() {
    let do = () => {
      console.log(this);
    }
    do();
  }
}
obj.a();

this找到最近的非箭头函数是a方法,a现在绑定着obj, 因此箭头函数中的this是obj。

03 apply,call,bind

bind,call,apply都是用于显性的绑定this,从而改变this的指向。

apply

MDN给出的定义是:

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为 一个数组(或类似数组的对象) 提供的参数

语法:

func.apply(thisArg, [argsArray])

  • thisArg 必选。在 func 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
  • argsArray 可选。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。
function Product(name, price) {
  this.name = name;
  this.price = price;
}

function Food(arr) {
  Product.apply(this, arr); //this指向实例对象food
  this.category = 'food';
}

let food = new Food(['cheese', 5])

call

MDN给出的定义是:

call() 方法使用一个指定的 this 值和单独给出的 一个或多个参数 来调用一个函数。

语法:

function.call(thisArg, arg1, arg2, …)

  • thisArg 可选。在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null 或 undefined 时会自动替换为指向全局对象,原始值会被包装。
  • arg1, arg2, ... 指定的参数列表。
function Product(name, price) {
  this.name = name;
  this.price = price;
}

function Food(name, price) {
  Product.call(this, name, price); //this指向实例对象food
  this.category = 'food';
}

let food = new Food('cheese', 5);

bind

MDN给出的定义是:

bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

语法:

function.bind(thisArg[, arg1[, arg2[, …]]])

  • thisArg 调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArg是null或undefined,执行作用域的 this 将被视为新函数的 thisArg。
  • arg1, arg2, ... 当目标函数被调用时,被预置入绑定函数的参数列表中的参数。
function Product(name, price) {
  this.name = name;
  this.price = price;
}

function Food(name, price) {
  Product.bind(this, name, price)(); //this指向实例对象food
  this.category = 'food';
}

let food = new Food('cheese', 5);

三者区别

  • apply 使用 数组 作为参数;
  • call 使用 参数列表 作为参数;
  • bind 使用 参数列表 作为参数,且创建了一个新的函数,需要 被调用 才会起作用。

04 手写模拟实现apply方法

Function.prototype.myapply = function (thisArg, args) {
  thisArg = Object(thisArg) || window;
  let fn = Symbol();
  thisArg[fn] = this;
  let result = thisArg[fn](...args);

  delete thisArg[fn];
  return result;
}

//test
let obj = {
  a: 1
}

function test(x1, x2) {
  console.log(x1, x2)  //2, 3
  console.log(this.a)  //1
}
test.myapply(obj, [2, 3])

05 手写模拟实现call方法

Function.prototype.mycall = function (thisArg, ...args) {
  thisArg = Object(thisArg) || window;
  let fn = Symbol();
  thisArg[fn] = this;
  let result = thisArg[fn](...args);

  delete thisArg[fn];
  return result;
}

//test
let obj = {
  a: 1
}

function test(x1, x2) {
  console.log(x1, x2)  //2, 3
  console.log(this.a)  //1
}
test.mycall(obj, 2, 3)

06 手写模拟实现bind方法

Function.prototype.mybind = function(thisArg, ...args){
  return (...newArgs) => {
    return this.call(thisArg, ...args, ...newArgs)
  }
}

// test
let obj = {
  a: 1
}

function test(x1, x2) {
  console.log(x1, x2)  //2, 3
  console.log(this.a)  //1
}
test.mybind(obj, 2, 3)()

评论区

Tz_BB
1粉丝

励志做一条安静的咸鱼,从此走上人生巅峰。

0

0

0