一个JS面试题

---只有程序员才懂的幽默---
一程序员去面试,面试官问:“你毕业才两面,这三年工作经验是怎么来的?!”程序员回答:“加班。”

最近一段时间呢,被一个JS面试题刷屏了,接下来我们也简单谈一下这道JS面试题所引发的思考。
题目是这样的:

//比较下面两段代码,试述两段代码的不同之处
// A--------------------------
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f();
}
checkscope(); 
// B---------------------------
var scope = "global scope";
function checkscope(){
    var scope = "local scope";
    function f(){
        return scope;
    }
    return f;
}
checkscope()();

首先A、B两段代码输出返回的都是 “local scope”,如果对这一点还有疑问的同学请自觉回去温习一下js作用域的相关知识。
接下来我们分析下这两段代码的执行过程:
首先是A:

进入全局环境上下文,全局环境被压入环境栈,contextStack = [globalContext]
全局上下文环境初始化,

globalContext={
    variable object:[scope, checkscope],
    scope chain: variable object // 全局作用域链
}

,同时checkscope函数被创建,此时 checkscope.[[Scope]] = globalContext.scopeChain
执行checkscope函数,进入checkscope函数上下文,checkscope被压入环境栈,contextStack=[checkscopeContext, globalContext]。随后checkscope上下文被初始化,它会复制checkscope函数的[[Scope]]变量构建作用域,即 checkscopeContext={ scopeChain : [checkscope.[[Scope]]] }
checkscope的活动对象被创建 此时 checkscope.activationObject = [arguments], 随后活动对象被当做变量对象用于初始化,checkscope.variableObject = checkscope.activationObject = [arguments, scope, f],随后变量对象被压入checkscope作用域链前端,(checckscope.scopeChain = [checkscope.variableObject, checkscope.[[Scope]] ]) == [[arguments, scope, f], globalContext.scopeChain]
函数f被初始化,f.[[Scope]] = checkscope.scopeChain。
checkscope执行流继续往下走到 return f(),进入函数f执行上下文。函数f执行上下文被压入环境栈,contextStack = [fContext, checkscopeContext, globalContext]。函数f重复 第4步 动作。最后 f.scopeChain = [f.variableObject,checkscope.scopeChain]
函数f执行完毕,f的上下文从环境栈中弹出,此时 contextStack = [checkscopeContext, globalContext]。同时返回 scope, 解释器根据f.scopeChain查找变量scope,在checkscope.scopeChain中找到scope(local scope)。
checkscope函数执行完毕,其上下文从环境栈中弹出,contextStack = [globalContext]
如果你理解了A的执行流程,那么B的流程在细节上一致,唯一的区别在于B的环境栈变化不一样,

A: contextStack = [globalContext] —> contextStack = [checkscopeContext, globalContext] —> contextStack = [fContext, checkscopeContext, globalContext] —> contextStack = [checkscopeContext, globalContext] —> contextStack = [globalContext]

B: contextStack = [globalContext] —> contextStack = [checkscopeContext, globalContext] —> contextStack = [fContext, globalContext] —> contextStack = [globalContext]

也就是说,真要说这两段代码有啥不同,那就是他们执行过程中环境栈的变化不一样,其他的两种方式都一样。

其实对于理解这两段代码而言最根本的一点在于,javascript是使用静态作用域的语言,他的作用域在函数创建的时候便已经确定(不含arguments)。

说了这么一大坨偏理论的东西,能坚持看下来的同学估计都要睡着了…是的,这么一套理论性的东西纠结有什么用呢,我只要知道函数作用域在创建时便已经生成不就好了么。没有实践价值的理论往往得不到重视。那我们来看看,当我们了解到这一套理论之后我们的世界到底会发生了什么变化:
这样一段代码:

function setFirstName(firstName){
 
    return function(lastName){
        return firstName+" "+lastName;
    }
}
 
var setLastName = setFirstName("kuitos");
var name = setLastName("lau");
 
// 调用setFirstName函数时返回一个匿名函数,该匿名函数会持有setFirstName函数作用域的变量对象(里面包含arguments和firstName),不管匿名函数是否会使用该变量对象里的信息,这个持有逻辑均不会改变。
// 也就是当setFirstName函数执行完之后其执行环境被销毁,但是他的变量对象会一直保存在内存中不被销毁(因为被匿名函数hold)。同样的,垃圾回收机制会因为变量对象被一直hold而不做回收处理。这个时候内存泄露就发生了。这时候我们需要做手动释放内存的处理。like this:
setLastName = null;
// 由于匿名函数的引用被置为null,那么其hold的setFirstName的活动对象就能被安全回收了。
// 当然,现代浏览器引擎(以V8为首)都会尝试回收闭包所占用的内存,所以这一点我们也不必过多处理。
                                                  2016-11-30 00:58:53

你可能感兴趣的:(一个JS面试题)