数据渲染机制及堆栈内存

1. 数据值操作机制

/*
* 1. 先声明一个变量a,没有赋值(默认值谁undefined)
* 2. 在当前作用域中开辟一个位置存储12这个值
* 3. 让变量a和12关联在一起(定义:赋值)
*/
var a = 12
var b = a;
b = 13;
console.log(a);
var ary1 = [12, 23];
var ary2 = ary1;
ary2.push(100);
console.log(ary1)
function sum() {var total = null;for(var i = 0; i< arguments.length; i++;) {var item = arguments[i];item = parseFloat(item);!isNaN(item) ? total += item : null;}return total;
}
console.log(sum(12, 23, '34', 'AA'))

解析如下图:

栈内存:作用域

  1. 提供一个供js代码自上而下执行的环境(代码都是在栈中执行)
  2. 由于基本数据类型值比较简单,它们都是直接在栈内存中开辟一个位置,把值直接存储进去的
  3. 当栈内存被销毁,存储的那些基本值也都跟着被销毁了

堆内存:引用值对应的空间

  1. 存储引用类型值的(对象:键值堆 函数:代码字符串)
  2. 当前堆内存释放销毁,那么这个引用值彻底没了
  3. 堆内存的释放: 当堆内存没有被任何的变量或者其它东西所占用,浏览器会在空闲的时候,自主的进行内存回收,把所有不被占用的堆内存销毁掉(谷歌浏览器)(xxx= null,通过空对象指针null可以让原始变量(或者其它东西)谁都不指向,那么原有被占用的堆内存就没有被东西占用了,浏览器会销毁它)

2. 变量提升机制

  • 什么事变量提升

变量提升: 当栈内存(作用域)形成,js代码自上而下执行之前,浏览器首先会把所有带va r/function关键词进行提前“声明”或者“定义”,这种预先处理机制称为“变量提升”
声明:(declare):var a/ function sum
定义:(defined)a= 12 (定义其实就是赋值操作)
在变量提升阶段:
1. 带“var”的只声明未定义
2. 带“function”的声明和赋值都完成了

  1. 变量提升只发生在当前作用域
  2. 在全局作用域下声明函数或者变量是“全局变量”,同理,在私有作用域下声明的变量是“私有变量”(带var/function的才是声明)
  3. 浏览器很懒,做过的事情不会重复执行第二遍,也就是,当代码执行遇到创建函数这部分代码后,直接的跳过即可(因为在提升阶段就已经完成函数的赋值操作了)
var a = 12
var b = a;
b = 13;
console.log(a);
var ary1 = [12, 23];
var ary2 = ary1;
ary2.push(100);
console.log(ary1)
function sum() {var total = null;for(var i = 0; i< arguments.length; i++;) {var item = arguments[i];item = parseFloat(item);!isNaN(item) ? total += item : null;}return total;
}
console.log(sum(12, 23, '34', 'AA'))

变量提升机制解析:

  • 带var和不带的区别

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

console.log(a) // undefined
console.log(window.a) // undefined
console.log('a' in window) // true 在全局作用域中声明了一个变量a,此时就已经把a当成属性赋值给window了,只不过此时还没有给a
//赋值,默认值undifined  in:检测某个属性是否隶属于这个对象
var a = 12; // 全局变量值修改,window的属性值也跟着修改
console.log(window.a) // window的一个属性名a 12
a = 13
console.log(window.a) // 13
window.a = 14;
console.log(a) // 14

以上例子说明:全局变量和window中的属性存在“映射机制”
**
不加var的本质是window的属性
如下面例子:

console.log(a) // Uncaught ReferenceError: a is not defined
console.log(window.a) // undefined
console.log('a' in window) // false
a = 12 // window.a = 12
console.log(a) // 12
console.log(window.a) // 12
var a = 12,b = 13; // 这样写b是带var的
var a = b = 12; // 这样写b是不带var的

私有作用域中带var和不带var也有区别:

  1. 带var的在私有作用域变量提升阶段,都声明为私有变量,和外界么有任何关系
  2. 不带var的不是私有变量,会向它的上级作用域查找,看是否为上级变量,不是,继续向上查找,一直查找到window为止(我们把这种查找机制叫做:“作用域”),也就是我们在私有作用域中操作的这个非私有变量,是一直操作别人的
console.log(a, b) // undefined , undefined
var a = 12,b = 12;
function fn() {console.log(a, b) // undefined, 12var a = b = 13console.log(a, b) // 13 13
}
fn();
console.log(a, b) // 12, 13

解析如下图:

作用域链的扩展:

