1、为什么需要this?

  • 在常见的编程语言中,几乎都有this这个关键字(Objective-C中使用的是self),但是JavaScript中的this和常见的面向对象语 言中的this不太一样:
    • 常见面向对象的编程语言中,比如Java、C++、Swift、Dart等等一系列语言中,this通常只会出现在类的方法中。
    • 也就是你需要有一个类,类中的方法(特别是实例方法)中,this代表的是当前调用对象。
    • 但是JavaScript中的this更加灵活,无论是它出现的位置还是它代表的含义。
  • 我们来看一下编写一个obj的对象,有this和没有this的区别

image.png

2、this指向什么呢?

  • 我们先说一个最简单的,this在全局作用于下指向什么?
    • 这个问题非常容易回答,在浏览器中测试就是指向window
    • image.png
  • 但是,开发中很少直接在全局作用于下去使用this,通常都是在函数中使用
    • 所有的函数在被调用时,都会创建一个执行上下文:
    • 这个上下文中记录着函数的调用栈、AO对象等;
    • this也是其中的一条记录;

2.1 规则一:默认绑定

  • 什么情况下使用默认绑定呢?独立函数调用。
    • 独立的函数调用我们可以理解成函数没有被绑定到某个对象上进行调用;
  • 我们通过几个案例来看一下,常见的默认绑定

image.png

2.2 规则二:隐式绑定

  • 另外一种比较常见的调用方式是通过某个对象进行调用的:
    • 也就是它的调用位置中,是通过某个对象发起的函数调用。
  • 我们通过几个案例来看一下,常见的默认绑定

image.png

3.3 规则三:显示绑定

  • 隐式绑定有一个前提条件:
    • 必须在调用的对象内部有一个对函数的引用(比如一个属性);
    • 如果没有这样的引用,在进行调用时,会报找不到该函数的错误;
    • 正是通过这个引用,间接的将this绑定到了这个对象上;
  • 如果我们不希望在 对象内部 包含这个函数的引用,同时又希望在这个对象上进行强制调用,该怎么做呢?
    • JavaScript所有的函数都可以使用call和apply方法(这个和Prototype有关)。
      • 它们两个的区别这里不再展开;
      • 其实非常简单,第一个参数是相同的,后面的参数,apply为数组,call为参数列表;
    • 这两个函数的第一个参数都要求是一个对象,这个对象的作用是什么呢?就是给this准备的。
    • 在调用这个函数时,会将this绑定到这个传入的对象上。
  • 因为上面的过程,我们明确的绑定了this指向的对象,所以称之为 显示绑定

3.4 call、apply、bind

  • 通过call或者apply绑定this对象
    • 显示绑定后,this就会明确的指向绑定的对象
    • image.png
  • 如果我们希望一个函数总是显示的绑定到一个对象上,可以怎么做呢?
  • image.png

3.5 内置函数的绑定思考

  • 有些时候,我们会调用一些JavaScript的内置函数,或者一些第三方库中的内置函数。
    • 这些内置函数会要求我们传入另外一个函数;
    • 我们自己并不会显示的调用这些函数,而且JavaScript内部或者第三方库内部会帮助我们执行;
    • 这些函数中的this又是如何绑定的呢?
  • setTimeout、数组的forEach、div的点击

image.png

3.6 new绑定

  • JavaScript中的函数可以当做一个类的构造函数来使用,也就是使用new关键字。
  • 使用new关键字来调用函数是,会执行如下的操作:

1.创建一个全新的对象;
2.这个新对象会被执行prototype连接;
3.这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成);
4.如果函数没有返回其他对象,表达式会返回这个新对象;
image.png

