JavaScript中的this:一场关于"我是谁"的哲学之旅

各位前端小伙伴们,今天咱们来聊聊JavaScript中的this关键字------这个让无数开发者抓耳挠腮的"小调皮"。它就像一个变脸大师,一会儿指向window,一会儿指向对象,一会儿又指向实例...今天,咱们就来揭开它的神秘面纱!

为什么要有this?它存在的意义是啥?

首先,咱们得弄明白:为什么JavaScript要设计this这么个玩意儿?答案很简单------为了让代码更优雅!

想象一下,如果没有this,咱们得这样写代码:

javascript:d:\学习\深索\js\this\1.js 复制代码
function identify(context) {
  return context.name.toUpperCase()
}

function speek(context) {
  var greeting = 'hello, I am ' + identify(context)
  console.log(greeting)
}

var me = { name: 'Tom' }
speek(me)  // 每次调用都得手动传context,多麻烦啊!

有了this之后,咱们可以这样写:

javascript:d:\学习\深索\js\this\1.js 复制代码
function identify() {
  return this.name.toUpperCase()
}

function speek() {
  var greeting = 'hello, I am ' + identify.call(this)
  console.log(greeting)
}

var me = { name: 'Tom' }
speek.call(me)  // 优雅多了!

this就像一个隐形的传递者,默默地帮咱们传递对象引用,让代码更简洁、更易于复用。

this的绑定规则:五大门派各显神通

this的指向并不是在定义时确定的,而是在调用时确定的。它有五大绑定规则,咱们一个个来看:

1. 默认绑定:无依无靠的this

当函数被独立调用时,this默认指向全局对象(浏览器中是window)。

javascript:d:\学习\深索\js\this\2.js 复制代码
function foo() {
  console.log(this)  // window
}
foo()  // 独立调用,this指向window

console.log(this)  // 全局作用域中的this也是window

注意啦,在严格模式下,默认绑定的this会变成undefined,这是个小小的陷阱!

2. 隐式绑定:有"靠山"的this

当函数引用有上下文对象,并且被该对象调用时,this就会绑定到这个上下文对象上。

javascript:d:\学习\深索\js\this\3.js 复制代码
var a = 3
function foo() {
  var a = 2
  function bar() {
    var a = 1
    console.log(this.a)  // 3,因为bar是独立调用,this指向window
  }
  bar()
}
foo()

上面这个例子,bar虽然在foo里面定义,但它是独立调用的,所以this还是指向window。那怎么让this指向对象呢?

javascript:d:\学习\深索\js\this\4.js 复制代码
var a = 1
function foo() {
  console.log(this.a)  // 2,因为是obj调用的foo
}
var obj = {
  a: 2,
  foo: foo
}
obj.foo()  // 方法调用,this指向调用者obj

3. 隐式丢失:this的"背叛"行为

有时候,this会"背叛"它的主人,这就是隐式丢失。最常见的情况是把对象的方法赋值给变量,然后独立调用这个变量。

javascript:d:\学习\深索\js\this\4.js 复制代码
var a = 1
var obj = {
  a: 2,
  foo: function() {
    console.log(this.a)
  }
}
var bar = obj.foo  // 这里只是把函数赋值给bar,没有调用
bar()  // 独立调用,this指向window,输出1

另外,当一个函数被多层对象调用时,this只会指向最近的那一层对象:

javascript:d:\学习\深索\js\this\4.js 复制代码
var a = 1
var obj = { a: 2, foo: foo }
var obj2 = {
  a: 3,
  obj: obj
}
obj2.obj.foo()  // this指向obj,输出2,而不是obj2

4. 显式绑定:强行指定this

咱们可以使用callapplybind这三个方法,强行把this绑定到指定的对象上。

javascript:d:\学习\深索\js\this\5.js 复制代码
function foo(x, y) {
  console.log(this.a, x + y)
}
var obj = { a: 1 }

// call方法:参数一个个传
foo.call(obj, 1, 2)  // 输出:1 3

// apply方法:参数用数组包起来
foo.apply(obj, [1, 2])  // 输出:1 3

