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

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

image.png

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

默认绑定

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

  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(function () {
  "use strict";
  console.log(this); //window
});
setTimeout(() => {
  console.log(this); //window
});
setTimeout(() => {
  "use strict";
  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

扩展

vue中延迟函数的this

vue中,延迟函数如果是箭头函数,它的this指向当前vue实例。

created() {
  console.log(this); // 当前vue实例
  setTimeout(() => {
    console.log(this); // 当前vue实例
  });
  setTimeout(function () {
    console.log(this); // window对象
  });
},

事件中的this

在事件中e.target始终指向触发事件的元素,e.currentTarget始终指向绑定事件的元素。

事件中的this指向又有所区别

  • 不管以何种方式绑定的事件,如果事件函数是箭头函数,this始终指向window。这也就印证了箭头函数的this只取决于裹箭头函数的第一个普通函数this
  • 如果事件函数是普通函数就分为两种情况,如果采用监听或者给dom元素间接绑定事件的方式this指向绑定事件的元素,如果是直接绑定事件this指向window

下面我用例子说明

<div id="div1">
  <span>点我吧1</span>
</div>
<div id="div2">
  <span>点我吧2</span>
</div>
<div id="div3" onclick="click3(event)">
  <span>点我吧3</span>
 </div>
const div1 = document.getElementById("div1");
div1.addEventListener("click", function (e) {
  console.log(this); // <div id="div1"><span>点我吧1</span></div>
  console.log("currentTarget", e.currentTarget); // <div id="div1"><span>点我吧1</span></div>
  console.log("target", e.target); // <span>点我吧1</span>
});
div1.addEventListener("click", (e) => {
  console.log(this); // window
  console.log("currentTarget", e.currentTarget); // <div id="div1"><span>点我吧1</span></div>
  console.log("target", e.target); // <span>点我吧1</span>
});

const div2 = document.getElementById("div2");
div2.onclick = function (e) {
  console.log(this); // <div id="div2"><span>点我吧2</span></div>
  console.log("currentTarget", e.currentTarget); // <div id="div2"><span>点我吧2</span></div>
  console.log("target", e.target); // <span>点我吧2</span>
};
// div2.onclick = (e) => {
//   console.log(this); // window
//   console.log("currentTarget", e.currentTarget); // <div id="div2"><span>点我吧2</span></div>
//   console.log("target", e.target); // <span>点我吧2</span>
// };

function click3(e) {
  console.log(this); // window
  console.log("currentTarget", e.currentTarget); // <div id="div3"><span>点我吧3</span></div>
  console.log("target", e.target); // <span>点我吧3</span>
}
// const click3 = (e) => {
//   console.log(this); // window
//   console.log("currentTarget", e.currentTarget); // <div id="div3"><span>点我吧3</span></div>
//   console.log("target", e.target); // <span>点我吧3</span>
// };

箭头函数中的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. 如果以上都不是,那么使用默认绑定。
  5. 如果把 null 或者 undefined 作为 this 的绑定对象传入 call、apply、bind非严格模式下这些值在调用时会被忽略,实际应用的是默认绑定规则。
  6. 如果是箭头函数,箭头函数的 this 只取决包裹箭头函数的第一个普通函数this。如果没有被普通函数包裹实际应用的是默认绑定规则。

总结就是new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

系列文章

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

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

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

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

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

都2022年了你不会还没搞懂JS Object API吧

都2022年了你不会还没搞懂js垃圾回收和内存泄露吧

都2022年你不会还没搞懂js执行上下文和事件循环机制吧

都2022年了你不会还没搞懂js中的事件吧

都2020年了你不会还没搞懂js异步编程吧

后记

感谢小伙伴们的耐心观看,本文为笔者个人学习笔记,如有谬误,还请告知,万分感谢!如果本文对你有所帮助,还请点个关注点个赞~,您的支持是笔者不断更新的动力!

相关文章
|
4天前
|
JavaScript 前端开发
【面试题】这道 JS 经典面试题不要背!今天帮你彻底搞懂它
【面试题】这道 JS 经典面试题不要背!今天帮你彻底搞懂它
|
6月前
|
机器学习/深度学习 人工智能 运维
10 分钟搞懂 LLMOps
10 分钟搞懂 LLMOps
421 0
|
6月前
|
人工智能 网络协议 算法
5 分钟搞懂 ECN
5 分钟搞懂 ECN
672 0
|
4天前
|
JavaScript 前端开发
【面试题】这道 JS 经典面试题不要背,今天帮你彻底搞懂它!
【面试题】这道 JS 经典面试题不要背,今天帮你彻底搞懂它!
|
4天前
|
C#
彻底搞懂 for、while、do...while
彻底搞懂 for、while、do...while
16 0
|
4天前
|
SQL 关系型数据库 MySQL
搞懂connectTimeout和socketTimeout的区别
搞懂connectTimeout和socketTimeout的区别
76 0
|
6月前
|
人工智能 安全 Cloud Native
5 分钟搞懂 NESAS
5 分钟搞懂 NESAS
63 0
|
10月前
|
Kubernetes 并行计算 负载均衡
一文搞懂 K3D
Hello folks,作为一款由 Google 开发的开源平台,Kubernetes 主要用于自动部署、资源扩展、管理以及编排容器化应用程序。其不仅是提供了一个简单的系统,用于管理跨多个服务器的容器,同时,具备出色的负载平衡和资源分配能力,以确保每个应用程序能够以最佳性能运行。
497 0
leetcode14(弄懂了一个知识点)
这个题有一点细节,所以就记录一下(可能不一定准确)
57 0
|
存储 文字识别 安全
一文搞懂SDIO
SDIO(Secure Digital Input and Output),即安全数字输入输出接口。它是在SD卡接口的基础上发展而来,它可以兼容之前的SD卡,并可以连接SDIO接口设备,比如:蓝牙、WIFI、GPS等。