首先看一些你们熟悉的代码:
code1:git
// foo调用在定义以前
foo();
function foo() {
console.log('hello world');
}
// hello world
复制代码
code2:github
// 报错
foo();
var foo = function() {
console.log('hello world');
}
// TypeError: foo is not a function
复制代码
code3:函数
// 不报错
console.log(`a is ${a}`);
var a = 'hello world'
// a is undefined
复制代码
code4:ui
// 报错
console.log(`a is ${a}`);
let a = 'hello world'
// ReferenceError: a is not defined
复制代码
code5:spa
// 报错
console.log(`a is ${a}`);
const a = 'hello world'
// ReferenceError: a is not defined
复制代码
下面进入正题: JS引擎会在正式执行以前先进行一些预处理,在这个过程当中,首先将变量定义和函数定义的各个阶段操做(建立、初始化和赋值)提高至当前做用域的顶端,而后进行接下来的处理。(注:因为引擎的不一样,预处理也会有所差别)。code
code1和code2比较好理解。code1中函数定义会被提高到当前做用域顶部,而后再作后续执行,即foo函数的定义被js引擎自动提高到foo()前面,因此能够正常调用。而code2中,foo其实是一个变量定义,而后将foo变量指向一个匿名函数, 那既然变量定义也会有提高,为什么会报错呢?作用域
code3的变量提高理解起来也比较简单,可是code4,code5中分别使用let、const抛出了错误,看来js对var、let、const三种定义方式的变量提高有所区别。下面将详细讲解:get
js变量和函数定义有三个阶段: 建立create、初始化initialize 和赋值assign,针对不一样的定义对不一样的阶段作提高:string
如今咱们详细梳理下上述代码:
如今咱们理解了js对各类变量和函数定义作提高时的区别。那么变量和函数定义的提高有没有优先级顺序呢?答案是有的。请看以下代码:
console.log(foo);
function foo() {
console.log('hello world');
}
var foo = 1;
console.log(foo);
// [Function: foo]
// 1
复制代码
这段代码可解释为: 首先找到js引擎先找到var定义语句,建立变量foo,而后将其初始化为undefined,以后找到function定义语句,从新建立foo,以后将其赋值为函数体(由于function的赋值过程也被提高了),而后执行第一个console.log,接下来 才执行foo变量的赋值过程(由于var定义的赋值过程未被提高),最后执行第二个console.log。若将代码中var换成let,将会抛出SyntaxError: Identifier 'foo' has already been declared。由于let变量定义不容许有第二次 建立过程, function定义的建立过程就抛错了。
关于let还有一个颇有意思的现象,假如咱们执行以下代码:
let x = x; // x以前未定义过
// Uncaught ReferenceError: Cannot access 'x' before initialization
x = 1;
// Uncaught ReferenceError: x is not defined
复制代码
在抛出一段错误以后会发现,在当前上下文中不能再使用名为x的变量了,也不能对x赋值。致使这个现象的缘由大概是: let定义首先建立x,以后执行代码,发现let x = x等号右边的x对x产生了引用,因为变量初始化以前就被引用了,因此抛出Cannot access 'x' before initialization,而且这个错误会致使let x语句在建立x后初始化失败(前面的 错误致使不能进入初始化过程),因为js变量定义的初始化过程只有一次,一旦失败就不能再次初始化了,此时的x处于一个已建立但又不能初始化的状态,即所谓的暂时性死区, 此后对x的引用或赋值都会抛出错误。
另外,ES6中的class声明也存在提高,可是它和let、const同样,有一些限制,若是在声明位置以前引用,会抛出一个异常。
最后提醒你们在写js代码过程当中,尽可能遵循一点: 先声明,后使用。