变量提升
JavaScript代码执行之前都会进行代码预解析,在预解析过程中会把使用var声明的变量和函数声明进行提前“声明”或者“定义”。
在变量提升阶段:
- 使用var声明的变量只声明不赋值,默认值为undefined。
- 函数声明进行声明并且赋值。 注意:在ES6中let方式声明的变量或者函数,不存在变量提升。
console.log(a); // undefined 变量被提升,变量 a 被提升,只声明未定义
console.log(b); // b 函数本身 函数声明被提升,函数 b 被提升,声明并且定义
var a = 12;
function b() {
console.log('ok');
}
console.log(a); // 12
console.log(e); // 报错 在ES6中let方式声明的变量或者函数,不存在变量提升
let e = 10;
条件判断下的变量提升
在当前的作用域下,不管条件是否成立,函数声明不再声明并定义,而只声明不定义。
console.log(fn); // undefined 条件判断下不管条件是否成立,函数声明只声明不定义
if (1 === 1) {
console.log(fn);
// fn 函数本身 当条件成立,进入到判断体中,在代码执行之前,函数声明就已经声明和定义了
function fn() {
console.log('ok');
}
}
console.log(fn); // fn 函数本身
映射机制
在全局作用域下声明的变量相当于给window全局对象设置了一个属性,变量的值就是属性的值,更改其中一个另一个也会跟着改。私有作用域中声明的私有变量和window没关系。
console.log(a); // undefined
var a = 12;
console.log(a); // 12
console.log(window.a); // 12
a = 13;
console.log(window.a); // 13
window.a = 14;
console.log(a); // 14
带var和不带var的区别
全局作用域中带var和不带var的区别
var a;表示声明全局变量a。如果不使用var声明变量,则是给window加上一个属性。
console.log(a); // 报错 没有使用var声明变量,则不存在变量提升
console.log(window.a); // undefined
a = 12; // => window.a = 12 给window加上属性a,本质是window的属性
console.log(a); // 12
console.log(window.a); // 12
私有作用域中带var和不带var的区别
在私有作用域中,使用var声明的变量和外界没有关系。没有使用var的变量则不是私有作用域的变量声明,会向上级作用域查找,判断是否为上级作用域的变量,直到查到为undefined为止。
var a;
function b() {
var c = 10;
a = 12;
console.log(a); // 12 在函数b 私有作用域里,a没有被声明,则向上级作用域查找
console.log(c); // 10
}
b();
console.log(a); // 12
console.log(c); // c is not defined 因为c是在函数b的私有作用域中,在全局作用域是访问不到的
let创建的变量不存在变量提升
在ES6中,let声明的变量不存在变量提升,也不符合全局变量和window属性的映射机制;并且let不能声明相同名字的变量
console.log(a); // 报错
let a = 12;
let a = 12;
console.log(window.a); // undefined
console.log(a); // 12
存在语法检测,查找当前作用域的所有变量,判断是否有重复的变量名,如果有直接报错。
a = 10;
console.log(a); // 报错
let a = 11;
console.log(a);
let a = 10;
let a = 11; // 报错
console.log(a);
变量提升的原理
函数预解析过程中:
- 第一步:创建AO(Activation Object)对象;
- 第二步:将所有形参、变量的名作为AO对象的属性名,值赋予unddefined;
- 第三步:形参和实参的值相统一;
- 第四步:将函数中的函数声明的函数名作为AO对象的属性名,赋予函数体。
function test(a, b) {
console.log(a); // a 函数本身
var c = 123,
a = 234;
console.log(c); // 123
function a() {
}
console.log(a); // 234
console.log(b); // 3
}
test(1, 3);
/*
第一步:创建AO(Activation Object)对象;
AO {};
第二步:将所有形参、变量的名作为AO对象的属性名,值赋予unddefined;
AO {
a: undefined,
b: undefined,
c: undefined
}
第三步:形参和实参的值相统一;
AO {
a: 1,
b: 3,
c: undefined
}
第四步:将函数中的函数声明的函数名作为AO对象的属性名,赋予函数体。
AO {
a: function () {},
b: 3,
c: undefined
}
*/
全局预解析过程中:
- 第一步:创建GO(Global Object)对象;
- 第二步:将所有全局变量的名作为GO对象的属性名,值赋予undefined;
- 第三步:将全局的函数声明的函数名作为GO对象的属性名并赋予函数体。
var a = 12;
function b() {
console.log(a); // undefined
var a = 13,
c = 14;
function d() {
}
console.log(a,c); // 13 14
}
var f = function () {
};
console.log(a); // 12
b();
/*
第一步:创建GO(Global Object)对象;
GO {};
第二步:将所有全局变量的名作为GO对象的属性名,值赋予undefined;
GO {
a: undefined,
f: undefined f不是函数声明,是函数表达式
}
第三步:将全局的函数声明的函数名作为GO对象的属性名并赋予函数体。
GO {
a: undefined,
f: undefined,
b: function () {}
}
*/