function fn() {b= 13;console.log('b' in window) // true,在作用链查找的过程中,如果找到window也没有这个变量,相当于给// 给window设置了一个属性b(window.b = 13)console.log(b)
}
console.log(b)
  • 等号左边变量提升
/** 变量提升:* var fn; 只对等号左边进行变量提升**/sum();
fn(); // Uncaught TypeError: fn is not a function// 匿名函数之函数表达式
var fn = function () {console.log(1)
}// 普通函数
function sum() {console.log(2)
}
  • 条件判断下的变量提升

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

  1. 带var的还只是声明
  2. 带function的在老版本浏览器渲染机制下,声明+定义,但是为了迎合ES6中的块级作用域,新浏览器对于函数(在条件判断中的函数)是否成立,都只是先声明,没有定义,类似var
  3. 在全局作用域下声明的全局变量也相当于给window设置了一个属性
/*** 在当前作用域下,不管条件是否成立都要进行变量提升* 带var的还只是声明* 带function的在老版本浏览器渲染机制下,声明+定义,但是为了迎合ES6中的块级作用域,新浏览器对于函数(在条件判断中的函数)是否成立,都只是先声明,没有定义,类似var
*/
console.log(a) // undefined
if(1 === 2) {var a = 12
}
console.log(a) //undefined
//在全局作用域下声明的全局变量也相当于给window设置了一个属性
console.log(a) //undefined
if('a' in window) {var a = 100
}
console.log(a) // 100
f= function () {return true;
}; // window.f = ....
g = function () {return false
}; // window.g = ...
~function() {/** 变量提升:* function g; g是私有变量*/if(g() && ([] == ![])) { // Uncaught TypeError: g is not a function [] == ![] => 0 == 0// 把全局中的f进行修改f= function () {return false};function g() {return true;}}
}()
console.log(f());
console.log(g());
/** 全局下变量提升* function fn
*/console.log(fn) // undefined
if(1 === 1) {console.log(fn)function fn() {console.log('ok')}
}
console.log(fn) //函数本身
  • 重名问题的处理
  1. 带var 和function 关键字声明相同的名字,这种也算事重名了(其实是一个,只是存储的值类型不一样)
  2. 关于重名处理:如果名字重复了,不会重新的声明,但是会重新的定义(重新赋值)【不管是变量提升还是代码执行阶段都是如此】
fn(); // 4
function fn() {console.log(1);
}
fn(); // 4
function fn() {console.log(2);
}
fn(); // 4
var fn = 100 // 带var的在提升阶段只把声明处理了,赋值操作没处理,所在在代码执行的时候需要完成赋值100
fn(); // Uncaught TypeError: fn is not a function
function fn() {console.log(3);
}
fn();
function fn() {console.log(4);
}
fn();

3. ES6中let不存在变量提升

  • 不允许重复定义
  • 不存在变量提升
  1. 在ES6中基于let/const等方式创建变量或者函数,不存在变量提升机制
  2. 切断了全局变量和window属性的映射机制
  3. 在相同的作用域中,基于let不能声明相同的名字的变量(不管用什么方式在当前作用域下声明了变量,再次使用let创建都会报错)
  4. 虽然没有变量提升机制,但是在当前作用域代码自上而下执行之前,浏览器会做一个重复性检测,自上而下查找当前作用域下所有变量,一旦发现有重复的,直接抛出异常,代码也不会再执行了(虽然没有把变量提升声明定义,但是浏览器已经记住了,当前作用下有哪些变量)
console.log(a) //Uncaught ReferenceError: a is not defined
let a = 12;
console.log(window.a) // undefined
console.log(a)  // 12//-------------
let a = 10,b = 10
let fn =function () {// console.log(a, b)  //Uncaught ReferenceError: a is not definedlet a = b = 20console.log(a, b) // 20, 20
}
fn();
console.log(a, b) // 10, 20//-------------
let a = 12
console.log(a)
let a = 13  // Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a)
  • 暂时性死区
  1. 基于let创建变量,会把大部分{}当作一个私有块级作用域(类似函数的私有作用域),在这里也是重新检测语法规范,看一下是否基于新语法创建的变量,如果是按照新语法规范来解析
  2. es6解决了浏览器的暂时性死区问题
var a = 12;
if(true) {console.log(a)let a = 13
}//--------
console.log(typeof a) // "undefined" 在原有浏览器渲染机制下,基于typeof等逻辑运算符检测一个未被声明的// 变量不会报错,返回undefined//--------
console.log(typeof a)  // Uncaught ReferenceError: a is not defined
let a //如果当前变量是基于es6语法处理,在没有声明这个变量的时候,使用typeof检测会直接报错,不会是undefined,解决了原有的js死区问题

闭包作用域(scope)

1. 区分私有变量和全局变量

在私有作用域中,只有以下两种情况是私有变量:

  1. 声明过的变量(带var/function)
  2. 行参也是私有变量

剩下的都不是自己私有变量,都需要基于作用域链的机制向上查找

var a = 12,b = 13,c = 14;
function fn(a) {/** 行参赋值 a = 12* 变量提升: var b* 在私有作用域中,只有以下两种情况是私有变量* 1.声明过的变量(带var/function)* 2.行参也是私有变量* 剩下的都不是自己私有变量,都需要基于作用域链的机制向上查找**/console.log(a, b, c) // 12, undefined, 14var b = c = a = 20;  // var b = 20; c=20; a = 20console.log(a, b, c) //20, 20, 20
}
fn(a)
console.log(a, b, c) // 12, 13, 20//---------
var ary = [12, 23]
function fn(ary) {console.log(ary) //[12, 23]ary[0] = 100;  //[100, 23]ary = [100] // [100]ary[0] = 0 // [0]console.log(ary) //[0]
}
fn(ary)
console.log(ary) //[100, 23]

解析如下图:

2. 查找上级作用域

  1. 当前函数执行,形成一个私有作用域A,A的上级作用域是谁,和它在哪执行的没有关系,和它在哪创建的有关系,在哪创建(定义)的,它的上级作用域就是谁
var a = 12
function fn() {// arguments:实参集合,arguments.callee:函数本身fnconsole.log(a)
}
function sum() {var a = 120fn(); // 12
}
sum();//-------
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



3. 闭包及堆栈内存释放

js中的内存分为堆内存和栈内存
堆内存:存储引用数据类型值(对象:键值对 函数: 代码字符串)
栈内存:提供js代码执行的环境和存储基本类型数据
【堆内存释放】
让所引用的堆内存空间地址的变量赋值为null即可(没有变量占用这个堆内存了,浏览器会在空闲的时候把它释放)
【栈内存释放】
一般情况下,当函数执行完成,所形成的私有作用域(栈内存)都会自动释放掉(在栈内存中存储的值也都会释放掉),但是也有特殊不销毁的情况:

  1. 函数执行完成,当前形成的栈内存中,某些内容被栈内存以外的变量占用了,此时栈内存不能释放(一旦释放,外面找不到原有的内容了)。
  2. 全局栈内存只有在页面关闭的时候才会被释放掉

如果当前栈内存没有释放,那么之前在栈内存中存储的基本值也不会释放,能够一直保存下来

var i = 1;
function fn(i) {return function (n) {console.log(n+ (++i));}
}
var f = fn(2); //先把fn执行(传递实参2),把fn执行的返回结果(return后面的值)赋值股f
f(3) // 把返回的结果执行 =》 6
fn(5)(6) //12 和上面两个步骤类似,都是把fn执行,都是先把fn执行,把fn执行的返回的结果再执行
fn(7)(8) // 16
f(4) //8//---------
// 在和其它值进行运算的时候一些区别
// i++ ;自身累加1(先拿原有值进行运算,运算结束后,本身累加)
// i++; 自身累加1 (先自身累加1,拿累加后的结果进行运算)
var k = 1;
console.log(5+(k++), k) // 6, 2
console.log(5+ (++k), k) //7, 2//--------

解析如下图:

4. 闭包之保护机制

闭包:
函数执行形成一个私有的作用域,保护里面的私有变量不受外界的干扰,这种保护机制称之为“闭包”。
市面上的开发者认为的闭包是:形成一个不销毁的私有作用域(私有栈内存)才是闭包。

// 闭包:柯理化函数
function fn() {return function(){}}
var f = fn()//闭包:惰性函数
var utils = (function(){return {}
})()

真实项目中为保证js的性能(堆栈内存的性能优化),应该可能减少闭包的使用(不销毁的堆栈内存是耗性能的)
闭包保护功能:

  1. 闭包具有“保护”作用:保护私有变量不受外界的干扰(在真实项目只能够,尤其是团队协作开发的时候,应该尽可能地减少全局变量的使用,防止相互之间的冲突(“全局变量污染”)),那么此时我们完全可以把自己这一部分内容封装到一个闭包中,让全局变量转换为私有变量。
  • jQuery方式:把需要暴露的方法抛到全局
  • zepto这种方式:基于return把需要供外面使用的方法暴露出去
  1. 闭包具有“保护”作用:形成不销毁的栈内存,把一些值保存下来,方便后面的调取使用

如果想了解更多,请扫描下面二维码,关注公众号

夯实基础,彻底掌握js的核心技术(三):堆栈内存及闭包详解相关推荐

  1. Android基础入门教程——8.3.1 三个绘图工具类详解

    Android基础入门教程--8.3.1 三个绘图工具类详解 标签(空格分隔): Android基础入门教程 本节引言: 上两小节我们学习了Drawable以及Bitmap,都是加载好图片的,而本节我 ...

  2. 操作系统:第三章 内存管理2 - 详解虚拟内存,页面置换算法,页面分配策略

    本文已收录至 Github(MD-Notes),若博客中有图片打不开,可以来我的 Github 仓库:https://github.com/HanquanHq/MD-Notes,涵盖了互联网大厂面试必 ...

  3. 圆形界面 开启相机_「基础篇三」手机摄影拍照界面详解

    ​[基础篇三]手机摄影拍照界面详解 手机拍照对我们来说已习以为常,每天我们都会用手机相机功能或多或少的拍出几张照片.故手机拍照界面对我们来说也不陌生,但手机拍照界面上的那些按钮,那些功能你都用过吗?你 ...

  4. spring(7)---深入理解Spring核心技术——Spring中的各模块详解

    深入理解Spring核心技术--Spring中的各模块详解 Spring框架的两个基本概念IOC容器和AOP,相信大家现在对Spring中的这两个部分的基本概念有了一定的认识,好了,那么今天我们就来正 ...

  5. 开启注解缓存_Spring Boot 2.x基础教程:进程内缓存的使用与Cache注解详解

    随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一.Spring 3开始提供了强大的基于注解的缓 ...

  6. QT快速入门、三点求圆心实现详解

    在编程中,会经常用到数学计算,所以C++将常用的数学计算,例如求正余弦等,封装成函数(正是我们在3.2 数学计算中学习到的),我们只需要写入简单的语句就可以执行所需要的功能,这正是函数的意义.在这一章 ...

  7. Linux 创建网页服务,Linux使用Node.js建立访问静态网页的服务实例详解

    Linux使用Node.js建立访问静态网页的服务实例详解 一.安装node.js运行所需要的环境,: 二.创建node目录(/node/www),并在目录下创建node.js服务文件server.j ...

  8. JS逆向之补环境过瑞数详解

    JS逆向之补环境过瑞数详解 "瑞数" 是逆向路上的一座大山,是许多JS逆向者绕不开的一堵围墙,也是跳槽简历上的一个亮点,我们必须得在下次跳槽前攻克它!! 好在现在网上有很多讲解瑞数 ...

  9. 华为、H3C、锐捷三家交换机配置命令详解

    一.华为交换机基础配置命令 1.创建vlan: <Quidway> //用户视图,也就是在Quidway模式下运行命令. <Quidway>system-view //进入配置 ...

最新文章

  1. 红黑树二叉查找树二叉排序树的理解
  2. [翻译]SQL Server 未公开的两个存储过程sp_MSforeachtable 和 sp_MSforeachdb
  3. 【HDU - 1254 】推箱子 (双bfs)
  4. unix dos mac 文件格式不同导致问题
  5. 检测范围_论文检测系统的检测范围有哪些
  6. 深度优化LNMP之PHP
  7. 单模光电转换器怎么接_单模光纤收发器怎么连接?
  8. 介绍一关于Navier-Stokes方程的神级博文
  9. 如何强大且优雅的搞定Linux文件系统,值得一读!
  10. [转载] Python中关于字符串的使用演示
  11. 集合框架之Connection(马士兵教育视频教程总结笔记)
  12. ftp用户名 密码输入正确 登陆报530 Login incorrect.331 Please specify the password.
  13. Simulink仿真Boost电路
  14. 没有产权证的车库能不能随时拆掉
  15. android 11.0 12.0添加系统字体并且设置为默认字体
  16. 串的基本操作及统计字符频度-数据结构类C语言
  17. python的几次方_python 几次方
  18. 解决:Import googleapiannotations.proto was not found or had errors
  19. Redux中的Reducers
  20. WebBench压力测试工具(详细源码注释+分析)

热门文章

  1. android安全补丁卸载,这种安全更新能不卸载就不卸载
  2. 关于软考高级作文的几点想法
  3. 软考网络规划设计师基础知识考察要点
  4. 将毫秒转换_上海科大:超强电镜技术!原子级分辨率,毫秒级可视化
  5. 看《Linux入门讲座》随记
  6. fastjson JSONObject.toJSONString 出现 $ref: $.的解决办法(重复引用)
  7. Microsoft Visual Studio Tools for AI
  8. 轻松逃脱某防火墙对ss的探测
  9. 技能拓展笔记-React(一)
  10. iOS 10 (X8)上CoreData的使用(包含创建工程时未添加CoreData)