前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >让天下没有难学的js之this到底是什么,怎么用,这里可能给你答案

让天下没有难学的js之this到底是什么,怎么用,这里可能给你答案

作者头像
十里青山
发布2022-08-07 10:56:59
5050
发布2022-08-07 10:56:59
举报
文章被收录于专栏:我的前端之路我的前端之路

前言

有很多初学的小伙伴在调用函数给对象进行赋值的时候经常会出现一些关于this的错误,例如this找不到啊,或者没报错却没有生效啊之类的问题,即便是一些入门级的同学在遇到这些问题时,也只是通过不断的尝试使用var _this = this.call()等方法去实现效果,最后虽然达到了想要的效果,但是却并没有明白问题所在,也懒得去仔细研究,那么今天我就来带大家一起看看js中this的庐山真面目

「温馨提示:内容较多,建议点赞收藏后阅读」

什么是this

❝ 当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包 含函数在哪里被调用(调用栈)、函数的调用方法、传入的参数等信息。 this 就是记录的 其中一个属性,会在函数执行的过程中用到。 ——《你不知道的JavaScript上卷》 ❞

上面是《你不知道的JavaScript》一书中对于this的解释,可能看起来很难理解,但我们不妨只看第一句话就可以了,「当一个函数被调用时,会创建一个活动记录」,这句话也就说明了,函数在定义时是没有this的,this只在函数被调用时产生,我们也暂且可以粗略的理解为,this就是代表调用这个函数的对象,这句话在大多数情况下是行得通的,那什么情况下不能这么理解呢,下面我们就来看看this的绑定规则

this的绑定规则

默认绑定

默认绑定就是直接调用函数,这也是最常见的一种情况,我们可以看看下面一段代码

代码语言:javascript
复制
function foo() {
  var a = 2
  foo.a = 3

  console.log(this) // Window
  console.log(this.a) // 4
}

var a = 4

foo()

*大家可以直接复制上述代码,然后打开控制栏,粘贴到console里运行一下

上述的结果可以看到,我们直接运行函数时候,函数里的this是直接绑定到Window上的,其实按我个人的理解是,所有的全局变量都是默认绑定到Window上的,全局变量a,用Window.a也一样可以获取到,所以此处的直接调用函数 `foo()`我们也可以理解为Window.foo(),所以默认绑定依然遵循我们上述得出的结论:**this就是代表调用这个函数的对象**

注意,在严格模式下,直接调用函数,this并不会被绑定到window上,而是被绑定到undefined上。

隐式绑定

隐式绑定我们可以简单的理解为,当函数被调用时被一个对象所包裹或拥有,或者可以理解为,该函数定义在某对象的一个属性下面或被对象的的一个属性所引用。看文字比较枯涩,直接看代码

代码语言:javascript
复制
function foo() {
  var a = 2
  foo.a = 3

  console.log(this)
  console.log(this.a)
}
var a = 4

var obj = {
  a: 5,
  foo: foo, // 此处函数foo()被引用给了对象obj的foo属性
}

obj.foo()
// 打印结果为
// {a: 5, foo: ƒ}
// 5

像上述方式,当函数作为对象的一个属性被调用时,我们则可以称之为隐形绑定,这种情况也同样适应于我们上面所说的,谁调用,this就是谁的说法。个人理解,这之所以叫做隐式绑定就是因为我们没有给this指定绑定对象,而this自动绑定到了所属的上下文对象中[也就是所处的对象中]。

对象属性引用链中只有最顶层或者说最后一层会影响调用位置。举例来说:

代码语言:javascript
复制
function foo() {
  console.log(this.a)
}
var obj2 = {
  a: 42,
  foo: foo,
}
var obj1 = {
  a: 2,
  obj2: obj2,
}
obj1.obj2.foo() // 42
隐式丢失

一个最常见的 this 绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把 this 绑定到全局对象或者 undefined 上,取决于是否是严格模式。

