JavaScript变量与函数提高

做者:小斯基@毛豆前端前端

本文要点:

  • 函数提高;
  • var,let,const三种方式变量提高的区别;

首先看一些你们熟悉的代码:
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

  1. 函数定义的建立、初始化和赋值三个阶段都被提高了;
  2. var定义的建立、初始化被提高了,赋值未被提高;
  3. let的建立被提高了,但初始化和赋值都未被提高;
  4. const的建立被提高了,初始化未被提高,特殊的一点是const没有赋值阶段,因此const定义的变量值不能改变;

如今咱们详细梳理下上述代码:

  1. code1中函数定义的三个阶段都被提高,因此是先建立变量foo(此时还没法使用foo),而后将foo初始化为undefined,再将foo赋值为函数体,最后执行foo();
  2. code2中是var变量定义,先建立foo,而后将其赋值为undefined,以后调用foo(),因此这里会报错;
  3. code3和code2相似,foo在初始化后执行console.log,因此打印出undefined;
  4. code4中是let变量定义,先建立foo,因为初始化未被提高,因此建立以后当即执行console.log,因而就报错了;
  5. code5与code4报错过程相似。

如今咱们理解了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代码过程当中,尽可能遵循一点: 先声明,后使用

相关文章
相关标签/搜索