JavaScript之this指向

105 阅读4分钟

this关键字是JavaScript中最复杂的机制之一。它是一个很特别的关键字,被自动定义在所有函数函数的作用域中。

this是什么?

this并不是指向自身,也不是指向函数作用域。this是在运行时被绑定的,并不是在编写的时候绑定的,他的上下文取决于调用函数时的各种条件。this的绑定与函数声明的位置没有任何关系,只取决于函数的调用方式。 this的绑定规则主要有以下4种:

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

默认绑定

默认绑定可以看做是无法应用其他规则时候的默认规则。

    function sayHello() {
        console.log(`hello,my name is ${this.name}`);
    }
    var name = 'Bob';
    sayHello(); // hello,my name is Bob

调用sayHello()时,应用的是默认绑定规则。在非严格模式下this将会被绑定到全局对象中,严格模式下this将会被绑定的undefined中,将会报错(如下)。

    function sayHello() {
        'use strict';
        console.log(`hello,my name is ${this.name}`);
    }
    var name = 'Bob';
    sayHello();  //  Cannot read property 'name' of undefined

隐式绑定

函数的调用是在某个对象上触发的,调用位置存在着上下文对象。

    function sayHello() {
        console.log(`hello,my name is ${this.name}`);
    }
    var obj = {
        name: 'liu',
        sayHello: sayHello
    }
    obj.sayHello(); // hello,my name is liu

sayHello函数声明定义在obj外部,并不属于obj,但是在调用sayHello的时候是通过obj来进行调用的,掉用位置会把obj的上下文来引用函数,隐式绑定会把函数调用的this绑定在obj上下文中。其中对象属性链中只有最后一层会影响到调用位置,如下

    function sayHello() {
        console.log(`hello,my name is ${this.name}`);
    }
    var obj1  = {
        name: 'hao',
        sayHello: sayHello
    }   
    var obj = {
        name: 'liu',
        sayHello: sayHello,
        obj1: obj1
    }            
    obj.obj1.sayHello()  // hello,my name is hao                                                                                       

只有最后一层会影响this,如上就是obj1,this被绑定在了obj1中,所以this.name是obj1中的。 隐式绑定会经常发生绑定丢失的问题

    function sayHello() {
        console.log(`hello,my name is ${this.name}`);
    }
    var obj  = {
        name: 'liu',
        sayHello: sayHello
    }
    var name = 'ran';
    var Hello = obj.sayHello;
    Hello() // ran    

上述情况就属于绑定丢失,我们会下意识以为this指向的是obj,但是其实this指向的是window(全局对象)。这是因为Hello是obj.sayHello的一个引用,本质上是引用的了sayHello本身,故直接调用Hello()其实就是一个简单的函数调用,故采用了默认绑定。 除了上面那种情况外,隐式绑定丢失还发生在回调函数之中

    function sayHello() {
        console.log(this.name);
    }                          
    var obj1 = {
        name: 'liu',
        sayHello: function(){
            setTimeout(function(){
                console.log(this.name)
            })
        }
    }             
    var obj2 = {
        name: 'hao',
        sayHello: sayHello
    }                     
    var name = 'ran';
    obj1.sayHello() // ran
    setTimeout(obj2.sayHello, 100); // ran
    setTimeout(function(){
        obj2.sayHello() // hao
    },200) 

setTimeout函数可以简单理解成以下的伪代码

	setTimeout(fn,delay){ // fn = xxx
    	fn();
    }

obj1.sayHello()和setTimeout(obj2.sayHello, 100)就可以看成是普通的函数调用,故this都指向全局。最后一个输出是在函数钟调用obj2.seyHello,应用的就是隐式绑定规则。

显示绑定

显示绑定是通过call,apply,bind( call,apply二者只是传参方式不同,bind是返回一共函数该函数需要手动调用)的方式来进行绑定,显示的去指定this的指向。

    function sayHello() {
        console.log(this.name);
    }                          
    var obj1 = {
        name: 'liu',
        sayHello: sayHello
    }
    var name = 'hao';
    var Hello = obj1.sayHello;
    Hello.call(obj1) // liu

在未采用显示绑定之前直接调用Hello函数时this将会采取默认绑定。

    function sayHello() {
        console.log(this.name);
    }                          
    var obj1 = {
        name: 'liu',
        sayHello: sayHello
    }    
    var name = 'hao'        
    var Hello = obj1.sayHello;
    Hello.call(null)    //  hao

注:如果我们讲null或者undefined作为参数传入希纳是绑定之中,将会采取默认绑定规则

new绑定

在JavaScript中,使用new来调用函数,会自动发生以下的操作。

  • 创建(或者说构造)一共全新的对象
  • 这个新对象会被执行[[Prototype]]连接
  • 这个新对象会绑定到函数调用的this
  • 如果函数没有返回其他对象,那么new表达式中的函数会自动返回这个新对象
    function sayHello(name) {
        this.name = name;
    }                          
    var Hello = new sayHello('liu')
    console.log(Hello.name) // liu   
    
    // 特殊情况
    function fn() {
      this.name = 'james';
      return {};
    }
    let a = new fn;
    console.log(a.name); // undefined
    

绑定优先级

在一共函数调用中应用了多种绑定规则时我们可以通过优先级来判定this的指向问题。 new绑定 > 显示绑定 > 隐式绑定 > 默认绑定

箭头函数

箭头函数是ES6中新增的,它和普通函数存在一些去呗,箭头函数没有本身的this,它的this是继承外层的this。

  • 函数体内不绑定this,会捕获其所存在的上下文this作为本身的this
  • 不可以当作构造函数使用,会抛出错误
  • 无法用call,apply,bind去改变this指向
  • 箭头函数不能当做Generator函数,不能使用yield关键字
  • 箭头函数不绑定arguments,取而代之用rest参数...解决
  • 箭头函数没有原型属性
  var obj = {
      hi: function(){
          console.log(this);
          return ()=>{
              console.log(this);
          }
      },
      sayHi: function(){
          return function() {
              console.log(this);
              return ()=>{
                  console.log(this);
              }
          }
      },
      say: ()=>{
          console.log(this);
      }
  }
  let hi = obj.hi();  //输出obj对象
  hi();               //输出obj对象
  let sayHi = obj.sayHi();
  let fun1 = sayHi(); //输出window
  fun1();             //输出window
  obj.say();          //输出window

1、obj.hi()对应隐式绑定,this绑定在obj中,所有输出obj

2、hi是箭头函数,没有本身的this,去捕获上层代码中的this,就是obj.hi()中的this故也指向obj.

3、let sayHi = obj.sayHi();obj.sayHi是把一个函数引用给了sayHi,直接调用sayHi,就是隐式绑定丢失的情况,执行默认绑定,输出window。

4、fun1()是箭头函数没有自己的this,执行上层代码块中this,也就是sayHi中的this,故事window

5、obj.say()是调用箭头函数,由于obj中没有this,继续往上找,故是window