javascript复习笔记(3)

作者: Juliiii | 来源:发表于2017-10-01 22:46 被阅读0次

    this

    上次讲了闭包,其实我感觉我上次没讲什么,闭包的确神秘,但是又不是很复杂,需要长篇大论去解释。刚好,我看到了js中的另一个重点,this。this也是会让初学者头疼的一个知识点。有时候感觉莫名其妙,直觉上应该是这个答案,实际上却是错的。

    错误的认识

    • this不只在js中出现,java, c++ 这些强类型语言也是有this的,不过它们都是叫this, 本质是不太一样的。学过这些强类型语言的同学很容易就认为this是指向自己的。然而,这在js中,是不正确的。下面的代码会打脸。
    function fun (val) {
      this.a = val;
    }
    fun(0);
    fun.a++;
    console.log(fun.a);
    console.log(a);
    

    如果this是指向自己的话,那么现在fun.a应该是1咯,对吧。然而真的是这样么? 打开chrome, F12将代码拷贝在控制台看看结果,fun.a的输出是NaN,第二个log出却是0。傻眼了吧?好吧,其实当时我也是一脸懵逼。这个结果,告诉了我们js的this不如你想象的那样。这是你不知道的javaScript的this。

    • 除了认为this指向自己,《你不知道的javaScript》中也提到另一种错误的理解。this指向函数的作用域。详细点说,就是一个函数,它的this是指向它的父级作用域。嗯,好吧,我觉得这种理解很自然,很舒服。下面的代码,就是这种错误理解导致的错误写法。
    function a () {
      console.log(this.c);
    }
    function b() {
      let c = 1;
      this.a();
    }
    b(); // undefined
    
    

    其实上面的结果不会输出1,所以说明了这个理解也是错误的。

    this的正确使用姿势

    this的指向呢,其实一句话就概况了。

    this的绑定和函数声明的位置没有任何关系,只取决于函数的调用方式。

    其实this的指向就看谁调用了它吧,总结下来,有以下4种情况:

    1. 默认绑定
      默认绑定,我曾经也听到说成是直接函数调用。下面的代码就是默认绑定
    function fun () {
      console.log(this.a);
    }
    var a = 1;
    fun() // 1
    

    默认绑定的情况,函数中的this是绑定在window上,当然是在非严格模式下。在严格模式下,this是指向undefined, 上面的代码就会抛出一个错误。说回非严格模式下的默认绑定,由于this指向了window。所以, this.a其实就是window.a。那么自然输出了1。

    1. 隐式绑定
      当一个函数是某个对象的属性时,我们直接对象.函数名调用该函数时,该函数的this是指向该对象的。其实,我个人认为,默认绑定的情况下 fun() 等价于 window.fun(),这也正好解释了为什么默认绑定时,this是指向了全局对象。下面是隐式绑定的一个例子:
    let obj = {
      name: '123',
      func () {
        console.log(this.name);
      }
    }
    
    obj.func() // '123'
    

    注意:隐式绑定当使用不当时,会出现this的丢失现象。比如:

    // 情况一
    window.name = '1234';
    let obj = {
      name: '123',
      func () {
        console.log(this.name);
      }
    }
    func = obj.func;
    func(); // '1234'
    
    // 情况二
    window.name = '1234';
    let obj = {
      name: '123',
      func () {
        return function () {
          console.log(this.name); 
        }
      }
    }
    func = obj.func();
    func(); // '1234'
    
    // 情况三
    window.name = '1234';
    let obj = {
      name: '123',
      func () {
        console.log(this.name);
      }
    }
    
    setTimeout(obj.func, 1000); // '1234'
    

    情况一:不是隐式绑定了obj么,输出应该是123才对啊。结果却输出了1234。嗯,我们要根据代码来分析,this的指向要对比四种情况,然后去找到对应的情况来确定。func()很明显就是之前我们说的默认绑定,所以输出了全局对象中的a属性的值。
    情况二:情况二其实就是返回了一个函数,然后这个函数在全局作用域直接调用了,所以就是默认绑定,如果需要绑定原来的obj,可以用以下的办法:

    // 解法1
    window.name = '1234';
    let obj = {
      name: '123',
      func () {
        let that = this;
        return function () {
          console.log(that.name); 
        }
      }
    }
    func = obj.func();
    func(); // '123'
    
    // 解法2
    window.name = '1234';
    let obj = {
      name: '123',
      func () {
        return () => console.log(this.name); 
       }
     }
    func = obj.func();
    func(); // '123'
    
    // 解法3
    window.name = '1234';
    let obj = {
      name: '123',
      func () {
        return function () {
          console.log(this.name); 
        }.bind(this);
       }
     }
    func = obj.func();
    func(); // '123'
    

    情况三: setTimeout的第一个参数是一个函数,其实setTimeout的内部实现的伪代码应该是这样的

    function setTimeout (fn, delay) {
      // 等了 delay 毫秒
      fn()
    }
    

    所以其实也是变成了默认绑定嘛。

    1. 显式绑定
      显式绑定就是用js的call, apply, bind的这些函数来绑定this啦。
    window.name = 'window';
    let xiaoming = { name: 'xiaoming' };
    function bar() { console.log(this.name); }
    // call
    bar.call(xiaoming); // 'xiaoming';
    // apply
    bar.apply(xiaoming); // 'xiaoming';
    // bind
    bar.bind(xiaoming)(); // 'xiaoming';
    

    它们间的异同:

      • 它们三者可以用来改变函数this的指向, 第一个参数都是要指向的对象
      • apply 和 call 接受的参数不太一样,call只能一个一个传入参数,apply可以传入一个参数的数组
      • bind返回的是绑定后的函数,apply 和 call是绑定时同时执行。
    1. new 绑定
      在js中,我们使用new运算符时,其实,经过了下面的操作

    1、创建(或者说构造)一个全新的对象
    2、这个新对象会被执行[[原型]]连接
    3、这个新对象会被绑定到函数的this
    4、如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象

    综上,那么下面的代码,

    function fn (a) { this.a = a; }
    let a = new fn(1);
    console.log(a.a); // 1
    

    就是上面的四个步骤过一遍的结果了,this绑定到了的这个新对象a上。

    总结

    逼逼了这么多。。其实this的指向只要弄清楚这四种情况,绝大部分的情况都可以自己判断出来。其实还是会有些很奇葩的情况的,如果我没记错,但是根据我目前的开发经验,只要你或者你同事不作死,就不会写出那么奇葩的情况。如果说是面试题,当我没说。来来来,最后总结遍,四个方法就是:

    • 如果是new出来的对象,那么this指向该对象无误。
    • 如果有用显式绑定,那么this指向绑定的对象
    • 是否作为一个对象的属性来调用,是的话,this指向该对象。当然要注意this丢失的情况。
    • 如果上面都没有,那么默认绑定应该无误。

    多练习慢慢就会一眼就看出this的指向了,工多手熟。如果有误,请指出。还有祝大家国庆快乐!

    相关文章

      网友评论

        本文标题:javascript复习笔记(3)

        本文链接:https://www.haomeiwen.com/subject/rdcaextx.html