(翻译) How variables are allocated memory in Javascript? | scope chain | lexicial scope
总结: 阅读下面文章需要15分钟
提问者的问题是JavaScript中内存是怎么分配的,在介绍的过程作者涉及计到了JS中 Scope Chain和调用函数call生成lexicial environment和environment record(被作者合并称为 binding objects)的过程.非常值得一看
个人翻译:
How variables are allocated memory in Javascript?
It's actually a very interesting area of JavaScript, and there are at least two answers:
- An answer in terms of what the specification defines, and
- An answer in terms of what JavaScript engines actually do, which may be optimized (and often is)
实际上这是JavaScript中非常有意思的一部分内容.具体你可以在规范中查阅,
这里给出两个方向的参考答案:
- 就规范而言..是怎么定义的
- 就特定的js引擎而言..是怎么做的(这些引擎还通常都包含一些优化的内容)
In terms of the specification: JavaScript's way of handling local variables is quite different from the way C does it. When you call a function, amongst other things a lexical environment for that call is created, which has something called an environment record. To keep things simple, I'm going to refer to them both together as the "binding object" (there's a good reason they're separate in the specification, though; if you want to get deeper into it, set aside a few hours and read through the spec).
就规范而言,Javascript处理局部变量的方式和C语言有一些不一样,当你调用(call)一个函数,
会创建一个 lexical environment,
内部会包含一条 environment record,
为了简化,我会把他们合并着称为’binding object’( 在 规范中他们是分开的,如果你想更深入的了解,可以自己去看规范.)
The binding object contains bindings for the arguments to the function, all local variables declared in the function, and all functions declared within the function (along with a couple of other things).
binding object 包含了 bindings , 包括函数的 arguments , 函数内部定义的局部变量,和函数内部定义的函数(还包含一些其它东西)
---译注: 答者虽然没说,但是我想应该是还包含了this
A binding is a combination of a name (like a) and the current value for the binding (along with a couple of flags we don't need to worry about here).
binding 是指 名称 和 当前值的一组绑定
An unqualified reference within the function (e.g., the foo in foo, but not the foo in obj.foo, which is qualified) is first checked against the binding object to see if it matches a binding on it; if it does, that binding is used.
未找到的引用(比如找直接引用foo,而不是引用obj.foo) 会先从binding object里找,如果有这个binding就会用它
When a closure survives the function returning (which can happen for several reasons), the binding object for that function call is retained in memory because the closure has a reference to the binding object in place where it was created. So in specification terms, it's all about objects.
如果函数执行完return结束后还保留着一个closure(闭包) , 那个函数调用(call)生成的的binding object 会被保留在内存中,因为closure引用着binding object. 所以就规范而言, 这些变量都是声明在对象中的.(it is all about objects. --译注: 这里是指object,C/C++中的object 一般情况对象会放在堆内存存储)
At first glance, that would suggest that the stack isn't used for local variables; in fact, modern JavaScript engines are quite smart, and may (if it's worthwhile) use the stack for locals that aren't actually used by the closure. They may even use the stack for locals that do get used by the closure, but then move them into an binding object when the function returns so the closure continues to have access to them. (Naturally, the stack is still used for keeping track of return addresses and such.)
这么一看,好像局部变量的不是保存在stack中的(因为会持久保留在堆内存中 后面还要一直被引用).实际上,现代的JavaScript引擎很聪明很高级了,而且有可能会使用stack来保存出不被闭包引用的局部变量,但是当函数返回时会将他们移动到binding object中,以便closures可以继续访问他们. (理所当然的,stack仍然用来跟踪函数返回地址等..)
Here's an example:
function foo(a, b) {
var c;
c = a + b;
function bar(d) {
alert("d * c = " + (d * c));
}
return bar;
}
var b = foo(1, 2);
b(3); // alerts "d * c = 9"
When we call foo, a binding object gets created with these bindings (according to the spec):
aandb— the arguments to the functionc— a local variable declared in the functionbar— a function declared within the function- (...and a couple of other things)
当调用foo的时候 创建了一个binding object,里面有这些bindings:
- a和b -函数的arguments)
- c-函数内定义的一个局部变量
- bar-函数内定义的一个函数
- (…还有一些其他的东西)
When foo executes the statement c = a + b;, it's referencing the c, a, and b bindings on the binding object for that call to foo.
When foo returns a reference to the bar function declared inside it, bar survives the call to foo returning. Since bar has a (hidden) reference to the binding object for that specific call to foo, the binding object survives (whereas in the normal case, there would be no outstanding references to it and so it would be available for garbage collection).
当foo执行到c =a+b;的时候
引用了binding object中的c,a,b 这些binding
当foo 返回一个 bar的引用的时候 ,bar被保留了,因为bar有着对binding object的引用,所以binding object也被保留了(在正常情况下 没有被引用的话就要被垃圾回收了)
Later, when we call bar, a new binding object for that call is created with (amongst other things) a binding called d — the argument to bar. That new binding object gets a parent binding object: The one attached to bar. Together they form a "scope chain".
Unqualified references within bar are first checked against the binding object for that call to bar, so for instance, d resolves to the d binding on the binding object for the call to bar.
然后,当我们调用bar的时候,一个新的binding object生成了,其中一个binding叫d – bar的参数,这个binding object的父亲就是原来的binding object,他们一起形成了 “scope chain” 作用域链. bar函数内会开始查找没有被保留的引用, 举例 d 被解析成了 d binding,但是
But an unqualified reference that doesn't match a binding on that binding object is then then checked against its parent binding object in the scope chain, which is the binding object for the call to foo that created bar. Since that has a binding for c, that's the binding used for the identifier c within bar. E.g., in rough terms:
但是在binding object中找不到的引用,会接下来在它作用域链父亲的binding object上面找,foo的 binding object里有一个c,于是于是那个c binding就被bar里面c标识符使用了,粗略的说:
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| global binding object |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| .... |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
^
| chain
|
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| `foo` call binding object |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| a = 1 |
| b = 2 |
| c = 3 |
| bar = (function) |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
^
| chain
|
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| `bar` call binding object |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
| d = 3 |
+−−−−−−−−−−−−−−−−−−−−−−−−−−−+
Fun fact: This scope chain is how global variables work in JavaScript. Note the "global binding object" in the above. So in a function, if you use an identifier that isn't in the binding object for that function call, and isn't in any of the other binding objects between that and the global binding object, if the global binding object has a binding for it, the global binding is used. Voilà, global variables.
这个作用域链就是JavaScript里全局变量的工作方式 被标记了”globall binding object”
在一个函数里如果你用了一个标识符但是不在函数call产生的binding object里,就会一直向上找,直到global binding object.
(ES2015 made this a bit more interesting by having two layers to the global binding object: A layer used by old-fashioned global declarations like var and function declarations, and a layer used by newer ones like let, const, and class.
The difference is that the older layer also creates properties on the global object, which you kind of access via window on browsers, but the newer layer doesn't. So a global let declaration doesn't create a window property, but a global var declaration does.)
ES2015 通过向global binding object上添加了两层 让这一部分更有趣了一些
一层是使用旧风格的全局声明如var和 function declarations, 另一层使用更新的像let const class.
区别在于旧的层会在global object上创建 properties,你可以通过浏览器里的window来访问, 但新的一层不可以
所以,一个全局的let声明不会在window下创建,但是一个全局的var声明会在window下创建一个属性
Implementations are free to use whatever mechanism they want under the covers to make the above seem to happen.
It's impossible to get direct access to the binding object for a function call, and the spec makes clear that it's perfectly fine if the binding object is just a concept, rather than a literal part of the implementation.
具体的实现会根据引擎的实现机制而不同
不能直接拿到函数调用时产生的binding object
并且规范说了其实有binding object这种概念就可以,并没有具体涉及到实现的细节机制
A simple implementation may well just literally do what the spec says; a more complicated one may use a stack when there are no closures involved (for the speed benefit), or may always use a stack but then "tear off" the binding object needed for a closure when popping the stack. The only way to know in any specific case is to look at their code. :-)
一个简单的实现会逐字的照规范来做,更复杂的会为了优化,当涉及到closures时引入栈.. 除非你去看这些引擎的代码.
More about closures, the scope chain, etc. here:
- Closures are not complicated (somewhat out of date terminology)
- Poor misunderstood 'var'
资料:规范中关于lexial environment的一段截图

