JS中的堆栈内存及闭包

1.数据值操作机制

1.1.值类型

 var a=1
 var b=a
 b=2
 console.log(a);//=>1
  • 打开浏览器是形成一个全局作用域:栈

  • 变量声明:var a , var b

  • 代码自上而下执行:a=1,b=a(将a的值复制一份放到新位置b上,a与b没有关联),b=2给b重新赋值为2,输出a,这时a依然是原来的1

JS中的堆栈内存及闭包_第1张图片

1.2.引用类型

 var arr1=[1,2]
 var arr2=arr1
 arr2.push(3)
 console.log(arr1);//=>[1,2,3]
  • 依然是先形成全局作用域:栈

  • 变量声明:var arr1,var arr2

  • 代码自上而下执行:

    • [1,2]会在堆中开辟一个内存空间存放0:1,1:2,length:2,假设这个内存空间的地址为aaafff111

    • arr1=aaafff111指向这个内存地址

    • arr2=arr1=aaafff111指向也为这个内存地址

    • arr2.push(3),其实质就是向这个内存地址里添加一个3,这时内存里面为0:1,1:2,2:3,length:3

    • console.log(arr1)输入的是这个内存aaafff111里面的数据,即为:[1,2,3]

JS中的堆栈内存及闭包_第2张图片

