大一菜鸡学妹的JavaScript学习之路(一)this绑定规则

200 阅读5分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

来自大一的学妹,刚进实验室,打算一步一步记录自己的学习总结。
“横看成岭侧成峰,远近高低各不同;不知庐山真面目,只缘身在此山中。” --不要以为看透了世界,我们身在世界,要不断探索知识,去无限接近构建出这个世界的模样

  • 发现:同样的函数却不同的this

  •     定义一个函数
  • function foo() {    console.log(this); }
    
  •     调用函数三种方式
  • //1.直接调用/默认调用 
    foo(); //指向widows  
    
    //2.通过对象调用
    var obj = { name : "Hi" } 
    obj.aaa = foo; 
    obj.aaa(); //this指向obj对象 
    
    //3.通过call/apply调用 
    foo.call("abc"); //指向String{"abc"}对象
    

this指向

  1. 函数在调用时,JavaScript会默认给this绑定一个值
  2. this的绑定与定义的位置无关。
  3. this的绑定与调用方式以及调用的位置有关
  4. this是在运行时被绑定的。

this绑定规则(主要4则)

  1. 默认绑定

    1. 独立函数调用时,使用默认绑定。
    2. 独立函数调用:没有绑定到任何的对象上面.
    3. //1.普通的函数独立调用
      //2.函数定义在对象中,但是独立调用
      foo();
      //3.高阶函数
      var obj = {
         bar: function() {
            console.log("bar:",this)
         }
      }
      function test(fn) {
         fn();
      }
      test(obj.bar);
      //都是指向windows,因为只和调用位置有关
      
      //3.严格模式下--
      "use strict"
      //独立调用的函数this指向undefined
      
  2. 隐式绑定

    1. 它的调用位置中,是通过某个对象发起函数的调用(系统自动绑定对象)
    2. 在调用的对象内部有一个对函数的引用
    3.  //隐式绑定
       function foo() {
         console.log("foo函数:",this)
       }
       var obj = {
         foo: foo
       }
       obj.foo()//this指向obj
       
       var bar = obj.foo;//只是引用了foo函数
       bar(); //this指向windows对象,隐式绑定丢失
      
  3. 显式绑定

    1. 与隐式相对,执行函数,并且强制this指向obj对象

    2. 不希望对象内部包含该函数的引用&&又希望强制调用

      •   var obj = {
           name: "why"
         }
         function foo() {
           console.log("foo函数:",this)
         } 
        //call 明确地绑定
        foo.call(obj)
        foo.call(123)
        foo.call("abc")
        /*
           {name: "why"}
           //如果传入基本类型,会返回包装类对象
           Number{123}
           String{'abc'}
        */
        
    3. 使用call、apply(帮助绑定this对象)

      • //apply
        //第一行参数:绑定this【无区别】
        //第二个参数:以数组形式传入额外的实参
        foo.apply("apply".[30,"fish"])
        //call
        //第一个参数:绑定this【无区别】
        //参数列表:后续参数以多参数形式传递
        foo.call("call",30,"fish")
        
    4. 使用bind【硬绑定】(调用某个函数总是绑定这个this对象)

      • 使用bind方法,bind()方法创建一个绑定函数
      • var bar = foo.bind(obj)//新函数
        bar()//this->obj
        
        //bind函数的其他参数
        var bar = foo.bind(obj,arg1,arg2)
        bar(arg3)
        
  4. new绑定

    1. JS中的函数可以当作一个构造函数来使用(使用new关键字)
    2. 创建新的空对象
    3. 将this指向这个空对象
    4. 执行函数体中的代码
    5. 没有显示返回其他对象时,默认返回这个新对象
    6. function foo() {
         console.log(this)
         this.name = "why"//往空对象加东西
      }
      new foo()
      /*this指向空对象
        foo{name: 'why'}
      */
      

内置函数的调用绑定

  • JS的内置函数,或者第三方库中的内置函数
  • 这些函数会要求我们传入另外一个函数
  • 我们不会显示的调用这些函数,而是JS内部或第三方库内部会帮助我们执行

