都2022年了你不会还没搞懂this吧

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战

简介

在JS中this的绑定规则有默认绑定隐式绑定显示绑定new绑定四种。绑定的优先级依次是 new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定,下面我们来一一探讨。

文章里的每个案例都是我亲自编写并验证的,建议阅读文章时,可以在浏览器执行案例,会更有利于理解。

默认绑定

默认绑定,在不能应用其它绑定规则时使用的默认规则,通常是使用全局变量或者独立函数调用。

  1. 全局环境下,this始终指向全局对象(window),无论是否严格模式。
  2. 对于延时函数内部的回调函数的this指向全局对象window,无论是否严格模式。
  3. 普通函数内部的this分两种情况,严格模式和非严格模式。非严格模式下,this 默认指向全局对象window。严格模式下,this指向undefined。
  4. 箭头函数下,this始终指向全局对象(window),无论是否严格模式。
console.log("全局环境下的this: ", this); //window

setTimeout(function () {
  console.log(this); //window
});
setTimeout(() => {
  console.log(this); //window
});

function f1() {
  console.log("方法下的this: ", this); //window
}
f1();

function f2() {
  "use strict";
  console.log("严格模式下方法下的this: ", this); // undefined
}
f2();

const f3 = () => {
  console.log("箭头函数方法下的this: ", this); //window
};

f3();

const f4 = () => {
  "use strict";
  console.log("严格模式下箭头函数方法下的this: ", this); //window
};

f4();
复制代码

隐式绑定

在隐式绑定中通常函数作为对象的方法被调用。

  1. 当函数作为对象里的方法被调用时,它们的 this 是调用该函数的对象。
  2. 多层嵌套的对象,内部方法的 this 指向离被调用函数最近的对象。
const obj1 = {
  name: "randy1",
  say() {
    return this.name;
  },
  b: {
    name: "randy2",
    say() {
      return this.name;
    },
  },
};
console.log(obj1.name); // randy1
console.log(obj1.b.name); // randy2
复制代码

显示绑定

显式绑定比较好理解,就是通过call、apply、bind的方式,显式的修改this所指向的对象。

  1. apply(this, [args])call(this, args)bind(this, args)()这三者的区别是apply参数列表是数组,bind需要再次调用。
  2. 如果 callapply 或者 bind 传入的第一个参数值是 undefined 或者 null,严格模式下 this 的值为传入的值 null /undefined。非严格模式下,实际应用的默认绑定规则,this 指向全局对象(node环境为global,浏览器环境为window)。
var name = "global name";
function say(age) {
  console.log(`${this.name}今年${age}啦!`);
}
const user = { name: "randy" };
const user2 = { name: "randy2" };
say(24); // global name今年24啦!
say.apply(user, [25]); // randy今年25啦!
say.apply(user2, [26]); // randy2今年26啦!
say.call(user, 25); // randy今年25啦!
say.call(user2, 26); // randy2今年26啦!
say.bind(user, 25)(); // randy今年25啦!
say.bind(user2, 26)(); // randy2今年26啦!

say.apply(null, [25]); // global name今年25啦!
say.apply(undefined, [26]); // global name今年26啦!
复制代码

new 绑定

  1. 当一个函数用作构造函数时(使用new关键字),它的this被绑定到正在构造的新对象。
  2. 构造器返回的默认值是this所指的那个对象,也可以手动返回其他的新对象。
  3. 如果构造函数返回了一个新的非空对象,则this指向新对象,否则指向我们创建的对象。
function People() {
  this.name = "randy";
}

const p1 = new People();
console.log(p1.name); // randy

function People2() {
  this.name = "randy";
  return { name: "demi" }; //手动设置返回{ name: 'demi' }新对象
}

const p2 = new People2();
console.log(p2.name); // demi
复制代码

扩展

事件中的this

事件中的this指向又有所区别,e.target指向触发事件的元素,e.currentTarget指向绑定事件的元素,下面我用例子说明

<div onclick="click1(event)" id="div1">
  <button>事件中的this</button>