1.3.函数

 function sum(){
     var total=0
     for (var i=0;i6
  • 形成全局作用域:栈

  • 函数声明+赋值一次完成,在内存中开辟一个空间,假如为aaafff222存储函数体字符串,然后将这个空间地址赋值给sum,sum=aaafff222

  • sum(1,2,'3','a'),执行函数,会形成一个私有的作用域,在私有作用域里,先执行形参赋值,接着变量声明,代码自上而下执行,最后执行结果为返回6

JS中的堆栈内存及闭包_第3张图片

1.4.补充

栈内存:

  • 提供一个供JS代码自上而下的执行环境

  • 由于基本数据类型值比较简单,他们都是直接在栈内存中开辟一个位置,把值直接存储进去的

  • 当栈内存被销毁,存储的基本数据类型的值也跟着被销毁

堆内存:

  • 存储引用类型的值(对象:键值对,函数:代码字符串)

  • 堆内存在没有被任何变量或者其他东西所占用时,浏览器会在空闲是,自主的进行内存回收,把所有不被占用的堆内存销毁掉(谷歌浏览器)

  • xxx=null,通过这种形式可以释放一个堆内存

2.变量提升机制

2.1.什么是变量提升

当栈内存(作用域)形成,并在JS代码自上而下执行前,浏览器首先会把所有带varfunction关键词的进行提前声明或者定义,这种预先处理的机制称之为变量提升

变量提升阶段:

  • var的之声明未定义

  • function的声明和定义(赋值)都完成

  • 变量提升只发生在当前作用域

  • 函数中存储的都是字符串

 console.log(a)//=>undefined
 var a=1

2.2.带var和不带的区别

不带var

 console.log(a);//=>js:1 Uncaught ReferenceError: a is not defined
 console.log(window.a);//=>undefined
 console.log('a' in window);//false

 

不添加var的声明,相当于在window上添加了一个属性

 a=1
 console.log(window.a);//=>1
 console.log('a' in window);//=>true

 

这样声明的变量相当于两个都带var

 var a=1,b=2

这样声明的变量,b不带var

 var a=b=2

 

带var

在全局作用域下声明一个带var的变量,相当于同时给window全局对象设置了一个属性,变量的值就是属性值(私有作用域中声明的私有变量和window没关系)

var声明的全局变量和window中的属性存在映射机制

 console.log(a);//=>undefined
 console.log(window.a);//=>undefined
 console.log('a' in window);//=>true
 var a=1
 console.log(a);//=>1
 console.log(window.a);//=>1

 

案例1

 console.log(a, b);//=>undefined.undefined
 var a = 12,
     b = 12;
 ​
 function fn() {
     console.log(a, b);//=>undefined,12
     var a = b = 13;
     console.log(a, b);//=>13,13
 }
 ​
 fn();
 console.log(a, b);//=>12,13

私有作用域中带var和不带的区别: 1,带var的在私有作用域变量提升阶段,都声明为私有变量,和外界没有任何关系 2,不带var不是私有变量,会向他的上级作用域查找,看是否为上级变量,不是,继续向上查找,一直找到window为止(我们把这种查找机制叫着:“作用域链”)

JS中的堆栈内存及闭包_第4张图片

2.3.补充

在ES3/ES5语法规范中,只有全局作用域和函数执行的私有作用域,其他大括号不会形成作用域

2.4.等号左边变量提升

 sum()
 fn()//=>Uncaught TypeError: fn is not a function
 ​
 var fn=function(){
     console.log(1);
 }
 ​
 function sum(){
     console.log(2);
 }
  • 形成全局作用域:栈

  • 变量提升:var fn,sum=aaafff111

  • 代码执行:sum()正常执行并输出,fn()由于之声明未赋值,所有报错

2.5.条件判断下的变量提升

当前作用域下,不管条件是否成立都要进行变量提升

  • 带var的之声明

  • 带function的在老版本浏览器声明+定义,在新版本浏览器之声明不定义,相当于var

 console.log(a);//=>undefined
 if('a' in window){
     var a=1
 }
 console.log(a);//=>1

不管if语句的条件是否成立,var a 会在变量提升阶段被声明,同时一旦声明了var a,由于与window映射的关系会在window的属性上添加一个a 属性

 

案例2

 /*
  * 变量提升:无
  */
 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());
  • 形成全局作用域

  • 变量提升:无

  • 代码自上而下执行:

    • window.f

    • window.g

    • 自执行函数,形参赋值:无,变量提升:不管判断条件是否成立,g会被提升,但是只声明未定义,所以当代码自上而下执行是,g()会报错

JS中的堆栈内存及闭包_第5张图片

案例3:

 /*
  * 变量提升:
  *   function fn;
  */
 console.log(fn);//=>undefined
 if (1 === 1) {
     console.log(fn);//=>函数本身:当条件成立,进入到判断体中(在ES6中它是一个块级作用域)第一件事并不是代码执行,而是类似于变量提升一样,先把FN声明和定义了,也就是判断体中代码执行之前,FN就已经赋值了
     function fn() {
         console.log('ok');
     }
 }
 console.log(fn);//=>函数本身

2.6.重名问题的处理

带var和function关键字声明相同的名字,算重名

 var fn
 function fn() {
     
 }

 

关于重名的处理:如果名字重复了,不会重新声明,但是会重新定义(赋值)【不管是变量提升还是代码执行阶段皆如此】

 /*
  * 变量提升:
  *   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();
  • 形成全局作用域:栈

  • 变量提升:fn=....(1) , fn=...(2) ,var fn , fn=...(3) , fn=...(4)

  • 代码执行:4,4,4,fn=100,fn()报错

3.let不存在变量提升

在ES6中基于let/const等方法创建的变量或者函数,不存在变量提升机制

  • 切断了全局变量和window映射机制

  • 同一作用域中,let不能声明相同名字的变量(不管用什么方式在当前作用域下声明了变量,再次使用let创建都会报错)

  • let声明的变量,在代码自上而下执行前,浏览器会做一个重复性检测),自上而下查找当前作用域下所有变量,一旦发现有重复的,直接抛出异常,代码也不会在执行了(虽然没有把变量提前声明定义,但是浏览器已经记住了,当前作用域下有哪些变量)

 console.log(a);//=>Cannot access 'a' before initialization
 let a=1
 let a = 10,
     b = 10;
 let fn = function () {
     console.log(a, b);//=>Uncaught ReferenceError: Cannot access 'a' before initialization
     let a = b = 20;
     /*
      * let a=20;
      * b=20; //=>把全局中的 b=20
      */
     console.log(a, b);
 };
 fn();
 console.log(a, b);

3.1.临时性死区

基于let创建的变量,会把大部分{}当做一个私有的块级作用域(类似于函数的私有作用域),在这里也是重新检测语法规范,看是否有基于新语法创建的变量,如果有按照新语法规范来解析

 var a=1
 if(true){
     console.log(a);//=> Cannot access 'a' before initialization
     let a=2
 }
 console.log(a);

大括号会形成一个块级作用域,并且里面声明了let a,在这个块级作用域执行代码之前,浏览器已经记住了let a,在代码执行时,会按照新语法规范检测

 

在原有浏览器渲染机制下,基于typeof等逻辑运算符检测一个未被声明过的变量,不会报错,返回undefined

 console.log(typeof a);//=>undefined

 

