这个问题最近一直困扰着我,我感到自己无法理解Function对象的本质是什么。
如果说是一个普通的js变量。比如
var a = 5;
我可以把它理解为开辟了某个内存给变量a,并把内容赋值为5。
那么如果我定义了一个函数:
var fn = function() { console.log(this); };
它在内存中又是怎么存储的?
其实把我的问题再具体话一点,可以这样问:
1. Function对象中如何保存作用域链的上下文(context)
2. Function对象的函数体是以字符串的形式存储下来的吗?
再看看下面这个例子:
var fn;
(function(){
var a = 5;
fn = function () {
console.log(a++);
};
})();
fn();
这是个常见的闭包例子,就拿这个例子来说,Function对象是如何把变量a保存在自己的上下文环境中的呢?
首先感谢大家热情的回答,我再补充说明一下:
我主要的问题是Function在解释器引擎(比如Google V8)里是以怎样的形式实现的?是把函数体以字符串的形式存储下来,并在执行时以类似eval方法来调用它,或者还是其他方式?所以我的实际问题可能比较底层一点。
在JS中,Function对象就是Function对象。
在引擎中,随便它怎么实现都可以,只要遵照ECMAScript Spec就行了。
不过只要不是玩具引擎,就肯定不会把代码作为字符串存储,到执行到函数时再去解析的。
要理解这个问题,你需要定个目标,你要理解到什么样的深度呢?
我有个建议,就是不要把问题描述得多么底层。这样除了提高B格之外,其实无助于理解问题。这个问题是一个语义上的问题,而非实现上的问题,不定非要你会C++、汇编才能理解。
你要知道Closure、This这些对象是如何传给函数的,即你说的闭包变量是如何传给函数的。你只需要参照ECMAScript Spec中「Creating Function Objects」、「Executable Code and Execution Contexts」这些章节就行了。
伪代码其实就是这样,对于下面的代码:
经过解析,会变成类似下面的对象:
至于引擎中怎么实现的,展开来太多了。理解这些至少需要理解 SouceCode => Tokens => AbstractSyntaxTree .....=> JIT Code 这个流程。将代码转换成抽象语法树的部分在最初JS加载解析时就已经执行了,不然怎么能不执行函数就能报告语法错误呢?虽然Spec用eval(code)这种方式抽述,但解释器实现显然最多只需要直接遍历已经Parse过后的Tree就行了,绝不会再去读取函数的FunctionBody的字符串解析执行的。如果还想更深入地理解,还不如直接去读编译原理。