JS 之 彻底搞懂this指向

目录

一、为什么使用this

1. 不使用this

2. 使用this

二、this的绑定规则

1. 栗子

2. 绑定方式一 : 默认绑定

01 - 栗子壹 : 普通函数调用

02 - 栗子贰 : 函数中调用另一个函数

03 - 栗子叁 : 函数作为参数调用

04 - 栗子肆 : 函数被赋值调用

05 - 小结

3. 绑定方式二 : 隐式绑定

01 - 栗子壹 : 对象调用函数

02 - 栗子壹 : 对象调用函数 ( 进阶 )

4. 绑定方式三 : 显式绑定

call 和 apply

        栗子

bind 

        01 - 栗子壹 : bind绑定this

        02 - 栗子贰 : bind绑定this并传入参数

        03 - 栗子叁 : bind后再使用call或apply指定this

5. 绑定方式四 : new绑定

栗子

三、this的规则优先级

1. 默认规则的优先级最低

2. 隐式绑定优先级高于默认绑定

3. 显示绑定优先级高于隐式绑定

01 - 栗子壹 : apply 和 call 高于隐式绑定

02 - 栗子贰 : bind高于隐式绑定

03 - 栗子叁 : bind的优先级高于apply、call 

4. new绑定优先级高于bind绑定

01 - 栗子壹 : new 高于隐式绑定

02 - 栗子贰 : new 不能和call、apply一起使用

03 - 栗子贰 : new 高于 bind 绑定

5. 总结一下

四、箭头函数中的this

1. 栗子一 

2. 栗子二

3. 栗子三

五、面试题

面试题一

面试题二

面试题三

面试题四

总结 : 注意箭头函数中没有this,注意连续点语法调用


一、为什么使用this

在某些函数或者方法的编写中,this可以让我们以更加优雅的方式来引用对象

1. 不使用this

const stu = {
  name: 'star',
  age: 18,
  eat() {
    console.log(stu.name + 'eating');
  },
  run() {
    console.log(stu.name + 'runing');
  }
};

那么在对象中定义的方法,需要获取自身的属性时,需要通过对象名来调用

一旦对象改了名字,函数中的名称也需要随之改变 

2. 使用this

const newStu = {
  name: 'star',
  age: 18,
  eat() {
    console.log(this.name + 'eating');
  },
  run() {
    console.log(this.name + 'runing');
  }
};

当通过对象去调用对应的方法时,而且不论对象名称怎么改变,方法中的this就指向该对象,可以让代码变得更加优雅些 


二、this的绑定规则

1. 栗子

同一个函数,不同的调用方式,函数中的this的指向都不一样

<script>
  function foo() {
    console.log(this);
  }

  // 第一种方式
  foo(); // window

  const obj = {
    name: 'star',
    foo: foo
  };
  // 第二种方式
  obj.foo(); // {name:star,foo:function}

  // 第三种方式
  foo.call(123); // Number{123}
</script>

所以可以得出 : 

  1. 1.函数在调用时,JavaScript会默认给this绑定一个值
  2. 2.this的绑定和定义的位置(编写的位置)没有关系
  3. 3.this的绑定和调用方式以及调用的位置 有关系
  4. 4.this是在运行时被绑定

2. 绑定方式一 : 默认绑定

默认绑定 : 独立函数调用

  • 函数被直接调用,并没有进行任何的对象关联
  • 通常默认绑定时,在非严格模式下,this指向window,严格模式下,this指向undefined

01 - 栗子壹 : 普通函数调用

<script>
  function foo() {
    console.log(this);
  }

  // 直接调用
  foo(); // window
</script>

02 - 栗子贰 : 函数中调用另一个函数

<script>
  function foo() {
    console.log(this); // window
  }
  function bar() {
    console.log(this); // window
    // 直接调用
    foo();
  }
  // 直接调用
  bar();
</script>

03 - 栗子叁 : 函数作为参数调用

<script>
  function foo() {
    console.log(this); // window
  }
  function bar(fn) {
    // 直接调用
    fn();
  }
  bar(foo);
</script>

04 - 栗子肆 : 函数被赋值调用

<script>
  function foo() {
    console.log(this); // window
  }
  const obj = {
    name: 'star',
    foo: foo
  };
  // 把obj属性中的foo函数的地址赋值给fn
  const fn = obj.foo;
  // 直接调用,所以还是window
  fn();