如果当前变量是基于ES6语法处理,在没有声明这个变量的时候,使用typeof检测会报错,解决了原来的JS死区

 console.log(typeof a);//=>Cannot access 'a' before initialization
 let a

4.作用域处理机制

4.1.全局变量和私有变量

 var a=1
     b=2
     c=3
 ​
 function fn(a){
     console.log(a,b,c);//=>1,undefined,3
     var b=c=a=10
     console.log(a,b,c);//=>10,10,10
 }    
 fn(a)
 console.log(a,b,c);//=>1,2,10
  • 形成全局作用域:栈

  • 变量提升:var a,var b, var c , fn=aaafff111,函数相当于在堆内存里面开辟了一个地址为aaafff111的内存,并把函数体以字符串的形式存在这个内存中

  • 代码自上而下执行,a=1,b=2,c=3,fn(1),在执行到fn(1)是,会开辟一个私有作用域,在私有作用域里,先是形参赋值,相当于var a=1,然后变量提升,var b,代码自上而下执行

JS中的堆栈内存及闭包_第6张图片

案例:

 var arr=[1,2]
 function fn(arr){
     console.log(arr);//=>[1,2]
     arr[0]=10
     arr=[100]
     arr[0]=0
     console.log(arr);//=>[0]
 }
 fn(arr)
 console.log(arr);//=>[10,2]

JS中的堆栈内存及闭包_第7张图片

4.2.作用域链机制

当前函数执行,形成一个私有作用域A,A的上级作用域是谁,和它在哪里执行没关系,和它在哪里创建有关系,在哪里创建,他的上级作用域就在那里

 var n = 10;
 function fn() {
     var n = 20;
     function f() {
         n++;
         console.log(n);
     }
     f();
     return f;
 }
 var x = fn();//21
 x();//22
 x();//23
 console.log(n);//10

5.JS中的堆内存释放

JS中的内存分为堆内存和栈内存

  • 堆内存:存储引用数据类型值(对象:键值对,函数:代码字符串)

  • 栈内存:提供JS代码执行的环境和存储基本类型值

堆内存释放

让所用引用堆内存空间地址的变量赋值为null即可(没有变量占用这个堆内存了,浏览器会在空闲的时候把他释放掉)

栈内存释放

一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉(在栈内存中存储的值也都会释放掉),但也有特使不销毁情况:

1.函数执行完成,当前形成的栈内存中,某些内容被栈内存以外的变量占用了,此时栈内存不能释放

2.全局栈内存只有在页面关闭时才会被释放

 var i = 1;
 function fn(i) {
     return function (n) {
         console.log(n + (++i));
     }
 }
 var f = fn(2);
 f(3);//=>6
 fn(5)(6);//=>12
 fn(7)(8);//=>16
 f(4);//=>8
 var i = 2;
 function fn() {
     i += 2;
     return function (n) {
         console.log(n + (--i));
     }
 }
 var f=fn();//i=4
 f(2);//=>i=3,3+2=5
 f(3);//=>i=2,2+3=5
 fn()(2);//=>i=4,i=3,3+2=5
 fn()(3);//=>i=2,2+3=5
 f(4);//=>i=4,i=3,3+4=7

6.闭包

函数形成一个私有作用域,保护里面私有变量不收外界的干扰,这种保护机制称为闭包

市面上的开发者认为的闭包:形成一个不销毁的私有作用域(私有栈内存)才是闭包

6.1.闭包:柯理化函数

 function fn(){
     return function(){
 ​
     }
 }
 var f=fn()

6.2.闭包:惰性函数

 var utils=(function(){
     return {
 ​
     }
 })()

项目中为了保证JS性能(堆栈内存的性能优化),应该尽可能的减少闭包的使用(不销毁的堆栈内存是耗性能)

特性:

  • 闭包具有保护作用,保护私有变量不收外界干扰

  • 闭包具有保护作用:形成不销毁的栈内存,把一些值保存下来,以便后面的调取使用

案例:

Jquery:把需要暴露的方法抛到全局

 (function(){
     function jQuery(){
         //...
     }
     window.jQuery=$=jQuery
 })()

Zepto:这种方式:基于RETURN把需要共外面使用的方法暴露出去

 var Zepto=(function () {
     //...
     return {
         xxx:function () {
 ​
         }
     };
 })();

你可能感兴趣的:(JS)