this绑定规则优先级

  • 默认绑定的优先级最低

  • 显示绑定高于隐式绑定

    • var bar = foo.bind('aaa')
      var obj = {
         name: "why";
         baz: bar
      }
      obj.baz()
      
  • new绑定优先级高于隐式绑定

    • var obj = {
        name: "why"
        foo: function() {
          console.log("foo:",this)
          console.log("foo:",this === obj)
        }
      }
      new obj.foo()
      /*
        foo{}
        false
      */
      
  • new绑定优先级高于bind

    • new不可以和apply/call一起使用,可以和bind一起使用。
    • var bindFn = foo.bind("aaa")
      new bindFn()
      /*
        foo()
      */
      
  • bind优先级高于apply/call

    • var bindFn = foo.bind("aaa")
      bindFn.apply('bbb')
      //String{"aaa"}
      

new > bind > apply/call > 显式 > 隐式 > 默认

this规则之外

  1. 忽略显式绑定,使用默认规则

    1. //特殊情况--没有包装类对象--指向windows
      foo.call(undefined)
      foo.apply(null)
      //指向windows
      //严格模式下--
      //undefined
      /*严格模式下--
        显式绑定中的包装类型->基本数据类型
        String{"abc"}
        abc
      */
      
  2. 隐式绑定丢失(解决方法)【用硬绑定】

    1. function foo() {
        console.log(this.a);
      }
      var obj = {
        a:2
      };
      var bar = function(){
        foo.call(obj);
      };
      bar();//2
      setTimeout(bar,100);//2
      bar.call(window);//2
      //硬绑定不能再修改它的this
      //因为硬绑定很常用,所以ES5提供了内置方法bind
      
  3. 间接函数引用,使用默认绑定规则

    1. var obj1 = {
        name: 'obj1',
        foo: function() {
          console.log("foo:",this)
        }
      }
      var obj2 = {
        name: "obj2"
      };//{}紧接着(),要在之间加分号;
      (obj2.foo = obj1.foo)()
      //this指向windows
      
  4. 箭头函数arrow function

    1. 箭头函数不会绑定this、arguments属性
    2. 箭头函数不能作为构造函数来使用(不能和new一起使用)
    3. //之前的方式
      function foo1() {}
      var foo2 = function(name,age){
        console.log("函数体代码")
        console.log(name, age)
      }
      //2.箭头函数 完整写法
      var foo3 = (name,age) => {
         console.log("箭头函数的函数体")
         console.log(name,age)
      }
      //3.forEach和setTimeout
      //4.箭头函数简写
      //-1.如果箭头函数只有一个参数,那么()可以省略
      var newNums = nums.filter(item => {
         return item % 2 ===0
      })
      //-2.如果函数执行体只有一行代码,那么可以省略大括号
      //并且这行代码的返回值会作为整个函数的返回值
      names.forEach(item => console.log(item))
      nums.filter(item => true)
      //-3.如果默认返回值是一个对象,那么这个对象必须加()
      //注意:在react中我会经常使用redux
      var arrFn = () => ({ name: "why"})
      console.log(arrFn())
      
  • [练习]箭头函数实现nums的多有偶数平方和

    • var nums = [20, 30, 11, 15, 111]
      var result = nums.filter(item => item % 2 === 0)
                       .map(item => item * item) 
                       .reduce((prevValue, item) => prevValue + item) 
      //1300
      
  1. 箭头函数的this使用(没有绑定)

    1. //1.普通函数中有this的标识符
      //2.箭头函数中,没有this
      var bar = () => {
        var message = "hello"
        console.log("bar:",this)
      }
      bar.apply("aaa")//windows
      //3.this的查找原因
      var obj = {
        name:"obj"
        foo:() => {
          var bar = ()=>{
            console.log("bar",this)
          }//箭头函数没有this,于是去外层作用域找【是在定义的时候,的父级作用域】
          return bar//作用域是代码块才有,不是对象
        }//所以找到最后,找到全局作用域
      }
      var fn = obj.foo() 
      fn.apply("bbb")
      
  2. 箭头函数中this的应用