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 的四大绑定规则,并解锁 箭头函数严格模式 等进阶场景下的行为差异。

相关推荐
柚子816几秒前
用transform给vue加点功能
前端·vue.js·vite
串串狗xk1 分钟前
爽!新标签页运行 HTML,createObjectURL的妙用
javascript
wordbaby5 分钟前
Vue 图片重试指令 (v-img-retry) 增强:集成 visibility 控制,实现无缝加载过渡
前端·vue.js·http
亦止辰8 分钟前
AceEditor使用
前端
前端涂涂9 分钟前
nodejs中文件的重命名,移动,删除;文件夹的创建,递归创建,删除,读取;查看资源状态,批量重命名的用法,创建文件时的相对路径和绝对路径的区别和参照
前端
前端程序猿i11 分钟前
Vue组件库开发实战:从0到1构建可复用的微前端模块
前端·javascript·vue.js
幼儿园技术家18 分钟前
微信小程序/H5 调起确认收款界面
前端
键指江湖18 分钟前
React 对state进行保留和重置
javascript·react.js·ecmascript
微笑边缘的金元宝23 分钟前
Echarts柱状图斜线环纹(图形的贴花图案)
前端·javascript·echarts
wuxiguala27 分钟前
【web考试系统的设计】
前端