全面解析JavaScript中this绑定

this对于很多前端来说,是个很容易踩坑的点,也是很多面试官喜欢问的点。本文将以具体案例的形式对this绑定做了一个深入浅出的全面梳理。

本文的所有案例都可直接在浏览器终端独立运行验证,建议感兴趣的可以copy案例中的代码自己试试,能帮助更好的理解。

this绑定该要

this绑定有默认绑定(严格/非严格模式)隐式绑定显示绑定(call/apply/bind)new绑定箭头函数绑定(ES6),这些绑定的优先级依次增高(箭头函数绑定除外),即new绑定的优先级最高,默认绑定的优先级最低。

this绑定.png

1.默认绑定

在没有特定对象调用某个方法时,默认使用window或undefined调用,一般可以理解为window绑定

this的默认绑定有非严格模式严格模式两种情况。

非严格模式

函数运行在非严格模式下,默认绑定能绑定到全局对象window。先来看一个案例

var name = '天下无敌'

function getName () {
  console.log(`my name is ${this.name}`)
}

let Person = {
  name: '谷底飞龙',
  age: 28,
}

getName();

在这个案例中,当我们执行 getName() 时,由于没有指定调用对象,默认是全局对象window,因此getName()方法里的this绑定的是window,而window对象里name值是天下无敌,所以这里会打印出my name is 天下无敌

严格模式

但是在严格模式下,不能将全局对象用于默认绑定,this会绑定到undefined,如以下案例:

var name = '天下无敌'

function getName () {
  // 使用严格模式
  "use strict"
  console.log(`my name is ${this.name}`)
}

let Person = {
  name: '谷底飞龙',
  age: 28,
}
// 执行函数
getName();

在这个案例中,由于是严格模式下,this无法绑定到全部对象window,绑定的是undefined,因此执行 getName() 时,会打印出报错日志Uncaught TypeError: Cannot read property 'name' of undefined

不过,在严格模式下,调用函数不会影响this的默认绑定,如以下案例:

var name = '天下无敌'

function getName () {
  console.log(`my name is ${this.name}`)
}

let Person = {
  name: '谷底飞龙',
  age: 28,
}

// 严格模式下调用函数不影响默认绑定
function consoleName () {
  "use strict"
  getName();
}
// 执行函数
consoleName()

执行方法consoleName(),在严格模式下调用函数getName()可以绑定到全局对象window,因此会打印出my name is 天下无敌

2.隐式绑定

当函数引用有上下文对象时,函数里的this绑定的是调用该函数的对象

对上面的默认绑定案例做一下修改,通过对象Person来调用函数,如下面的案例:

var name = '天下无敌'

function getName () {
  console.log(`my name is ${this.name}`)
}

let Person = {
  name: '谷底飞龙',
  age: 28,
  getName,
}
// 执行函数
Person.getName()

执行方法Person.getName(),这里指定了方法的调用对象是Person,因此方法里的this绑定的就是Person,而Person里的属性name的值是谷底飞龙,所以这里打印出my name is 谷底飞龙

  • 隐式绑定的时候,需要注意隐式丢失的情况,被隐式绑定的函数特定情况下会丢失绑定对象,变成默认绑定,把this绑定到全局对象window或者undefined上。主要有两类场景:
隐式丢失-函数引用

使用函数引用时,容易使this的隐式绑定变成默认绑定。我们来对隐式绑定的案例做一个修改,将Person.getName的引用赋给consoleName并执行,如以下案例:

var name = '天下无敌'

function getName () {
  console.log(`my name is ${this.name}`)
}

let Person = {
  name: '谷底飞龙',
  age: 28,
  getName,
}

// 增加Person.getName的引用
let consoleName = Person.getName;

// 执行函数
consoleName()

在这个案例里,方法consoleName()Person.getName的引用,但是实际上,它引用的是getName函数本身。consoleName()是一个不带任何修饰的函数调用,属于默认绑定,所以绑定的是全局window,打印出my name is 天下无敌

隐式丢失-回调函数

函数作为参数传递,回调函数执行时也会被隐式赋值。回调函数中丢失this绑定是非常常见的。我们来对隐式绑定的案例做一个修改,将Person.getName作为函数consoleName的参数传递,如以下案例:

var name = '天下无敌'

function getName () {
  console.log(`my name is ${this.name}`)
}

let Person = {
  name: '谷底飞龙',
  age: 28,
  getName,
}

// 将Person.getName的作为函数consoleName的参数传递
let consoleName = function (fn){
   fn()
};

// 执行函数
consoleName(Person.getName)

这里的this也会由隐式绑定变成默认绑定,绑定的是全局的window,打印出的也是my name is 天下无敌

3.显式绑定

通过调用call/apply/bind方法,强制将this绑定到指定的对象。

我们来对默认绑定的案例做一下修改,如下:

var name = '天下无敌'

function getName () {
  console.log(`my name is ${this.name}`)
}

let Person = {
  name: '谷底飞龙',
  age: 28,
}

// 执行函数
getName.call(Person);

执行getName方法时,将对象Person作为call方法的参数来将getName方法里的this的默认绑定的全局对象window改为指定对象Person,所以这里打印出来的是my name is 谷底飞龙

  • 注:这把call换成apply/bind的效果也是一样的,在使用上有些区别,call/apply/bind的具体使用及区别,我再单独写一篇文章来详细介绍下。

4.new绑定

用 new 调用函数时,JavaScript 解释器会在底层创建一个新对象,this绑定的就是这个新对象。

看以下案例:

function Person (name, age) {
  this.name = name
  this.age = age
}
// 通过new创建对象绑定this
const person = new Person('谷底飞龙', 28)
console.log(`my name is ${person.name}`)

在这个案例里,通过new调用函数Person(),会创建一个新对象person,而这个对象会绑定到函数里的this,也就是person.name实际就是函数里的this.name,因此这里打印出my name is 谷底飞龙

5.箭头函数绑定

箭头函数没有自己的this,绑定的是外层的对象

箭头函数是ES6 引入的,使用起来很顺手,也可以避免 ES5 中使用 this 很多坑。先看一个案例:

var name = "天下无敌";

var Person = {
  name : "谷底飞龙",

  getName: function () {
    console.log(`my name is ${this.name}`)
  },

  consoleName: function () {
    // 回调函数不使用箭头函数,属于隐式绑定中的隐式丢失的情况,this绑定的是全局对象window
    setTimeout(function (){
        this.getName()
    },100);
  }
};

// 执行函数
Person.consoleName()  

执行Person.consoleName()方法时,内部调用setTimeout()的回调函数不使用箭头函数,回调函数中this绑定的是调用setTimeout()的对象,也就是全局window,而window下面没有getName()方法,因此这里会打印出错误信息Uncaught TypeError: this.getName is not a function

如果把以上案例做一下修改,使用箭头函数,如下:

var name = "天下无敌";

var Person = {
  name : "谷底飞龙",

  getName: function () {
    console.log(`my name is ${this.name}`)
  },

  consoleName: function () {
    // 回调函数使用箭头函数,this绑定的是外层对象Person
    setTimeout(()=>{
        this.getName()
    },100);
  }
};

// 执行函数
Person.consoleName()  

由于箭头函数没有自己的this,指向的是外层的对象,因此setTimeout()中this绑定的是外层的对象Person,所以打印出来是my name is 谷底飞龙

结语


image.png

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值