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

再学前端JavaScript(三)作用域、原型链、垃圾回收机制、闭包、事件执行顺序

时间:2023-04-05 11:02:57 HTML5

本文主要介绍作用域、原型链、垃圾回收机制、闭包、事件在JavaScript中的顺序执行环境和scope1.DefinitionofexecutionenvironmentExecutionenvironment:执行环境定义了变量或函数可以访问的其他数据,并决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都存储在这个对象中。尽管我们编写的代码无法访问该对象,但解析器在处理数据时会在幕后使用它。全局执行环境:在网络浏览器中,全局执行环境被视为窗口对象,因此所有全局变量和函数都被创建为窗口对象的属性和方法。一个执行环境中的所有代码执行完毕后,这个环境就被销毁,其中存储的所有变量和函数定义也被销毁。直到应用程序退出,例如关闭网页或浏览器,全局执行环境才被销毁。函数执行环境:每个函数都有自己的执行环境。当执行流进入函数时,函数的环境将被压入环境堆栈。函数执行后,堆栈弹出其环境并将控制权返回到先前的执行环境。2.作用域链1)定义:当代码在一个环境中执行时,会创建一个变量对象的作用域链。保证对执行环境有权访问的所有变量和函数的有序访问。2)目的:保证对执行环境有权访问的所有变量和函数的有序访问。作用域的前端始终是当前执行代码所在环境的变量对象。如果这个环境是一个函数,它的活动对象被用作一个变量对象。活动对象一开始只包含一个变量,即arguments对象(全局环境中不存在该对象)。作用域中的下一个变量对象来自包含(外部)环境,下一个变量对象来自下一个包含环境。就这样一直延续到全局执行环境;全局执行环境的变量对象总是作用域链中的最后一个对象。3)搜索过程:标识符解析沿着作用域链逐一搜索标识符。搜索过程总是从作用域的前端开始并向后工作,直到找到标识符(如果找不到,通常会导致错误)。4)注意:内层环境可以通过作用域链访问所有的外层环境,但外层环境不能访问内层环境中的任何变量和函数。参考自:JavaScript高级程序设计(第3版)4.2执行环境与作用域P73原型继承与原型链一、原型链的定义原型链:每个实例对象都有一个私有属性(称为__proto__)指向其原型对象(prototype)。原型对象也有自己的原型对象(__proto__),层层叠叠,直到原型链的末尾为null。根据定义,null没有原型,作为原型链(Object.prototype.__proto__)的最后一环,所以原型的终点为null,因为null没有__proto__属性,所以返回undefined。除了Object.create(null)之外,JavaScript中的几乎所有对象都是原型链顶部的Object的实例。当访问一个对象的属性或方法时,如果在当前对象上找不到,就到它的原型上找,如果还是找不到,就沿着它的原型往上找,找到了就返回结果,如果不是就一直查找,直到原型链的末尾为null(Object.prototype.__proto__,指向null),返回undefined。2、原型链详解1)如果A通过new创建B,则B.__proto__=A.prototype;就是在A.prototype中查找。如果还是没有A.prototype,就继续往上找。最后,将找到Object.prototype。如果没有找到,因为Object.prototype.__proto__指向null,所以会返回undefined;3)在原型链的最顶端,必须有Object.prototype.__proto__——>null。3.原型分类原型分类:A.函数对象:它的实例是Function的对象。typeofObj==="function",例如String、Number、Boolean、Object、Function、Array、Date、RegExp、Error。拥有原型和原型。B.普通对象:它的实例是Object的一个对象。typeoffn==="对象",如constobj={};constfn=newfun1(){};只有__proto__。原型:隐式原型。1)在大多数情况下,__proto__可以理解为“构造函数的原型”。主动指向构造的原型(这个属性会指向对象的原型),Object.create()创建的对象不适用于这个等式constobj={};obj.__proto__===对象.原型;功能fn(){};fn.__proto__===Function.prototype2)创建函数和对象时自动生成该属性,nullprototype除外:显示原型1)创建函数对象时自动生成该属性由__proto__属性functionFun(){}//以下内容由JavaScript隐式执行;func.prototype.__proto__===Object.prototype为真Fun.__proto__=Function.prototype;Fun.prototype={constructor:Fun,__proto__:Object.prototype}四、new关键字对普通函数做了什么隐式操作?:functionmyNew(Cons,...args){letobj={};obj.__proto__=Cons.prototype;//执行原型链接letres=Cons.call(obj,args);返回typeofres==='object'?资源:对象;}参考自:你还没学过JavaScript原型和原型链吗?三张图理解JavaScript原型对象和原型链垃圾回收机制1.定义垃圾回收:JavaScript有自动垃圾回收机制,这意味着执行环境会负责管理代码执行过程中使用的内存。垃圾回收机制的原理就是找出那些不再使用的变量,然后释放它们占用的内存。为此,垃圾收集器以固定的时间间隔(或代码执行期间的预定收集时间)定期执行此操作。2.垃圾回收方法1)标记去除:是目前主流的垃圾回收算法。它在进入环境时将变量标记为“进入环境”(例如,在环境中声明一个变量);当变量离开环境时,它标记为“离开环境”。垃圾收集器在运行时,会标记所有存储在内存中的变量,然后会清除环境中的变量以及环境中变量引用的变量。在此之后添加到标记中的变量将被视为要删除的变量,因为环境中的变量无法再访问这些变量。最后,垃圾回收器完成内存清理,销毁那些标记的值,回收它们占用的内存空间。2)引用计数:JavaScript引擎目前不再使用这种算法。它跟踪每个值被引用的次数。当一个变量被声明并且引用类型的值赋给该变量时,该值的引用计数为1。如果相同的值被赋给另一个变量,该值的引用计数加1。反之,如果包含对该值的引用的变量取另一个值,该值的引用计数减1。当该值的引用计数变为0时,表示无法访问该值,因此它占用的内存空间可以康复了。这样,下次垃圾收集器运行时,它将释放那些零引用值占用的内存。引用计数的陷阱:当出现循环引用时,会出现严重的问题。循环引用是指对象A中包含了指向对象B的指针,对象B中也包含了对象A的引用。当函数中出现循环引用时,函数执行后objectA和objectB会继续存在,因为它们的引用counts永远不会为0,如果重复调用这个函数,会导致大量内存得不到回收。3.内存优化优化方法:优化内存使用的最好方法是只保存执行代码所需的数据。一旦数据不再有用,最好通过将其值设置为null来释放其引用,这种做法称为取消引用。这种做法用于大多数全局变量和全局对象的属性。局部变量在离开执行环境时会自动取消引用。取消引用的真正作用是从执行环境中获取值,以便下次垃圾收集器运行时可以回收它。参考:JavaScript高级程序设计(第3版)4.3垃圾收集P78闭包1.定义定义:闭包是一个函数,它可以访问另一个函数范围内的变量。2、闭包的优缺点优点:a.可以读取函数内部的变量。在闭包中使用this对象也会导致问题。在全局函数中,this等于window,当函数作为对象的方法被调用时,this等于该对象。但是匿名函数的执行环境是全局的,所以它的this对象通常指向window。3.创建闭包的常用方法在一个函数内部再创建一个函数,然后返回。函数add(){让num=1;返回函数(){返回++num;}}constgetCount=add();获取计数();//2获取计数();//34.为什么闭包可以访问另一个函数范围内的变量?注意:外层函数称为fnA,返回的内层函数称为fnB当fnA从fnA返回fnB时,fnB的作用域链被初始化为包含fnA函数和全局对象的活动对象。所以fnB可以访问fnA中定义的所有变量。由于fnB的作用域链一直引用fnA的活动对象,所以当fnA执行时,虽然fnA的执行环境作用域被销毁,但其活动对象仍会保留在内存中,直到fnB被销毁。fnA的活动对象将被销毁。5.清除闭包将引用匿名函数的变量的值设置为null,从而移除对该函数的引用,相当于通知垃圾回收机制将其清除。随着匿名函数的作用域链被销毁,其他作用域(除了全局作用域)可以被安全地销毁。参考:JavaScript高级程序设计(第3版)7.2闭包P178JavaScript中setTimeout/setInterval和promise的执行顺序一、同步任务和异步任务的执行顺序(不考虑Promise)1、同步任务和异步任务分别进入不同的执行“Place”,同步进入主线程,异步进入EventTable并注册函数2。当指定的事情完成后,EventTable会将这个函数移入EventQueue。3、主线程中的任务执行完后为空,会去EventQueue中读取相应的函数,进入主线程执行4、以上过程会不断重复,也就是常说的事件循环(eventLoop)loop)指的是setTimeout和setInterval2.宏任务和微任务JavaScript中的任务大致可以分为同步任务和异步任务两类,又可以进一步分为宏任务和微任务。宏任务(macrotask/macrotask):宿主机(浏览器,Node)发起的任务称为宏任务。即宿主环境将一段代码传递给JavaScript引擎,引擎直接顺序执行这段代码。该任务也是宿主发起的任务。宏任务包括整体代码脚本、setTimeout和setInterval。微任务(micro-task/micro-task):由JavaScript引擎发起的任务称为微任务。即ES6的Promise,无需浏览器的安排,JavaScript引擎本身也可以发起任务。微任务包括Promise、process.nextTick(node.js中的API)。3.整体事件循环顺序整体事件循环顺序:进入整体代码(macrotask)后,开始第一个循环,执行所有同步任务--->然后执行所有满足条件的微任务--->然后从宏任务,找到其中一个待执行的任务队列,执行所有满足条件的微任务。4.事件循环、宏任务、微任务案例分析}).then(function(){console.log('then');})console.log('console');步骤分析:1)这段代码作为宏任务进入主线程2)首先遇到setTimeout,然后注册它的回调函数,分发到宏任务EventQueue3)接下来遇到Promise,执行新的Promise立即,输出'promise',然后将函数分发到microtaskEventQueue4)遇到console.log('console'),立即执行。5)整个代码脚本作为第一个宏任务执行,然后查询满足条件的微任务,找到promise的then,执行Queue启动,在宏任务EventQueue中找到setTimeout对应的回调函数,执行马上就7)完参考源:这一次,我彻底理解了JavaScript的执行机制。以上内容如有错误,希望大家指出,谢谢。