// bind方法:返回一个新函数,参数可以分两次传
const bar = foo.bind(obj, 2, 3)
bar(4)  // 输出:1 9(2+3+4?不,bind的参数和调用时的参数会合并,2+3是bind传的,4是调用时传的)

这三个方法的区别在于:callapply会立即执行函数,而bind会返回一个新函数,需要再次调用。

5. new绑定:this的"认祖归宗"

当使用new关键字调用构造函数时,this会绑定到新创建的实例对象上。

javascript:d:\学习\深索\js\this\6.js 复制代码
function Person() {
  this.name = '超超'
  this.age = 18
  console.log(this)  // 指向新创建的实例对象 {name: '超超', age: 18}
}
const p1 = new Person()

new操作符做了什么呢?简单来说:

  1. 创建一个新对象
  2. 把这个新对象的__proto__指向构造函数的prototype
  3. 把构造函数的this指向这个新对象
  4. 如果构造函数没有返回对象,就返回这个新对象

箭头函数:this的"叛逆者"

ES6引入的箭头函数,对this做了特殊处理------它根本就没有自己的this!箭头函数里的this,其实是外层作用域的this

javascript:d:\学习\深索\js\this\7.js 复制代码
var a = 1
var obj = {
  a: 2,
  bar: function() {
    const baz = () => {
      console.log(this.a)  // 2,因为baz的this是bar的this,也就是obj
    }
    baz()
  }
}
obj.bar()

箭头函数的这个特性,特别适合解决回调函数中的this问题。比如在定时器、事件监听器中,再也不用写const that = this这样的代码了!

this的绑定优先级:谁的话更管用?

当多种绑定规则同时存在时,this该听谁的呢?这里有个优先级排序:

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

箭头函数比较特殊,它不遵循这些规则,它的this由外层作用域决定,而且一旦确定就不能改变。

总结:this的"使用指南"

  1. 独立调用this指向window(非严格模式)或undefined(严格模式)
  2. 方法调用this指向调用该方法的对象
  3. call/apply/bind调用this指向指定的对象
  4. new调用this指向新创建的实例对象
  5. 箭头函数this指向外层作用域的this

记住这些规则,遇到this相关的问题时,先分析函数是怎么被调用的,再根据规则判断this的指向,就能轻松解决大部分问题啦!

最后,给大家留个小练习:分析下面代码中this的指向,欢迎在评论区分享你的答案~

javascript 复制代码
var a = 1
const obj = {
  a: 2,
  foo: function() {
    setTimeout(function() {
      console.log(this.a)  // 这里的this指向谁?
    }, 0)
  },
  bar: function() {
    setTimeout(() => {
      console.log(this.a)  // 这里的this又指向谁?
    }, 0)
  }
}
obj.foo()
obj.bar()

好啦,今天关于this的分享就到这里。希望这篇文章能帮你更好地理解JavaScript中的this关键字,让你在写代码时不再被它困扰!如果觉得有用,别忘了点赞收藏哦~

相关推荐
奶昔不会射手1 分钟前
css3之flex布局
前端·css3·flex
跟橙姐学代码7 分钟前
Python 装饰器超详细讲解:从“看不懂”到“会使用”,一篇吃透
前端·python·ipython
pany26 分钟前
体验一款编程友好的显示器
前端·后端·程序员
Zuckjet31 分钟前
从零到百万:Notion如何用CRDT征服离线协作的终极挑战?
前端
ikonan36 分钟前
译:Chrome DevTools 实用技巧和窍门清单
前端·javascript
Juchecar36 分钟前
Vue3 v-if、v-show、v-for 详解及示例
前端·vue.js
ccc101840 分钟前
通过学长的分享,我学到了
前端
编辑胜编程40 分钟前
记录MCP开发表单
前端
可爱生存报告40 分钟前
vue3 vite quill-image-resize-module打包报错 Cannot set properties of undefined
前端·vite
__lll_40 分钟前
前端性能优化:Vue + Vite 全链路性能提升与打包体积压缩指南
前端·性能优化