JavaScript 的 this 原理

作者: 阮一峰

日期: 2018年6月18日

一、问题的由来

学懂 JavaScript 语言,一个标志就是理解下面两种写法,可能有不一样的结果。


var obj = {
  foo: function () {}
};

var foo = obj.foo;

// 写法一
obj.foo()

// 写法二
foo()

上面代码中,虽然obj.foofoo指向同一个函数,但是执行结果可能不一样。请看下面的例子。


var obj = {
  foo: function () { console.log(this.bar) },
  bar: 1
};

var foo = obj.foo;
var bar = 2;

obj.foo() // 1
foo() // 2

这种差异的原因,就在于函数体内部使用了this关键字。很多教科书会告诉你,this指的是函数运行时所在的环境。对于obj.foo()来说,foo运行在obj环境,所以this指向obj;对于foo()来说,foo运行在全局环境,所以this指向全局环境。所以,两者的运行结果不一样。

这种解释没错,但是教科书往往不告诉你,为什么会这样?也就是说,函数的运行环境到底是怎么决定的?举例来说,为什么obj.foo()就是在obj环境执行,而一旦var foo = obj.foofoo()就变成在全局环境执行?

本文就来解释 JavaScript 这样处理的原理。理解了这一点,你就会彻底理解this的作用。

二、内存的数据结构

JavaScript 语言之所以有this的设计,跟内存里面的数据结构有关系。


var obj = { foo:  5 };

上面的代码将一个对象赋值给变量obj。JavaScript 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量obj

也就是说,变量obj是一个地址(reference)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。

原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo属性,实际上是以下面的形式保存的。


{
  foo: {
    [[value]]: 5
    [[writable]]: true
    [[enumerable]]: true
    [[configurable]]: true
  }
}

注意,foo属性的值保存在属性描述对象的value属性里面。

三、函数

这样的结构是很清晰的,问题在于属性的值可能是一个函数。


var obj = { foo: function () {} };

这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性。


{
  foo: {
    [[value]]: 函数的地址
    ...
  }
}

由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。


var f = function () {};
var obj = { f: f };

// 单独执行
f()

// obj 环境执行
obj.f()

四、环境变量

JavaScript 允许在函数体内部,引用当前环境的其他变量。


var f = function () {
  console.log(x);
};

上面代码中,函数体里面使用了变量x。该变量由运行环境提供。

现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。


var f = function () {
  console.log(this.x);
}

上面代码中,函数体里面的this.x就是指当前运行环境的x


var f = function () {
  console.log(this.x);
}

var x = 1;
var obj = {
  f: f,
  x: 2,
};

// 单独执行
f() // 1

// obj 环境执行
obj.f() // 2

上面代码中,函数f在全局环境执行,this.x指向全局环境的x

obj环境执行,this.x指向obj.x

回到本文开头提出的问题,obj.foo()是通过obj找到foo,所以就是在obj环境执行。一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。

(完)

留言(90条)

文中讲到 “环境” ,我脑中浮现的是“调用者级级别”或者“调用者所在的栈层”

简单明了

上面代码中,函数f在全局环境执行,this.x指向全局环境的this。——>上面代码中,函数f在全局环境执行,this.x指向全局环境的x。

很喜欢前辈的文章,通俗易懂

Hello

有个小建议

这样 是不是更好一点?

引用焦某的发言:

Hello

有个小建议


这样 是不是更好一点?

没想到是纯html。。。。。

搜索按钮的onclick值改为
this.form.wd.value = this.form.origin.value + ' 阮一峰'


是不是稍微好点?

如果不这样还有其他奇怪的百度知道的提问加上你的名字乱入搜索结果。

对this的描述依然抽象, 感觉画了那么多diagram意义不大, 只保留第一部分就好了

对 this 的理解又进一步加深,感谢

@dc:

谢谢指出,已经改正。

简洁明了

分享一篇自己总结的有道笔记:
http://note.youdao.com/noteshare?id=b2fab3b044aa90033395df0c8c9ca3a4&sub=6DA85DF56143475588CA9E0F9690FCBE