代码语言:javascript
复制
function foo() {
  console.log(this.a)
}
var obj = {
  a: 2,
  foo: foo,
}
var bar = obj.foo // 函数别名!
var a = 'oops, global' // a 是全局对象的属性
bar() // "oops, global"

虽然 bar 是 obj.foo 的一个引用,但是实际上,它引用的是 foo 函数本身,因此此时的 bar() 其实是一个不带任何修饰的函数调用,因此应用了默认绑定。如果不能理解这句话的话,我们可以直接这么理解

代码语言:javascript
复制
var bar = boj.foo = foo
var() 相当于 foo()

另一种非常相似的情况可能会令我们感到迷惑,但是道理是一样的,代码如下

代码语言:javascript
复制
function foo() {
  console.log(this.a)
}
var obj = {
  a: 2,
  foo: foo,
}
var a = 'oops, global' // a 是全局对象的属性
setTimeout(obj.foo, 100) // "oops, global"

JavaScript 环境中内置的 setTimeout() 函数实现和下面的伪代码类似:

代码语言:javascript
复制
function setTimeout(fn, delay) {
  // 等待 delay 毫秒
  fn() // <-- 调用位置!
}

这样我们就好理解多了,在这里obj.foo也就是foo作为回调函数被传入,执行的时候依然执行的是foo(),同样适用于默认绑定的原则

显示绑定

我们常用的.apply().call().bind()这种改变函数this指向的方法,因为我们可以直接指定 this 的绑定对象,因此我们称之为显式绑定。 这三个函数都用来改变this的指向,下面我们就简单说一下这三个函数的用法和区别

代码语言:javascript
复制
function foo() {
  console.log('我叫:' + this.name + ',我今年' + this.age)
}
var obj = {
  name: '小豪',
  age: '18',
}
foo.apply(obj) // 我叫:小豪,我今年18
foo.call(obj) // 我叫:小豪,我今年18
foo.bind(obj)() // 我叫:小豪,我今年18

可以看出,三个函数接受的第一个参数都为要绑定的对象,用法上并无区别,只不过.bind()方法返回的是一个函数,我们需要加一个括号去调用它。

代码语言:javascript
复制
function foo(from, to) {
  console.log('我叫:' + this.name + ',我今年' + this.age + ',来自' + from + ',要去' + to)
}
var obj = {
  name: '小豪',
  age: '18',
}
foo.apply(obj, ['河北','北京']) // 我叫:小豪,我今年18,来自河北,要去北京
foo.call(obj, '河北' , '北京') // 我叫:小豪,我今年18,来自河北,要去北京
foo.bind(obj, '河北' , '北京')() // 我叫:小豪,我今年18,来自河北,要去北京

以上可以看出,call和bind的传参方式是一样的,只要用逗号隔开就可以了,而apply则必须把所有的参数都放到一个数组里。

「注意,此类函数不可叠加使用,如foo.call(obj1).call(obj2)是错误的行为,foo.call(obj1).apply(obj2)也是」

new绑定

代码语言:javascript
复制
function foo(a) {
  this.a = a
}
var bar = new foo(2)
console.log(bar.a)

使用new进行的this绑定将始终被绑定到创建时候赋值的对象bar上,后续也无法再修改他的this绑定。

关于用new调用函数后是如何执行的,《你不知道的JavaScript》一书中是这么说的

❝ 使用 new 来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。

  1. 创建(或者说构造)一个全新的对象。
  2. 这个新对象会被执行 [[ 原型 ]] 连接。
  3. 这个新对象会绑定到函数调用的 this 。
  4. 如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象。

我们现在关心的是第 1 步、第 3 步、第 4 步,所以暂时跳过第 2 步,由于构造函数我也不是太懂,这里先不提,感兴趣的同学可以自己去搜索,我们只要知道new返回的是一个对象就可以了。不信打印一下代码

代码语言:javascript
复制
function foo(a) {
  this.a = a
}
var bar = new foo(2)
console.log(typeOf(bar)) // object

