从零开始详解js中的this(下)

先解释一下为什么已经两个星期没有更新关于this的后半部分吧,这两个星期笔者有一次打了18个小时麻将(从早上10点打到第二天早上5点,中途就吃了个晚饭)然后加上这两周有一些考试就没写关于js中this的后半部分。

今天终于有时间来写了,掘友们可以先去看看这篇文章从零开始详解js中的this(上) 再来看本篇,本文将详细讲述this的优先级、绑定例外及this的词法。

this的优先级

我们已经了解了函数调用中this绑定的四条规则,我们只需要找到函数的调用位置并判断应该使用哪条规则就好啦,但如果某个调用位置同时出现多条规则怎么办?为了解决这个问题我们不得不给这些规则设定优先级。比较之前我们先来回顾一下它的四种绑定规则

this的四种绑定规则

  1. 默认绑定 :当函数独立调用时,this指向全局对象(浏览器中是window,严格模式下是undefined)。
  2. 隐式绑定 :当函数作为对象的方法调用时,this指向调用该方法的对象。
  3. 显式绑定 :使用call()apply()bind()时,可以显式指定this的值。
  4. new绑定 :使用new关键字创建对象时,this指向新创建的对象实例。

比较方式

我们可以通过以下几种情况来比较这些规则的优先级:

1. 默认绑定 vs 隐式绑定
javascript 复制代码
function foo() {
    console.log(this.a);
}

var a = 2;

var obj = {
    a: 3,
    foo: foo
};

foo(); //2,默认绑定
obj.foo(); // 3,隐式绑定

在这个例子中,foo()是独立调用的,因此this指向全局对象,输出2;而obj.foo()是作为对象的方法调用的,因此this指向obj,输出3

注意

上面的代码如果在node中运行就不是这个结果了,第一个foo()就会打印出undefined,这其实是因为环境问题导致的,在node里全局变量 a 会自动成为 global 对象的属性。 当 foo() 被调用时,this 指向 global 对象。 但是,Node.js 的 global 对象并没有直接继承全局变量 a。因此,this.aundefined

2. 隐式绑定 vs 显式绑定
javascript 复制代码
function foo() {
    console.log(this.a);
}

var obj1 = {
    a: 2
};

var obj2 = {
    a: 3,
    foo: foo
};

var bar = obj2.foo;
bar.call(obj1); // 2,显式绑定

在这个例子中,虽然bar是从obj2中获取的,但通过call()显式指定了thisobj1,因此输出2

3. 显式绑定 vs new绑定
javascript 复制代码
function foo(a) {
    this.a = a;
}

var obj = {};

var bar = foo.bind(obj);

bar(2); // obj.a = 2,显式绑定
new bar(3); // 创建了一个新的对象实例,new绑定优先
console.log(obj.a); // 2

在这个例子中,bar是通过bind()显式绑定了obj,由结果可以看出在使用new时,new绑定优先,创建了一个新的对象实例,而不是修改obj

4. new绑定 vs 默认绑定
javascript 复制代码
function Foo() {
    console.log(this instanceof Foo);
}

Foo(); // false,默认绑定
new Foo(); // true,new绑定

在这个例子中,Foo()是独立调用的,因此this指向全局对象,instanceof检查失败;而new Foo()是通过new调用的,this指向新创建的对象实例,instanceof检查成功。

通过上面几个例子可以看出,this的绑定规则有明确的优先级顺序。这四种绑定规则的优先级从高到低依次为:

  • new绑定
  • 显式绑定
  • 隐式绑定
  • 默认绑定

绑定例外

在 js 中,this 的绑定规则是理解函数调用上下文的关键。然而,有些情况下 this 的绑定行为并不遵循常规的默认、隐式、显式和 new 绑定规则。这些例外情况可能会导致意外的行为。下面一起来看看吧

箭头函数中的 this

箭头函数是 ES6 引入的一种新语法,它改变了 this 的绑定方式。与普通函数不同,箭头函数没有自己的 this,而是继承自外层作用域(通常是定义它的上下文)。

javascript 复制代码
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 }); // 输出: id: 42

在这个例子中,setTimeout 内部的箭头函数并没有创建新的 this 绑定,而是继承了外层 foo 函数的 this 值。因此,输出的是 42 而不是全局变量 id 的值 21

严格模式下的 this

在严格模式下,this 的默认绑定不再是全局对象,而是 undefined。这可以避免一些潜在的错误。

javascript 复制代码
function foo() {
  'use strict';
  console.log(this.a);
}

var a = 2;