</script>

05 - 小结

在真正函数调用的位置,都是直接调用该函数,所以都是指向window 

3. 绑定方式二 : 隐式绑定

隐式绑定 : 通过对象进行调用

01 - 栗子壹 : 对象调用函数

<script>
  function foo() {
    console.log(this); // obj对象 {name: 'star', foo: ƒ}
  }
  const obj = {
    name: 'star',
    foo: foo
  };
  // 通过对象调用该方法
  obj.foo();
</script>

02 - 栗子壹 : 对象调用函数 ( 进阶 )

<script>
  function foo() {
    console.log(this);
  }
  const obj = {
    name: 'star',
    foo: foo
  };
  const obj2 = {
    name: 'coder',
    obj: obj,
    foo: obj.foo
  };
  // 通过对象调用该方法
  obj2.foo();  // obj2对象 {name: 'coder', obj: {…}, foo: ƒ}
  
  // 注意,这里相当于obj2叫obj去调用foo函数,本质是obj在调用,别搞错啦
  obj2.obj.foo(); // obj对象 {name: 'star', foo: ƒ}
</script>

4. 绑定方式三 : 显式绑定

注 : 如果在显示绑定中,我们传入一个null或者undefined,那么这个显示绑定会被忽略,使用默认规则

call 和 apply

可以直接指定函数内部的this是谁,通过call和apply方法

call和apply : 

  • 第一个参数是相同的,要求传入一个对象
    • 这个对象就是给this准备的
    • 在调用这个函数时,会将this绑定到这个传入的对象上
  • 后面的参数,apply为数组,call为参数列表
    • func.apply ( thisArgs, [arg1, arg2, arg3] )
    • func.call ( thisArgs, arg1, arg2, arg3 )

        栗子

<script>
  function foo(num1, num2, num3, num4) {
    console.log(this, num1, num2, num3, num4);
  }
  // 显示绑定this为 { name: 'star' } ,并且传入参数,参数是数组格式
  foo.apply({ name: 'star' }, [1, 2, 3, 4]); // {name: 'star'} 1 2 3 4

  // 显示绑定this为 { age: 18 },并且传入参数,参数是一个一个的
  foo.call({ age: 18 }, 5, 6, 7, 8); // {age: 18} 5 6 7 8

  foo.call(null) // window
</script>

bind 

如果希望一个函数总是显示的绑定到一个对象上,使用bind方法

  • bind 方法创建一个新的绑定函数(bound function,BF)
  • 在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用

        01 - 栗子壹 : bind绑定this

<script>
  function foo() {
    console.log(this); //{name: 'star'}
  }
  // 使用bind绑定this,返回一个新函数,新函数中的this就是指定的这个对象
  const bar = foo.bind({ name: 'star' });
  bar();
</script>

        02 - 栗子贰 : bind绑定this并传入参数

<script>
  function foo(arg1, arg2, arg3) {
    console.log(this, arg1, arg2, arg3); //{name: 'star'} 'coder' 18 1.88
  }
  // 使用bind绑定this,同时传入参数
  const bar = foo.bind({ name: 'star' }, 'coder', 18);
  // 这里也可以传入参数,不过会放在后面,也可以全部的参数放在调用的时候执行
  bar(1.88);
</script>

        03 - 栗子叁 : bind后再使用call或apply指定this

bind的优先级高于call和apply

<script>
  function foo() {
    console.log(this);
  }
  // 使用bind绑定this,返回一个新函数,新函数中的this就是指定的这个对象
  const bar = foo.bind({ name: 'star' });
  // 更改不了
  bar.call(123); // {name: 'star'}
  // 更改不了
  bar.apply({ name: 'coder' }); // {name: 'star'}
</script>

5. 绑定方式四 : new绑定

使用new关键字来调用函数是,会执行如下的操作:

  1. 创建一个全新的对象
  2. 这个新对象会被执行prototype连接,对象的proto会指向该构造函数的prototype
  3. 这个新对象会绑定到函数调用的this上(this的绑定在这个步骤完成)
  4. 运行函数内的代码
  5. 如果函数没有返回其他对象,表达式会返回这个新对象

栗子

