预编译/变量提升
当栈内存(作用域)形成,JS代码自上而下执行之前,浏览器首先会把所有带 “VAR”/“FUNCTION” 关键词的进行提前 “声明” 或者 “定义” ,这种预先处理机制称之为 “变量提升”或预编译
- 声明(declare):var a (默认值undefined)
- 定义(defined):a=12 (定义其实就是赋值操作)
- [变量提升阶段]
带“VAR”的只声明未定义
带“FUNCTION”的声明和赋值都完成了
变量提升只发生在当前作用域(例如:开始加载页面的时候只对全局作用域下的进行提升,因为此时函数中存储的都是字符串而已)
在全局作用域下声明的函数或者变量是“全局变量”,同理,在私有作用域下声明的变量是“私有变量” [带VAR/FUNCTION的才是声明]浏览器很懒,做过的事情不会重复执行第二遍,也就是,当代码执行遇到创建函数这部分代码后,直接的跳过即可(因为在提升阶段就已经完成函数的赋值操作了
栈内存:
由于数据类型比较简单,他们都是直接在栈内存中开辟一个位置,把值存储进去,当栈内存销毁,存储的那么些值也被销毁了
堆内存
存储引用类型值得:如对象(键值对)函数(字符串)
当前堆内存释放了,那么这个引用值彻底没有了
堆内存的释放:
当堆内存没有被任何变量或者其他东西占用,浏览器会在空闲的时候,自主的进行内存回收,把所有不被占用的堆内存销毁掉(谷歌浏览器)
通过xxx=null:通过空对象指针null,可以让原始变量或其它变量,谁都不指向,那么原有的被占用的堆内存就没有被东西占用了,浏览器会销毁他
例
var a = 13;
var a = b;
b = 12;
console.log(a);//13
函数执行前一刻进行变量提升,形成一个栈内存 , 全局作用域,声明和定义了a , 声明了b,然后把存贮的值放到b上,直接操作值,然后把b的值变为12
var ary1 = [12,23]
var ary2 = ary1;
ary.push(100)
console.log(ary1)//[12,23,100]
任意数求和
function sum (){
var total = null;
for(i = 0;i<arguments.length;i++){
var item = arguments[i];
!isNaN(item) ? total+=item:null
}
return total
}
sum(1,2,4,5)
如下图执行机制
带var 和不带var的区别
在 全局作用域下声明一个变量,也相当于给WINDOW全局对象设置了一个属性,变量的值就是属性值(私有作用域中声明的私有变量和WINDOW没啥关系)
console.log(a);//=>undefined
console.log(window.a);//=>undefined
console.log('a' in window); //=>TRUE 在变量提升阶段,在全局作用域中声明了一个变量A,此时就已经把A当做属性赋值给WINDOW了,只不过此时还没有给A赋值,默认值UNDEFINED
* in:检测某个属性是否隶属于这个对象*
var a = 12;//=>全局变量值修改,WIN的属性值也跟着修改
console.log(a);//=>全局变量A 12
console.log(window.a);//=>WINDOW的一个属性名A 12
a = 13;
console.log(window.a);//=>13
window.a = 14;
console.log(a);//=>14
//=>全局变量和WIN中的属性存在 “映射机制”
只对等号左边进行变量提升
变量提升:
* var fn; =>只对等号左边进行变量提升
* sum = AAAFFF111;
sum();
fn();//=>Uncaught TypeError: fn is not a function
//=>匿名函数之函数表达式
var fn = function () {
console.log(1);
};//=>代码执行到此处会把函数值赋值给FN
fn();
//=>普通的函数
function sum() {
console.log(2);
}
条件判断下的变量提升
- 在当前作用域下,不管条件是否成立都要进行变量提升
- 带VAR的还是只声明
- 带FUNCTION的在老版本浏览器渲染机制下,声明和定义都处理,但是为了迎合ES6中的块级作用域,新版浏览器对于函数(在条件判断中的函数),不管条件是否成立,都只是先声明,没有定义,类似于VAR
变量提升
var a; =>在全局作用域下声明的全局变量也相当于给WIN设置了一个属性 window.a=undefined
console.log(a);//=>undefined
if ('a' in window) {
var a = 100;
}
console.log(a);//=>100
f = function () {return true;};//=>window.f=...(TRUE)
g = function () {return false;};//=>window.g=...(FALSE)
~function () {
/*
* 变量提升:
* function g; //=>g是私有变量
*/
if (g() && [] == ![]) {//=>Uncaught TypeError: g is not a function (此时的g是undefined)
//=>[]==![]:TRUE
f = function () {return false;};//=>把全局中的f进行修改 window.f=...(FALSE)
function g() {return true;}
}
}();
console.log(f());
console.log(g());
只对等号左边的变量 进行变量提升
/*
* 变量提升:
* var fn; =>只对等号左边进行变量提升
* sum = AAAFFF111;
*/
sum();
fn();//=>Uncaught TypeError: fn is not a function
//=>匿名函数之函数表达式
var fn = function () {
console.log(1);
};//=>代码执行到此处会把函数值赋值给FN
fn();
//=>普通的函数
function sum() {
console.log(2);
}
条件判断下的变量提升
/*
* 在当前作用域下,不管条件是否成立都要进行变量提升
* =>带VAR的还是只声明
* =>带FUNCTION的在老版本浏览器渲染机制下,声明和定义都处理,但是为了迎合ES6中的块级作用域,新版浏览器对于函数(在条件判断中的函数),不管条件是否成立,都只是先声明,没有定义,类似于VAR
*/
/*
/!*
* 变量提升
* var a; =>在全局作用域下声明的全局变量也相当于给WIN设置了一个属性 window.a=undefined
*!/
console.log(a);//=>undefined
if ('a' in window) {
var a = 100;
}
console.log(a);//=>100
*/
/*
* 变量提升:无
*/
f = function () {return true;};//=>window.f=...(TRUE)
g = function () {return false;};//=>window.g=...(FALSE)
~function () {
/*
* 变量提升:
* function g; //=>g是私有变量
*/
if (g() && [] == ![]) {//=>Uncaught TypeError: g is not a function (此时的g是undefined)
//=>[]==![]:TRUE
f = function () {return false;};//=>把全局中的f进行修改 window.f=...(FALSE)
function g() {return true;}
}
}();
console.log(f());
console.log(g());
/*
* 变量提升:
* function fn;
*/
// console.log(fn);//=>undefined
if (1 === 1) {
console.log(fn);//=>函数本身:当条件成立,进入到判断体中(在ES6中它是一个块级作用域)第一件事并不是代码执行,而是类似于变量提升一样,先把FN声明和定义了,也就是判断体中代码执行之前,FN就已经赋值了
function fn() {
console.log('ok');
}
}
// console.log(fn);//=>函数本身
重名问题处理
/*
* 1.带VAR和FUNCTION关键字声明相同的名字,这种也算是重名了(其实是一个FN,只是存储值的类型不一样)
*/
/*
var fn = 12;
function fn() {
}
*/
/*
* 2.关于重名的处理:如果名字重复了,不会重新的声明,但是会重新的定义(重新赋值)[不管是变量提升还是代码执行阶段皆是如此]
*/
/*
* 变量提升:
* fn = ...(1)
* = ...(2)
* = ...(3)
* = ...(4)
*/
/*
fn();//=>4
function fn() {console.log(1);}
fn();//=>4
function fn() {console.log(2);}
fn();//=>4
var fn=100;//=>带VAR的在提升阶段只把声明处理了,赋值操作没有处理,所以在代码执行的时候需要完成赋值 FN=100
fn();//=>100() Uncaught TypeError: fn is not a function
function fn() {console.log(3);}
fn();
function fn() {console.log(4);}
fn();
*/
let创建的变量不能进行变量提升
let a = 10,
b = 10;
let fn = function () {
console.log(a, b);//=>Uncaught ReferenceError: a is not defined
let a = b = 20;
/*
* let a=20;
* b=20; //=>把全局中的 b=20
*/
console.log(a, b);
};
fn();
console.log(a, b);
暂时性死区
/*
var a = 12;
if (true) {
console.log(a);//=>Uncaught ReferenceError: a is not defined
let a = 13;//=>基于LET创建变量,会把大部分{}当做一个私有的块级作用域(类似于函数的私有作用域),在这里也是重新检测语法规范,看一下是否是基于新语法创建的变量,如果是按照新语法规范来解析
}
*/
/*
// console.log(a);//=>Uncaught ReferenceError: a is not defined
console.log(typeof a);//=>"undefined" 在原有浏览器渲染机制下,基于typeof等逻辑运算符检测一个未被声明过的变量,不会报错,返回UNDEFINED
*/
// console.log(a);//=>Uncaught ReferenceError: a is not defined
console.log(typeof a);//=>Uncaught ReferenceError: a is not defined
let a;//=>如果当前变量是基于ES6语法处理,在没有声明这个变量的时候,使用TYPEOF检测会直接报错,不会是UNDEFINED,解决了原有的JS的死区