还在为this晕头转向?来看看这篇this指北!

122 阅读4分钟

js为什么需要this?

在对象内部方法中调用对象属性更加方便

可以思考下面一种情况

var person = {
    name:“person”,
    say:function(){
        return this.name
    }
}
//上面的say方法里可以使用person.name,但是如果对象名称频繁改动,say方法里也要随之进行修改
//所以直接使用this可以利用隐式绑定(persoon.say()的形式)把this始终绑定在所处对象上

js中this到底怎么指向?

this指向和定义的位置没有关系,和调用方式及调用位置有关系

this是在运行时被绑定的,可以看做是函数调用时被绑定的一个对象

不同场景分类如下:

全局环境

浏览器环境

window全局作用域时,this就指向window

Node环境

node环境下直接在js文件打印this,this指向空对象{}

打印为{} 因为node中js文件是当做module模块, 然后进行解析-->加载-->编译

会将js页面所有的东西放到一个函数

运行这个函数.apply({}),利用了apply将this绑定了一个空对象

函数中的this指向(重要)

默认绑定

也可以理解为独立的函数调用

  1. 普通函数调用

    function foo1(){
      console.log(this); //window
    }
    function foo2(){
      console.log(this); //window
      foo1()
    }
    foo2()
    
  2. 函数赋值给另一个变量再调用(也可以看做是隐式绑定丢失)

    const obj1 = {
      name:'obj1',
      foo:function(){
        console.log(this);
      }
    }
    const bar = obj1.foo
    bar() //window
    
  3. 函数调用链

    function foo(){
      function bar(){
        console.log(this);
      }
      return bar
    }
    const fn = foo()
    fn() //window
    

隐式绑定

必须在调用的对象内部有一个对函数的引用, 是通过某个对象发起的函数调用

  1. 通过对象直接调用函数

    const obj = {
      name:'obj',
      eating:function(){
        console.log(this.name + ' is eating');
      }
    }
    obj.eating() //'obj is eating'
    
  2. 将对象1属性对应的函数对象赋值给对象2中的某个属性

    const obj1 = {
      name:'obj1',
      foo:function(){
        console.log(this);
      }
    }
    const obj2 = {
      name:'obj2',
      bar:obj1.foo
    }
    obj2.bar() // {name: 'obj2', bar: ƒ}
    

显式绑定

当不希望对象内部包含某个函数的引用, 又希望在对象上进行强制调用

比如apply/bind/call函数, 可以显式绑定this到上述对象

关于apply/bind/call函数的手动实现, 可以查看我的js专栏相关文章

  1. call/apply函数绑定

    function foo(num1,num2){
      const num = num1+num2
      console.log('num:',num,this);
    }
    foo.call('call',1,2,3) // 3, 'call'
    foo.apply('apply',[2,3,4]) //5, 'apply'
    
  2. bind函数绑定

    function foo(){
      console.log('foo:',this);
    }
    const newFoo = foo.bind('newFoo') //bind完之后生成一个新的函数
    newFoo() // 'foo:' newFoo
    //注意此时,既是显式绑定又是默认绑定,优先级: 显式绑定>默认绑定
    

new关键字绑定

js函数可以用new关键字被当做一个类的构造函数使用

new关键字调用函数(构造器),this是在调用构造器时创建出来的对象

  1. 构造函数直接用new调用

    function Person(name,age){
      this.name = name
      this.age = age
    }
    const p1 = new Person('p1',100)
    console.log(p1.name,p1.age); // p1 100
    const p2 = new Person('p2',200)
    console.log(p2.name,p2.age); // p2 200
    
  2. 构造函数和显式绑定冲突的情况

    const obj = {
      foo:function(){
        return this
      }
    }
    console.log(new obj.foo()); //foo对象
    //可以看出 new关键字>显式绑定
    

综合上述例子, 可以发现this绑定优先级:

new关键字 > 显式绑定 > 隐式绑定 > 默认绑定

this绑定的特殊情形

setTimeout

setTimeout内部通过apply进行绑定this到全局对象

所以this指向window

setTimeout(function(){
  console.log('setTimeout',this); //window
},0)
监听点击事件

this指向点击的dom对象

const boxDiv = document.querySelector('.box')
boxDiv.addEventListener('click',function(){
  console.log(this); //box实例
})
forEach/map方法

默认在传入的函数中打印的也是Window对象

因为在默认情况是自动调用函数(默认绑定)

但是可以传入第二个参数进行this绑定

const names = ['a','b','c']
names.forEach(function(item){
  console.log(item,this); //'thisArg'
},'thisArg')
间接引用
const obj1 = {
  name:'obj1',
  foo:function(){
    console.log(this);
  }
}
const obj2 = {
  name:'obj2',
};
obj2.foo = obj1.foo
obj2.foo() //{name: 'obj2', foo: ƒ}
立即引用

这种调用方式注意分号;的使用

const obj1 = {
  name:'obj1',
  foo:function(){
    console.log(this);
  }
}
const obj2 = {
  name:'obj2',
};
(obj2.bar = obj1.foo)() //window 
//独立调用的一种:可以看做直接拿到了foo函数进行调用
箭头函数

在函数声明时就绑定好了this, 可以看做是一个闭包

且箭头函数不支持new运算符

const name = 'name'
const foo = ()=>{
  console.log(this);
}
foo()
const obj = {foo:foo} //window
obj.foo() //window
foo.call('call') //window

根据外层作用域决定this 会往上一级寻找

const obj = {
  data:'',
  getData:function(){
    //在此处不需要定义_this=this
    setTimeout(()=>{    
      console.log(this); 
    },1000)
  }
}
obj.getData() // {data: '', getData: ƒ}