<script>
  function Foo(name, age) {
    this.name = name;
    this.age = age;
    console.log(this); // Foo {name: 'star', age: 18}
  }
  const obj = new Foo('star', 18);
  console.log(obj); // Foo {name: 'star', age: 18}

  // obj接收的就是构造函数中的this,obj就指向this
</script>

三、this的规则优先级

1. 默认规则的优先级最低

默认规则的优先级是最低的,因为存在其他规则时,就会通过其他规则的方式来绑定this 

2. 隐式绑定优先级高于默认绑定

<script>
  function foo() {
    console.log(this);
  }
  const obj = {
    foo: foo
  };
  obj.foo(); // {foo: ƒ}
</script>

3. 显示绑定优先级高于隐式绑定

01 - 栗子壹 : apply 和 call 高于隐式绑定

<script>
  function foo() {
    console.log(this);
  }
  const obj = {
    foo: foo
  };

  // 既有隐式绑定和显示绑定,显示优先级高
  obj.foo.apply({ name: 'star' }); // {name: 'star'}
  obj.foo.call({ name: 'coder' }); // {name: 'coder'}
</script>

02 - 栗子贰 : bind高于隐式绑定

<script>
  function foo() {
    console.log(this);
  }
  const bar = foo.bind({ name: 'star' });
  const obj = {
    bar: bar
  };

  // 既有隐式绑定和显示绑定,显示优先级高
  obj.bar(); // {name: 'star'}
</script>

03 - 栗子叁 : bind的优先级高于apply、call 

<script>
  function foo() {
    console.log(this);
  }
  const bar = foo.bind({ name: 'bar' });
  bar.call({ name: 'call' }); // {name: 'bar'}
  bar.apply({ name: 'apply' }); // {name: 'bar'}
</script>

4. new绑定优先级高于bind绑定

  • new绑定和call、apply是不允许同时使用的,所以不存在谁的优先级更高
  • new绑定可以和bind一起使用,new绑定优先级更高

01 - 栗子壹 : new 高于隐式绑定

<script>
  const obj = {
    bar: function () {
      console.log(this);
    }
  };
  new obj.bar(); // {} this为空对象,因为new中会创建空对象
</script>

02 - 栗子贰 : new 不能和call、apply一起使用

<script>
  function foo() {
    console.log(this);
  }
  // 报错,不可以一起使用
  new foo.apply({ name: 'star' });
  new foo.call({ name: 'star' });
</script>

03 - 栗子贰 : new 高于 bind 绑定

<script>
  function foo() {
    console.log(this);
  }
  const bar = foo.bind({ name: 'bar' });
  const obj = {
    bar: bar
  };
  // new 的优先级 高于 bind优先级
  new obj.bar(); // {} this为空对象,因为new中会创建空对象
</script>

5. 总结一下

 new 优先级 > bind 优先级 > apply | call 优先级 > 隐式绑定  > 默认绑定


四、箭头函数中的this

箭头函数不使用this的四种标准规则(也就是不绑定this),而是根据外层作用域来决定this

1. 栗子一 

<script>
  const bar = () => {
    // 因为箭头函数中没有this,所以回去上层作用域中找this,上层作用域是全局,所以该this指向全局
    console.log(this, this === window); // window  true
  };
  bar();
</script>

2. 栗子二

<script>
  const obj = {
    name: 'star',
    foo: function () {
      // 2. 这里因为是被obj.foo() 这样调用的,所以这里的this指向obj
      const bar = () => {
        // 1. 这里没有this,会去上层作用域中找this
        console.log(this);
      };

      return bar;
    }
  };
  const fn = obj.foo();
  fn(); // {name: 'star', foo: ƒ}
</script>

3. 栗子三

<script>
  // 注,这里是对象,是没有作用域的
  const obj = {
    name: 'star',
    foo: () => {
      // 2. 因为这里也没有this,继续往上找,找到了全局,所以是window
      const bar = () => {
        // 1. 这里没有this,会去上层作用域中找this
        console.log(this);
      };

      return bar;
    }
  };
  const fn = obj.foo();
  fn(); // window
</script>

五、面试题

面试题一

