this(一)深入浅出 JavaScript 中的 `this`:它为什么存在?你误解了它什么?

1. 为什么要用 this

对于有经验的JavaScript开发者来说,this都是一种非常复杂的机制,那它到底有用在哪里呢?真的值得我们付出这么大代价学习吗?的确,在介绍怎么做之前我们需要明白为什么。下面我们来解释一下为什么要使用this。

示例:

javascript 复制代码
function identify(){
    return this.name.toUpperCase();
}
function speak(){
    var greeting = "Hello, I'm " + identify.call(this);
    console.log(greeting);
}
var me = {name:"Kyle"};
var you = {name:"Reader"};
identify.call(me);//KYLE
identify.call(you);//READER
speak.call(me);//Hello, I'm KYLE
speak.call(you);//Hello, I'm READER

这段代码一开始看不懂,没关系,下一章就讲解。我们先专注于为什么。

这段代码可以在不同的上下文对象( me 和 you )中重复使用函数identify( )和speak( ),不用针对每个对象编写不同的版本。

如果不使用this,那就需要给identify( )的和speak( )显式传入一个上下文对象。

javascript 复制代码
function identify(context){
    return context.name.toUpperCase(); 
}
function speak(context){
    var greeting = "Hello, I'm " + identify(context);
    console.log(greeting); 
}
identify(me);//KYLE
speak(you);//Hello, I'm READER

this 让代码更简洁,更符合面向对象的设计逻辑

随着你的使用模式越来越复杂,显示纯利上下文对象会让代码越来越混乱,使用this就不会这样。当了解对象和原型时,你就会明白函数可以自动引用适合的上下文对象有多重要。


2. 对 this 的常见误解

2.1 误解一:this 指向函数自身

我们可以试着分析一下,看看this是不是指向函数本身。

javascript 复制代码
function foo(num){
    console.log("foo:"+num);
    //记录foo被调用的次数
    this.count++;
}
foo.count=0;
foo(1);//foo:1
foo(2);//foo:2
foo(3);//foo:3
console.log(foo.count);//0

最后输出竟然是0吗?!

按照 this 指向函数自身来说,this.count 就是 foo.count,那么foo被调用时foo.count应该会加 1。上面console.log语句的确产生了3条输出,证明 foo 确实是被调用了3次,但是 foo.count 仍然是0,那就反向证明了 this.count 和 foo.count 就不是一个东西。

那你一定会问,如果我增加的count属性和预期的不一样,那我增加的是哪个count?实际上,如果深入探究的话,就会发现这段代码在无意中创建了一个全局变量count,它的值为NaN。

有一个简易的解决方法就是使用foo标识符替代this来引用函数对象:

javascript 复制代码
function foo(num){
    console.log("foo:"+num);
    //记录foo被调用的次数
    foo.count++;
}
foo.count=0;
foo(1);//foo:1
foo(2);//foo:2
foo(3);//foo:3
console.log(foo.count);//3

确实,这个方法解决了问题,但是它回避了this的含义和工作原理,而是返回了词法作用域这个舒适区。

另一种方法是强制this指向foo函数对象:

javascript 复制代码
function foo(num){
    console.log("foo:"+num);
    //记录foo被调用的次数
    this.count++;
}
foo.count=0;
for(var i=1;i<4;i++){
    //使用call(...)可以确保this指向函数对象foo本身
    foo.call(foo,i);
}
console.log(foo.count);//3

这次我们就接受了this,这是this的显示绑定,下一篇咱会继续探索。

2.2 误解二:this 指向函数的作用域

需要明确的是,this在任何情况下都不指向函数的词法作用域。在JavaScript内部,作用域确实和对象类似,可见的标识符都是它的属性。但是作用域"对象"无法通过JavaScript代码访问,它存在于JavaScript引擎内部。

思考下面的代码:

javascript 复制代码
function foo(){
    var a=2;
    this.bar();
}
function bar(){
    console.log(this.a);
}
foo();//ReferenceError: a is not defined

这里试图使用this.bar()来调用bar()函数,但它竟然成功了,虽然也变相地说明了this不指向函数作用域,但是调用bar()应该直接省略前面的this,直接使用词法引用标识符。这里能对完全是凑巧函数调用时应用了this的默认绑定,因此this指向全局对象。

此外,编写这段代码还试图使用this联通foo()和bar()的词法作用义,从而让bar()可以访问foo()作用域里的变量a。这是不可能实现的,使用this是不可能在词法作用中查到什么。

每当想要你把this和词法作用域的查找混合时,一定要提醒自己这是不对的。

3. this 到底是什么?

简而言之
this 是一个在函数运行时动态绑定的上下文对象,它的指向完全由 函数调用方式 决定,而非函数定义的位置。具体规则包括:

  • 默认绑定 :独立调用时指向全局对象(严格模式下为 undefined)。
  • 隐式绑定:通过对象方法调用时指向该对象。
  • 显式绑定 :通过 call/apply/bind 强制指定。
  • new 绑定:构造函数中指向新创建的对象。

当一个函数被调用时,会创建一个活动记录(有时候也称为执行上下文)。这个记录会包含函数在哪里被调用(调用栈)、函数的调用方式、传入的参数等信息。this就是这个记录的一个属性,会在函数执行的过程中用到。 (具体细节将在后续的全面解析中展开。)


结语

理解 this 的关键是 抛弃直觉,关注调用方式 。它不是魔法,而是一套基于调用规则的上下文传递机制。在后续文章中,我们将深入探讨 this 的四大绑定规则,并解锁 箭头函数严格模式 等进阶场景下的行为差异。

相关推荐
敲敲了个代码14 小时前
从硬编码到 Schema 推断:前端表单开发的工程化转型
前端·javascript·vue.js·学习·面试·职场和发展·前端框架
dly_blog16 小时前
Vue 响应式陷阱与解决方案(第19节)
前端·javascript·vue.js
消失的旧时光-194316 小时前
401 自动刷新 Token 的完整架构设计(Dio 实战版)
开发语言·前端·javascript
console.log('npc')16 小时前
Table,vue3在父组件调用子组件columns列的方法展示弹窗文件预览效果
前端·javascript·vue.js
用户479492835691517 小时前
React Hooks 的“天条”:为啥绝对不能写在 if 语句里?
前端·react.js
我命由我1234517 小时前
SVG - SVG 引入(SVG 概述、SVG 基本使用、SVG 使用 CSS、SVG 使用 JavaScript、SVG 实例实操)
开发语言·前端·javascript·css·学习·ecmascript·学习方法
用户479492835691517 小时前
给客户做私有化部署,我是如何优雅搞定 NPM 依赖管理的?
前端·后端·程序员
C_心欲无痕17 小时前
vue3 - markRaw标记为非响应式对象
前端·javascript·vue.js
qingyun98918 小时前
深度优先遍历:JavaScript递归查找树形数据结构中的节点标签
前端·javascript·数据结构
胡楚昊18 小时前
NSSCTF动调题包通关
开发语言·javascript·算法