**栈内存、作用域、执行上下文【执行环境】**
当浏览器开辟出供代码执行的栈内存后,代码并没有自上而下立即执行,而是继续做了一些事情:把当前作用域中所有带var、function关键字的进行提前的声明和定义 =>变量提升机制 【预解析】
- 带var的只是提前声明(declare): “var a;” ,如果只声明,没有赋值,默认值是undefined
- 带function的不仅声明,而且还定义了(defined): “a=13”定义其实就是赋值,准确来说就是让变量和某个值进行关联
带var和不带var的区别
// => 在全局作用域下的区别
/*
* 不带var的:相当于给全局对象window设置了一个属性a 【而不是变量。】
* window.a = 13;
*/
a = 13;
console.log(a); // => window.a
/*
* 栈内存变量存储空间
* b
* 带var的:是在全局作用域下声明了一个变量b(全局变量),但是在全局下声明的变量也同样相当于给window增加了一个对应的属性(只有全局作用域具备这个特点) 【即是变量,又是window的属性。】
*/
var b = 14; // => 创建变量b & 给window设置了属性b
console.log(b); // => 14
console.log(window.b); // => 14
console.log(a); // undefined
var a = 12;
var b = a;
b = 13;
console.log(a); // => 12
// ---------------------
console.log(sum(10, 20)); // 30
function sum(n, m) {
return n + m;
}
// ---------------------
// 函数表达式方式,由于使用VAR来创建SUM,变量提升阶段只会声明变量,不会赋值,所以此时函数在前面执行,函数是没有值的,不能执行(真实项目中这种方式最常用,因为它操作严谨)
console.log(sum); // =>undefined
// sum(10, 20); // =>Uncaught TypeError: sum is not a function
var sum = function (n, m) {
return n + m;
};
// let sum = (n, m) => n + m;
console.log(sum(10, 20));
/*
* 变量提升:在当前上下文中(全局/私有/块级),JS代码自上而下执行之前,浏览器会提前处理一些事情(可以理解为词法解析的一个环节,词法解析一定发生在代码执行之前)
* 会把当前上下文中所有带VAR、FUNCTION关键字的进行提前的声明或者定义
* var a=10;
* 声明declare:var a;
* 定义defined:a=10;
* 带VAR的只会提前的声明
* 带FUNCTION会提前的声明 + 定义
*/
/*
* 代码执行之前: 全局上下文中的变量提升
* var a; 默认值是undefined
*/
console.log(a); // => undefined
var a = 12; // => 创建值12 不需要在声明a了(变量提升阶段完成了,完成的事情不会重新处理) a=12赋值
a = 13; //全局变量a=13
console.log(a); // => 13
// ------------------------------------------
/*
* 全局上下文中的变量提升
* func=函数 函数在这个阶段赋值都做了
*/
func();
function func() {
var a = 12;
console.log('OK');
}
// ------------------------------------------
func(); // => Uncaught TypeError: func is not a function
var func = function () {
// 真实项目中建议用函数表达式创建函数,因为这样在变量提升阶段只会声明FUNC,不会赋值
console.log('OK');
};
func();
// ------------------------------------------
var func = function AAA() {
// 把原本作为值的函数表达式 匿名函数“具名化”(虽说是起了名字,但是这个名字不能在外面访问 => 也就是不会在当前当下文中创建这个名字)
// 当函数执行,在形成的私有上下文中,会把这个具名化的名字做为私有上下文中的变量(值就是这个函数)来进行处理
console.log('OK');
console.log(AAA); // => 当前函数
AAA(); // 递归调用, 而不用严格模式下都不支持的 arguments.callee 了
};
// AAA(); // => Uncaught ReferenceError: AAA is not defined
func();
// ------------------------------------------
/*
* EC(G)变量提升
*/
console.log(a); // => Uncaught ReferenceError: a is not defined
a = 13;
console.log(a);
// ------------------------------------------
/*
* EC(G)变量提升:只有VAR/FUNCTION会变量提升(ES6中的LET和CONST不会)
*/
// 这里不是词法解析错误
console.log('OK'); // => 'OK'
console.log(a); // => Uncaught ReferenceError: Cannot access 'a' before initialization 不能在LET声明之前使用变量
let a = 12;
a = 13;
console.log(a);
// ------------------------------------------
/*
* 基于“VAR或者FUNCTION”在“全局上下文”中声明的变量(全局变量)会“映射”到GO(全局对象window)上一份,作为他的属性;而且接下来是一个修改,另外一个也会跟着修改;
*/
var a = 12;
console.log(a); // => 12 全局变量
console.log(window.a); // => 12 映射到GO上的属性a
window.a = 13;
console.log(a); // => 13 映射机制是一个修改另外一个也会修改
// ------------------------------------------
/*
* EC(G): 全局上下文中的变量提升
* 不论条件是否成立,都要进行变量提升
* 细节点:条件中带function的,在新版本浏览器中只会提前声明,不会再提前的赋值了
* [老版本]
* var a;
* func = 函数; 【老版本的函数,声明、赋值都提前。】
* [新版本]
* var a; 全局上下文中声明一个a也相当于 window.a
* func;
*/
console.log(a, func); // => undefined undefined
if (!("a" in window)) {
// => "a" in window:检测a是否为window的一个属性 !TRUE => FALSE
var a = 1;
function func() { }
}
console.log(a); // => undefined
// ------------------------------------------
/*
* EC(G)变量提升
* fn => 1
* => 2
* var fn; 已经声明过了,不再重复声明,只需重新赋值
* => 4
* => 5
* 全局上下文中有一个全局变量fn, 值是输出5的函数(此时window.fn => 5)
*/
fn(); // => 5
function fn() { console.log(1); } // => 不再处理,变量提升阶段搞过了
fn(); // => 5
function fn() { console.log(2); }
fn(); // => 5
// => var fn不用再处理了,但是赋值在变量提升阶段没处理过,此处需要处理 fn = window.fn => 3
var fn = function () { console.log(3); }
fn(); // => 3
function fn() { console.log(4); }
fn(); // => 3
function fn() { console.log(5); }
fn(); // => 3
// ------------------------------------------
var foo = 1;
function bar() {
if (!foo) {
var foo = 10;
}
console.log(foo); // 10
}
bar();
// ------------------------------------------
var a = 0;
if (true) {
a = 1;
function a() { };
a = 21;
console.log(a); // 21
}
console.log(a); // 1
// ------------------------------------------
var a = 0;
if (true) {
a = 1; // => Uncaught ReferenceError: Cannot access 'a' before initialization
let a = 10;
a = 21;
console.log(a)
}
console.log(a);
// ------------------------------------------
// Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a);
{
// 【块级作用域,function提升,a被声明过。】
console.log(a);
var a = 10;
function a() { }
console.log(a);
}
console.log(a);
变量提升练习题1
* 在变量提升阶段,遇到大括号、判断体等,不论条件是否成立,都要进行变量提升
* 起作用的:var、function
*
* IE低版本浏览器(<= IE10):
* 都会进行变量提升,而且函数依然是
声明 + 定义
都完成了* 高版本浏览器:
* 虽然也会进行变量提升,但是对于函数只声明,不会再定义了
*
* ES6新语法规范中,存在块级上下文(除
对象的大括号
中,出现let、const、function,都会把当前大括号形成一个私有的跨级上下文) => 兼容高版本浏览器*
* 现在的浏览器很悲催,因为既要兼容低版本语法规范,还要适应高版本语法规范,所以对于有冲突的规范,会采用一些取中间的方式....
console.log(foo) // undefined
{
function foo() { } // 【是这句代码之前的所有操作都提升,这句代码不提升,不包括这句代码】
foo = 1;
console.log(foo); // => 1
}
console.log(foo); // => 函数foo
// -------------
// 全局变量提升: function foo;
console.log(foo) // undefined
{
/*
* 私有的块级上下文的变量提升:
* function foo() {alert('1')}
* function foo() {alert('2')} 【保留的】
*/
console.log(foo); // => alert('2')的 函数foo
function foo() { alert('1') }
foo = 1;
// 会把当前代码之前对于foo的操作映射到全局一份 【是这句代码之前的所有操作都提升,这句代码不提升,不包括这句代码】
function foo() { alert('2') }
}
console.log(foo); // => 1
// -------------
// 全局 function foo;
{
// 私有
// function foo() {alert('1')}
// function foo() {alert('2')} 【保留】
function foo() { alert('1') }
foo = 1;
function foo() { alert('2') } //把之前的操作映射给全局一份
foo = 2;
console.log(foo); // => 2
}
console.log(foo); // => 1
变量提升练习题2
* 如果同时符合了这两个条件:
* 1. 形参有赋值默认值
* 2. 函数体中有声明过变量 var、function...
* 此时的函数执行会形成两个上下文
* 1. 私有的上下文
* 2. 函数体所在大括号中的块级上下文 【在私有上下文中,用var、let、const等声明的变量属于块级上下文。】
* => 函数体中遇到一个变量,我们首先看是否为块上下文中的变量,如果是,接下来都操作块上下文中的变量,和私有没关系;如果不是,操作的是私有的或者全局的...
/*
* 全局上下文
* var x;
* function func(x,y...){...};
*/
var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
/*
* 私有上下文EC(1)
* 形参赋值:x = 5, y = anonymous1
* 变量提升:--
*/
x = 3; // 私有x = 3
y();
/*
* anonymous1() 私有向下文EC(2)
* 形参赋值:--
* 变量提升:--
* x = 2 让EC(1)上级上下文中的x=2
*/
console.log(x); // => 2
}
func(5);
console.log(x); // => 1
// ------------------------------------
/*
* 如果同时符合了这两个条件:
* 1. 形参有赋值默认值
* 2. 函数体中有声明过变量 var、function...
* 此时的函数执行会形成两个上下文
* 1. 私有的上下文
* 2. 函数体所在大括号中的块级上下文 【在私有上下文中,用var、let、const等声明的变量属于块级上下文。】
* => 函数体中遇到一个变量,我们首先看是否为块上下文中的变量,如果是,接下来都操作块上下文中的变量,和私有没关系;如果不是,操作的是私有的或者全局的...
*/
var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
// 【形参赋值】私有上下文 x = 5,y = anonymous1 【这里可以把私有上下文当做块级上下文的上级上下文】
// 块级上下文 x = 5 【x的初始值是5】
console.log(x); // => 5 【块级】
var x = 3; // 块级中的 x = 3
y(); // x = 2 私有中的 x = 2 【因为没有用var、let等重新声明y,所以y是上级的私有上下文的y。】
console.log(x); // => 3
}
func(5);
console.log(x); // => 1
// ------------------------------------
var x = 1;
function func(x, y = function anonymous1() { x = 2 }) {
// 私有上下文 x = 5,y = anonymous1
// 块级上下文 x = 5,y = anonymous1,在还没有执行到指定代码之前,存储的值和私有上下文中的值是一样的
var x = 3; // 块级上下文x=3
var y = function anonymous2() { x = 4 }; // 块级上下文y = anonymous2
y(); // anonymous2() x = 4 块级上下文中的x = 4
console.log(x); // => 4
}
func(5);
console.log(x); // => 1