Skip to content

JavaScript作用域链

一、执行上下文与作用域

1.1 执行上下文的创建过程

在JavaScript中,代码执行前会创建执行上下文,包含三个重要部分:

javascript
ExecutionContext = {
    // 变量对象/活动对象
    VO/AO: {
        arguments: {},
        variables: [],  // 变量声明
        functions: []   // 函数声明
    },
    // 作用域链
    ScopeChain: [],
    // this绑定
    ThisBinding: {}
}

示例代码:

javascript
function outer() {
    var a = 1;
    function inner() {
        var b = 2;
        console.log(a); // 1
    }
    inner();
}
outer();

// 执行上下文栈(ECS)变化过程:
// 1. 全局执行上下文入栈
// 2. outer函数执行上下文入栈
// 3. inner函数执行上下文入栈
// 4. inner函数执行完毕,出栈
// 5. outer函数执行完毕,出栈

1.2 变量对象与活动对象

javascript
function foo(a) {
    var b = 2;
    function c() {}
    var d = function() {};
}
foo(1);

// 创建阶段的AO对象
AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}

// 执行阶段的AO对象
AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 2,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}

二、作用域链的形成过程

2.1 词法作用域的建立

javascript
var scope = "global";

function checkscope() {
    var scope = "local";
    function f() {
        return scope;
    }
    return f;
}

// f函数的作用域链
f.[[Scope]] = [
    checkscope.AO,
    globalContext.VO
];

2.2 作用域链与闭包

javascript
function createCounter(initial) {
    // 创建私有作用域
    let counter = initial;
    
    // 返回包含多个闭包的对象
    return {
        increment(step = 1) {
            counter += step;
            return counter;
        },
        decrement(step = 1) {
            counter -= step;
            return counter;
        },
        get value() {
            return counter;
        }
    };
}

const counter = createCounter(0);
console.log(counter.value);      // 0
console.log(counter.increment()); // 1
console.log(counter.increment(2)); // 3
console.log(counter.decrement(1)); // 2

三、作用域链查找机制

3.1 标识符解析过程

javascript
var x = 10;

function foo() {
    var y = 20;
    
    function bar() {
        var z = 30;
        console.log(x + y + z);
    }
    
    return bar;
}

// 标识符解析过程
// 1. 当前作用域查找
// 2. 沿作用域链向上查找
// 3. 直到全局作用域
// 4. 未找到则报ReferenceError

3.2 with和eval对作用域链的影响

javascript
function withExample(obj) {
    with(obj) {
        // with会在作用域链前端添加obj
        var x = y; // 可能访问obj.y或全局y
    }
}

function evalExample(code) {
    // eval会创建新的作用域
    eval(code); // 可以访问和修改当前作用域的变量
}

// 不建议使用with和eval,因为:
// 1. 性能问题
// 2. 代码难以预测和维护
// 3. 严格模式下with被禁用

四、作用域链优化

4.1 变量访问优化

javascript
// 不优化的版本
function slowLoop() {
    var i;
    var len = document.getElementsByTagName('*').length;
    for(i = 0; i < len; i++) {
        document.getElementsByTagName('*')[i].style.display = 'none';
    }
}

// 优化后的版本
function fastLoop() {
    var elements = document.getElementsByTagName('*');
    var len = elements.length;
    var i;
    for(i = 0; i < len; i++) {
        elements[i].style.display = 'none';
    }
}

4.2 闭包优化

javascript
// 不优化的闭包
function createFunctions() {
    var result = [];
    for(var i = 0; i < 10; i++) {
        result[i] = function() {
            return i;
        };
    }
    return result;
}

// 优化后的闭包
function createFunctions() {
    var result = [];
    for(var i = 0; i < 10; i++) {
        result[i] = (function(num) {
            return function() {
                return num;
            };
        }(i));
    }
    return result;
}

五、实际应用场景

5.1 模块化实现

javascript
const Module = (function() {
    // 私有变量和方法
    let privateData = [];
    
    function privateMethod() {
        return 'private';
    }
    
    // 特权方法
    function privilegedMethod() {
        return privateData;
    }
    
    // 公共API
    return {
        publicMethod: function() {
            privateData.push('data');
            return privilegedMethod();
        }
    };
})();

5.2 事件处理优化

javascript
// 不优化的版本
for(var i = 0; i < buttons.length; i++) {
    buttons[i].onclick = function() {
        console.log('Button ' + i + ' clicked');
    };
}

// 优化后的版本
for(var i = 0; i < buttons.length; i++) {
    (function(index) {
        buttons[index].onclick = function() {
            console.log('Button ' + index + ' clicked');
        };
    })(i);
}

// ES6优化版本
for(let i = 0; i < buttons.length; i++) {
    buttons[i].onclick = function() {
        console.log('Button ' + i + ' clicked');
    };
}

六、注意事项与最佳实践

6.1 避免全局变量污染

javascript
// 不好的实践
function bad() {
    name = 'global'; // 意外创建全局变量
}

// 好的实践
function good() {
    'use strict';
    let name = 'local'; // 局部变量
}

6.2 合理使用闭包

javascript
// 内存泄漏的闭包
function leakMemory() {
    const largeData = new Array(1000000);
    return function() {
        return largeData[0];
    };
}

// 优化后的闭包
function optimizedClosure() {
    const data = new Array(1000000)[0];
    return function() {
        return data;
    };
}

总结

  1. 作用域链是由当前执行环境的变量对象及其所有父执行环境的变量对象组成
  2. 标识符解析是沿着作用域链一级一级搜索的过程
  3. 优化建议:
    • 减少作用域链查找层级
    • 使用局部变量缓存频繁访问的外部变量
    • 避免使用with和eval
    • 合理使用闭包,防止内存泄漏
  4. 实际应用中要注意:
    • 模块化封装
    • 变量访问优化
    • 闭包性能
    • 内存管理