原来 JS 是这样的 - 关于 this (2)

显然,我们的 bind() 内返回了一个函数,其使用 apply() 将 this 绑定到了其参数 _obj 上。所以我们调用 alias() 时 this 总是绑定在了我们指定的 obj 上了。

当然,实际由于这个需求很常见,所以我们可以直接使用 Function.prototype.bind() 而不再需要编写自己的辅助函数了。其用法类似 var alias = printA.bind(obj)。

另外,由于很多时候我们需要指定 this 所要绑定的对象,一些较新的内置函数和第三方 API 都会提供一个可选的参数供显式指定这个对象(称之为 上下文 (context)),比如 forEach() 。

3. new 绑定

JavaScript 在遇到 new 操作符时也会改变 this 所绑定的对象,会将其绑定到所新创建的对象上。这个规则看上去的确没有什么问题,但 new 操作符的工作方式却可能和你想象的不太一样。

在很多种面向对象语言中你很可能已经见过 someVariable = new SomeClass(); 的这种写法,new 会调用对应类的构造函数,然而 JavaScript 并没有“类”的概念,就连某个对象内的函数其实也只是持有的函数的引用而已。实际上 JavaScript 的 new 操作符只是调用了某个函数并执行了一些其它操作而已,于是在 JavaScript 语境下,“构造函数” 其实就是被 new 操作符所调用的一个普通函数,而 new 操作符所作的 “其它操作” 就包含了修改对 this 的绑定。

使用 new 来调用构造函数(或者说,发生构造调用时),所执行的操作实际是这些:

创建一个全新的对象(这是为什么我们称之为 “构造函数” 或 “构造调用”)

对这个新对象执行 [[Prototype]] 链接(不再本次讨论范围内)

将新对象绑定到 this (嗯哼)

将这个新对象作为 new 表达式的返回值(如果函数没有返回其它对象的话)

function setA(a) { this.a = a; } var bar = new setA(61); console.log(bar.a); // 61

上面例子中的 new foo(61); 即通过调用函数 setA 创建了一个新的对象,由于这时执行了对 this 的绑定,故这里 this.a 就是 bar 上的 a 了。另外,这也是又一个可以说明 this 所指向的对象是随运行时的调用情况而改变的不错的例子。

4. 默认绑定

如果你到现在还没有忘掉第一个例子,会发现第一个例子并不符合上述的三种绑定规则。默认绑定则就是在不符合上面所说的规则时所会执行的绑定了,而说是默认不如说这是一种 fallback 策列,而默认绑定的例子实际也就是在此情况将 this 绑定到全局对象的策列了。

你肯定见过很多做法来避免污染全局对象,而默认绑定显然在有时不是我们想要的,于是在严格模式下,就不再会允许将 this 绑定到全局对象了(而是绑定到了 undefined )。不过当混合使用严格模式和非严格模式时则会有一些别的规则:

a = 61; function foo() { "use strict"; console.log(this.a); // 位于严格模式范围内,不允许默认绑定发生。 } foo(); // TypeError: this is undefined function bar() { console.log(this.a); // 并非位于严格模式范围内。 } (function IIFE() { "use strict"; // 尽管是严格模式范围,但该范围内并未发生默认绑定,绑定发生在对严格模式范围外的函数调用内。 bar(); // 61 })();

上面的例子中, foo() 中的 this.a 由于位于严格模式下,于是并不能发生默认绑定。而下方的 bar() 包含的 this.a 由于并非位于严格模式范围内,故不受严格模式的影响,换句话说,严格模式并非影响整个运行时内的调用栈范围。

值得一提的是,同样作为 “fallback” ,有时候我们希望在出现问题时将 this 绑定到我们希望的别的变量上而不是全局对象或 undefined 对象上,我们可以通过自己实现这种策列来达到这样的目的,这种做法有些时候被称为 。

优先级和例外,以及箭头函数

我们知道了 this 绑定的这些规则,接下来需要关心的就是他们之间的优先级关系了。他们的关系是 new 绑定 > 显式绑定 > 隐式绑定 > 默认绑定 。这个规则看上去并不使人意外,故这里不再讨论这个顺序相关的细节了。而此处则简单讨论几个 “试图破坏这个规则” 所产生的 “意外”。

回到 显式绑定 的范畴,想象我们传入的 this 所要绑定的对象参数,如果是 null 会怎样?我们会发现它最终会使用默认绑定,尽管这看上去或许也不算是个例外——我们不能绑定到 null 上,故只能采取 fallback 方案,也就是默认绑定了。

虽然这种做法看上去很奇怪,但我们实际使用场景也会使用给 apply() 或类似函数传入 null 的做法,而这种做法通常表示我们并不需要关心 this ,例如假定存在一个 function teji(a, b),我们可以使用类似 teji.apply(null, [61, 616]) 的写法来达到将参数展开以供函数使用的目的。或是类似 var wolf = teji.bind(null, 61); wolf(616); 这样使用 bind 来达到为函数进行柯里化 (Currying,即预先设置一些参数的值)的目的。

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

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