其中主要的内容是这篇文章里的,是我读过的,关于this指向写的最简单的文章了:
http://davidshariff.com/blog/javascript-this-keyword/#first-article

其中这句话算是精华了吧:
Whenever a function is called, we must look at the immediate left side of the brackets / parentheses “()”. If on the left side of the parentheses we can see a reference, then the value of “this” passed to the function call is exactly of which that object belongs to, otherwise it is the global object.

谢谢阮的分享,作为一个初学者,令我在JavaScript上更容易理解。
不知道阮是否愿意分享一下计算机的独特见解,最近在了解图灵,自己也有一些对计算机重新的认识,不知道阮在这方面的理解是什么,可能有些冒昧,请谅解

通俗易懂,非常好

这什么都没有解释啊。用运行环境解释运行环境。还引出了属性描述对象,但后面又并没有解释任何相关性。

个人理解 this指的是调用者所处的栈的内存地址, 不知是否正确

可以这么理解么:

说白了就是看具体执行的那一句的作用域:

foo()就是在全局执行
bar.foo()就是在bar里执行

想起来去年学校里的Java考试在考察继承和多态的时候好像也有类似的知识点 = =

很不错,理解了函数和值在内存空间的存在形式。也理解了环境会因不同的赋值而有不同的结果。谢谢

我有个建议,希望老师您能在每个网页加一个回到顶部的按钮.这样方便阅读

望采纳,哈哈

Javascript 是一个文本作用域的语言, 就是说, 一个变量的作用域, 在写这个变量的时候确定. this 关键字是为了在 JS 中加入动态作用域而做的努力. 所谓动态作用域, 就是说变量的作用范围, 是根据函数调用的位置而定的. 从这个角度来理解 this, 就简单的多.

this 是 JS 中的动态作用域机制, 具体来说有四种, 优先级有低到高分别如下:
1. 默认的 this 绑定, 就是说 在一个函数中使用了 this, 但是没有为 this 绑定对象. 这种情况下, 非严格默认, this 就是全局变量 Node 环境中的 global, 浏览器环境中的 window.
2. 隐式绑定: 使用 obj.foo() 这样的语法来调用函数的时候, 函数 foo 中的 this 绑定到 obj 对象.
3. 显示绑定: foo.call(obj, ...), foo.apply(obj,[...]), foo.bind(obj,...)
4. 构造绑定: new foo() , 这种情况, 无论 foo 是否做了绑定, 都要创建一个新的对象, 然后 foo 中的 this 引用这个对象.

详见: Your Donot Know JS : this & Object

@zhangjg:

对对,看了 Your Donot Know JS 这本书,恍然大悟。

这篇文章,学到了!

谢谢分享,通俗易懂,直观明了!赞一个!

引用zhangjg的发言:

Javascript 是一个文本作用域的语言, 就是说, 一个变量的作用域, 在写这个变量的时候确定. this 关键字是为了在 JS 中加入动态作用域而做的努力. 所谓动态作用域, 就是说变量的作用范围, 是根据函数调用的位置而定的.从这个角度来理解 this, 就简单的多.

this 是 JS 中的动态作用域机制, 具体来说有四种, 优先级有低到高分别如下:
1. 默认的 this 绑定, 就是说 在一个函数中使用了 this, 但是没有为 this 绑定对象. 这种情况下, 非严格默认, this 就是全局变量 Node 环境中的 global, 浏览器环境中的 window.
2. 隐式绑定: 使用 obj.foo() 这样的语法来调用函数的时候, 函数 foo 中的 this 绑定到 obj 对象.
3. 显示绑定: foo.call(obj, ...), foo.apply(obj,[...]), foo.bind(obj,...)
4. 构造绑定: new foo() , 这种情况, 无论 foo 是否做了绑定, 都要创建一个新的对象, 然后 foo 中的 this 引用这个对象.

详见: Your Donot Know JS : this & Object

补充一下:严格模式下,默认的this就是undefined了

常来老师的blog,最中意这种简单明了又探究到底的文章,无限个赞!

