当前位置: 代码迷 >> 综合 >> JS 全面理解this
  详细解决方案

JS 全面理解this

热度:28   发布时间:2024-02-10 04:26:50.0

this 关键字是javascript最复杂的机制之一

this是什么

当一个函数被调用时,会创建一个活动记录(执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。而this就是这个记录的一个属性,可以在函数执行过程中使用。

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的绑定规则

找到调用位置后,判断需要应用下面四条规则中的哪一条。

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

默认绑定

可以把这条规则看作是无法应用其他规则时的默认规则。

首先要介绍最常用的函数调用类型:独立函数调用。

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

为什么这里是this指向全局对象呢?

因为foo()是直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,所以this会指向全局对象。

有个细节:在严格模式下调用foo()不影响默认绑定,但如果foo函数内使用了严格模式,则此时this会指向undefined。

    function foo() {"use strict";console.log(this.a);}var a = 2;foo(); // TypeError: Cannot read property 'a' of undefined
    function foo() {console.log(this.a);}var a = 2;(function () {"use strict";foo();})(); //2

隐式绑定

该规则需要考虑的是调用位置是否有上下文对象,或者说是否被某个对象拥有或包含。

如下,因为调用位置使用了obj上下文来引用函数,所以可以理解为函数被调用时obj对象“拥有”或者“包含”它。在foo()被调用时,它的前面加上了对obj的引用,所以当函数引用有上下文对象时,隐式绑定会把函数调用中的this绑定到这个上下文对象上。所以此时,this.a和obj.a等价。

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

隐式丢失

一个常见的this绑定问题就是,有时候被隐式绑定的函数会丢失绑定对象,此时它会应用默认绑定,从而this绑定到了全局对象或undefined上(取决于是否为严格模式)。

如下,虽然bar是obj.foo的一个引用,但它实际上引用的是foo函数本身,所以此时的bar()是不带任何修饰的函数调用,因此应用了默认绑定。

    function foo() {console.log(this.a);}var obj = {a: 2,foo: foo};var bar = obj.foo;var a = 3;bar(); // 3

同理,这个例子的原理跟上一个例子一样。

    function foo() {console.log(this.a);}function doFoo(func) {func(); // 调用位置}var obj = {a: 2,foo: foo};var a = 3;doFoo(obj.foo); // 3

显示绑定

未完待续,先去吃饭

  相关解决方案