3.7 规则优先级

  • 如果一个函数调用位置应用了多 条规则,优先级谁更高呢?
  • 1.默认规则的优先级最低
    • 毫无疑问,默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this
  • 2.显示绑定优先级高于隐式绑定 ```javascript var obj = { name: “obj”, foo: function() {
    1. console.log(this)
    } }

obj.foo()

// 1.call/apply的显示绑定高于隐式绑定 obj.foo.apply(‘abc’) obj.foo.call(‘abc’)

// 2.bind的优先级高于隐式绑定 var bar = obj.foo.bind(“cba”) bar()

// 3.更明显的比较 function foo() { console.log(this) }

var obj = { name: “obj”, foo: foo.bind(“aaa”) }

obj.foo()

  1. - **3.new绑定优先级高于隐式绑定
  2. **
  3. ```javascript
  4. var obj = {
  5. name: "obj",
  6. foo: function() {
  7. console.log(this)
  8. }
  9. }
  10. // new的优先级高于隐式绑定
  11. var f = new obj.foo()
  • 4.new绑定优先级高于bind
    • new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
    • new绑定可以和bind一起使用,new绑定优先级更高 ```javascript // 结论: new关键字不能和apply/call一起来使用

// new的优先级高于bind function foo() { console.log(this) }

var bar = foo.bind(“aaa”)

var obj = new bar()

// new绑定 > 显示绑定(apply/call/bind) > 隐式绑定(obj.foo()) > 默认绑定(独立函数调用)

  1. <a name="ACFv8"></a>
  2. ## 3.8 this规则之外—忽略显示绑定
  3. - 我们谈到的规则已经足以应付平时的开发,但是总有一些语法,超出了我们的规则之外。(神话故事和动漫中总是
  4. - 有类似这样的人物)
  5. - 如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则:
  6. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21707377/1638615720198-922f5cd2-5d39-4d62-a37a-524d17099232.png#clientId=ubd2b384d-7c59-4&from=paste&id=uf2ca6b3e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=248&originWidth=343&originalType=binary&ratio=1&size=49916&status=done&style=none&taskId=u6ec72538-89f5-4b15-bd55-13eb5382b83)
  7. <a name="R8Vlv"></a>
  8. ## 3.9 this规则之外—间接函数引用
  9. - 另外一种情况,创建一个函数的 间接引用,这种情况使用默认绑定规则。
  10. - 赋值(obj2.foo = obj1.foo)的结果是foo函数;
  11. - foo函数被直接调用,那么是默认绑定;
  12. - ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21707377/1638615771369-51ef0db0-b801-4030-a55c-f9c6ec62c7d5.png#clientId=ubd2b384d-7c59-4&from=paste&id=ud33f5d0f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=263&originWidth=340&originalType=binary&ratio=1&size=49664&status=done&style=none&taskId=u8df17a89-c219-4d52-8681-c95c5a38388)
  13. <a name="zazOk"></a>
  14. ## 3.10 this规则之外—ES6箭头函数
  15. - 在ES6中新增一个非常好用的函数类型:箭头函数
  16. - 这里不再具体介绍箭头函数的用法,可以自行学习。
  17. - 箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this。
  18. - 我们来看一个模拟网络请求的案例:
  19. - 这里我使用setTimeout来模拟网络请求,请求到数据后如何可以存放到data中呢?
  20. - 我们需要拿到obj对象,设置data;
  21. - 但是直接拿到的this是window,我们需要在外层定义:var _this = this
  22. - 在setTimeout的回调函数中使用_this就代表了obj对象
  23. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21707377/1638615847643-b0c54d56-31c2-49d4-869d-09f18207ce1d.png#clientId=ubd2b384d-7c59-4&from=paste&id=u38d86287&margin=%5Bobject%20Object%5D&name=image.png&originHeight=285&originWidth=436&originalType=binary&ratio=1&size=99367&status=done&style=none&taskId=uc4bad9c0-916a-4c05-9f5d-5792a8e9a89)
  24. <a name="Lsy7j"></a>
  25. ## 3.11 ES6箭头函数this
  26. - 之前的代码在ES6之前是我们最常用的方式,从ES6开始,我们会使用箭头函数:
  27. - 为什么在setTimeout的回调函数中可以直接使用this呢?
  28. - 因为箭头函数并不绑定this对象,那么this引用就会从上层作用于中找到对应的this
  29. ![image.png](https://cdn.nlark.com/yuque/0/2021/png/21707377/1638615902301-a7ee6dae-e0d4-4768-a37a-10100c4db3ad.png#clientId=ubd2b384d-7c59-4&from=paste&height=200&id=ud83004d8&margin=%5Bobject%20Object%5D&name=image.png&originHeight=399&originWidth=1464&originalType=binary&ratio=1&size=355967&status=done&style=none&taskId=ub6adbf3d-e5a4-4401-8007-91a840ae333&width=732)
  30. <a name="kVnfj"></a>
  31. # 4、 this面试题
  32. <a name="Onv31"></a>
  33. ## 4.1 面试题1
  34. ```javascript
  35. var name = "window";
  36. var person = {
  37. name: "person",
  38. sayName: function () {
  39. console.log(this.name);
  40. }
  41. };
  42. function sayName() {
  43. var sss = person.sayName;
  44. sss(); // window: 独立函数调用
  45. person.sayName(); // person: 隐式调用
  46. (person.sayName)(); // person: 隐式调用
  47. (b = person.sayName)(); // window: 赋值表达式(独立函数调用)
  48. }
  49. sayName();

