欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

this的简单了解与指向

程序员文章站 2022-06-17 12:21:17
...

在面试中或者在学习中我们都会接触到,this,可是这到底是什么意思? 有什么作用? 下面做一些简单的介绍。

this的误区

误区一:this指向自身

this并不是我们想的那样指向函数的本身,如下列子:

 function foo(num) {
        console.log("num", num);
        this.count++;
      }
      foo.count = 0;
      for (let i = 0; i < 10; i++) {
        if (i > 5) {
          foo(i);
        }
      }
      console.log("foo", foo.count); // 这里输出foo.count为0

按照想法是,this指向的是foo函数,在for循环中,if执行了四次,按理来说foo.count输出因该为4,这样想是错误的,我们先解释怎么让代码正常输出,如下列:

 function foo(num) {
        console.log("num", num);
        data.count++;
      }
      var data = {
        count: 0,
      };
      for (let i = 0; i < 10; i++) {
        if (i > 5) {
          foo(i);
        }
      }
      console.log("foo", data.count);

或者:

 function foo(num) {
        console.log("num", num);
        foo.count++;
      }
      foo.count = 0;
      for (let i = 0; i < 10; i++) {
        if (i > 5) {
          foo(i);
        }
      }
      console.log("foo", foo.count);

这其实是用词法作用域去解决问题

误区二: this的作用域

我们通常会把this的作用域看为指向函数的作用域,这在某种情况下是错误的,this在任何情况下都不能指向函数的词法作用域,如下列:

 <script>
      function foo() {
        var a = 2;
        this.bar();
      }
      function bar() {
        console.log("输出", this.a);
      }

      foo;
    </script>

这里的输出是undefind

这里对常见的误区做出简单的分析,值得注意的是this不是在编译的时候被绑定的,而是在运行的时候被绑定的,this的绑定和函数的声明的位置没有任何的关系,只取决于函数的调用方式,简单地说就是this在函数调用的时候发生,它的指向取决于函数在哪里被调用。

this的绑定规则:

1、默认绑定

默认绑定这里只说非严格模式下的列子,在这里this的绑定指向的是windos(全局变量),在之前的笔记中,var声明的变量会是全局变量,结合起来如下列:

<script>
      function foo() {
        let a = 2;
        console.log("输出", this.a);
      }
      var a = 10;
      foo();
    </script> // 输出的是10

至于为什么不是2,上面介绍了,this的指向不是指向函数自身,是10的原因当然就是规则中的默认绑定

严格模式下的默认绑定指向全局变量,非严格模式下指向undefinde

2、隐式绑定

默认绑定绑定的是全局变量往windos哪里靠,而隐式绑定我理解的是看作用域和执行上下文,下面用列子理解下:

 <script>
      var tom = {
        age: 16,
        speak: foo,
      };
      function foo() {
        console.log("年龄", this.age);
      }
      tom.speak();
    </script>  

输出 年龄,16

这里我们思考下为什么foo()函数中的this不指向windos,回忆下我们说过,this指向不指向所在的函数内,简单的说就是谁调用,this就指向谁!

再举出一个列子:

<script>
      function foo() {
        console.log("年龄", this.age);
      }
      var jack = {
        age: 34,
        speak: foo,
      };
      var tom = {
        age: 12,
        jack: jack,
      };

      tom.jack.speak();
    </script>

这里更加清楚的描述了this的指向问题,即谁调用指向谁,值得注意的是,jack必须定义在tom之上,那如果tom定义在jack之上会发生上面?为什么?

简单想想,是不是或报错? 说jack这个值undefined对吧,原因如下:

1、为undefined是因为var具有变量提升

2、变量提升后,赋值的位置不变,即Jack是在tom后赋值的,所以自然tom先调用的话这里就卡死了

上面两种方法都是隐式绑定,但是隐式绑定会出现this丢失问题,举个列子:

<script>
      function foo() {
        console.log(this.age);
      }
      var jack = {
        age: 34,
        speak: foo,
      };
      var ming = jack.speak;
      var age = 100;
      ming();
    </script>

这是输出的是100,原因如下

var ming =jack.speak

这里ming实际上是对foo的调用,ming没有携带任何的修饰符,所以这里应用了默认绑定。

直观可能不正确的理解就是:

var ming是定义了一个全局作用域,jack.speak中的this指向指向了ming,所以指向了windos。

还有用新建的函数调用、函数的回调函数调用都会使得this丢失

新建函数调用:

<script>
      function foo() {
        console.log(this.age);
      }
      function dooFn(fn) {
        fn();
      }
      var jack = {
        age: 34,
        speak: foo,
      };
      var age = 100;
      dooFn(jack.speak); //输出100
    </script>

​ 回调函数调用:

    <script>
      function foo() {
        console.log(this.age);
      }
      var jack = {
        age: 34,
        speak: foo,
      };
      var age = 100;
      setTimeout(jack.speak, 1000);  //输出100
    </script>
3、 显示绑定

说到this显示绑定可能没什么映像,但是apply、call、bind肯定是记忆犹新,毕竟这是前端新手刚入门的噩梦,至少我这个菜鸡是的。

