【JavaScript】【分享】关于this

前言

本文旨在总结实践中关于this关键字的应用经验,期待读者在评论区留言指正与补充,以促进共同学习与进步。

方向

  1. 宿主环境为浏览器(网页),创建一个html文件用浏览器打开即可。如下:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>关于this</title>
  </head>
  <body>
    <script>
      // 代码
    </script>
  </body>
</html>
  1. 严格模式和非严格模式。
  2. 普通函数和箭头函数。
  3. bind,call,apply。
  4. new关键字,构造函数模式。

1.为什么要用this?

看下面例子:

     // 这是一个错误实践 请不要参考 在此只是举例使用。 这种错误实践被称为猴子修补(monkey patching)。使用猴子修补存在向前兼容的风险,因为如果语言在未来添加了此方法但具有不同的签名,你的代码将会出错。
      Object.prototype.eat = function () {
        console.log(this.name);
      };
      const peach = {
        name: "peach",
      };  
      peach.eat();

原型最常被用作方法的容器。相似的对象都有相似的方法,所以将所有方法挂载到同一个共享原型上。那么,原型上的函数如何知道自己在执行的时候应该作用于哪个对象呢?这就要用到this了。

再看一个例子:

function eat() {
   console.log(this.name);
 }
const peach = {
  name: "peach",
};
const plum = {
 name: "plum",
};
eat.call(peach); // peach
eat.call(plum); // plum

这段代码可以在不同的上下文对象(peach和plum)中重复使用函数eat(),不用针对每个对象编写不同版本的函数。

情况一

看下面例子:

function fruit_shop() {
 console.log(this);
}
fruit_shop() // window
"use strict"
function fruit_shop() {
console.log(this);
}
fruit_shop() // undefined
function fruit_shop() {
  console.log(this);
}
(function () {
  "use strict";
  fruit_shop(); // window
})();
function fruit_shop_eat() {
  console.log(this);
  function eat_plum() {
    console.log(this);
  }
  eat_plum();
}
const fruit_shop = {
  name: "welcome to 1 of xiao wu fruit shop",
};
fruit_shop_eat.call(fruit_shop); // fruit_shop  window

小结

this是在运行时进行绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件。this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。
当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。

将上面列子换成箭头函数在来看下结果

const fruit_shop = () => {
  console.log(this);
};
fruit_shop(); // window
"use strict"
const fruit_shop = () => {
  console.log(this);
};
fruit_shop(); // window
const fruit_shop = () => {
  console.log(this);
};
(() => {
  "use strict";
  fruit_shop(); // window
})();
const fruit_shop_eat = () => {
  console.log(this);
  const eat_plum = () => {
    console.log(this);
  };
  eat_plum();
};
const fruit_shop = {
  name: "welcome to 1 of xiao wu fruit shop",
};
fruit_shop_eat.call(fruit_shop); // window  window

额外的例子

const fruit_shop_eat = () => {
  console.log(this);
  return () => {
    console.log(this);
  };
};
const fruit_shop = {
  name: "welcome to 1 of xiao wu fruit shop",
};
const eat = fruit_shop_eat.call(fruit_shop); // window
eat(); //   window
function fruit_shop_eat() {
  console.log(this);
  return () => {
    console.log(this);
  };
}
const fruit_shop = {
  name: "welcome to 1 of xiao wu fruit shop",
};
const eat = fruit_shop_eat.call(fruit_shop); // fruit_shop
eat(); // fruit_shop

小结

ES2015 引入了箭头函数,箭头函数不提供自身的 this 绑定(this 的值将保持为闭合词法上下文的值)。而是根据当前的词法作用域来决定this,具体来说,箭头函数会继承外层函数调用的this绑定(无论this绑定到什么)。这其实和ES6之前代码中的self = this机制一样。

情况二

看下面例子:

function eat() {
  console.log(this.name);
}
const peach = {
  name: "peach",
  eat,
};
peach.eat(); // peach

小结

调用位置会使用peach上下文来引用函数,当eat()被调用时,它的前面加上了对peach的引用。当函数引用有上下文对象时,会把函数调用中的this绑定到这个上下文对象。因此this.name和peach.name是一样的。

看下面例子:

function eat() {
  console.log(this.name);
}

const peach = {
  name: "peach",
  eat,
};

const fruit_shop = {
  name: "fruit shop",
  peach,
};
fruit_shop.peach.eat(); // peach

小结

对象属性引用链中只有上一层或者说最后一层在调用位置中起作用。

看下面例子:

function eat() {
  console.log(this);
}
const peach = {
  name: "peach",
  eat,
};
const eat_fresh = peach.eat;
eat_fresh(); // window
console.log(eat_fresh === eat); // true

小结

peach.eat和eat_fresh引用的是eat函数本身,eat_fresh不是一个对象属性。在非严格模式下指向window。

情况三

使用apply,bind,call方法

function fruit_shop() {
  console.log(this); 
}
const fruit_shop_bind = fruit_shop.bind({});
fruit_shop_bind(); // {}
fruit_shop.apply({}); // {}
fruit_shop.call({}); // {}
const fruit_shop = () => {
  console.log(this);
};
const fruit_shop_bind = fruit_shop.bind({});
fruit_shop_bind(); // window
fruit_shop.apply({}); // window
fruit_shop.call({}); // window

小结
Function 实例的 apply() ,bind(),call()方法会以给定的 this 值。三种方法不适应于箭头函数(this 的值将保持为闭合词法上下文的值)。

情况四

看下以下例子:

function FruitShop() {
  this.peach = "peach"
  console.log(this);
}
new FruitShop(); // FruitShop
function FruitShop() {
  this.peach = "peach";
}    
console.log(new FruitShop().peach); // peach

小结

该情况需要清楚使用new关键字会发生什么:

  1. 创建一个空的简单 JavaScript 对象。为方便起见,我们称之为 newInstance。
  2. 如果构造函数的 prototype 属性是一个对象,则将 newInstance 的 [[Prototype]] 指向构造函数的 prototype 属性,否则 newInstance 将保持为一个普通对象,其 [[Prototype]] 为 Object.prototype。
  3. 使用给定参数执行构造函数,并将 newInstance 绑定为 this 的上下文(换句话说,在构造函数中的所有 this 引用都指向 newInstance)。
  4. 如果构造函数返回非原始值,则该返回值成为整个 new 表达式的结果。否则,如果构造函数未返回任何值或返回了一个原始值,则返回 newInstance。(通常构造函数不返回值,但可以选择返回值,以覆盖正常的对象创建过程。)

总结

以上是我个人实践的总结,难免有疏漏或偏差之处,诚邀各位在评论区不吝赐教,让我们一起在交流中学习成长。

主要文献

  1. 《JavaScript高级程序设计(第4版)》
  2. 《JavaScript语法简明手册》
  3. 《你不知道的JavaScript(上卷)》
  4. mdn web docs - this
  5. mdn web docs - new
  6. mdn web docs - apply
  7. mdn web docs - bind
  8. mdn web docs - call
  • 10
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值