【JavaScript】JS高级-堆栈内存与闭包作用域

《知识补充》

JS数据类型

  • 基本数据类型(值类型)
    • 数字number、字符串string、布尔boolean、null、undefined
  • 引用数据类型
    • object对象(实例):对象、数组、正则、日期、Math数学函数 实例对象…
    • function函数对象
  • es6新增:Symbol唯一值类型
    注:
    1、对象的属性名一定不能是引用类型值,而是基本数据类型,如果是引用类型值做属性名-会调用对应原型上的toString()方法转成字符串处理,当object对象作为属性名是会统一转成“[object Object]:”,函数对象则转为相应字符串;
    2、数组是特殊的object对象,原因是数组属性名是有序递增的索引,当然最后还有length属性;
    3、Symbol唯一值作为对象属性名时,就不会一样。
let a = {},b = Symbol('1'),c = Symbol('1');
a[b] = '哈哈';
a[c] = '呵呵';
console.log(a[b]);//哈哈

闭包

闭包就是能够读取其他函数内部变量的函数。在javascript中,只有函数内部的子函数才能读取局部变量.
所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将子函数内部和函数外部连接起来的桥梁。


一、堆(heap)、栈(stack)内存

1、浏览器执行代码机制
第一步编译器:把代码解析成浏览器看得懂的AST结构;

  • 词法解析
  • AST抽象语法树
  • 构建浏览器能够执行的代码

第二步引擎:(渲染引擎和JS引擎,V8 /webkit内核);

  • 变量提升
  • 作用域/闭包
  • 变量对象
  • 堆栈内存
  • GO/VO/AO/EC/ECStack

2、JS引擎执行机制–栈内存(执行上下文环境栈)
栈内存==执行上下文环境栈(ECStack):JS引擎执行代码时创建的一个执行栈,用于存放多个执行上下文;存储基本类型的值;

let a = 12;
let b = 12;
变量赋值的三步:
第一步:创建值:基本值直接在栈中创建和存储即可,如果已经存在,不会创建新的,一个变量只能跟一个值关联;
第二步:创建变量(声明)//声明declare,如果变量已存在,就不新建;所以变量就是指针可以改变的量;
第三步:让变量和值关联(赋值=指向)//定义defined,未关联的就是undefined
//const定义常量:其实就是指针不能改变的变量;
-----
let a=0;
let b=a;
b++;//b=b+1 ==> b=1,重新赋值
alert(a);//"0"

执行上下文(EC:Execution Context):每个域下的代码执行都有自己的执行上下文,不是作用域,EC()会被按次序压缩进栈ECStack中执行,当前EC执行完后没用的出栈,还有用的继续压缩在栈底(闭包)

  • 全局执行上下文(ECG)、函数等执行上下文(EC…)

3、JS引擎执行机制–堆内存(heap)

let a = {n:12};
变量赋值三步依旧:
1、创建值
2、创建变量
3、给变量赋值
<1>//特别:
第一步,创建的值是引用类型,此时不再是直接在栈内存存放,
而是在栈内存之外单独创建一块堆内存,专门用于存放该引用类型数据,
如对象中键值对、函数中代码等;每块堆内存都有一个16进制地址,存在栈内存中,
栈内存中将变量与这个地址关联存放起来。
<2>//注!:
当栈内存中有变量关联这个堆内存时,这个堆内存就不会被销毁,
当没有变量关联时,浏览器会在空闲时销毁这个堆内存;
当想销毁某块堆内存时,可以将与其对应的这个变量的指针指向null,
而不能指向‘0’,因为‘0’是基本类型也会占用栈内存;

null、0、undefined区别:null是空,表示要给变量赋值还没赋,用于让变量释放堆内存而不占用其他内存空间;0是基本数据类型,占用栈内存;undefined是未定义,表示变量只被定义从未赋过值。

4、例题

//阿里面试
let a={n:10};
let b=a;
b.m=b={n:20};//相当于b.m={n:20};b={n:20}
console.log(a);//{n:10,m:{n:20}}
console.log(b);//{n:20}
//360面试题
let x=[12,23];
function fn(y){
y[0]=100;
y=[100];
y[1]=200;
console.log(y);
}
fn(x);//[100,200]
console.log(x);//[100,23]

二、GO全局对象、VO变量对象、AO活动变量对象

1、GO全局对象:存放全局的属性,在ECG全局执行上文中被浏览器创建,并赋值给window对象,包含了很多全局属性;

GO={ 
setTimeout:function(){},
...
window:this
}//很多全局属性

2、VO变量对象:存储当前上下文中的变量;每个执行上下文都有变量对象;函数执行上下文中是AO对象;不是全局对象的都在变量对象中存放,一般就是人为创建的变量;
3、AO活动变量对象:相当于函数中的VO变量对象;

三、函数