感谢您的指导。感谢。看了几年,第一次回复!
谢谢~

感谢老师的指导。从入行就在看您的文章,谢谢~

谢谢阮老师分享

简单概括起来:this指向当前环境。
当js运行起来,会有一个大环境,直接使用函数,this会指向当前大环境。this.x就会在大环境中找x。
对象会带有小环境,所以,对象中的this.x会去寻找小环境中的x。
结论:有对象就是不一样~~~

阮老师,加油~

函数在定义的时候,解释器已经把当前的环境和函数一起打包保存起来了吧,所以才说函数有词法作用域啰。至于像 obj.foo() 这种对象调用函数的语法,无非就是 obj.foo(obj)的语法糖了吧, 如果是直接 foo() 这样函数调用,没有传this引用,函数体在执行时就只好从定义时打包的环境中找了,找不到就一层一层往上找,直到顶层发现this代表window对象,就用window啰。

引用展翼骐骥的发言:

这什么都没有解释啊。用运行环境解释运行环境。还引出了属性描述对象,但后面又并没有解释任何相关性。

我也是这样觉得,一开始就说“这种解释没错,但是教科书往往不告诉你,为什么会这样?”,然后讲了怎么存。就到了再后面“上面代码中,函数体里面的this.x就是指当前运行环境的x。”,“上面代码中,函数f在全局环境执行,this.x指向全局环境的x。”...

这不也是和“往往不告诉你,为什么会这样?”一样吗?根本没讲原理什么的。肯定通俗易懂啊。。。

我沒有看過 JavaScript 實作,如果作者是看過 JavaScript 實作寫了這篇,那表示 JavaScript 的 this 就是保存環境物件,而不是實例(Instance),而且 this 環境物件與實例在存取上具有相同的介面,這樣就不用區別兩者的不同。

也就難怪沒有 dot 運算子的時候,this 會是指向全域,因為這時一定是頂層的環境物件。

