复习
首先,我们来复习一下,什么是堆栈内存?
前提:
Stack:栈内存
- 也叫ECStack执行环境栈
- 为了后期代码执行中,可以访问到GO中的那些属性和方法,浏览器在全局上下文EC(G)中,默认声明了一个变量:”window”,存储到VO(G)中,并且让window存储GO的地址,后期基于window.xxx就可以访问到那些内置的属性和方法,所以经常把window称为全局对象
- 编写代码的时候,可以省略window
- alert(‘…’)=>window.alert(‘…’)
Heap:堆内存
- 一开始加载页面,浏览器就会在开辟的堆内存中,分配一块空间,用来存储一些内置的属性和方法,也称为GO(global object)全局对象
- alert confirm prompt isNaN…
在全局上下文中【这个前提很重要】
- 基于var/function声明的变量,是直接存储到GO中的,相当于给window设置了相关的属性
- 而基于let/const/class声明的变量是存储到VO(G)中的,他们才是纯正血统上的全局变量,和window没啥关系
- 没有基于任何关键字修饰/声明的变量,其实是省略了window,核心也是给GO增加了一个属性
在全局上下文,代码执行的过程中:
- 如果是window.xxx访问,直接到GO中找即可
- 如果是直接输出一个变量,则先去看VO(G)中是否有,他里面有,获取的就是全局变量;如果没有,则继续去GO中找,如果有就是全局对象中的属性,如果没有就直接报错
变量提升机制
1.什么是变量提升?
在“当前上下文”中,代码执行之前,浏览器首先会把所有带var/function关键字的进行提前声明或者定义,这种机制就叫做变量提升
【变量提升阶段】
- 带var的只是提前声明
- 带function的此阶段声明+定义{赋值}都完成了
- 只提升等号左边的变量。
- 不管条件成不成立,都要进行变量提升。
- 新版标准浏览器,在{ }块级作用域中,对于function只声明不定义
2.let/const/import/class声明的变量不存在变量提升
//分析过程
/*
* EC(G)
* VO(G) / GO
* fn1 ----> 0x001 [[scope]]:EC(G)
* fn2
*
* 变量提升:
* function fn1(){console.log('OK');} 声明declare+定义defined
* var fn2;
*/
-----------------------------------------------------------------------
fn1(); //'OK'
fn2(); //Uncaught TypeError: fn2 is not a function fn2===undefined
function fn1() {
console.log('OK');
}
var fn2 = function () {
console.log('NO');
}; //fn2 ---> 0x002 [[scope]]:EC(G)
fn1();//'OK'
fn2();//'NO'
复制代码
3.重复声明的问题
var
对于var的不会进行重复声明,但是会重新赋值
- 在变量提升阶段,看到第一行var num ,会声明一个变量num,此时看到第二行还有一个就不用再声明了
/
* EC(G)
* VO(G)/GO
* num: 当第二行变量提升时就先判断存在不,看到第二行有就不在声明了
*/
var num=2;
var num=3;
console.log(num);
复制代码
function
对于function的在变量提升阶段是声明和定义一起完成的,【先定义】如果遇到重复声明定义的,会进行重新赋值
函数创建 会开辟堆地址 0X001
分为三部分
- 1 作用域:[scope]:EC(G) 函数在哪个上下文中创建的,那么它的作用域就是谁
- 2 代码字符串
- 3 键值对 name:’fn’ //函数名 length:0 形参个数
//EC(stack)
// EC(G)
// VO(G)/GO
// 变量提升 function fn=>AAFF000=>AAFF001=>12 var fn=>不会在重新声明
---------------------------------------------------------------------------------
console.log(fn);//函数
function fn(){ console.log(1); }//不执行
console.log(fn);//函数
var fn = 12;
console.log(fn);//12
function fn(){ console.log(2); }//不执行
console.log(fn); //12
复制代码
4.推荐使用函数表达式,确保函数执行只能放在“创建函数代码”的下面,保证逻辑的严谨性
匿名函数的 函数的表达式 :把一个匿名函数作为值赋给一个变量 或者赋给一个事件绑定 btn.onclick=fn;
复制代码
普通函数和*匿名函数的函数表达式区别:
变量提升区别
- 普通函数:在变量提升阶段 声明+定义
- 匿名表达式:在变量提升阶段 只声明
[练习题]
/*
* EC(G)
* VO(G)/GO
* fn======》xxx01=》2
* 变量提升阶段 fn
*/
-------------------------------------------------------------------------------
console.log(fn); //函数本身
function fn(){
console.log(1);//此步略过
}
var fn=2;//更改fn的值 重新赋值
console.log(fn) //2
复制代码
/*
*
* EC(G)
* VO(G)/GO
* num ======>1======>2
* fn =====>ex001
* =====>ex002
* =====>ex003
* =====>ex004 console.log(4)
* 100
*
* fn堆内存 ex001 作用域 scope([EC(G)]),函数体,name:fn,length:0 //形参的个数
*
* 变量提升
* num
* fn
*/
----------------------------------------------------------------------------------------
console.log(num); //undefined
var num = 1;//此步变量提升略过
console.log(num);//1
var num = 2; //此步变量提升略过
console.log(num);//2
fn();// 4
function fn() {
console.log(1);
}
function fn() {
console.log(2);
}
fn();//4
function fn() {
console.log(3);
}
fn = 100;
function fn() {
console.log(4);
}
fn();//报错 fn is not a function
复制代码
<script>
console.log(a);//undefined
console.log(func);//[Function: func]
var a=10;
function func(){
console.log(b);//undefined
var b=20;
console.log(b);//20
}
func();
console.log(a);//10
console.log(func);//[Function: func]
</script>
复制代码
5.变量提升的特殊性
1)条件判断
不论判断条件是否成立,都会进行变量提升
在当前上下文中,变量提升阶段,不论条件是否成立,都要进行变量提升「条件是否成立,是执行代码阶段确定的事情,变量提升的时候,代码还没执行呢」
- var :还是和之前一样只声明不赋值
- function:
- 新版本浏览器中的 “判断体中出现的function”在变量提升的时候 也只是声明 但不赋值了
- 在IE 10 及以前以及谷歌等浏览器低版本的状态下还是声明和定义(仅限判断语句)
[练习题]
/*
* EC(G)
* VO(G) / GO
* a「window.a」
*
* 变量提升:
* var a;
*/
---------------------------------------------------------------------
console.log(a); //undefined
if (!('a' in window)) { //'a' in window===true
var a = 13;
}
console.log(a); //undefined
复制代码
console.log(a);//undefined:不管条件是否成立,都会进行变量提升,var a;
if(1==2){
var a=12;// 条件不成立,所以进不来
}
console.log(a);//undefined
复制代码
console.log(fn);// 在新版本浏览器中,判断条件中的function相当于只是声明(跟var一样),所以undefined
if(1===1){
//此时条件成立 进来的第一件事情还是先定义函数(也是为了迎合ES6中的块作用域)
console.log(fn);//函数体
function fn(){
console.log(1)
}
console.log(fn);//函数体
}
console.log(fn); //函数体
复制代码
在条件判断语句中,如果条件成立,会把执行体当成私有作用域,再进行变量提升
console.log(fn);// undefined 在新版本浏览器中,不管条件是否成功,都会进行变量提升,function 只声明,
if(1==1){
console.log(fn);// fn 函数:在条件判断语句中,如果条件成立,会把执行体当成私有作用域,再进行变量提升
// 再从上往下执行代码,此时fn 定义完成。
function fn(){
console.log("ok");
}
}
console.log(fn) // 条件成立,给fn进行了赋值,打印出fn函数
复制代码
在条件判断下,如果有function定义的变量,在这个function这个函数后面的更改变量的值,更改的都是私有变量。
/*
*EC(G)
* VO(G)/GO
* a-----------1
*EC(block)
* a:21
*
* 变量提升 var a;function a
*/
---------------------------------------------------------------------
var a=0;
if(true){
a=1;
function a(){}
a=21;
console.log(a);//21
}
console.log(a);//1
复制代码
2)只对等号左边的做变量提升
//变量提升 fn
console.log(fn);//undefined
console.log(fn(1,2));//报错
var fn=function (n,m){
return n+m;
}
console.log(fn(3,4));//7
复制代码
/*
* EC(G)
* VO(G)/GO
* fn:undfiend
* sum===>x001
*
*变量提升
* var fn,sum/obj
*
/
--------------------------------------------------------------------------
sum();//2
fn();//报错 is not function
var fn=function(){
console.log(1);
};
function sum(){
console.log(2);
}
fn();//1
sum();//2
console.log(obj.f1);//TypeError: Cannot read property 'f1' of undefined
var obj={
f1:function(){
console.log(1)
}
}
复制代码
3)return
return 下面的代码虽然不能执行,但是可以进行变量提升,return 后面的代码不进行变量提升
4) 自执行函数
自执行函数在当前所在的作用域中不进行变量提升(自执行函数自己所形成的私有作用域照常进行)
function f2(){
console.log("f2");
}
// 自执行函数在此处不进行变量提升
(function (){
console.log(a);// undefined, 照常进行变量提升
var a=3;
})();
复制代码
5)带var 和不带var的区别
- 带var 的时候就是声明变量,不带var的时候,没有变量提升,
- 带var 声明的变量(是不可配置的),用delete 删除不掉,不带var 的变量 可以删除掉(是可配置的)
在全局作用域下,带var 还是不带var 都是给全局window添加了一个属性,属性名就是此变量,属性值就是变量值
console.log(a); //undefined
var a=3;
b=6;
console.log(window.a);//3
console.log("a" in window);//true
复制代码
【 判断一个对象到底有没有一个属性】:用 “属性名” in 对象,如果返回值是false 说明就是不存在,如果是true说明就是存在。
obj={"name":"lili"};
console.log(“name” in obj )// true 说明name就是obj的属性
console.log("age" in obj)//false 说明age 不是obj的属性
复制代码
6.in 操作符
属性名in对象,验证这个对象有没有这个属性,有 返回 true ;没有 返回 false 。
//验证 name或者age是不是obj的属性
var obj={
name:'东方淼淼'
}
if('age' in obj){
console.log('age存在');
}
//AGE是OBJ属性返回TRUE 不是它的属性返回False
复制代码
es6 中let
1、es6不存在变量提升
console.log(a);//Uncaught ReferenceError: a is not defined
let a=2;
复制代码
2、阻断了与window的关系
let a=2;
console.log(window.a);// undefined
复制代码
3、不能重复声明
es6中没有变量提升,但是有一个自我检测的一个机制,在代码自上而下执行前,会先进行检测,看是否有重复声明的变量,如果有的话,就先报错
let a=2;
console.log(a);
var a=3; // 不能进行重复的声明:Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(3);
复制代码
let a=2;
console.log(a);// 这里不会输出,在代码执行前先进行语法检测,发现重复声明变量,直接报错。此时代码还没从上而下执行
var a=3;
console.log(3);
复制代码
let a=10,
b=10;
let fn=function(){
console.log(a);
// 函数执行,形成一个私有作用域,这里没变量提升,但是有自我检测机制,知道用let声明了一个变量,进行了记录,不存在变量提升,不能在let 之前进行获取变量,所以报错:a is not defined
let a=b=20;
console.log(a,b);
};
fn();
console.log(a,b)
复制代码
let a=10,
b=10;
let fn=function(){
//a 是这里的私有变量,在私有作用域中没有b这个变量,向上级查找,b是全局变量,此时更改的也是全局变量b
let a=b=20;
console.log(a,b);// 20 20
};
fn();
console.log(a,b);// 10 20
复制代码