当前位置: 首页 > Web前端 > HTML

浏览器工作原理与实践(二)——浏览器中的JavaScript执行机制

时间:2023-04-02 18:51:36 HTML

js执行顺序当一段代码被执行时,js引擎会先对其进行编译,并创建一个执行上下文。当执行全局代码时,编译全局代码并创建全局执行上下文。整个页面生命周期中只有一份全局上下文。调用函数时,会编译函数体中的代码并创建函数执行上下文。函数结束后,上下文将被销毁。栈遵守后进先出的原则,比如死胡同,很多人进入的地方,只有最后进入的人先退出。vara=2;functionadd(b,c){returnb+c}functionaddAll(b,c){vard=10,result=add(b,c);returna+result+d}addAll(3,6)创建一个全局上下文,放在最下面:advanced。变量环境:a=undefined,add=function(){...},addAll=function(){...}执行全局代码,a=2赋值,a=2的上下文变量被调用到addAll(),js编译addAll函数,创建其函数执行上下文,并将其压入栈中。参数列表,d=undefined,result=undefinedexecuted=10,执行上下文赋值到add(b,c)时,创建一个函数执行上下文,压入栈。当add函数返回时,add函数上下文从栈顶弹出,结果设置为add函数的返回值,add函数上下文被销毁。addAll执行完加法操作返回,addAll执行上下文从栈顶弹出并销毁。只剩下全局上下文,结束。callstack调用栈,在函数中添加console.trace(),打印函数调用关系。当栈的执行上下文超过一定的数据量时,栈就会溢出。letconst具有块级作用域,当执行到块级时,会存储在执行上下文的词法环境中。词法环境也是一个栈结构。最外层的变量位于堆栈的底部。一次块级执行完成后,该范围内的信息将从栈顶弹出并销毁。闭包执行顺序functionfoo(){varmyName="geektime";让test1=1;const测试2=2;varinnerBar={getName:function(){console.log(test1)返回myName;},setName:function(newName){myName=newName;}}returninnerBar;}varbar=foo();bar.setName("GeekState");bar.getName();console.log(bar.getName());outer:外部引用,指向上级作用域;变量环境:var,函数。词法环境:let,const。innerBar是一个对象,包括seeName和getName两个方法,定义在foo函数内部,使用foo的myName和test12变量。词法作用域规定内部函数始终可以访问其外部函数的变量。虽然foo已经结束,它的执行上下文已经弹出并销毁了,但是由于使用了它的变量myName和test1,所以这两个变量仍然保存在内存中,对里面的闭包是独占的,其他地方是访问不到的。在JavaScript中,根据词法作用域的规则,内部函数总是可以访问在其外部函数中声明的变量。当调用外部函数返回内部函数时,即使外部函数已经执行完毕,内部函数引用外部函数的变量仍然保存在内存中,我们称这些变量的集合为闭包。例如,如果外部函数是foo,那么这些变量的集合就称为foo函数的闭包。当执行bar.setName方法中的代码myName="GeekState"时,JavaScript引擎会按照"当前执行上下文->foo函数闭包->全局执行上下文"的顺序搜索myName变量闭包回收如果引用闭包的函数是一个全局变量,闭包将一直存在,直到页面关闭;但是如果以后不再使用闭包,就会造成内存泄漏。如果引用闭包的函数是局部变量,闭包函数销毁后,js引擎下次回收时,会判断不再使用,回收这块内存。注意一个原则:如果闭包会一直被使用,那么它可以作为一个全局变量存在;但如果不经常使用,占用内存大,那就尽量让它成为局部变量。varmyObj={name:'Thinking',showThis:function(){this.name='Thinking1'console.log(this)}}varfoo=myObj.showThis;//相当于赋值指向同一个块memory的地址,但是调用对象变了,//foo在全局执行上下文中指向window,所以foo函数this指向windowfoo()//foo在window全局中有8种js类型:Boolean,Null,未定义、数字、BigInt、字符串、符号、对象。调用堆栈中存在七种原始类型。Object引用类型实际存在于堆中,栈中存放的是堆的引用地址。调整垃圾回收调用栈的执行上下文后,调用栈会弹出,占用的内存也会被其他人占用。调用栈中引用地址指向的堆空间需要使用JavaScript中的垃圾回收器。V8将堆分为新生代和老年代。新生代容量为1-8M,对象存活时间短。其他人在老一代。被标记空间中的活动对象和非活动对象回收非活动对象占用的内存,即所有标记完成后,统一清理内存中所有标记为可回收的对象。内存整理主要是识别对象是否在使用,标记完成回收,清理不活跃的对象。由于非活动对象分布在不同的内存地址,大小不同,回收后会产生大量的内存碎片。新生代有一个对象区和一个空闲区。新添加的对象将存储在对象区域中。当对象区域快满时,需要进行一次垃圾清理操作。剩余的对象被回收并放入空闲区,空闲区成为对象区。当老年代的标记被清除后,会循环调用栈寻找对象地址的引用。如果找不到它,它将被回收。回收之后,让剩下的所有对象都移动到一端,聚集在一起,内存碎片就变成了一大片空间。js和回收是交错的。