乐趣区

关于javascript:this-之谜揭底从浅入深理解-JavaScript-中的-this-关键字一

前言

系列首发于公众号『前端进阶圈』,若不想错过更多精彩内容,请“星标”一下,敬请关注公众号最新消息。

this 之谜揭底:从浅入深了解 JavaScript 中的 this 关键字(一)

为什么要用 this

  • 思考以下代码:

    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, 我是 KYLE
    speak.call(you); // Hello, 我是 READER
  • 这段代码再不同的上下文对象 (me 和 you) 中重复使用函数 identify() 和 speak(), 不必针对每个对象编写不同版本的函数。
  • 若不应用 this 如下代码:

    function identify(context) {return context.name.toUpperCase();
    }
    
    function speak(context) {var greeting = "Hello, I'm " + identify( context);
    console.log(greeting);
    }
    
    identify(you); // READER
    speak(me); //hello, 我是 KYLE

打消对 this 的误会

  • 在解释下 this 到底是如何工作的,首先必须打消对 this 的错误认识。

    指向本身

  • 为什么须要从函数外部援用函数本身呢?

    • 最常见的起因是递归。
  • 其实 this 并不像咱们所想的那样指向函数自身。
  • 思考以下代码:

    function foo(num) {console.log( "foo:" + num);
    
     // 记录 foo 被调用的次数
    this.count++;
    }
    
    foo.count = 0;
    
    var i;
    
    for (i=0; i<10; i++) {if (i > 5) {foo( i);
    }
    }
    // foo: 6
    // foo: 7
    // foo: 8
    // foo: 9
    
    // foo 被调用了多少次?console.log(foo.count); // 这里会输入多少次呢?
  • 先思考,后查看
    <details>
    <summary> 查看答案 </summary>
    <pre>
function foo(num) {console.log( "foo:" + num);
// 记录 foo 被调用的次数
    this.count++;
}
foo.count = 0;
var i;
for (i=0; i<10; i++) {if (i > 5) {foo( i);
    }
}
// foo: 6
// foo: 7
// foo: 8
// foo: 9
// foo 被调用了多少次?console.log(foo.count); // 0
// 为什么会输入 0 呢?// 从字面意思来看,下面的函数执行了 4 此,理当来说,foo.count 应该是 4 才对。

