欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-07 21:43:50
数据渲染机制及堆栈内存1. 数据值操作机制/** 1. 先声明一个变量a,没有赋值(默认值谁undefined)* 2. 在当前作用域中开辟一个位置存储12这个值* 3. 让变量a和12关联在一起(定义:赋值)*/var a = 12var b = a;b = 13;console.log(a);var ary1 = [12, 23];var ary2 = ary1;ary2.push(100);console.log(ary1)function sum() {var ....

数据渲染机制及堆栈内存

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'))

解析如下图:
夯实基础,彻底掌握js的核心技术(三):堆栈内存及闭包详解
栈内存:作用域

  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'))

变量提升机制解析:
夯实基础,彻底掌握js的核心技术(三):堆栈内存及闭包详解

  • 带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, 12
  var a = b = 13
  console.log(a, b) // 13 13
}
fn();
console.log(a, b) // 12, 13

解析如下图:
夯实基础,彻底掌握js的核心技术(三):堆栈内存及闭包详解
作用域链的扩展:

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 defined
	let a = b = 20
  console.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, 14
  var b = c = a = 20;  // var b = 20; c=20; a = 20
  console.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]

解析如下图:
夯实基础,彻底掌握js的核心技术(三):堆栈内存及闭包详解

2. 查找上级作用域

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

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

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


//--------

解析如下图:
夯实基础,彻底掌握js的核心技术(三):堆栈内存及闭包详解
夯实基础,彻底掌握js的核心技术(三):堆栈内存及闭包详解

4. 闭包之保护机制

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

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

//闭包:惰性函数
var utils = (function(){
	return {
  }
})()

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

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

如果想了解更多,请扫描下面二维码,关注公众号
夯实基础,彻底掌握js的核心技术(三):堆栈内存及闭包详解

本文地址:https://blog.csdn.net/yilanyoumeng3/article/details/107498061

相关标签: js