js之堆、栈内存基础
函数渲染规则
全局作用域:当浏览器打开页面时,浏览器会给当前JS代码提供一个可以执行的运行环境,那么这个环境就是全局作用域。
- 一个页面只对应一个全局作用域;在当前的全局作用域,浏览器给当前作用域提供了全局的对象叫
window
; - JS是单线程的,每次只能执行一行代码。
- 在JS中只要遇到报错,代码立即停止,不再向下执行。
全局变量:在全局作用域定义的变量都是全局变量。全局变量都会给window新增一个键值对。
私有变量:在私有作用域中定义的变量;
- 外层作用域获取不到私有作用域下的变量;
- 形参也是私有变量;
- 在私有作用域下定义的也是私有变量。
闭包:函数执行会形成一个私有的作用域,保护里面的变量不受外界干扰。
在原有浏览器渲染机制下(ES6之前),基于typeof等逻辑运算符检测一个未被声明过得变量,不会报错,返回undefined;ES6版本直接报错
变量提升
在当前作用域下,代码执行之前,把当前作用域下带var和function的要进行提前声明(通知当前作用域有这么一个变量),带var的只声明不定义,function不仅声明而且还要定义(赋值)。
变量提升五种特殊情况
- 如果有条件,不管条件是否成立,都要进行变量提升;对于function函数,在最新的版本浏览器中,只声明不定义;在旧版本浏览器中,带function的不仅声明,还要定义。
在块级作用域下,定义的函数,只要一进块级作用域,它会立即开辟空间,并且赋值。
console.log(num);//undefined
console.log(ef);//undefined
if([]){
console.log(ef);//整个函数
var num=100;
function ef(){
}
}
- 变量提升只发生在等号的左边。
var fn=function fn1(){
}
//等号的右边不会发生变量提升,只有执行到这一行时,右边函数才开始开辟堆内存。
- return下面的代码需要进行变量提升;但是如果return后面紧跟的代码(返回的值)不进行变量提升。
- 如果变量名重复,不需要重新声明,但是要重新定义。(不管是变量提升还是代码执行阶段都是如此)
//例1
console.log(fn); //函数本身
var fn = 88;
function fn() {
}
console.log(fn);//88
//例2
fn();//3
function fn() {//在变量提升阶段已经对这段代码解析过了,代码运行时,不再解析
console.log(1);
}
fn();//3
function fn() {
console.log(2);
}
fn = 100;//变量提升阶段不提升
function fn() {
console.log(3);
}
fn();//报错
- 匿名函数不需要进行变量提升。当代码解析到匿名函数时,会先开辟一个堆内存,然后马上形成一个栈内存。
在ES6:ECMAScript中:
let声明变量
let声明的变量不进行变量提升
在同一作用域下,let声明的变量不可以重名
ES6中,块级作用域:if...else...、for、while...,唯独对象的大括号不是块级作用域
上一级作用域
当获取变量对应值时,如果当前作用域不存在,需要向上一级作用域查找。
函数的上一级作用域跟函数在哪执行没关系,跟函数在哪定义有关,函数在哪个作用域下定义,上一级作用域就是谁。
作用域链:当获取变量值时,首先会看自己私有作用域有没有,如果没有,需要向上一级作用域查找,如果上一级 也没有,需要再向上一级查找,指导找到全局作用域为止,如果全局作用域也没有,那就报错,这样一级一级向上查找形成一条作用域链。
堆内存与栈内存
在JS中,浏览器会形成两个虚拟的内存:栈内存和堆内存
堆内存
主要用来存储引用数据类型内容的。
栈内存:作用域
- 提供了代码的运行环境;
- 存储基本数据类型值。
堆内存的销毁
堆内存的回收:
- 谷歌浏览器:每隔一段时间会对引用类型的空间地址进行检测,检测当前地址有没有被占用;如果没有被占用,那么将其回收;如果占用,不能回收。
- 火狐和IE:采用了计数的规则,当前的引用地址被占用一次,就会+1;如果不再占用,就会-1;如果当前地址被占用次数为0,那么浏览器将其立即回收。
栈内存的销毁
全局作用域的销毁
全局作用域只有当关闭页面或关闭浏览器时,才会销毁;属于不销毁的作用域。
作用域一旦销毁,那么作用域所提供的代码运行环境也会随着销毁,而且存储的基本数据类型值也会随着回收。
私有作用的销毁
- 函数执行会产生私有作用域;
- 函数每执行一次,都会形成一个新的栈内存;
立即销毁的作用域
一般情况下,函数执行完毕,浏览器会对其栈内存立即进行回收,这就是立即销毁的作用域。
不销毁的作用域
满足两个条件:
- 函数执行返回一个引用数据类型的值;
- 返回的引用数据类型值一定要被外界接收;
//不销毁的作用域
var total=10;
function s(){
//s形成的作用域是不销毁的;
var total=100;
return function(){ //满足条件1
//执行完,就销毁
console.log(total);//100
}
}
var f=s();//满足条件2
f();
不立即销毁的作用域
//不立即销毁的作用域
function s(){
//此处s形成的作用域不立即销毁,它会等到里面小函数执行完成之后,销毁当前的作用域
var total=100;
return function(){
console.log(total);//100
}
}
s()();
var num = 1;// 2
var obj = {
//这不是一个作用域;
num : 10,
// fn的属性值是自执行函数的返回值;
fn : (function () {
// 自执行函数的上一级作用域是全局作用域
// 当代码解析到这一行时;
// 在执行自执行函数时,obj没有空间地址;
// 当obj以键值对存储时,obj现在没有空间地址,执行了这个自执行函数;
return function(n){
console.log(n+(++num))
}
})()// obj --> undefined
}
var f = obj.fn;
f(10);// 12
f(20);// 23
obj.fn(30);// 34
obj.fn(40);// 45
this
this——关键字,在JS中有特定的意义;
- 全局作用域下的this指向window;和window是同一个空间地址。
- 如果给元素的事件行为绑定函数,那么函数中的this指向当前被绑定的那个元素。
- 函数中的this,要看函数执行前有没有 “.”,有点的话,点前面是谁,this就指向谁,如果没有点,那么会指向window。
- 自执行函数中的this永远指向window。
- 定时器中函数的this指向window。
- 构造函数中的this指向当前的实例。
- call、apply、bind可以改变函数中的this指向。