我在實現 ToyLang(https://github.com/JustinSDK/toy_lang) 時的作法不同,使用了函式本身保存環境物件,至於 this,單純就當成函式的隱含參數,若是透過 dot 運算子,在呼叫函式時會將 dot 左邊的物件當成是隱含的引數,因此,當函式不透過 dot 運算子時,是不會有 this 的,也就不會有指向全域的問題。

JavaScript 的作法在好處之一,是實現 Closure 時比較方便,因為都是在環境物件上查找,我的作法在實現 Closure 時,在目前環境物件上找不到變數時,要繼續在函式的環境物件上查找。

我的作法好處是,理解 this 簡單的多,它就是函式隱含的參數罷了,只有在使用 dot 運算時才會有這個參數,也沒有必須區別是否指向全域的問題 … XD

感谢老师的分享,对this的本质了解又加深一步了

您好,有一个疑问,foo中的value写的是指向函数的地址,那么指向箭头是不是应该由value:函数的地址 ---> function(){console.log(this.x)}

正巧看到一片类似的文章和大家分享下。

Understanding JavaScript Function Invocation and "this"
https://yehudakatz.com/2011/08/11/understanding-javascript-function-invocation-and-this/

充分体现了JavaScript的博大精深,还是像这样剖析起来才容易理解

感谢分享,不过老师你在上面引用描述对象的时候为什么要用两个[[]]嵌套使用

结合你的图,附上作用域链的思想应该可以理解了!

啊,我明白了,感谢阮大

>this指向所在环境或者调用它的对象。
这是最常见的解释,但是好像并不准确,因为它并不能解释下面的例子:
```
var value = 1;

var foo = {
value: 2,
bar: function () {
return this.value;
}
}
console.log((false || foo.bar)()); // 1
```

感觉什么都没有解释

没想到2018年了阮老师还在讲述this的知识
只可惜老师讲得还是和我之前所了解得并无太大差别

@Lasy:

个人看法:
`(false || foo.bar)()`,这条执行语句可以分步理解。
let step_1 = foo.bar; // 把 foo.bar 方法赋值给 step_1;
let step_2 = step_1(); // 此时 step_1 作为一个普通函数执行,this 指向 window

总结:由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。JavaScript 允许在函数体内部,引用当前环境的其他变量。而为了获得当前的运行环境,所以this就出现了,因此函数里面的this函数当前的运行环境。开头提出的问题,obj.foo()是通过obj找到foo(这时,引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性),所以就是在obj环境执行。一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。

一句话:this 总是指向调用它所在的函数的那个对象

一直没想明白的是,为什么函数的设计中,要使用隐式的this,如果函数中调用非参数的变量的时候,显示使用this会不会使开发者能够更加清晰的知道函数引用的变量的上下文是哪个

当前所运行的环境,一句话点醒!!

但是如果使用lambda表达式,如var obj = {foo:()=>{console.log(this.bar)},bar:1} ,那么在global调用obj.foo()的时候this就指向全局,返回2了……

你用通俗易懂的方式解释this,大神

var obj = {
foo: function () { console.log(this.bar) },
boo: function () { console.log(bar) },
bar: 1
};

var bar = 2;

obj.foo() //1

obj.boo() //2 想请教这个结果为啥是2啊

@Lasy:

因为这里console.log((false || foo.bar)())相当于执行(function () {
return this.value;
})()

引用sll的发言:

var obj = {
foo: function () { console.log(this.bar) },
boo: function () { console.log(bar) },
bar: 1
};

var bar = 2;

obj.foo() //1

obj.boo()//2想请教这个结果为啥是2啊

因为只有一个变量bar,它的值是2

Hi, 一峰
this 是一个 reference , 应该在栈内存中存放把, 指向堆内存中的实例对象~

1. https://stackoverflow.com/questions/51223872/this-keyword-in-stack-or-heap

2. https://stackoverflow.com/questions/29472208/this-keyword-working-mechanism-in-java

非常感谢,对this的理解更深了

引用sll的发言:

var obj = {
foo: function () { console.log(this.bar) },
boo: function () { console.log(bar) },
bar: 1
};

var bar = 2;

obj.foo() //1

obj.boo()//2想请教这个结果为啥是2啊

谁调用指向谁

棒啊,这么一想又是一种新的思路。

要是念头不通达,就这么想:

函数f,首先把f当作对象来搞,“调用对象”的写法是这样子的: f.call(e, para1, para2)
把这个e就当作是“环境变量”吧。又给这个“环境变量”起个别名叫this。
其次,把f当作函数来搞:认为函数调用f(para1, para2)只不过是f.call(global, para1, para2)的语法糖,那this就是global
obj.f(para1, para2)只不过是f.call(obj, para1, para2)的语法糖,那this就是obj


暗暗对自己(不要对别人)说,js中根本没有函数调用,只有“对象调用”,函数调用只是迎合其它语言的习惯写法搞的语法糖。

阮老师,我在vc code中执行了你的这段代码
var f = function () {
console.log(this.x);
}

var x = 1;
var obj = {
f: f,
x: 2,
};

// 单独执行
f() // 1

// obj 环境执行
obj.f() // 2

f()输出的是undefiend

阮老师好,
三、函数
这张图片,value中是地址,地址指向内存中的函数,所以这个箭头调转个方向是不是好一点。

引用Reacher的发言:

Hi, 一峰
this 是一个 reference , 应该在栈内存中存放把, 指向堆内存中的实例对象~

1. https://stackoverflow.com/questions/51223872/this-keyword-in-stack-or-heap

2. https://stackoverflow.com/questions/29472208/this-keyword-working-mechanism-in-java

你说的是理解this的重点

引用sll的发言:

var obj = {
foo: function () { console.log(this.bar) },
boo: function () { console.log(bar) },
bar: 1
};

var bar = 2;

obj.foo() //1

obj.boo()//2想请教这个结果为啥是2啊

因为只定义了一个值为2的bar,你把var语句删掉后第二个输出会报错,控制台输出“bat is not defined”

如果能添加在回调函数中this的例子就更好了

学过c++,更加方便理解。
c++里,类方法的第一个参数就是this。

这个只是解释一下吧。。。感觉和原理还是有些差距。。

环境是不是可以理解为内存地址

大神受我一拜

在刚接触js不久就开始慢慢去理解this的含义。最近在复习导航栏的代码时又遇到了。才感觉函数引用this是很重要的。
...
栏目1
栏目2
栏目3
...

var spanList=document.getElementsByTagName('span');
for(var i=0; i spanList[i].index=i; //对每个span元素添加属性
spanList[i].onclick=function(){ //为每个span添加点击事件
for(var j=0; j spanList[j].className=""; //
}
this.className='showNr'+this.index
//第一次看代码不明白为啥不用spanList[i].className , 这里定义和阐明的是一个点击事件引起的函数,所以每次
//点击span,函数的作用域都在变化,this的作用就体现出来。
}
}

去年下半年看这一篇看到属性描述对象(也就是属性的4个特征)的时候还一脸蒙蔽,最近订阅了winter的专栏,看的时候就明白了许多。

看了好几个小时才看懂,例子那个图啊 实在是不敢恭维,但还是感谢

文章很好,可是看完之后还有一个关键疑问就是怎么判断一个函数所处的运行环境

一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。 为什么变量foo直接指向函数本身,foo()就变成全局环境执行

关于这个MDN中的对象一节有说明: 关键字"this"指向了当前代码运行时的对象( 原文:the current object the code is being written inside )



链接: https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Objects/Basics

this在回调函数里面的应用呢??

我用node 测试,得到的结果是1和undefined。

老师的讲解很吸引我,赞

其实就是 javascript 语言太不严谨导致的问题,历史原因!

前辈。请问请问,在函数内一直使用箭头函数,让this指向当前函数好还是先将this保存为_this再去使用好呢。this是怎么生成的呢,是this.xx一次就检查一下this属于谁吗,重复使用this.xx和_this.xx这俩谁好啊。

请问阮老师,这些知识点是在哪里看到的?请赐教

对于箭头函数改变this指针的问题好像没有在这个里面提到。

最后两张图里面的函数是‘f’不是‘foo’吧

我想说 这个这个措辞是不是不太准确,我无数次以为这个运行环境就是执行栈概念中的执行上下文。阮神是不是要更新一下这文章了。

let obj={
funs(){
let fun2=function(){console.log(this)} //window
fun2()
}
}
obj.funs()
为什么这种情况下this会指向全局对象不指向 obj呢 不是在obj环境运行的吗?

foo不是函数,还能foo()?

图画得高大上的,但通篇看了两遍下来,确实没有回答“函数的运行环境到底是怎么决定的?”这个问题。。。

文章中,
第三部分,“由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。”——嗯,没解释运行环境是怎么决定的。
第四部分,“上面代码中,函数f在全局环境执行,this.x指向全局环境的x”——陈述了事实,还是没解释为什么。
“obj.foo()是通过obj找到foo,所以就是在obj环境执行。一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。”——哈?!这是结论还是解释?

为什么函数要放在栈中,才能在不同的上下文中执行,哪位大神可以解释下?

引用165692640的发言:

文中讲到 “环境” ,我脑中浮现的是“调用者级级别”或者“调用者所在的栈层”

说的非常的好,实际上我认为可以抛开所谓的"全局环境"还是"局部环境".根据调用的栈层来理解,基本可以抛开一切表象上的陷阱,直指this的指向
掌握好this的指向问题,是JS开发者进阶的明显标志

引用Uganda的发言:

对this的描述依然抽象, 感觉画了那么多diagram意义不大, 只保留第一部分就好了

这也抽象吗?这不是已经很清晰明了了?this的作用是用来指向当前函数的上下文

引用165692640的发言:

文中讲到 “环境” ,我脑中浮现的是“调用者级级别”或者“调用者所在的栈层”

可以理解为执行函数时,通过谁找到的这个函数,函数就把谁作为当前环境。
在执行obj.foo(),的时候,通过在内存中找obj的foo属性,再找到该属性指向的函数,进而放入调用栈中去执行,因此是通过obj这个对象来找出来的这个执行帧。

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接