(翻译) 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):
a
andb
— 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分配出来的那段空间了
随机推荐
- Hive运行引擎Tez的安装
简介 Tez是Apache开源的支持DAG作业的计算框架,它直接源于MapReduce框架,核心思想是将Map和Reduce两个操作进一步拆分,即Map被拆分成Input.Processor.Sort ...
- JAVA遇见HTML——Servlet篇:Servlet基础
代码实现: HelloServlet package servlet; import java.io.IOException; import java.io.PrintWriter; import j ...
- 02-命令篇——基础命令&常用命令
基础命令 docker 启动与停止 启动docker systemctl start docker 停止docker systemctl stop docker 重启docker systemctl ...
- C# 重写IComparer 接口
首先定义比较类 继承自IComparer<Racer> public class RacerComparer : IComparer<Racer> { public enum ...
- selenium报错以及各解决方法
1.driver.findElement(By.name("wd")).sendKeys("selenium"); 报错:The method sendKeys ...
- BZOJ 3903 反垄断 (最大流推的结论题)
题目 中文题目,不解释: BZOJ传送门 分析 这道题BZOJ上也只有几个人过-奇怪了 下面是正解 原问题为一个二分图边染色问题.首先考虑最好情况.最理想情况的分配为:设一个点xxx的度为dgr(x) ...
- [Google Guava] 排序: Guava强大的”流畅风格比较器”
原文链接 译者: 沈义扬 排序器[Ordering]是Guava流畅风格比较器[Comparator]的实现,它可以用来为构建复杂的比较器,以完成集合排序的功能. 从实现上说,Ordering实例就是 ...
- 基于steam的游戏销量预测 — PART 3 — 基于BP神经网络的机器学习与预测
语言:c++ 环境:windows 训练内容:根据从steam中爬取的数据经过文本分析制作的向量以及标签 使用相关:无 解释: 就是一个BP神经网络,借鉴参考了一些博客的解释和代码,具体哪些忘了,给出 ...
- leetcode解题报告(10):Merge Two Sorted Lists
描述 Merge two sorted linked lists and return it as a new list. > The new list should be made by sp ...
- 在Android中使用OpenGL ES进行开发第(二)节:定义图形
一.前期基础知识储备笔者计划写三篇文章来详细分析OpenGL ES基础的同时也是入门关键的三个点: ①OpenGL ES是什么?与OpenGL的关系是什么?——概念部分 ②使用OpenGLES绘制2D ...