</div>
复制代码
function click1(e) {
  // 触发事件的元素
  console.log(e.target); // <button>事件中的this</button>
  // 绑定事件的元素
  console.log(e.currentTarget); // <div onclick="click1(event)" id="div1"><button>事件中的this</button></div>
  console.log(this); // window
}

const div1 = document.getElementById("div1");
div1.addEventListener("click", function (e) {
  // 触发事件的元素
  console.log(e.target); // <button>事件中的this</button>
  // 绑定事件的元素
  console.log(e.currentTarget); // <div onclick="click1(event)" id="div1"><button>事件中的this</button></div>
  // 绑定事件的元素
  console.log(this); // <div onclick="click1(event)" id="div1"><button>事件中的this</button></div>
  console.log(this === e.currentTarget); // true
});
复制代码

箭头函数中的this

  1. 箭头函数的 this 是在定义函数时绑定的,不是在执行过程中绑定的。简单的说,函数在定义时,this 就继承了定义函数的对象。
  2. 箭头函数中的 this 只取决包裹箭头函数的第一个普通函数this,否则应用的是默认绑定规则。
  3. 箭头函数不能通过 apply call bind 改变 this
  4. 箭头函数不能使用 arguments。得使用reset参数
  5. 箭头函数不能用于构造函数。
  6. 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数。

下面我将举例重点说明下箭头函数的绑定问题,其他特性就不详细举例了,小伙伴们可以自行测试。

const obj = {
  hi: function () {
    console.log(this);
    return () => {
      console.log(this);
    };
  },
  sayHi: function () {
    return function () {
      console.log(this);
      return () => {
        console.log(this);
      };
    };
  },
  say: () => {
    console.log(this);
  },
};

let hi = obj.hi(); //输出obj对象
hi(); //输出obj对象
let sayHi = obj.sayHi();
let fun1 = sayHi(); //输出window
fun1(); //输出window
obj.say(); //输出window
复制代码

下面我们来分析下。

  1. obj.hi()应用隐式绑定规则,this就是obj对象,所以输出obj。
  2. hi()箭头函数里再输出this,应用我们上面说的特性,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this,所以也输出obj对象。
  3. obj.sayHi()返回一个普通的函数。
  4. sayHi()就是调用刚返回的普通函数,应用默认绑定规则,返回window对象。
  5. fun1()箭头函数里再输出this,应用我们上面说的特性,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this,所以也输出window对象。
  6. obj.say()箭头函数里再输出this,应用我们上面说的特性,箭头函数中的 this 只取决包裹箭头函数的第一个普通函数的 this,如果没有被普通函数包裹,就会应用默认绑定规则所以输出window对象。

如何准确判断this

说了这么多,那我们到底如何来判断this指向问题呢?

  1. 函数是否在 new 中调用(new 绑定),如果是,那么 this 绑定的是新创建的对象。
  2. 函数是否通过 call,apply 调用,或者使用了 bind(即硬绑定),如果是,那么 this 绑定的就是指定的对象。
  3. 函数是否在某个上下文对象中调用(隐式绑定),如果是的话,this 绑定的是那个上下文对象。
  4. 如果以上都不是,那么使用默认绑定。如果在严格模式下,则绑定到 undefined,否则绑定到全局对象。
  5. 如果把 null 或者 undefined 作为 this 的绑定对象传入 call、apply 或者 bind,这些值在调用时会被忽略,实际应用的是默认绑定规则。
  6. 如果是箭头函数,箭头函数的 this 只取决包裹箭头函数的第一个普通函数的 this。如果没有被普通函数包裹实际应用的是默认绑定规则。

系列文章

都2022年了你不会还没搞懂JS数据类型吧

都2022年了你不会还没搞懂JS原型和继承吧

都2022年了你不会还没搞懂JS赋值拷贝、浅拷贝、深拷贝吧

都2022年了你不会还没搞懂对象数组的遍历吧

后记

本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~~

おすすめ

転載: juejin.im/post/7066275985289084965