<script>
  // 在顶层作用域中,使用var声明的变量,会自动挂载到window对象中
  var name = 'window';
  const person = {
    name: 'person',
    sayName: function () {
      console.log(this.name);
    }
  };
  function sayName() {
    const sss = person.sayName;
    // 1. 直接调用,所以是window
    sss(); // window

    // 2. 隐式调用,指向该对象
    person.sayName(); // person

    // 3. 和2是一样的意思
    (person.sayName)(); // person

    // 4. 如果是表达式,会把结果执行,相当于是直接调用,所以也是window
    (b = person.sayName)() // window
  }
  sayName()
</script>

面试题二

<script>
  var name = 'window';
  var person1 = {
    name: 'persion1',
    foo1: function () {
      console.log(this.name);
    },
    foo2: () => {
      console.log(this.name);
    },
    foo3: function () {
      return function () {
        console.log(this.name);
      };
    },
    foo4() {
      return () => {
        console.log(this.name);
      };
    }
  };
  var person2 = { name: 'person2' };

  person1.foo1(); // 不用怀疑,就是指向persion1
  person1.foo1.call(person2); // person2

  person1.foo2(); // 箭头函数没有this,找到全局,所以是window
  person1.foo2.call(person2); //  箭头函数没有this,绑定无用,还是往上找,找到全局,还是window

  person1.foo3()(); // 把返回的函数执行了,相当于直接调用,所以this指向window
  person1.foo3.call(person2)(); // person2执行了foo3,然后又是直接调用了返回的函数,所以this还是指向window
  person1.foo3().call(person2); // person1执行了foo3, 然后让persion2执行内部的函数,所以this指向person2

  person1.foo4()(); // person1执行了foo4,然后直接调用了内部函数,因为内部函数没有this,所以往上层中寻找,找到persion1,所以this为persion1
  person1.foo4.call(person2)(); // person2执行了foo4,然后直接调用了内部函数,往上层中寻找,找到persion2,所以this为persion2
  person1.foo4().call(person2); // person1执行了foo4,因为内部函数为箭头函数,所以绑定无用,往上层中寻找,找到persion1,所以this为persion1
</script>

面试题三

<script>
  var name = 'window';
  function Person(name) {
    this.name = name;
    this.foo1 = function () {
      console.log(this.name);
    };
    this.foo2 = () => console.log(this.name);
    this.foo3 = function () {
      return function () {
        console.log(this.name);
      };
    };
    this.foo4 = function () {
      return () => {
        console.log(this.name);
      };
    };
  }

  const person1 = new Person('person1');
  const person2 = new Person('person2');

  person1.foo1(); // persion1
  person1.foo1.call(person2); // person2

  person1.foo2(); // 箭头函数中没有this,所以往上层作用域中找,找到构造函数,此时this指向person1
  person1.foo2.call(person2); // 箭头函数中没有this,所以绑定无用,还是由person1调用,所以this指向person1

  person1.foo3()(); // 直接调用,指向window
  person1.foo3.call(person2)(); // 直接调用,指向window
  person1.foo3().call(person2); // person1.foo3后,返回的函数由person2调用,所以this指向person2

  person1.foo4()(); // 箭头函数中没有this,所以往上层作用域中找,找到person1
  person1.foo4.call(person2)(); // person2调用foo4,然后直接执行箭头函数,所以this指向person2
  person1.foo4().call(person2); // 箭头函数中没有this,所以绑定无用,所以还是指向person1
</script>

面试题四

<script>
  var name = 'window';
  function Person(name) {
    this.name = name;
    this.obj = {
      name: 'obj',
      foo1: function () {
        return function () {
          console.log(this.name);
        };
      },
      foo2: function () {
        return () => {
          console.log(this.name);
        };
      }
    };
  }
  const person1 = new Person('person1');
  const person2 = new Person('person2');

  person1.obj.foo1()(); // 相当于直接执行,所以指向window
  person1.obj.foo1.call(person2)(); // 相当于直接执行,所以指向window
  person1.obj.foo1().call(person2); // 相当于person2调用的内部函数,所以this指向person2

  person1.obj.foo2()(); // 箭头函数内部this往上层作用域找,因为是obj调用的,所以指向obj
  person1.obj.foo2.call(person2)(); // 箭头函数内部this往上层作用域找,找到person2
  person1.obj.foo2().call(person2); // 箭头函数绑定this无用,还是往上层作用域找,找到obj
</script>

总结 : 注意箭头函数中没有this,注意连续点语法调用

猜你喜欢

转载自blog.csdn.net/a15297701931/article/details/125702311