总结: 阅读下面文章需要15分钟

提问者的问题是JavaScript中内存是怎么分配的,在介绍的过程作者涉及计到了JS Scope Chain和调用函数call生成lexicial environmentenvironment 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中非常有意思的一部分内容.具体你可以在规范中查阅,

这里给出两个方向的参考答案:

  1. 就规范而言..是怎么定义的
  2. 就特定的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

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:

  1. function foo(a, b) {
  1.     var c;
  1.  
  1.     c = a + b;
  1.  
  1.     function bar(d) {
  1.         alert("d * c = " + (d * c));
  1.     }
  1.  
  1.     return bar;
  1. }
  1.  
  1. var b = foo(1, 2);
  1. b(3); // alerts "d * c = 9"

When we call foo, a binding object gets created with these bindings (according to the spec):

  • a and b — the arguments to the function
  • c — a local variable declared in the function
  • bar — a function declared within the function
  • (...and a couple of other things)

当调用foo的时候 创建了一个binding object,里面有这些bindings:

  1. a和b -函数的arguments)
  2. c-函数内定义的一个局部变量
  3. bar-函数内定义的一个函数
  4. (…还有一些其他的东西)

When foo executes the statement c = a + b;, it's referencing the ca, 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标识符使用了,粗略的说:

  1. +−−−−−−−−−−−−−−−−−−−−−−−−−−−+
  1. |   global binding object   |
  1. +−−−−−−−−−−−−−−−−−−−−−−−−−−−+
  1. | ....                      |
  1. +−−−−−−−−−−−−−−−−−−−−−−−−−−−+
  1.              ^
  1.              | chain
  1.              |
  1. +−−−−−−−−−−−−−−−−−−−−−−−−−−−+
  1. | `foo` call binding object |
  1. +−−−−−−−−−−−−−−−−−−−−−−−−−−−+
  1. | a = 1                     |
  1. | b = 2                     |
  1. | c = 3                     |
  1. | bar = (function)          |
  1. +−−−−−−−−−−−−−−−−−−−−−−−−−−−+
  1.              ^
  1.             | chain
  1.              |
  1. +−−−−−−−−−−−−−−−−−−−−−−−−−−−+
  1. | `bar` call binding object |
  1. +−−−−−−−−−−−−−−−−−−−−−−−−−−−+
  1. | d = 3                     |
  1. +−−−−−−−−−−−−−−−−−−−−−−−−−−−+

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 letconst, 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:

资料:规范中关于lexial environment的一段截图

(翻译) How variables are allocated memory in Javascript? | scope chain | lexicial scope的更多相关文章

  1. 深入理解JavaScript系列(14):作用域链(Scope Chain)

    前言 在第12章关于变量对象的描述中,我们已经知道一个执行上下文 的数据(变量.函数声明和函数的形参)作为属性存储在变量对象中. 同时我们也知道变量对象在每次进入上下文时创建,并填入初始值,值的更新出 ...

  2. JavaScript的语法要点 2 - Scope Chain

    前文所述,JavaScript是基于词法作用域(lexically scoped)的,所以标识符被固定在它们被定义的作用域而不是语法上或是其被调用时的作用域.即全局变量的作用域是整个程序,局部变量的作 ...

  3. JavaScript变量作用域(Variable Scope)和闭包(closure)的基础知识

    在这篇文章中,我会试图讲解JavaScript变量的作用域和声明提升,以及许多隐隐藏的陷阱.为了确保我们不会碰到不可预见的问题,我们必须真正理解这些概念. 基本定义 作用范围是个“木桶”,里面装着变量 ...

  4. JavaScript的作用域(Scope)和上下文(Context)

    JavaScript对于作用域(Scope)和上下文(Context)的实现是这门语言的一个非常独到的地方,部分归功于其独特的灵活性. 函数可以接收不同的的上下文和作用域.这些概念为JavaScrip ...

  5. Javascript 执行上下文 context&scope

    执行上下文(Execution context) 执行上下文可以认为是 代码的执行环境. 1 当代码被载入的时候,js解释器 创建一个 全局的执行上下文. 2 当执行函数时,会创建一个 函数的执行上下 ...

  6. [WASM] Write to WebAssembly Memory from JavaScript

    We write a function that converts a string to lowercase in WebAssembly, demonstrating how to set the ...

  7. [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 ...

  8. 翻译:谷歌HTML、CSS和JavaScript风格规范

    我喜欢浏览风格规范.他们通常有明显的规则,虽然有些有荒诞之感,但是却可以发现之前未注意到的宝石.不幸的是,鲜有公司有这个勇气来发布自己内部的风格规范.BBC 2010年时候公开其文档以及Google最 ...

  9. Dynamically allocated memory 动态分配内存【malloc】Memory leaks 内存泄漏

    内存泄露Memory leaks :没有指针指向原来a分配出来的那段空间了

随机推荐

  1. Microsoft 中间语言

  2. Python(phone)模块获取手机号归属地、区号、运营商等

    Python(phone)模块获取手机号归属地.区号.运营商等 一.我使用的是python3,可以自行搜索下载 二.安装phone模块, pip install phone 三.测试代码如下: fro ...

  3. Python使用selenium模拟点击,进入下一页(三)

    嗯,昨天呢,我们已经实现了自动输入百度然后搜索Cgrain,然后点击按钮,进入我的页面,在这里呢,有个问题 ActionChains(seleniumGoo).move_by_offset(-480, ...

  4. 5.Hbase API 操作开发

    Hbase API 操作开发需要连接Zookeeper进行节点的管理控制 1.配置 HBaseConfiguration: 包:org.apache.hadoop.hbase.HBaseConfigu ...

  5. Spark2 jar存档

    spark.yarn.archive需要手动将spark应用依赖jar上传到hdfs,该属性可以避免每一次运行spark应用时都重复打zip包上传到hdfs. 官网http://spark.apach ...

  6. 0011SpringBoot的@EnableWebMvc全面接管SpringMVC的自动配置(源码)

    所谓的@EnableWebMvc全面接管SpringMVC的自动配置,是指@EnableWebMvc注解会使SpringMVC的自动配置失效,原理如下: 1.查看@EnableWebMvc的源码,如下 ...

  7. 09—mybatis注解配置join查询

    今天来聊mybatis的join查询,怎么说呢,有的时候,join查询确实能提升查询效率,今天举个left join的例子,来看看mybatis的join查询. 就不写的很细了,把主要代码贴出来了. ...

  8. 慕课网SSMOA办公系统

    目录 需求分析 1 用例图 系统设计 包及全局配置 数据库设计 工具类 具体功能实现 1 dao层功能实现 2 编码过滤器及登陆拦截器 3 单元测试 遇到的问题总结 1 新建一个Modul不会打开新页 ...

  9. java中vector中add,addElement区别

    这两个方法最大的区别就是返回值不一样,在作用上基本没有区别. add是实现List接口重写的方法,返回值为boolean.addElement是Vector类中的特有方法,返回值是void.

  10. luogu 2052 [NOI2011]道路修建 BFS序

    据说dfs会爆栈,写一个 BFS 序更新就好了~ #include <bits/stdc++.h> #define N 1000005 #define ll long long #defi ...