this的简单了解与指向

在面试中或者在学习中我们都会接触到,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表达式中的函数调用会自动返回这个对象

举个列子:

   <script>
      function Mother(tip) {
        this.tip = tip;
      }
      var son = new Mother("tip");
    </script>

1、创建一个son 对象

2、son对象会和Mother的[[prototype]]连接:

son._proto_ =Mother.prototype

3、新对象和函数调用的this会绑定起来

Mother.call(son,"tip")

4、执行构造函数中的代码:

son.tip=tip

5、如果函数没有指定返回值,则返回这个新对象

上面一些内容就是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])

image-20210618215533051

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>

image-20210618215842176

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>

image-20210618220443791

注意bind在ES5中已经提出了

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值