1、 函数也是变量,与let、var创建的变量本质一样,区别在于存储的值是函数类型–>
(1)存储代码字符串【函数特点】;
(2)存储键值对【对象特点】;

function fn(y){
y[0]=100;y=[100];y[1]=200;console.log(y);
}
fn(x);//x是传进来的实参;
//堆内存中存储两类:
1、代码字符串:(控制台看不到,浏览器不让看)
“y[0]=100;y=[100];y[1]=200;console.log(y);2、键值对:(控制台能看到)
length:1(形参个数)
name:"fn"
prototype:...原型

2、自执行函数(function(){})()支持返回值,+/-/!function(){}()不支持返回值;

3、函数执行时,ECStack栈内存中会形成全新的函数执行上下文EC(FN),因为JS是单线程,所以执行到函数时会把没有执行完的全局代码压缩到栈底让函数上下文进栈开始执行函数代码,执行完如果没有被外界引用就出栈;

  • 函数的执行上下文中有形成AO(活动的变量对象)、内置的实参集合arguments={};

4、函数赋值

let x = [12,23];
function fn(y){
y[0]=100;y=[100];y[1]=200;console.log(y);
}
fn(x);//执行函数,生成函数执行上下文ECFN,引发函数赋值
//ECFN函数执行上下文中函数赋值三步:创建+执行。
第一步:初始化实参集合:arguments={0:x的堆内存指针地址}
第二步:创建形参变量并赋值:y = x的指针地址//没有传,就是undefined;
第三步:代码执行:y[0]=100;y=[100];y[1]=200;console.log(y);
//非严格模式下:
实参arguments与形参有映射关系,所以两者之一发生改变都会变化;
//“use strict”严格模式下,阻隔映射关系,各自独立;

5、函数代码执行部分

  • 逻辑或、逻辑与
A||B逻辑或:A为真,返回A,否则返回BA&&B逻辑与:A为真,继续走,返回BA为假,直接返回A&&>||:逻辑或优先级高于逻辑与
~function(x){
x=x||20&&30||40;
console.log(x);//30
}();
  • 逻辑或||:一般用于避免形参为undefined
当函数没有传入实参时,避免形参为undefined的两种方式:
~function(x){}()
arguments = {};x=undefined;
//方式一:ES6赋值初始值
function fn(x=0){}
//方式二:传统方式
function fn(x){
if(typeof x==='undefined'){x=0;}
或者
x=x||0;//逻辑或
}
  • 逻辑与&&:一般用于实参为回调函数时的自动执行
//当callback是正确的回调函数时才执行;
function fn(callback){
typeof callback ==="function"?callback():null;
或者
callback&&callback();//默认实参callback只能是正常函数或没有传,所以可以执行;
}
  • 私有变量:执行上下文中所有在变量对象中的变量都是私有变量,如函数执行上下文ECFN中私有变量有两种:一、形参;二、在该上下文中声明的

扩展:
1、let定义变量的书写格式区别

let x=10,y=20;
相当于let x=10;let y=20;
let x=y=20;
相当于let x=20;y=20;//let声明的变量一定是当前作用域新建的变量(全局变量或函数作用域私有变量),不是let声明的就未必了,要不是全局有的,要不是undefined;

2、Array数组的浅拷贝
只拷贝外层,如果数组中有多维数组,那么深层的指针地址还是不变;所以会有受原数组变化影响的风险。

let x=[1,2];//堆内存地址为AAAFFF000
x=x.slice(0);//拷贝一份到新的堆内存中;//堆内存地址为BBBFFF000
slice() :arrayObject.slice(start必填项,end)
//方法可从已有的数组中返回选定的元素。

3、浏览器的“垃圾回收机制”(内存释放机制)

  • 谷歌浏览器的“垃圾回收机制”

在空闲时,把所有不被占用的堆内存进行释放和销毁;

  • IE浏览器的“垃圾回收机制”

利用计数器,当堆内存被占用一次,加1并累计;取消占用就减1,一直减到0就销毁;不足之处是可能计数混乱,引发内存泄漏;《红宝书》

4、学习路径:JS—引擎—编译器(如webpack)—c++/c写浏览器—汇编语言(创造语言而诞生的语言)


四、闭包作用域

Scope作用域、Scope Chain作用域链
1、创建函数时:

  • 创建一个堆内存(存储代码字符串和对应的键值对);
  • 初始化了当前函数的作用域[[scope]]==所在上下文EC中的变量对象VO/AO,就是当前执行上下文的一部分;

2、函数执行时:

  • 在栈内存创建一个新的执行上下文EC(压缩到ECStack栈内存中执行);
  • 初始化this的指向;
  • 初始化作用域链[[scopeChain]];
    ==>遇到变量一层一层往外层作用域(就是变量对象部分)找。
  • 创建AO变量对象用来存储变量;
    ==>arguments =>形参 => 代码执行。
    在这里插入图片描述
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值