foo(); // 输出: TypeError: Cannot read property 'a' of undefined

在严格模式下,this 默认绑定为 undefined,因此尝试访问 this.a 会导致 TypeError

bind 方法的限制

虽然 bind 可以创建一个新的函数并永久绑定 this,但需要注意的是,bind 创建的新函数不能被再次绑定或修改 this

javascript 复制代码
function foo() {
  console.log(this.a);
}

var obj1 = { a: 2 };
var obj2 = { a: 3 };

var bar = foo.bind(obj1);

bar(); // 输出: 2
bar.call(obj2); // 输出: 2,bind 后无法再改变 this

在这个例子中,bind 创建的 bar 函数已经绑定了 obj1,即使后续使用 callapply 尝试改变 this,也不会生效。

this词法

箭头函数与词法 this

箭头函数是 ES6 引入的一种新语法形式,它没有自己的 this 绑定,而是继承自外层作用域(通常是定义它的上下文)。这种绑定方式被称为 词法 this ,即 this 的值是在函数定义时确定的,而不是在调用时确定的。

javascript 复制代码
function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 }); // 输出: id: 42

这个例子又来了,我们可以看到,setTimeout 内部的箭头函数并没有创建新的 this 绑定,而是继承了外层 foo 函数的 this 值。因此,输出的是 42 而不是全局变量 id 的值 21

词法 this 的优势

词法 this 解决了许多传统函数中常见的 this 绑定问题,特别是在回调函数和异步操作中。它使得代码更加简洁和直观,避免了不必要的 thatself 变量的使用。

javascript 复制代码
function Person(name) {
  this.name = name;
  this.age = 29;

  setInterval(() => {
    console.log(`Name: ${this.name}, Age: ${this.age}`);
  }, 1000);
}

var p = new Person('Kyle');
// 每秒输出: Name: Kyle, Age: 29

在这个例子中,箭头函数内部的 this 继承自 Person 构造函数的实例,因此可以直接访问 nameage 属性,而不需要额外的绑定操作。

箭头函数与 this 绑定的限制

尽管箭头函数简化了 this 绑定,但也有一些限制:

  • 箭头函数不能用作构造函数,因此不能使用 new 关键字。
  • 箭头函数没有自己的 arguments 对象,但可以通过剩余参数(rest parameters)来替代。
  • 箭头函数没有自己的 this,因此不能使用 callapplybind 来改变 this 的绑定。
javascript 复制代码
function foo() {
  'use strict';
  return () => {
    console.log(this.a);
  };
}

var a = 2;
var obj = { a: 3 };

var bar = foo.call(obj);
bar(); // 输出: undefined (因为 foo 的 this 是 obj,但箭头函数的 this 是全局对象)

在这个例子中,虽然 foothis 绑定到了 obj,但箭头函数的 this 继承自外层作用域,即全局对象,因此输出 undefined

注意事项

在实际开发中,了解 this 的词法绑定规则可以帮助我们编写更健壮和可预测的代码。特别是在处理回调函数、事件处理器和异步操作时,箭头函数可以显著减少 this 绑定错误的发生。请看下面这段代码:

javascript 复制代码
// 使用箭头函数解决 this 绑定问题
function Person(name) {
  this.name = name;
  this.age = 29;

  this.growOlder = function() {
    setTimeout(() => {
      this.age++;
      console.log(`Age: ${this.age}`); // this 正确指向 Person 实例
    }, 1000);
  };
}

var p = new Person('Kyle');
p.growOlder(); // 每秒输出: Age: 30, Age: 31, ...

在这个例子中,使用箭头函数后,this 正确地指向了 Person 实例,从而解决了 this 绑定的问题。

如果这篇文章对你有些许帮助的话,麻烦帮我投个票吧~

相关推荐
燃先生._.24 分钟前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
黄油饼卷咖喱鸡就味增汤拌孜然羊肉炒饭40 分钟前
SpringBoot如何实现缓存预热?
java·spring boot·spring·缓存·程序员
高山我梦口香糖1 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
m0_748235241 小时前
前端实现获取后端返回的文件流并下载
前端·状态模式
m0_748240252 小时前
前端如何检测用户登录状态是否过期
前端
black^sugar2 小时前
纯前端实现更新检测
开发语言·前端·javascript
寻找沙漠的人3 小时前
前端知识补充—CSS
前端·css
GISer_Jing3 小时前
2025前端面试热门题目——计算机网络篇
前端·计算机网络·面试
m0_748245523 小时前
吉利前端、AI面试
前端·面试·职场和发展