2015-11-03

What is closure?

In JavaScript, closures are often regarded as some sort of magical unicorn that only advanced developers can really understand, but truth be told it is just a simple understanding of the scope chain. A closure, as Douglas Crockford says, is simply:

An inner function always has access to the vars and parameters of its outer function, even after the outer function has returned…

The code below is an example of a closure:

// ==>进入全局执行上下文
var dongcCard = card('dongc');

dongcCard();

function card(sname) {
    var gender = 'male';
    return function showInfo() {
        console.log('[carInfo] name:%s gender:%s', sname, gender);
    }
}

read above code , have two doubts:

  • 为何card函数在定义之前,可以被调用?
  • 为何card函数执行完毕后,card函数中“gender”属性仍然可以被访问?

究其原由,深入内核底层,一探究竟。

How do javascript engine work?

The JavaScript interpreter in a browser is implemented as a single thread. Actions or Events being queued in what is called the execution stack. The diagram below is an abstract view of a single threaded stack: ecstack As we already know, when a browser first loads your script, it enters the global execution context by default. The browser will always execute the current execution context that sits on top of the stack, and once the function completes executing the current execution context, it will be popped off the top of the stack, returning control to the context below in the current stack.There are 4 key points to remember about the execution stack:

  • Single threaded
  • Synchronous execution
  • Global execution context
  • Each function call creates a execution context the following describes the above-mentioned professional terms Execution Context.

Execution context

An execution context is an abstract concept used by the ECMSScript specification (ECMA 262 3rd edition) to define the behaviour required of ECMAScript implementations. The specification does not say anything about how execution contexts should be implemented but execution contexts have associated attributes that refer to specification defined structures so they might be conceived (and even implemented) as objects with properties, though not public properties. That is to say, when any javaScript code is executing, it needs some place to store its local variables. Let’s call this place as a scope object.

Scope object are allocated in heap instead (or at least they behave like this), so they might stay allocated even if function already returned. More on that later.

“执行上下文对象(scope object)”的特性,类似于java中对象类型(Object Type)。如果,该函数的“执行上下文对象”没有被其他函数作用域链所引用,就会被gc标记、回收。

Execution context has two stage, as follows:

  • Creation stage: the interpreter creates the executionContextObj by scanning the function for parameters or arguments passed in, local function declarations and local variable declarations. The result of this scan becomes the variableObject(变量的值默认为undefined) in the executionContextObj.
  • Execution stage: assign values, references to functions and execute code(变量赋值、this绑定、执行代码) Under dom environment, divide into “Global execution context” and “Function execution context”

① Global execution context: a browser first loads your script, it enters the global execution context by default. it’s internal structure as follows:

globalContext["VO"] = {
    variables:'inner defined variable',
    functions:'inner function declarations',
    Math: 'js buitin function',
    String:'js buitin function',
    ...
    this: 'window(当前执行对象)'
};

② Function execution context: function execution “environment“, it’s internal structure as follows:

functionContext["VO"] ={
    scopeChian:{/* variableObject + all parent execution context's variableObject */},
    variableObject:{/* function arguments / parameters,inner variable and function declarations */},
    this:{}
}
  • scopeChain: 定义函数执行时变量可搜索范围,类似prototypal inheritance。其内部包含“当前函数的执行上下文” 和 “上层父作用域的执行上下文” (Lexical Scope)。
  • variableObject: 用于存储函数内部定义的参数、变量、函数等相关信息。在函数被调用时,其内部的变量被重新赋值,生成新的对象压入到scope chain栈中。
  • this:执行该函数的当前对象。this角色可以被动态绑定(参见Dynamic Scope)。

上述介绍“作用域链”(scope chain:函数执行时变量搜索的范围),下面介绍js解析器如何找寻变量。

Resolving the value of variables

The interpreter climb up scope chain to keeps checking each [VO] in sequence for the existence of the variable name, in which case the value will be returned to the original evaluated code, or the program will throw a ReferenceError if none is found.

scopchain

The global scope is the last stop in the scope chain, therefore, variables defined in the global scope can be accessed from anywhere.

有时,我们将上层父作用域中对象,引入到当前函数执行上下文。这样,js解析器不用“挨家挨户、苦苦找寻”,提高执行效率。实例代码如下:

(function(W,$){

......

})(window,jQuery);
// 将'window','jQuery'以函数参数的身份,引入到当前函数执行上下文对象中,作其内部属性.
全局对象局部化,避免interpreter苦苦寻觅

Case Analysis

剖析上述“实例代码一”,解答文章开头提出的两个疑问。
js执行过程可划分为两个阶段(① 解析阶段 ② 执行阶段),类似于java先编译、后执行。其中,“解析阶段” 对应着函数的Creation stage,“执行阶段”对应着函数的Execution stage。

① 解析阶段

“实例代码一”中函数card的执行上下文对象:

card[“VO”] ={
    scopeChain:{,globalContext[“VO”]},
    variableObject:{
        ‘gender’:’undefined’,
        ‘sname’:’undefined’
    }
    this:’undefined’
};
card函数收录内部变量gender、参数sname;及确定函数的词法作用域为“全局上下文

“实例代码一”中函数showInfo的执行上下文对象:

showInfo[“VO”] ={
    scopeChain:{,card[“VO”] + globalContext[“VO”]},
    variableObject:{},
    this:’undefined’
};
确定showInfo函数的词法作用域为“card上下文对象” + “全局上下文”。

“实例代码一”中全局执行上下文对象:

globalContext[“VO”] = {
    ‘dongcCard’:’undefined’,
    ‘card’:’refer to card function’,
    Math: ‘buitin function’,
    String:’buitin function’,
    …
  this: ‘undefined’
};
全局执行上下文对象” 收录在全局范围下定义的变量(dongcCard)、函数(card)及 js内建的函数(如:Math等)信息。所以,card函数在解析阶段,就被装载到全局上下文中。这就解释“ 为何函数card在定义之前,可以被调用?”产生原因

② 执行阶段

2 行代码调用card函数,创建新的执行上下文,压入到execution stack中。与此同时,生成新的执行上下对象,并对其内部变量进行赋值,压入到函数的作用域链中。此时,card函数执行上下文对象,内部结构如下:

card[“VO”]={
    scopeChain:{card[“VO”]+globalContext[“VO”]},
    variableObject:{
        ‘gender’:’male’,
        ‘sname’:’dongc’,
    }
    this:window
};
card函数执行完毕,返回函数showInfo,赋值给变量dongcCard(函数可以当’值’传递,参见 Functions as First-Class Things),dongcCard指向showInfo函数。此时,showInfo的执行上下文对象结构如下:

showInfo[“VO”]={
    scopeChain:{,card[“VO”]+globalContext[“VO”]},
    variableObject:{},
    this:’undefined’
};
由于,card函数执行时,生成的card[“VO”]仍被showInfo函数的作用域链引用,因此,card[“VO”]不会被gc回收。

第 4 行代码dongcCard函数执行时,仍然可以访问到gender属性。这就解释“为何函数card执行完毕后,card中”gender”属性仍然可以被访问?”产生的原因。上述“实例代码一”的执行堆栈图,如下: EC24

Summary

Closures are functions that refer to independent (free) variables. In other words, the function defined in the closure “remembers” the environment in which it was created. Technically, in JavaScript, Every Function Is A Closure” . It always has an access to variables defined in the surrounding scope.

 

附 录




“框架太多,回归原生”




(转载本站文章请注明出处 dongc.github.io ,请勿用于任何商业用途)