(翻译) How variables are allocated memory in Javascript? | scope chain | lexicial scope的更多相关文章
- 深入理解JavaScript系列(14):作用域链(Scope Chain)
前言 在第12章关于变量对象的描述中,我们已经知道一个执行上下文 的数据(变量.函数声明和函数的形参)作为属性存储在变量对象中. 同时我们也知道变量对象在每次进入上下文时创建,并填入初始值,值的更新出 ...
- JavaScript的语法要点 2 - Scope Chain
前文所述,JavaScript是基于词法作用域(lexically scoped)的,所以标识符被固定在它们被定义的作用域而不是语法上或是其被调用时的作用域.即全局变量的作用域是整个程序,局部变量的作 ...
- JavaScript变量作用域(Variable Scope)和闭包(closure)的基础知识
在这篇文章中,我会试图讲解JavaScript变量的作用域和声明提升,以及许多隐隐藏的陷阱.为了确保我们不会碰到不可预见的问题,我们必须真正理解这些概念. 基本定义 作用范围是个“木桶”,里面装着变量 ...
- JavaScript的作用域(Scope)和上下文(Context)
JavaScript对于作用域(Scope)和上下文(Context)的实现是这门语言的一个非常独到的地方,部分归功于其独特的灵活性. 函数可以接收不同的的上下文和作用域.这些概念为JavaScrip ...
- Javascript 执行上下文 context&scope
执行上下文(Execution context) 执行上下文可以认为是 代码的执行环境. 1 当代码被载入的时候,js解释器 创建一个 全局的执行上下文. 2 当执行函数时,会创建一个 函数的执行上下 ...
- [WASM] Write to WebAssembly Memory from JavaScript
We write a function that converts a string to lowercase in WebAssembly, demonstrating how to set the ...
- [WASM] Read WebAssembly Memory from JavaScript
We use an offset exporting function to get the address of a string in WebAssembly memory. We then cr ...
- 翻译:谷歌HTML、CSS和JavaScript风格规范
我喜欢浏览风格规范.他们通常有明显的规则,虽然有些有荒诞之感,但是却可以发现之前未注意到的宝石.不幸的是,鲜有公司有这个勇气来发布自己内部的风格规范.BBC 2010年时候公开其文档以及Google最 ...
- Dynamically allocated memory 动态分配内存【malloc】Memory leaks 内存泄漏
内存泄露Memory leaks :没有指针指向原来a分配出来的那段空间了
随机推荐
- [Mac][Python][Virtualenv]安装配置和使用
安装帮助文档安装VirtualEnv报错如下 tekiMacBook-Air:workspaces hbai$ source /usr/local/bin/virtualenvwrapper.sh / ...
- linux 欢迎界面
开博第一篇文章,简单地写一篇linux欢迎界面吧 可以通过修改/etc/motd 或/etc/issue两个文件实现修改登录显示 区别:/etc/motd:( 登录成功才会显示 ) /etc/issu ...
- Spark(三)角色和搭建
目录 Spark(三)角色和搭建 一.Spark集群角色介绍 二.集群的搭建 三.history服务 四.使用spark-submit进行计算Pi 五.Spark On Yarn 六.shell脚本 ...
- 解决python中调用 imread 报错:ImportError: cannot import name imread
安装了scipy后,报cannot import name imread错误, 1.网上查阅资料后说是需要安装pillow,安装pillow之后,仍然报该错误, 2.网上说是pillow与SciPy安 ...
- 什么是调整后的R方
当给模型增加自变量时,复决定系数也随之逐步增大,当自变量足够多时总会得到模型拟合良好,而实际却可能并非如此.于是考虑对R2进行调整,记为Ra2,称调整后复决定系数.R2=SSR/SST=1-SSE/S ...
- go deep copy map
func deepCopyJSON(src map[string]interface{}, dest map[string]interface{}) error { if src == nil { r ...
- Linux-删除文件空间不释放问题解决
场景描述: 收到zabbix监控报警,晋中生产机器出现磁盘空间不足报警. 远程到该服务器,排查占员工磁盘空间的原因,发现tomcat日志过多,于是清除3天前的日志. 日志清理后,发现磁盘空间没有释放, ...
- Jquery select 三级联动 (需要JSON数据)
Scripts/Category.js //Jquery三级类别联动 $(function () { BindCategory(); }) function BindCategory() { var ...
- danfu添加商品实例
GoodsBaseInfoVO extends GoodsBaseInfo JSONResponse saveOrUpdateBaseGoodinfo void insertGoodBaseInfo ...
- 第2组 Alpha冲刺(4/4)
队名:十一个憨批 组长博客 作业博客 组长黄智 过去两天完成的任务:了解整个游戏的流程 GitHub签入记录 接下来的计划:继续完成游戏 还剩下哪些任务:完成游戏 燃尽图 遇到的困难:没有美术比较好的 ...