[JS] You Don't Know JavaScript [this & Object Prototypes] - this Or That?

前言

this是JavaScript中最令人困惑的关键字之一,他会自动在每个function作用域中生成,但是this实际上是指向什麽对很多资深的JS开发人员来说也是困惑的,对於this这个机制而言并没有这麽先进,但是开发者常常会将它复杂化或令人困惑的方式引用他,如果没有对於this的充分了解那麽this这个关键字将会变得跟魔术一样神奇。


Why this?

如果this是个令人困惑的机制,那麽为什麽JavaScript会需要用到它?在了解他之前我们应该知道他的价值。

function identify() {
	return this.name.toUpperCase();
}
function speak() {
	var greeting = "Hello, I'm " + identify.call( this );
	console.log( greeting );
}

var me = {
	name: "Kyle"
};
var you = {
	name: "Reader"
};

identify.call( me ); // KYLE
identify.call( you ); // READER
speak.call( me ); // Hello, I'm KYLE
speak.call( you ); // Hello, I'm READER

上面的程序中允许identify(...)speak(...)重复使用meyouobject,而不是每个object都需要自己的function。

如果不使用this,你也可以将object显性的传递给function。

function identify(context) {
	return context.name.toUpperCase();
}
function speak(context) {
	var greeting = "Hello, I'm " + identify( context );
	console.log( greeting );
}

var me = {
	name: "Kyle"
};
var you = {
	name: "Reader"
};

identify( you ); // READER
speak( me ); // Hello, I'm KYLE

以上面的例子来说,可以看到虽然this不是必要的,但是他可以更优雅的传递object,从而使API个简洁并易於重新使用;而随着模式更加复杂,将上下文使用显性参数传递的方式会比隐式使用this的传递更加混乱。


Confusions

开发者对於this如果是使用字面上的意思去解释他的时候往往都会造成混乱,最常见的有两种假设,但他们都是错的。

itself

第一种假设是this代表着函数本身,当你使用递归(在函式内部呼叫function)或一个在首次调用可以解除绑定的事件处理时会在自身函式中呼叫function。

对於刚接触JS的开发者,他们认为由於function是object(所有function在JS中都是object)所以可以在函数调用之间储存状态(function属性中的值),虽然这是可行的但是他的功能有限。

function foo(num) {
	console.log( "foo: " + num );
	this.count++; // keep track of how many times `foo` is called
}

foo.count = 0;
for (var i=0; i<10; i++) {
	if (i > 5) {
		foo( i );
	}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

// how many times was `foo` called?
console.log( foo.count ); // 0 -- WTF?

上面的程序中,即使我们对於foo(...)调用了四次但是foo.count依然是0,虽然我们确实有在foo的属性中加入了count,但是因为this并不是只向该function的object,就算属性名称相同但是所指向的object不同所以依然是0。

function foo(num) {
	console.log( "foo: " + num );

	// keep track of how many times `foo` is called
	// Note: `this` IS actually `foo` now, based on
	// how `foo` is called (see below)
	this.count++;
}

foo.count = 0;

var i;

for (i=0; i<10; i++) {
	if (i > 5) {
		// using `call(..)`, we ensure the `this`
		// points at the function object (`foo`) itself
		foo.call( foo, i );
	}
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9

console.log( foo.count ); // 4

把原本的程序更改为上面就可以顺利的访问到foo中的count了,你可能会感到困惑,但是我们会在後面详细地做解释。

Its Scope

还有一个主要的误解,this是function的作用域,从某种意义上来说他是对的,但另一种意义上来说他是完全错误的。

对於scope来说他确实有点像object,在他的范围中具有每个可用的标示符的属性,但是JS引擎无法访问到scope object。

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

foo(); //undefined

在上面的程序中使用this.bar()来调用bar(...)虽然在这里行得通(之後的章节会解释)但是其实是错误的,对於呼叫bar(...)最自然的使用方法就是直接引用他的标示符,但是写上面这个程序的开发者希望将bar(...)foo(...)的lexcial scope连结再一起以便bar(...)可以访问到a,这样的连结是不可能的,不能够通过使用this来连结两个lexcial scope。


What's this?

对於this来说,他不是取决於开发者时间所绑定而是运行时绑定,他是基於上下文取决於函数调用的条件,这个绑定与函式声明的位置无关而是与函式调用的方式有关。

当呼叫一个function时会创建一个启动纪录(执行上下文),这个纪录包含有关从何处调用function的信息、呼叫此function的方式、传递的参数等等;this会在function执行的期间使用纪录的属性之一。

参考文献:
You Don't Know JavaScript


<<:  ProxmoxVE PVE VM 安装 ChromeOS

>>:  Mobile Number Tracker Online

[1][STM32G4系列] GPIO笔记 - CubeMX GPIO整理与应用

前言 GPIO为最基础应用也最广泛之功能,本篇主要纪录GPIO中所学习到的知识。 以STM32G43...

学习Python纪录Day30

参赛心得: 今天是铁人赛最後一天,会参加铁人赛是因为学校做也得要求,虽然学习的内容不算难,主要的文章...

[28] 30 天从 Swift 学会 Objective-C:Swift friendly 的 API Swift name

在 Objective-C 与 Swift 的命名有明显的区别,虽然 Swift interface...

[Day14] - Virtual DOM (一) - diff 演算法

今天原本想要介绍一下 Virtual DOM 结果忙一下 , 就没时间研究 diff 演算法了 di...

Unity与Photon的新手相遇旅途 | Day1-环境安装

这是我第一次参加铁人赛,如果内容有不清楚或有误欢迎大家指正,影片皆是我自学Unity并且规划的教学内...