为JavaScript里面的概念,温故而知新。javascript
在JavaScript中,若是试图使用还没有声明的变量,会出现ReferenceError
错误。毕竟变量都没有声明,JavaScript也就找不到这变量。加上变量的声明,可正常运行:java
console.log(a); // Uncaught ReferenceError: a is not defined
var a; console.log(a); // undefined
考虑下若是是这样书写:git
console.log(a); // undefined var a;
直觉上,程序是自上向下逐行执行的。使用还没有声明的变量a,按理应该出现ReferenceError
错误,而实际上却输出了undefined
。这种现象,就是Hoisting。var a
因为某种缘由被"移动"到最上面了。能够理解为以下形式:github
var a; console.log(a); // undefined
须要注意:函数
hoisting只是针对声明,赋值并不会。this
console.log(a); // undefined var a = 2015; // 理解为以下形式 var a; console.log(a); // undefined a = 2015;
这里var a = 2015
理解上可分红两个步骤:var a
和a = 2015
。prototype
函数表达式不会hoisting
。设计
fn(); // TypeError: fn is not a function var fn = function () {} // 理解为以下形式 var fn; fn(); fn = function () {};
这里fn()
对undefined
值进行函数调用致使非法操做,所以抛出TypeError
错误。code
函数声明和变量声明,都会hoisting
,须要注意的是,函数会优先hoisting
:blog
console.log(fn); var fn; function fn() {} // 理解为以下形式 function fn() {} var fn; // 重复声明,会被忽略 console.log(fn);
对于有参数的函数:
fn(2016); function fn(a) { console.log(a); // 2016 var a = 2015; } // 理解为以下形式 function fn(a) { var a = 2016; // 这里对应传参,值为函数调用时候传进来的值 var a; // 重复声明,会被忽略 console.log(a); a = 2015; } fn(2016);
总结一下,能够理解Hoisting
是处理全部声明的过程。须要注意赋值及函数表达式不会hoisting。
能够处理函数互相调用的场景:
function fn1(n) { if (n > 0) fn2(n); } function fn2(n) { console.log(n); fn1(n - 1); } fn1(6);
按逐行执行的观念来看,必然存在前后顺序,像fn1
与fn2
之间的相互调用,若是没有hoisting
的话,是没法正常运行的。
具体能够参考规范ECMAScript 2019 Language Specification。与Hoisting相关的,是在8.3 Execution Contexts
。
一篇很不错的文章参考Understanding Execution Context and Execution Stack in Javascript
参考里面的例子:
var a = 20; var b = 40; let c = 60; function foo(d, e) { var f = 80; return d + e + f; } c = foo(a, b);
建立的Execution Context
像这样:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", c: < uninitialized >, foo: < func > } outer: <null>, ThisBinding: <Global Object> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: undefined, b: undefined, } outer: <null>, ThisBinding: <Global Object> } }
在运行阶段,变量赋值已经完成。所以GlobalExectionContext
在执行阶段看起来就像是这样的:
GlobalExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Object", c: 60, foo: < func >, } outer: <null>, ThisBinding: <Global Object> }, VariableEnvironment: { EnvironmentRecord: { Type: "Object", // Identifier bindings go here a: 20, b: 40, } outer: <null>, ThisBinding: <Global Object> }
当遇到函数foo(a, b)
的调用时,新的FunctionExectionContext
被建立并执行函数中的代码。在建立阶段像这样:
FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", Arguments: {0: 20, 1: 40, length: 2}, }, outer: <GlobalLexicalEnvironment>, ThisBinding: <Global Object or undefined>, }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", f: undefined }, outer: <GlobalLexicalEnvironment>, ThisBinding: <Global Object or undefined>, } }
执行完后,看起来像这样:
FunctionExectionContext = { LexicalEnvironment: { EnvironmentRecord: { Type: "Declarative", Arguments: {0: 20, 1: 40, length: 2}, }, outer: <GlobalLexicalEnvironment>, ThisBinding: <Global Object or undefined>, }, VariableEnvironment: { EnvironmentRecord: { Type: "Declarative", f: 80 }, outer: <GlobalLexicalEnvironment>, ThisBinding: <Global Object or undefined>, } }
在函数执行完成之后,返回值会被存储在c
里。所以GlobalExectionContext
更新。在这以后,代码执行完成,程序运行终止。
回顾规范:Hoisting的运行规则
,能够注意到在建立阶段,无论是用let
、const
或var
,都会进行hoisting
。而差异在于:使用let
和const
进行声明的时候,设置为uninitialized
(未初始化状态),而var
会设置为undefined
。因此在let
或const
声明的变量以前访问时,会抛出ReferenceError: Cannot access 'c' before initialization
错误。对应的名词为Temporal Dead Zone
(暂时性死区)。
function demo1() { console.log(c); // c 的 TDZ 开始 let c = 10; // c 的 TDZ 结束 } demo1(); function demo2() { console.log('begin'); // c 的 TDZ 开始 let c; // c 的 TDZ 结束 console.log(c); c = 10; console.log(c); } demo2();
果然是温故而知新,发现本身懂得其实好少。鞭策本身,后续对this
、prototype
、closures
及scope
等,进行温故。