这里我们重点关注一下第四句话:「如果函数没有返回其他对象,那么 new 表达式中的函数调用会自动返回这个新对象」,正常的我们在这一小节开头已经看过了,那下面我们就来看看不正常的:当函数返回了其他对象

代码语言:javascript
复制
function foo(a) {
  this.a = a
  return {
    b: 5
  }
}
var bar = new foo(2)
console.log(bar) // {b: 5}
console.log(bar.a) // undefined

看,这时候bar就等于函数里新返回的对象了,所以说,如果你在函数里返回了新的对象,那么 new 表达式中的函数调用会自动返回这个我们手动创建的新对象,否则将返回自动创建的那个对象,注意当函数返回了非对象和undefind、null时,bar依然等于我们创建的那个实例

优先级

毫无疑问,肯定是默认绑定的优先级最低,因为它会在你不进行其他所有绑定的时候才会选择默认绑定。 其次的优先级为

❝ new > 显示绑定(apply、call、bind) > 隐式绑定(对象调用)> 默认绑定 ❞

箭头函数

我们之前介绍的四条规则已经可以包含所有正常的函数。但是 ES6 中介绍了一种无法使用这些规则的特殊函数类型:箭头函数。

箭头函数并不是使用 function 关键字定义的,而是使用被称为“胖箭头”的操作符 => 定 义的。箭头函数不使用 this 的四种标准规则,而是根据外层(函数或者全局)作用域来决 定 this 。

代码语言:javascript
复制
function foo() {
  // 返回一个箭头函数
  return () => {
    //this 继承自 foo()
    console.log(this.a)
  }
}
var obj1 = {
  a: 2,
}
var obj2 = {
  a: 3,
}
var bar = foo.call(obj1)
bar.call(obj2) // 2, 不是 3 !

以上代码可以看出,箭头函数的this指向取决于它所在作用域的this,并且箭头函数无法被改变this指向,无论是用call还是new。

其实箭头函数和我们之前使用的一种方法几乎一样,那就是用一个变量暂存this

代码语言:javascript
复制
function foo() {
  var self = this // lexical capture of this
  setTimeout(function () {
    console.log(self.a)
  }, 100)
}
var obj = {
  a: 2,
}
foo.call(obj) // 2

看,在这种情况下,函数里的this指向也是取决于它所在的作用域,不同的是,箭头函数的this指向取决于它所在的最近的作用域,而用变量暂存this的方法可以让它指向任意作用域的this(前提是可以访问到那个变量)

所以,在不考虑es6的兼容问题或者已经做好兼容处理的情况下,我们不妨使用箭头函数代替我们以前var _this = this的写法,这样写既简单也更美观。

代码语言:javascript
复制
function foo() {
  var self = this // lexical capture of this
  setTimeout(function () {
    console.log(self.a)
  }, 100)
}
var obj = {
  a: 2,
}
foo.call(obj) // 2

 // 改写为

function foo() {
  setTimeout(() => {
    console.log(this.a)
  }, 100)
}
var obj = {
  a: 2,
}
foo.call(obj) // 2

不过,在《你不知道的JavaScript》一书中,编者建议,如果你在编写代码的过程中有使用上面四种绑定规则的话,需要尽量避免使用var _this = this和箭头函数,因为在同一个函数或者同一个程序中混 合使用这两种风格通常会使代码更难维护,并且可能也会更难编写

作者注

❝ 作者基础较为薄弱,文章为本人参考相关技术博客和技术书籍所总结,所以可能并不会完全准确,有些地方为了通俗易懂也没有使用专业词汇,可能会出现用词错误的情况,希望各位同仁可以多加指正,共同进步,谢谢。 ❞

❝ 本文大部分内容参考自书籍《你不知道的JavaScript》上卷,如果想了解完整内容,请阅读书籍。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-06-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 什么是this
  • this的绑定规则
    • 默认绑定
      • 隐式绑定
        • 隐式丢失
      • 显示绑定
        • new绑定
        • 优先级
        • 箭头函数
        • 作者注
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档