</pre>
</details>

  • 当执行 foo.count = 0; 时,确实向函数对象 foo 中增加了一个属性 count, 然而函数外部代码中 this.count 中的 this 并不是指向那个函数对象,尽管属性名雷同,跟对象却并不相同,困惑随之产生。
  • 如果你会有“如果我减少的 count 属性和预期的不一样,那我减少的是那个 count?”纳闷。实际上,如果你读过之前的文章,就会发现这段代码会隐式地创立一个全局变量 count。它的值为 NaN。如果你发现为什么是这么个奇怪的后果,那你必定会有“为什么它的值是 NaN, 而不是其余值?”的纳闷。(原理参考:https://mp.weixin.qq.com/s/H1gpn0vfmUwrglMZwk2gzw)
  • 当然也有办法对上述代码进行躲避:

    function foo(num) {console.log( "foo:" + num);
    
     // 记录 foo 被调用的次数
    data.count++;
    }
    
    var data = {count: 0};
    
    var i;
    
    for (i=0; i<10; i++) {if (i > 5) {foo( i);
    }
    }
    // foo: 6
    // foo: 7
    // foo: 8
    // foo: 9
    
    // foo 被调用了多少次?console.log(data.count); // 4
  • 尽管从某种角度来说,解决了问题,但疏忽了真正的问题——无奈了解 this 的含意和工作原理,上述代码而是返回了舒服区——词法作用域。
  • 下面提到的如果匿名函数须要援用本身,除了 this 还有曾经被废除的 arguments.callee 来援用以后正在运行的函数对象。
  • 对于上述提到的代码,更进阶的形式就是应用 foo 标识符来代替 this 来援用函数对象,如下代码:

    function foo(num) {console.log( "foo:" + num);
    
    // 记录 foo 被调用的次数
    foo.count++;
    }
    foo.count=0
    var i;
    
    for (i=0; i<10; i++) {if (i > 5) {foo( i);
    }
    }
    // foo: 6
    // foo: 7
    // foo: 8
    // foo: 9
    
    // foo 被调用了多少次?console.log(foo.count); // 4
  • 这种解决形式仍然躲避了 this 问题,并且齐全依赖于变量 foo 的词法作用域。
  • 更进阶的形式是强制 this 指向 foo 函数对象, 应用 call, bind, apply 关键字来实现。

    function foo(num) {console.log( "foo:" + num);
    
     // 记录 foo 被调用的次数
     // 留神,在以后的调用形式下(参见下方代码),this 的确指向 foo
    this.count++;
    }
    
    foo.count = 0;
    
    var i;
    
    for (i=0; i<10; i++) {if (i > 5) {// 应用 call(..) 能够确保 this 指向函数对象 foo 自身
        foo.call(foo, i);
    }
    }
    // foo: 6
    // foo: 7
    // foo: 8
    // foo: 9
    
    // foo 被调用了多少次?console.log(foo.count); // 4

它的作用域

  • 常见的误会:this 指向函数的作用域,其实在某种状况下是正确的,但在其余状况下是谬误的。
  • 其实,this 在任何状况下都不指向函数的词法作用域。
  • 考虑一下代码:

    function foo() {
    var a = 2;
    this.bar();}
    
    function bar() {console.log( this.a);
    }
    
    foo();
    
    // 这段代码你一共能发现几处谬误?并且报错后会抛出什么?
  • 先思考,后查看
    <details>
    <summary> 查看答案 </summary>
    <pre>
function foo() {
    var a = 2;
    this.bar();
    // bar(); // 以后形式会依据词法作用域的规定来查找 bar() 办法,并且调用它
}
function bar() {console.log(this.a); // ReferenceError: a is not defined
    // console.log(a); // 这种形式也不行,因为函数会创立一个块作用域,所以无奈通过 bar 的作用域拜访到下层 foo 作用域。}
foo(); // TypeError: this.bar is not a function

</pre>
</details>

  • 首先,这段代码试图通过 this.bar() 来援用 bar() 函数。这是相对不可能胜利的,咱们之后会解释起因。调用 bar() 最天然的办法是省略后面的 this,间接应用词法援用标识符。
  • 此外,编写这段代码的开发者还试图应用 this 联通 foo() 和 bar() 的词法作用域,从而让 bar() 能够拜访 foo() 作用域里的变量 a。这是不可能实现的,你不能应用 this 来援用一个词法作用域外部的货色。

this 到底是什么

  • 说了这么多,那 this 到底是一个什么样的机制呢?

    • 之前咱们说过 this 是在运行时进行绑定的,而不是在编写时绑定的,它的上下文取决于函数调用时的各种条件。
    • this 的绑定和函数申明的地位没有任何关系,只取决于函数的调用形式。
  • 当一个函数被调用是,会创立一个执行上下文,这个执行上下文汇总会蕴含函数在哪里被调用 (也就是调用栈),函数的调用办法,传入的参数等信息。而 this 就是这样一个属性,会在函数执行的过程中被用到。

小结

  • 学习 this 的第一步要明确 this 既不指向函数本身也不指向函数的词法作用域。
  • this 实际上是在函数被调用时产生的绑定,它指向什么齐全取决于函数在哪里被调用。

特殊字符形容:

  1. 问题标注 Q:(question)
  2. 答案标注 R:(result)
  3. 注意事项规范:A:(attention matters)
  4. 详情形容标注:D:(detail info)
  5. 总结标注:S:(summary)
  6. 剖析标注:Ana:(analysis)
  7. 提醒标注:T:(tips)

    往期举荐:

  8. 前端面试实录 HTML 篇
  9. 前端面试实录 CSS 篇
  10. JS 如何判断一个元素是否在可视区域内?
  11. Vue2、3 生命周期及作用?
  12. 排序算法:QuickSort
  13. 箭头函数与一般函数的区别?
  14. 这是你了解的 CSS 选择器权重吗?
  15. JS 中 call, apply, bind 概念、用法、区别及实现?
  16. 罕用位运算办法?
  17. Vue 数据监听 Object.definedProperty() 办法的实现
  18. 为什么 0.1+ 0.2 != 0.3,如何让其相等?
  19. 聊聊对 this 的了解?
  20. JavaScript 为什么要进行变量晋升,它导致了什么问题?

    最初:

  21. 欢送关注『前端进阶圈』公众号,一起摸索学习前端技术 ……
  22. 公众号回复 加群 或 扫码, 即可退出前端交流学习群,一起高兴摸鱼和学习 ……
  23. 公众号回复 加好友,即可添加为好友
退出移动版