原来 JS 是这样的 - 关于 this

习惯了别的语言的思维习惯而不专门了解 JavaScript 的语言特性的话,难免踩到一些坑。 上一篇文章 中简单总结了关于 提升, 严格模式, 作用域 和 闭包 的几个常见问题,当然这仅仅是了解 JavaScript 的一个开始,这次则依然从另一个大多人都见过但很容易搞错的概念开始了解 JavaScript 的不同之处 —— this。

首先看这段代码:

function plusOne() { this.count++; console.log("plusOne get called, this.count: " + this.count); } plusOne.count = 0; plusOne(); // 改成 plusOne.call(plusOne); console.log("plusOne.count = " + plusOne.count); console.log("window.count = " + window.count);

我们知道 JavaScript 中所有的东西都是对象,那么很可能会把 this 自然的理解成当对象“身上”的属性,对于上面的代码(注意,并非严格模式下),我们为 plusOne 创建了一个属性 count 并试图通过在 plusOne() 中使用 this.count++ 改变它的值。然而如果实际执行这段代码(注:假设是在浏览器 F12 的 Console 中),得到的结果会是这样:

plusOne get called, this.count: NaN plusOne.count = 0 window.count = NaN

可以看出 plusOne() 中的 this.count 实际是 NaN,plusOne.count 却没有变化,并且 window.count 被创建了。而尝试按照上面代码中的注释那样把 plusOne() 改成 plusOne.call(plusOne) ,就会发现结果成了:

plusOne get called, this.count: 1 plusOne.count = 1 window.count = undefined

那么为什么会这样呢?

this 是运行时到某个对象的绑定

首先分析上面的输出结果就可以知道,在我们使用 plusOne(); 时,this 并不是像想象的那样是一个指向 plusOne 的变量,而我们可以发现在浏览器中执行时 windows.count 被意外创建了,显然在这个情况,这里的 this.count 无意中在全局范围(即浏览器的 window)创建了 count ,而很明显 this 在此时 this 即绑定到了全局对象。至于值 NaN ,则是简单的 undefined++ 的结果。

而当我们改用 plusOne.call() 时,可以看出这时候 this.count 才真正改变了 plusOne.count 的值,此时 this 即指向 plusOne 对象了。这是因为 改变了 this 所绑定的值。

于是我们知道了 this 并非指向自身,并且其所绑定到的对象是变化的,那么,规则是什么呢?

1. 隐式绑定

其实在仔细研究 this 的规则之,我并没有被 JavaScript 的 this 规则坑到过,而我通常使用的写法像是这样:

function printA() { console.log(this.a); } var obj = { a: 616, print: printA }; obj.print(); // 输出为 616

在这段代码中,我们使用了 obj.print() 来调用被引用到 obj.print 的 printA() 函数。这时候 printA() 所处于被 obj 调用的上下文,故此时 this 被绑定到了 obj。这种根据被调用情况将 this 绑定到对应对象上的情况我们称之为 隐式绑定。而称之为隐式绑定的原因则是,我们是在一个对象(上例中为 obj)内包含一个指向函数的属性(上例中的 obj.print),并通过这个属性间接(隐式)引用函数。

当然这里其实有一个比较有意思的地方,在了解它之前,我一直认为这里 printA() 就直接属于了 obj,并认为 obj.print 就总表示被 obj 所拥有的 printA(),即只要我们使用 obj.print ,this 就一定是绑定到 obj 的。当然实际并非如此:

function printA() { console.log(this.a); } var obj = { a: 616, print: printA }; var alias = obj.print; alias(); // 输出为 undefined function callFunc(fn) { fn(); } callFunc(obj.print); // 输出为 undefined

这个例子中我们把 obj.print 赋值给了 alias ,本意为给 obj.print 起的别名,却发现结果得到了 undefined 。实际上,obj.print 持有的只是对 printA() 的引用而已,而 alias 也自然实际是指向 printA() 了。此时 alias() 显然不是由 obj 调用的,故并不能遵循隐式绑定的规则,此时发生的情况也就和本文最初的例子一样了。至于 callFunc() ,我们传递进来的 obj.print 实际也是 printA() ,故它也不是被 obj 调用的,所以结果也是 undefined 了。

2. 显式绑定

回顾最初的例子,我们使用了 把 plusOne() 函数被调用时的 this 绑定到了 plusOne 对象。如果你查阅 MDN ,会发现它和 都可以显式的把 this 绑定到向 call() 或 apply() 所提供的第一个参数上。我们把这种 this 绑定规则称为显式绑定。

回顾我们在隐式绑定中后面提到的例子,我们试图使用 var alias = obj.print 来创建别名以达到得到一个关联着 obj 作为上下文的函数 printA(),但是失败了。我们则可以使用显式绑定创建一个辅助函数解决这个问题:

function printA(extra) { console.log(this.a + extra); } function bind(fn, _obj) { return function() { return fn.apply(_obj, arguments); }; } var obj = { a: 616 }; var alias = bind(printA, obj); console.log(alias(" CodingCat!"));

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpsjwj.html