4.2 面试题2

  1. var name = 'window'
  2. var person1 = {
  3. name: 'person1',
  4. foo1: function () {
  5. console.log(this.name)
  6. },
  7. foo2: () => console.log(this.name),
  8. foo3: function () {
  9. return function () {
  10. console.log(this.name)
  11. }
  12. },
  13. foo4: function () {
  14. return () => {
  15. console.log(this.name)
  16. }
  17. }
  18. }
  19. var person2 = { name: 'person2' }
  20. person1.foo1(); // person1(隐式绑定)
  21. person1.foo1.call(person2); // person2(显示绑定优先级大于隐式绑定)
  22. person1.foo2(); // window(不绑定作用域,上层作用域是全局)
  23. person1.foo2.call(person2); // window
  24. person1.foo3()(); // window(独立函数调用)
  25. person1.foo3.call(person2)(); // window(独立函数调用)
  26. person1.foo3().call(person2); // person2(最终调用返回函数式, 使用的是显示绑定)
  27. person1.foo4()(); // person1(箭头函数不绑定this, 上层作用域this是person1)
  28. person1.foo4.call(person2)(); // person2(上层作用域被显示的绑定了一个person2)
  29. person1.foo4().call(person2); // person1(上层找到person1)

4.3 面试题3

  1. var name = 'window'
  2. function Person (name) {
  3. this.name = name
  4. this.foo1 = function () {
  5. console.log(this.name)
  6. },
  7. this.foo2 = () => console.log(this.name),
  8. this.foo3 = function () {
  9. return function () {
  10. console.log(this.name)
  11. }
  12. },
  13. this.foo4 = function () {
  14. return () => {
  15. console.log(this.name)
  16. }
  17. }
  18. }
  19. var person1 = new Person('person1')
  20. var person2 = new Person('person2')
  21. person1.foo1() // person1
  22. person1.foo1.call(person2) // person2(显示高于隐式绑定)
  23. person1.foo2() // person1 (上层作用域中的this是person1)
  24. person1.foo2.call(person2) // person1 (上层作用域中的this是person1)
  25. person1.foo3()() // window(独立函数调用)
  26. person1.foo3.call(person2)() // window
  27. person1.foo3().call(person2) // person2
  28. person1.foo4()() // person1
  29. person1.foo4.call(person2)() // person2
  30. person1.foo4().call(person2) // person1
  31. var obj = {
  32. name: "obj",
  33. foo: function() {
  34. }
  35. }

4.4 面试题4

  1. var name = 'window'
  2. function Person (name) {
  3. this.name = name
  4. this.obj = {
  5. name: 'obj',
  6. foo1: function () {
  7. return function () {
  8. console.log(this.name)
  9. }
  10. },
  11. foo2: function () {
  12. return () => {
  13. console.log(this.name)
  14. }
  15. }
  16. }
  17. }
  18. var person1 = new Person('person1')
  19. var person2 = new Person('person2')
  20. person1.obj.foo1()() // window
  21. person1.obj.foo1.call(person2)() // window
  22. person1.obj.foo1().call(person2) // person2
  23. person1.obj.foo2()() // obj
  24. person1.obj.foo2.call(person2)() // person2
  25. person1.obj.foo2().call(person2) // obj
  26. //
  27. // 上层作用域的理解
  28. // var obj = {
  29. // name: "obj",
  30. // foo: function() {
  31. // // 上层作用域是全局
  32. // }
  33. // }
  34. // function Student() {
  35. // this.foo = function() {
  36. // }
  37. // }