深度解析javascript中的this(二)

本文深入讲解JavaScript中this的绑定规则,包括默认绑定、隐式绑定、显式绑定及new绑定,并通过实例说明每种绑定的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

      上篇文章我们初步了解了有关this的知识,在不考虑箭头函数的情况下,this指向函数的调用者,这一章我们将进一步学习了解this

正文

调用位置

      在理解 this 的绑定过程之前,首先要理解调用位置:调用位置就是函数在代码中被调用的位置(而不是声明的位置)。最重要的是要分析调用栈(就是为了到达当前执行位置所调用的所有函数)。比如看下面的例子:

function baz() {
	// 当前调用栈是:baz
	// 因此,当前调用位置是全局作用域
	console.log( "baz" );
	bar(); // <-- bar 的调用位置
}
function bar() {
	// 当前调用栈是 baz -> bar
	// 因此,当前调用位置在 baz 中
	console.log( "bar" );
	foo(); // <-- foo 的调用位置
}
function foo() {
	// 当前调用栈是 baz -> bar -> foo
	// 因此,当前调用位置在 bar 中
	console.log( "foo" );
}
baz(); // <-- baz 的调用位置

绑定规则

      对于this,其实并不简单是针对调用者,它还有更细致的绑定规则,分为四种情况,下面我们会就这四种情况做一个细致的分析

默认绑定

      首先要介绍的是最常用的函数调用类型:独立函数调用。可以把这条规则看作是无法应用其他规则时的默认规则。

function foo() {
	console.log( this.a );
}
var a = 2;
foo(); // 2

      这里按照我们上一章的解释,因为foo可以视为window.foo,所以this指向也会指向window,但是更准确地说,其实这里应用的是默认绑定的规则,所以this指向全局对象。但是如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined

function foo() {
	"use strict";
	console.log( this.a );
}
var a = 2;
foo(); // TypeError: this is undefined

隐式绑定

      另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含,其实还是我们上一章说到的规则,在不考虑箭头函数的情况,this指向它外层函数的调用者,我们来看一个简单的例子:

function foo() {
	console.log( this.a );
}
var obj2 = {
	a: 42,
	foo: foo
};
var obj1 = {
	a: 2,
	obj2: obj2
};
obj1.obj2.foo(); // 42

      把注意力集中在foo函数本身的调用者就可以,也就是obj2,至于别的不用在意只是干扰项而已

隐式丢失

      一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。比如下面的例子:

function foo() {
	console.log( this.a );
}
var obj = {
	a: 2,
	foo: foo
};
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"

      这里本来直接执行obj.foo应该是返回2的,但是虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。也就是隐式丢失的现象
      一种更微妙、更常见并且更出乎意料的情况发生在传入回调函数时:

function foo() {
	console.log( this.a );
}
function doFoo(fn) {
	// fn 其实引用的是 foo
	fn(); // <-- 调用位置!
}
var obj = {
	a: 2,
	foo: foo
};
var a = "oops, global"; // a 是全局对象的属性
doFoo( obj.foo ); // "oops, global"

      参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样。
      所以请记住,对隐式绑定的函数如果进行了间接调用,便会出现隐式丢失的问题,将自动采用默认绑定

显式绑定

      那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,该怎么做呢?。具体点说,可以使用函数的 call(…) 和apply(…) 方法。大家应该对这两个方法已经很熟悉了,用这个两个方法就可以实现在某个对象上强制调用函数。上一小结我们提到的隐式丢失问题,我们就可以尝试用显式绑定来解决

硬绑定
function foo() {
	console.log( this.a );
}
var obj = {
	a:2
};
var bar = function() {
	foo.call( obj );
};
bar(); // 2
setTimeout( bar, 100 ); // 2
// 硬绑定的 bar 不可能再修改它的 this
bar.call( window ); // 2

      我们来看看这个变种到底是怎样工作的。我们创建了函数 bar(),并在它的内部手动调用了 foo.call(obj),因此强制把 foo 的 this 绑定到了 obj。无论之后如何调用函数 bar,它总会手动在 obj 上调用 foo。这种绑定是一种显式的强制绑定,因此我们称之为硬绑定。
      由于硬绑定是一种非常常用的模式,所以在 ES5 中提供了内置的方法 Function.prototype.bind

API调用的“上下文”

      第三方库的许多函数,以及 JavaScript 语言和宿主环境中许多新的内置函数,都提供了一个可选的参数,通常被称为“上下文”(context),其作用和 bind(…) 一样,确保你的回调函数使用指定的 this。

new绑定

      new绑定是this的最后一条绑定规则,在传统的面向类的语言中,“构造函数”是类中的一些特殊方法,使用 new 初始化类时会调用类中的构造函数。JavaScript 也有一个 new 操作符,使用方法看起来也和那些面向类的语言一样,绝大多数开发者都认为 JavaScript 中 new 的机制也和那些语言一样。然而,JavaScript 中 new 的机制实际上和面向类的语言完全不同。
      javascript中有一个重要但是非常细微的区别:实际上并不存在所谓的“构造函数”,只有对于函数的“构造调用”。

      使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[ 原型 ]] 连接。
  3. 这个新对象会绑定到函数调用的 this。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。

小结

      这一章我们主要讲了javascript中的this绑定的四个规则,默认绑定,隐式绑定,显式绑定和new绑定,同学们可能会有一点疑惑,如果同时有这几种绑定,他们之间互相冲突应该怎么办,因为篇幅有限,我将在下一章给大家介绍这四种规则的优先级以及造成这种优先级的源码原因,谢谢学习
       小伙伴们今天的学习就到这里了,如果觉得本文对你有帮助的话,欢迎转发,评论,收藏,点赞!!!
       每天学习进步一点点,就是领先的开始。如果想继续提高,欢迎关注我,或者关注公众号”祯民讲前端“。大量前端技术文章,面试资料,技巧等助你更进一步!
在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值