浏览器加载HTML页面时,首先会提供一个供全局JS代码执行的环境称之为全局作用域全局作用域在后台用global 表示,在js中用 window 表示。
预解释(变量提升):
当栈内存(作用域)形成,js代码执行之前,浏览器首先会默认的把所有带 var
和 function
的进行提前声明或定义。
理解声明和定义:
1、 var num = 13
声明:var num;告诉浏览器在全局作用域中有一个 num 的变量了
定义:num = 13;给变量进行赋值
2、对于带 var 和 function 关键字的在预解释的时候操作是不一样的
var 在预解释的时候只是提前声明
function 在预解释的时候提前的声明+定义都完成了
3、预解释只发生在当前的作用域下,例如:开始只对 window 下的进行预解释,只有函数执行的时候才会对函数中的进行预解释
var num = 13;
var obj = {name: "小明", age: "7"}
function fn(num1,num2){
var total = num1 + num2
console.log(total)
} //内存图如下

JS中的内存分类:
1、栈内存:用来提供一个供 JS 代码执行的环境(全局作用域 / 私有作用域),所有基本类型值都会在栈内存开一个位置进行储存
2、堆内存:用来存储引用数据类型的值(对象存储的是属性名和属性值,函数存储的是代码字符串
)
如何区分私有变量和全局变量:
1、在全局作用域下声明(预解释的时候)的变量是全局变量
2、在 “私有作用域中声明的变量” 和 “函数形参” 都是私有变量
作用域链:
在私有作用域中,代码执行的时候遇到一个变量,首先我们需要确定它是否为私有变量,如果是私有变量那么和外面没有任何关系;如果不是则往当前作用域的上级进行查找,如果上级作用域也没有则继续查找,一直找到 window 为止。
函数的执行过程:
函数执行的时候(直接目的:让函数体中的代码执行),首先会形成一个新的私有作用域链,然后执行如下步骤。
1、如果有形参首先给形参赋值
2、进行私有作用域中的预解释
3、私有作用域中的代码从上到下执行
4、.....
闭包:
函数形成一个新的私有作用域保护了里面的私有变量不受外界干扰(外面的修改不了私有的,私有的也修改不了外面的)。闭包作用:保护私有变量和外界没有联系,形成不销毁栈内存里面私有变量信息保存下来
案例图解:
console.log(total) //undefined
var total = 0
function fn(num1,num2){
console.log(total) //undefined
var total = num1 + num2
console.log(total) //300
}
fn(100,200)
console.log(total) //0

案例:
console.log(total) //undefined
var total = 0
function fn(num1,num2){
console.log(total) //total不是私有的在全局下查找,total为是全局的0,
total = num1 + num2 //全局的total = 300
console.log(total) //300
}
fn(100,200)
console.log(total) //全局的total = 300
注意:
1、私有作用域中出现的一个变量不是私有的,则往上级作用域进行查找,上级没有则继续向上查找,一直找到 window 为止,如果 window 下也没有?
如果是获取值:console.log(xxx) 则报错
如果是设置值:xxx = 100 相当于给 window 增加了一个属性名 xxx ,属性值是 100
2、条件判断下变量提升
预解释的时候不管你的条件是否成立,都要把带 var 的提前声明
带function的在老版本浏览器下,声明和定义都处理,为了迎合ES6中的块级作用域新版浏览器对于函数(在条件判断中的函数),不管条件是否成立,都是先声明,没有定义,类似于var。如果成立声明加定义不成立只声明。条件成立进来后的第一件事是给函数赋值然后代码执行
//window 的预解释:var num;-> widndow.num;
if(!("num" in window)){ //"num" in window -> true
var num = 12;
}
console.log(num) //undefined
<!--- ================ -->
f = function (){return true}; //window.f = ...(true)
g = function (){return false}; //window.g = ...(false)
!function(){
//变量提升:函数值声明未定义 function g是私有变量未undefined
if(g() && [] === ![]){//undefined()报错
f = function(){return false;};
function g(){return true};
}
}();
console.log(f());
console.log(g());
in:检测某个属性是否隶属于这个对象
3、预解释的时候只预解释 ”=“ 左边的,右边的是值,不参与预解释
匿名函数之函数表达式:把函数定义的部分当作一个值赋值给变量/元素的某一个事件
//window下的预解释:var fn
fn(); //报错
var fn = function({
console.log("ok")
})
4、自执行函数定义的那个 function 在全局作用域下不进行预解释,当代码执行到这个位置的时候定义和执行一起完成了
自执行函数:定义和执行一起完成
!function(num){}(100)
(function(num){}(100))
5、函数体中 return 下面的代码虽然不执行了,但还需要进行预解释;return 后面跟着的是返回值,所以不进行预解释
function fn(){
//预解释:var num;
console.log(num); //undefined
return function(){
};
var num = 100;
}
fn();
6、重名问题处理
在预解释的时候,如果名字已经声明过了(带var和function关键字声明相同名字,算是重名),不需要重新声明,但需要重新赋值;不管是变量提升还是代码执行阶段皆是如此
//window预解释:
//声明 + 定义fn = xxxfff000
//声明 var fn(前面声明了不需要重新声明)
//声明(不重复进行)+ 定义 fn = xxxfff111
//fn = xxxfff111
fn(); //-> 2
function fn(){console.log(1);};
fn(); //-> 2
var fn = 10; //fn = 10
fn(); //10() 报错
function fn(){console.log(2)};
fn()
7、let创建的变量不存在变量提升
在ES6中基于let或const等方式创建变量或者函数,不存在变量提升机制
切断了全局变量和window属性的映射机制
在相同的作用域中,基于let不能声明相同名字的变量
虽然没有变量提升,但是当前作用域代码执行前,浏览器会做一个重复性检测当前作用域下所有变量,一旦发现重复直接报错,代码不会执行
如何查找当前作用域的上一级作用域?
看当前函数在哪个作用域下定义的,那么他的上一级作用域就是谁,和函数在哪执行没有任何关系
在作用域链查找的过程中,如果找到window也没有这个变量相当于给window设置了一个属性
案例图解:
var num = 12;
function fn(){
var num = 120;
return function(){
console.log(num)
}
}
var f = fn();
f(); //120
!function(){
var num = 1200;
f(); //120
}()

带var和不带var区别:
在全局作用域下声明一个变量,相当于给window全局对象设置了一个属性,变量的值是属性值(私有作用域中声明的私有变量和window没啥关系),如果不加var的本质是window的属性,且全局变量和window中的属性存在“映射机制”
私有作用域中带var为私有变量和外界没由任何关系,不带var不是私有变量,会向通过作用域链进行查找
浏览器在开辟栈内存供代码自上而下执行之前,不仅有变量提升还有其他操作比喻”词法解析“检测当前要执行的代码是否出现语法错误,如果有错误代码不再执行。如下
console.log(1);
let a = 12;
console.log(a);
let a = 13;
console.log(a);
//代码不会输出直接报错
网友评论