全面解析this-理解this指向的原理

参考资料

  • 《你不知道的JavaScript》- this全面解析

this 是什么

  • this 是一个代词,代指一个对象
  • this 提供了一种更优雅的方式来隐式的传递一个对象引用,可以让代码更加简洁易于复用

调用位置

调用位置就是函数在代码中被调用的位置 (而不是声明的位置)。调用位置决定了 this 的绑定

比如下面代码:

js 复制代码
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的绑定规则

this的指向有以下四条特性/规则

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

默认绑定

当函数被独立调用时,函数的 this 指向 window

独立调用就是像这样:foo() 的调用

比如

js 复制代码
function foo() {
	console.log(this.a);
}
var a = 2;
foo(); // 2

如果使用严格模式 (strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined

隐式绑定

当函数引用有上下文对象 且被该对象调用时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象

js 复制代码
function foo() {
	console.log(this.a);
}
var obj = {
  a: 2,
  foo: foo
};
obj.foo(); // 2
// 这里就是隐式绑定,foo函数的this绑定到了obj上

需要注意的是,对象属性引用链中只有最顶层或者说最后一层会影响调用位置

比如

js 复制代码
function foo() {
	console.log(this.a);
}
var obj2 = {
	a: 42,
	foo: foo,
};
var obj1 = {
	a: 2,
	obj2: obj2,
};
obj1.obj2.foo(); // 42
// 相当于 obj2.foo(),因为只有最后一层会影响调用位置

隐式丢失

隐式绑定的函数可能会丢失绑定对象,也就是说它会应用默认绑定

隐式丢失的几种情况:

  1. 函数别名
js 复制代码
function foo() {
	console.log(this.a);
}
var obj = {
	a: 2,
	foo: foo,
};

// 注意这里的函数别名,会导致隐式绑定丢失,导致foo函数的this指向全局
var bar = obj.foo; // 函数别名!
var a = "oops, global"; // a 是全局对象的属性
bar(); // "oops, global"
  1. 函数作为参数传入,并调用时
js 复制代码
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"

参数传递其实就是一种隐式赋值 ,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样

显式绑定

  • fn.call(obj, x, x, ...) 将fn函数的this指向obj,并调用,call的剩余参数是fn需要的参数
javascript 复制代码
function foo(aram) {
	console.log("foo", param.a); // foo 1

	function bar(x, y) {
		console.log("bar", this.a, x, y); // bar 1 2 3
	}
	bar.call(param, 2, 3);
}

foo({ a: 1 });
  • fn.apply(obj, [x, x, ...]) 将fn函数的this指向obj,并调用,apply的第二个参数是一个数组
javascript 复制代码
function foo(param) {
	console.log("foo", param.a); // foo 1

	function bar(x, y) {
		console.log("bar", this.a, x, y); // bar 1 2 3
	}
	bar.apply(param, [2, 3]);
}

foo({ a: 1 });
  • fn.bind(obj, x, x, ...)(x, x, ...) 将fn函数的this指向obj,bind会返回一个新的函数,新的函数也可以传递参数
javascript 复制代码
function foo(param) {
	console.log("foo", param.a); // foo 1

	function bar(x, y) {
		console.log("bar", this.a, x, y); // bar 1 2 3
	}
	return bar.bind(param, 2);
}

const bar = foo({ a: 1 });
bar(3);

new 绑定

new 绑定 - this会绑定到新创建的对象上

javascript 复制代码
function Person(name, age) {
  // this 指向新创建的对象
  this.name = name;
  this.age = age;

  // 通过new调用构造函数时,this指向新创建的对象
	// 直接调用构造函数时,应用默认绑定规则,this指向全局或undefined
  console.log(this);
}

// 使用 new 关键字调用构造函数
const person1 = new Person("张三", 25);
console.log(person1.name); // "张三"
console.log(person1.age);  // 25

// 不使用 new 调用,this 会指向全局对象(非严格模式)或 undefined(严格模式)
const person2 = Person("李四", 30); // this 不会指向新对象
console.log(person2); // undefined (因为没有显式返回)

箭头函数

  1. 箭头函数没有自己的this 指向,它需要继承外层函数的this指向
  2. 箭头函数即使是new也无法改变this指向,因此箭头函数不能用于编写构造函数
js 复制代码
var a = 1;
function foo() {
	var obj1 = {
		a: 2,
		bar: function () {
			console.log("bar", this.a);
			var obj2 = {
				a: 3,
				baz: () => {
					console.log("baz", this.a);
				},
			};
			// 箭头函数不会创建自己的 this,它会捕获外层函数的 this
			obj2.baz(); // baz 2
		},
	};
	console.log("foo", this.a);
	obj1.bar(); // bar 2
}
foo(); // foo 1
相关推荐
@大迁世界3 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路11 分钟前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug15 分钟前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu1213817 分钟前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中38 分钟前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路42 分钟前
GDAL 实现矢量合并
前端
hxjhnct44 分钟前
React useContext的缺陷
前端·react.js·前端框架
冰暮流星1 小时前
javascript逻辑运算符
开发语言·javascript·ecmascript
前端 贾公子1 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端
菩提小狗1 小时前
Sqlmap双击运行脚本,双击直接打开。
前端·笔记·安全·web安全