执行上下文与提升
执行上下文与提升
作用域this
的使用,而作用域则与变量的可见性相关;而
执行上下文
每个执行上下文又会分为内存创建(Creation Phase)与代码执行(Code Execution Phase)两个步骤,在创建步骤中会进行变量对象的创建this
对象。所谓的
'variableObject': {
// contains function arguments, inner variable and function declarations
}
在
'scopeChain': {
// contains its own variable object and other variable objects of the parent execution contexts
}
而执行上下文则可以表述为如下抽象对象:
executionContextObject = {
scopeChain: {}, // contains its own variableObject and other variableObject of the parent execution contexts
variableObject: {}, // contains function arguments, inner variable and function declarations
this: valueOfThis,
};
变量的生命周期与提升
变量的生命周期包含着变量声明

而

如上文所说,我们可以在某个变量或者函数定义之前访问这些变量,这即是所谓的变量提升(Hoisting
// const hoisting
num; // => undefined
const num;
num = 10;
num; // => 10
// function hoisting
getPi; // => function getPi() {...}
getPi(); // => 3.14
function getPi() {
return 3.14;
}
变量提升只对
console.log(b);
b = 1;
上面的语句将会报错,提示 ReferenceError: b is not defined
,即变量let
声明的变量同样会被提升,只不过不允许在实际声明语句前使用:
> let x = x;
ReferenceError: x is not defined
at repl:1:9
at ContextifyScript.Script.runInThisContext (vm.js:44:33)
at REPLServer.defaultEval (repl.js:239:29)
at bound (domain.js:301:14)
at REPLServer.runBound [as eval] (domain.js:314:12)
at REPLServer.onLine (repl.js:433:10)
at emitOne (events.js:120:20)
at REPLServer.emit (events.js:210:7)
at REPLServer.Interface._onLine (readline.js:278:10)
at REPLServer.Interface._line (readline.js:625:8)
> let x = 1;
SyntaxError: Identifier 'x' has already been declared
函数的生命周期与提升
基础的函数提升同样会将声明提升至作用域头部,不过不同于变量提升,函数同样会将其函数体定义提升至头部;譬如:
function b() {
a = 10;
return;
function a() {}
}
会被编译器修改为如下模式:
function b() {
function a() {}
a = 10;
return;
}
在内存创建步骤中,

如果我们在作用域中重复地声明同名函数,则会由后者覆盖前者:
sayHello();
function sayHello() {
function hello() {
console.log("Hello!");
}
hello();
function hello() {
console.log("Hey!");
}
}
// Hey!
而
const sayHello = function () {
console.log("Hello!");
};
sayHello();
// Hello!
函数表达式遵循变量提升的规则,函数体并不会被提升至作用域头部:
sayHello();
function sayHello() {
function hello() {
console.log("Hello!");
}
hello();
const hello = function () {
console.log("Hey!");
};
}
// Hello!
在
f; // Uncaught ReferenceError: f is not defined
(function () {
f; // undefined
x; // Uncaught ReferenceError: x is not defined
if (true) {
f();
let x;
function f() {
console.log("I am function!");
}
}
})();
避免全局变量
在计算机编程中,全局变量指的是在所有作用域中都能访问的变量。全局变量是一种不好的实践,因为它会导致一些问题,比如一个已经存在的方法和全局变量的覆盖,当我们不知道变量在哪里被定义的时候,代码就变得很难理解和维护了。在let
关键字来声明本地变量,好的
function sayHello() {
hello = "Hello World";
return hello;
}
sayHello();
console.log(hello);
在上述代码中因为我们在使用
函数包裹
为了避免全局变量,第一件事情就是要确保所有的代码都被包在函数中。最简单的办法就是把所有的代码都直接放到一个函数中去
(function (win) {
"use strict"; // 进一步避免创建全局变量
const doc = window.document; // 在这里声明你的变量 // 一些其他的代码
})(window);
声明命名空间
const MyApp = {
namespace: function (ns) {
const parts = ns.split("."),
object = this,
i,
len;
for (i = 0, len = parts.length; i < len; i++) {
if (!object[parts[i]]) {
object[parts[i]] = {};
}
object = object[parts[i]];
}
return object;
},
};
// 定义命名空间
MyApp.namespace("Helpers.Parsing");
// 你现在可以使用该命名空间了
MyApp.Helpers.Parsing.DateParser = function () {
//做一些事情
};
模块化
另一项开发者用来避免全局变量的技术就是封装到模块 Module
中。一个模块就是不需要创建新的全局变量或者命名空间的通用的功能。不要将所有的代码都放一个负责执行任务或者发布接口的函数中。这里以异步模块定义 Asynchronous Module Definition (AMD)
为例,更详细的
//定义
define("parsing", ["dependency1", "dependency2"], function (
//模块名字 // 模块依赖
dependency1,
dependency2
) {
//工厂方法
// Instead of creating a namespace AMD modules
// are expected to return their public interface
const Parsing = {};
Parsing.DateParser = function () {
//do something
};
return Parsing;
});
// 通过 Require.js 加载模块
require(["parsing"], function (Parsing) {
Parsing.DateParser(); // 使用模块
});