js
执行顺序
当一段代码被执行时,js
引擎会先对其编译,并创建执行上下文。
- 当执行全局代码时,会编译全局代码并创建全局执行上下文,整个页面生存周期内,全局上下文只有一份。
- 调用到函数时,函数体内代码编译并创建函数执行上下文,函数结束后,下下文会被销毁。
栈遵守后进先出的原则,比如一条死胡同,进去的多人,只能最后进来的人先出。
var a = 2;
function add(b,c){ return b+c }
function addAll(b,c){
var d = 10,
result = add(b,c);
return a + result + d
}
addAll(3,6)
- 创建全局上下文,放最底下:先进。变量环境:
a = undefined, add = function(){...}, addAll = function(){...}
- 执行全局代码,
a = 2
赋值,上下文变量的a = 2
- 调入
addAll()
,js
编译addAll
函数,并创建其函数执行上下文,压入栈中。参数列表,d = undefined, result = undefined
- 执行
d = 10
,把上下文赋值 - 执行到
add(b,c)
时,创建函数执行上下文,压入栈中。 add
函数返回时,add
函数上下文从栈顶弹出,将result
设为add
函数的返回值,add
上下文销毁。addAll
执行相加操作后返回,addAll
执行上下文从栈顶弹出,销毁。只剩全局上下文,结束。
call stack
调用栈,函数里加console.trace()
打印函数调用关系。
当栈的执行上下文超过一定数据会栈溢出。let
const
有块级作用域,当执行到块级时,会被存放在执行上下文的词法环境。词法环境也是一个栈的结构,最外层变量在栈底,一个块级执行完毕,该作用域的信息就会从栈顶弹出,销毁。
闭包的执行顺序
function foo() {
var myName = "极客时间" ;
let test1 = 1 ;
const test2 = 2 ;
var innerBar = {
getName:function(){
console.log(test1)
return myName ;
},
setName:function(newName){
myName = newName ;
}
}
return innerBar;
}
var bar = foo();
bar.setName("极客邦");
bar.getName();
console.log(bar.getName());
outer
:外部引用,指向上级作用域;变量环境:var,function
。词法环境:let,const
。
innerBar
是个对象,包含seeName
和getName
这2个方法,这2个方法是在foo
函数内部定义的,用了foo
的myName
和test1
2个变量。
词法作用域规则,内部是函数总是可以访问它们外部函数的变量。虽然foo
已经结束,其执行上下文也已经弹出去销毁了,但由于其变量myName
和test1
被使用,所以这2个变量依然保存在内存中,是专属于里面的闭包的,其它任何地方无法访问。
在 JavaScript
中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,当通过调用一个外部函数返回一个内部函数后,即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,我们就把这些变量的集合称为闭包。比如外部函数是 foo
,那么这些变量的集合就称为 foo
函数的闭包。
当执行到 bar.setName
方法中的myName = "极客邦"
这句代码时,JavaScript
引擎会沿着“当前执行上下文–>foo
函数闭包–> 全局执行上下文”的顺序来查找 myName
变量
闭包回收
如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭;但如果这个闭包以后不再使用的话,就会造成内存泄漏。
如果引用闭包的函数是个局部变量,等闭包函数销毁后,下次js
引擎回收时判断不再用,会回收这块内存。
注意一个原则:如果该闭包会一直使用,那么它可以作为全局变量而存在;但如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量。
var myObj={
name:'思否',
showThis:function(){
this.name='思否1'
console.log(this)
}
}
var foo=myObj.showThis;//相当于赋值指向同一块内存地址,但是调用对象变了,
//foo在全局执行上下文指向window,所以foo函数this指向window
foo()//foo在window全局里
js
的8
种类型:Boolean,Null,Undefined,Number,BigInt,String,Symbol,Object
。7
种原始类型是存在调用栈中。Object
引用类型真实存在堆中,栈中存的是堆的引用地址。
垃圾回收
调用栈的执行上下文调完了,会弹出调用栈,所占用的内存也会被其它占用。而调用栈中引用地址指向的堆空间就需要用到 JavaScript
中的垃圾回收器了。V8
把堆分成新生代和老生代,新生代容量1-8M
,生存时间短的对象。其它则在老生代。
- 标记空间中活动对象和非活动对象
- 回收非活动对象所占据的内存,就是在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象。
- 内存整理
主要是标识对象是否在使用,标记完全回收清理非活动对象。由于非活动对象分布在不同的内存地址,大小也不同,回收之后会有很多内存碎片。
新生代有对象区域和空闲区域,新加入的对象都会存放到对象区域,当对象区域快被写满时,就需要执行一次垃圾清理操作。回收剩下的对象放到空闲区域,空闲区域变成了对象区域。
老行生代的标记清除,会循环调用栈找对象地址的引用 ,找不到就是要回收。回收之后让所有剩下对象移向一端集合在一起,内存碎片就变成大空间。js
和回收交叉进行。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。