重新认识一下this

98 阅读4分钟

对于this指向的问题,永远不会过时,这次就好好聊一聊这个this这个小可爱


this的原理

 var obj = {
      fn: function() {
         console.log(this.a);
      },
      a:1
    };
    var fn = obj.fn;
    var a = 2
    // 写法一
    obj.fn();    // 1
    // 写法二
    fn();        // 2

对于这两种写法,我们知道,谁调用,this指向谁写法一是通过对象来调用这个方法,运行在obj这个环境,所以this指向这个对象,而写法二是直接调用这个方法,运行在全局环境,this指向winodw

但是为什么会这样,函数运行的环境是怎么怎么决定的?那就得看一下javascript这样处理的原理。

内存方式

  • js的对象数据储存在堆和栈中。栈的数据读取,写入速度快,但是存储的内容较少。堆的读取和写入速度慢,但是存储的内容多。
  • 栈中存储的基本数据类型的值本身,而堆中存储的这个对象。
  • 当创建一个对象时,会把数据存储在堆中,但是会在栈中存储一个地址值,这个地址指向在堆中存储的这些数据,而栈中也可以有多个不同的地址值指向同一个数据源。
var obj = {
      fn: function() { console.log(this.a); },
      a:1
    };
  var a = 2
  console.log(obj.a)   // 1
  console.log(a)       // 2
  • 当创建对象后,调用需要先从obj中拿到地址, 然后再读取fn。
  • 如果存储的是一个函数,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给fn属性

环境变量

  • 函数是一个单独的值,所以可以在任何环境下执行
  • 由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境。所以,this就出现了,它的设计目的就是在函数体内部,指函数当前的运行环境。
function fn() {
    console.log(this.x)
}
fn()    // undefined

上面代码中,函数体里面使用了变量x。该变量由运行环境提供。fn()运行在全局环境中,指向他本身的window对象,window对象中没有变量x,所以报undefined

 var obj = {
      fn: function() {
         console.log(this.a);
      },
      a:1
    };
    obj.fn()     // 1

当我们执行obj.fn时,是通过obj对象找到的fn执行,所以它的运行环境是obj对象,this指向obj

回到开头,一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。


注意
  • var具有变量提升,使用它创建的变量可以变成全局变量,可以直接通过window调用
  • es6新增的letconst创建的变量,可以单独变成一个块级作用域,没有添加到window上,不能通过全局的this调用。

this的指向


this说白了就是找拥有当前上下文的对象。

谁调用,指向谁

  • 在普通情况下就是全局,浏览器里就是window;在use strict严格模式下,就是undefined
function fnThis () {
  console.log(this)
}

function fnStrictThis () {
  'use strict'
  console.log(this)
}

fnThis() // window
fnStrictThis() // undefined
  • 谁调用,指向谁说白了就是找这个函数前面的点.
   var obj = {
       fn: function () {
         console.log(this);
       },
    };
    obj.fn()     // obj

直接上一道面试题

    var obj1 = {
      fn1() {
        console.log(this);
      },
    };
    var obj2 = {
      fn2() {
        return obj1.fn1();
      },
    };
    var obj3 = {
      fn3() {
        var fn = obj1.fn1;
        return fn();
      },
    };
    obj1.fn1(); // boss1
    obj2.fn2(); // ?
    obj3.fn3(); // ?

答案是obj1window

obj2.fn2里,使用this的函数是obj1.fn1,所以this绑定到obj1

obj3.fn3里,使用this的函数是fn,所以this绑定到window

如果this要绑定到obj2呢?

var obj1 = {
      fn1() {
        console.log(this);
      },
    };
    var obj2 = {
      fn2: obj1.fn1,
    };
    obj2.fn2()    // obj2,只要使用this的函数是obj2就可以

改变this的指向

Object.prototype.callObject.prototype.applyObject.prototype.bind可以改变this的指向。

function changeThis () {
  console.log(this)
}
var obj = { name: '张三' }

changeThis() // window
changeThis.call(obj) // obj
changeThis.apply(obj) // obj

Object.prototype.bind,他不但通过一个新函数来提供永久的绑定,还会覆盖callapply的命令。

  function returnThis() {
      console.log(this);
    }
    var obj = { name: "obj" };
    var bindThis = returnThis.bind(obj);
    bindThis(); // obj
    var obj2 = { name: "obj2" };
    bindThis.call(obj2); // obj
    bindThis.apply(obj2); // obj

实例化后的this

  • 当我们使用new实例化一个函数时,调用这个函数。this指向这个函数本身,当改变自身this的指向时,会报错。
  • 使用bind改变this的指向时,如果这个对象使用new实例化,会覆盖bind的指向,还指向本身。
  • 所以只要使用new实例化的函数,都会指向本身这个函数
 function FnThis() {
      console.log(this);
   }
   FnThis()    // window
   var newThis = new FnThis()    // FnThis

   var obj = {name:'obj'}
   FnThis.call(obj)        // obj
   FnThis.apply(obj)        // obj
   newThis.call(obj)       // TypeError
   
   var bindThis = FnThis.bind(obj)
   bindThis()        // obj
   new bindThis()    // FnThis

箭头函数

在es6中,新增了一种箭头函数


  • 箭头函数没有this : 这意味着 call() apply() bind() 无法修改箭头函数中的this
  • 箭头函数中的this指向 :访问上一个作用域的this
     var funs = () => {
      console.log(this);
    };
    funs(); // window

    var obj = {
      cb: () => {
        console.log(this);
      },
    };
    obj.cb(); // winodw
    
    var obj2 = {
      fn1: function () {
        console.log(this); //obj2
        //在局部作用域声明一个箭头函数
        let fn2 = () => {
          // fn2是一个箭头函数, 所以this访问的是上一级作用域中的this
          console.log(this); //obj2
        };
        fn2();
      },
    };
    obj2.fn1();

所以对于箭头函数,只要看它在哪里创建的就行。