是的,apply、call、bind(es5提出的),能强制改变函数中的this指向,就是显示绑定了,不再准寻谁调用,this就指向谁了。

举个例子:

<script>
      function foo() {
        console.log(this.age);
      }
      var jack = {
        age: 34,
        speak: foo,
      };
      var age = 100;
      setTimeout(jack.speak.apply(jack), 1000); 输出34
    </script>

上面用call也是一样的,可以看出,就这样就强制把指向windos的this改为指向jack中了,是不是简单粗暴

显示绑定不能还是不能解决绑定丢失的问题,但是显示绑定中的硬绑定可以

1、硬绑定

下段代码简单了解下硬绑定:

 <script>
      function foo() {
        console.log(this.age);
      }
      var jack = {
        age: 34,
        speak: foo,
      };
      // 这段是硬绑定
      var bar = function () {
        foo.call(jack);
      };

      bar();
    </script>

这里可以看出foo的this改变是在bar函数内执行的,只要bar函数被调用,foo的this指向就会改变一次,这种就是简单的硬绑定,注意,bar硬绑定后,不允许再去修改绑定的this值

<script>
      function foo() {
        console.log(this.age);
      }
      var jack = {
        age: 34,
        speak: foo,
      };
      // 这段是硬绑定
      var bar = function () {
        foo.call(jack);
      };

      bar();

      bar.call(window);
      setTimeout(bar, 1000); 
    </script> 

上面的输出都是34,可以看出硬绑定后this不能被修改了

硬绑定的经典场景:

1、负责接受参数并返回值

 var bar = function () {
        foo.call(obj,arguments);
      };

2、 创建辅助函数并重复使用

关于显示绑定中的apply、call、bind我会单独记录一篇博客,这里不介绍了

4、new绑定

说起new绑定,肯定2第一时间注意new这个关键字,那么面试题中高发点就是,new的过程中发生了什么? 只去枯燥的背肯定记忆不深刻,这里从this的指向中简单的了解下

首先,用new关键字,那是使用了构造函数,但是在js中定义不一样,在js中构造函数只是一些使用new操作符时被调用的函数,它们并不会属于某个类,也不会实列化一个类。简单说就是被new操作符调用的普通函数而已,所以不存在构造函数而是对于这个函数的构造调用

下面是new过程发生的事:

1、创建(构造)一个全新的对象

2、这个new对象会被执行[[Prototype]]连接

3、这个new对象会绑定到函数调用的this

4、如果这个函数没有返回其他对象,则new表达式中的函数调用会自动返回这个对象

上面一些内容就是new绑定

this绑定的优先级

这里不上代码,直接上结论

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

this绑定列外

当让不是所有的列子都适用于这四个方法,下面举出一些列子:

1、把null、undefined作为this对象绑定在call、apply,在调用时就会忽略掉,实际上是对默认绑定的规则

 <script>
      function foo() {
        console.log(this.a);
      }
      var a = 2;
      foo.call(null);
    </script>

输出的是2

当让直接给null可能不太好,我们可以使用这样:

var b = Object.create(null)

2、间接使用的时候

理解为就是赋值的时候

this的总结

1、this的指向和所在的函数无关、和所在的函数作用域无关,谁调用了this,this就指向谁。

2、this中new调用,this指向绑定到新创建的对象。

3、由call、apply、bind绑定this,this的指向绑定到指定的对象中。

4、默认模式下(非严格模式)this指向全局windos中,严格指向undefined.

5、在进行硬绑定后,不能再去改变同一个绑定的this指向了。

6、在箭头函数中,不适用于上述的四条规则,它是根据当前的词法作用域来判断this,简单的说就是箭头函数会继承外层的函数来调用this,无论this绑定的是什么。

对显示绑定的扩展

上面我们对显示绑定简单的做了一些描述,它与call、apply、bind这三个字段有关,下面我们简单介绍下它的用法以及区别:

apply

用法:改变函数中的this指向。

xxx.apply(对象名,数组) 就是将xxx函数中的this指向指向对象名,数组中的元素依次与元素的参数对应

function fn(a, b) {
            console.log(this);
            console.log(a + b);
        }
        fn(1, 2)
        var obj = {
            name: 'tom'
        }
        fn.apply(obj, [3, 4])

this的简单了解与指向

call

用法:改变函数中的this指向问题,和apply的区别是第二个参数不为数组

xxx.call(对象名,参数1,参数2,参数3…) 就是将xxx函数中的this指向指向对象名,参数中的元素依次与元素的参数对应

<script>
        function fn(a, b) {
            console.log(this);	
            console.log(a + b);
        }
        fn(1, 2)
        var obj = {
            name: 'tom'
        }
        fn.call(obj, 3, 4)
    </script>

this的简单了解与指向

bind

用法:改变函数中的this指向问题,和call写法一样,不同的地方是bind返回的是一个函数,所以我们在调用的时候在进行传参

xxx.bind(对象)(参数1,参数2…)

 <script>
        function fn(a, b) {
            console.log(this);
            console.log(a + b);
        }
        fn(1, 2)
        var obj = {
            name: 'tom'
        }
        fn.bind(obj)(3, 4)
    </script>

this的简单了解与指向

注意bind在ES5中已经提出了