js中的this规则

137 阅读4分钟

js中的this绑定规则

JavaScript中的this关键字用于指向当前函数的执行上下文(execution context)。 不同于作用域是编译时确定的,this的值在运行时确定,并且取决于函数的调用方式。JavaScript中this的绑定规则比较复杂,以下是常见的有四种情况:

1. 独立调用(默认规则)

const foo = function (){
    console.log(this)
}
foo() // window (node环境下为global对象)

独立调用时,this默认指向全局 在浏览器环境下全局指向window,而node环境下全局指向global

2. 隐式绑定(对象调用)

const obj = {
    name:'张三',
    foo:function (){
        console.log(this.name)
    }
}
obj.foo() // 张三 foo函数内的this会被绑定到obj对象

隐式绑定发生在对象调用方法的方式,也是比较常见的this绑定方式

3.显式绑定(call apply bind)

const obj = {
    name:'asd',
    age:10
}
const fn = function (num1,num2){
    console.log(this.age,num1 + num2)
}
fn.call(obj,10,20)  // 10 30
fn.apply(obj,[10,20]) // 10 30
// bind会返回绑定好this的函数,而不是直接调用,后续需要手动调用
fn.bind(obj)(10,20) // 10 30

显式绑定是通过call,apply,bind方法,将函数内部的this绑定到传入的对象上

4.new绑定 (通过new构造函数绑定this)

function Person(name,age){
    this.name = name
    this.age = age
}
//`new`关键字调用了`Person`函数,从而创建了一个新的对象,并将`this`绑定到了该对象上
const p1  = new Person('张三',16)
console.log(p1.name,p1.name) // '张三'  16

new 绑定是在执行构造函数时,将构造函数内的this绑定到要构建的实例对象上

优先级:

new绑定 > 显式绑定 > 隐式绑定 > 默认规则

其中new绑定与call,apply同时使用无意义,但可以与bind,new优先级依旧最高

function foo() {
    console.log(this); // foo {}  foo函数创建的实例对象
}
const bar = foo.bind("asd");
const obj = new bar();

特殊情况

1. 箭头函数

箭头中的this默认指向父级执行上下文,call,apply,bind无法改变箭头函数的this指向

const obj = {
  name: "obj",
  foo1: () => {
    console.log(this);
  },
  foo2: function () {
    console.log(this);
  },
};
const temp = {
  name: "temp",
};
//需要注意对象定义时的大括号不构成块级作用域,所以此处的剪头函数的this指向其父级执行上下文
obj.foo1(); // window 
obj.foo1.call(temp); // window 
obj.foo2(); // obj
obj.foo2.call(temp); //temp

tips: 有个容易混淆的地方,对象定义中的箭头函数foo,其this(执行上下文)指向对象定义时的父级执行上下文(对象的大括号不构成块级作用域)

window.name = 'window'
const obj = {
    name:'obj',
    foo:() =>{
        console.log(this.name) 
    },
    fn:function(){
        console.log(this.name) 
    }
}
obj.foo()  // window 箭头函数作用域指向父级执行上下文
obj.fn() // obj 

2. setTimeout

  • setTimeout函数由浏览器内部Chromium实现,源码采用apply显式形式将this绑定到Window
fakeWin.setTimeout = function(fn, time) {
    fakeWin.setTimeout.called = true;
    fakeWin.setTimeout.that=this;
    if (typeof fn === 'string') f
        eval(fn);
    }else{
        fn.apply(this, Array.prototype.slice.call(arguments, 2)) ;
    }
}

传入函数内的执行上下文(即 this 值)取决于你如何调用该函数,了解了绑定原理,让我们结合this规则来判断this的值

setTimeout(function(e){
    console.log(this) // window (this已被绑定到window)
},100)
setTimeout(() =>{
    console.log(this) // window (箭头函数无法被apply改变this指向,其内部又不存在this,所以去上层作用域找,找到全局也就是window)
},100)
// 传入普通函数时默认指向window
const obj = {
    fn:function(){
        setTimeout(function(){
            console.log(this)  
        },1000)    
    }
}
obj.fn() // window(this已被绑定到window,隐式绑定权重小于显示绑定)

//传入箭头函数时指向父级执行上下文
const obj = {
    name:'obj'   
    fn:function(){
        setTimeout(() =>{
            console.log(this)   // 箭头函数无法被apply改变this指向
        },1000)    
    }
}
obj.fn() // {name:'obj',fn: ƒ}  
// tip:
// 最后一行fn函数中的this被隐式绑定到了obj对象上,而箭头函数中不存在this,会去上层作用域找(fn函数中的this),此时this也就等于obj对象

3. DOM事件对象

DOM对象在绑定事件监听器时

传入普通函数 内部的的this指向触发该事件的DOM元素

传入钩子函数 内部的this指向定义该箭头函数的父级执行上下文

<button id="myButton">点击我</button>
const myButton = document.getElementById('myButton');


// 普通函数
myButton.addEventListener('click', function(event) {
  // 在这里,`this`将指向 `myButton` 元素
  console.log(this); // 输出:<button id="myButton">点击我</button>
});


//箭头函数
//指向定义该箭头函数的父级执行上下文
myButton.addEventListener('click', (event)=>{
  // 在这里,`this`将指向父级执行上下文
  console.log(this); // 输出:window
});