# fed-e-task-01-02 **Repository Path**: sfljskeprim_admin/fed-e-task-01-02 ## Basic Information - **Project Name**: fed-e-task-01-02 - **Description**: No description available - **Primary Language**: NodeJS - **License**: MulanPSL-1.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-08-01 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # fed-e-task-01-02 ### 一、请说出下列的执行结果,并解释为什么 ```javascript var a = []; for(var i =0 ;i<10;i++){ a[i] = function(){ console.log(i); } } a[6](); ``` 执行的结果是:10 因为var i 是存在全局作用域中,没有存在函数内部局部的作用域中,导致的结果就是获取i的值会是for循环完毕后i存储在全局变量的值,解决这种一般有两种方案: 闭包解决:闭包可以在另一个作用域中调用一个函数的内部函数并访问到该函数的作用域中的成员 ```javascript var a = []; for(var i =0 ;i<10;i++){ a[i] = (function(i){ return function(){ console.log(i); } })(i); } a[6](); ``` let局部作用域解决: ```javascript var a = []; for(let i =0 ;i<10;i++){ a[i] = function(){ console.log(i); } } a[6](); ``` ### 二、请说出下列的执行结果,并解释为什么? ```javascript var temp = 3; if(true){ console.log(temp); let temp; } ``` 执行结果:报错Cannot access 'temp' before initialization 原因:let 声明的变量不会变量提升,块级作用域只要let 声明变量,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。 所以在if(){}内部的temp访问的是let声明的temp而不是var 声明的temp ### 三、ES6新语法找到数组中的最小值 ```javascript var arr = [12,34,32,89,4]; var min = Math.min(...arr); console.log(min); ``` ### 四、请详细说明var let const三种声明变量的方式具体差别 - let定义变量,在块级作用域的外部是无法访问的 - var定义变量,定义在全局作用域,可以在全局访问到 - let不存在变量提升,var存在变量提升 - 块级作用域只要let 声明变量,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响 - let不允许重复声明,var可以重复声明 - const 声明一个只读的常量,声明过后不允许修改内存地址 - const 声明的变量必须有初始值否则会报错 ### 五、请说出下列的执行结果,并解释为什么? ```javascript var a = 10; var obj = { a:20, fn(){ let _this = this; setTimeout(()=>{ console.log(_this.a); }); } } obj.fn(); ``` 执行结果:20 原因:setTimeout()虽然会最终运行在全局作用域调用函数,但是函数使用的是箭头函数,不会改变this指向,this指向的还是obj 如果把箭头函数换成function(){}那就this指向的是调用它的作用域,全局作用域去执行函数打印结果就是:10,如果是普通函数的解决方案 记录下来this的指向,借助闭包的机制保存当前的作用域 ```javascript fn(){ let _this = this; setTimeout(()=>{ console.log(_this.a); }); } ``` 箭头函数与普通函数的区别,不会改变this的指向 也就是说在箭头函数外面的this是什么,函数里面this就是什么。一般需要用到闭包解决的this都可以使用箭头函数解决 ### 六、简述Symbol类型的用途 Symbol的用途:为对象的属性添加独一无二的名字 ES5 的对象属性名都是字符串,这容易造成属性名的冲突,从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。 ```javascript //对象支持symbol作为键 const ss=Symbol('foo'); const aa = Symbol('foo'); const obj = { [ss]:123, }; obj[aa]='test' console.log(obj[ss],obj[aa]);//123 test ``` 使用Symbol可以实现对象的私有成员 ```javascript //a.js================== //对外暴露对象 const name = Symbol(); const person = { [name]:'ace', say(){ console.log(this[name]); } } exprot person; //b.js ============== import person from 'a.js'; console.log(person.say());//ace //b.js 就无法获取name的属性因为没有对外暴露,而且是Symbol独一无二的值 不用担心属性名冲突 ``` ### 七、什么是浅拷贝?什么是深拷贝 浅拷贝:浅拷贝只会复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存,修改新对象会影响旧对象;浅拷贝直接=赋值即可 深拷贝:深拷贝会复制并创建一个一模一样的新对象,不会共享内存,修改新对象不会影响旧对象; 深拷贝一般可以通过 JSON转换为json字符串在转换为JSON对象,或者通过循环遍历对象。 但是使用json转换,遇到undefined、function、symbol 会在转换过程中被忽略,这样就无法得到相应对象的属性了 ```javascript function deepClone(origin){ var clone={}; try{ clone= JSON.parse(JSON.stringify(origin)); } catch(e){ } return clone; } ``` 通过循环遍历的方式,来深拷贝一个对象或者数组 ```javascript /* 工具函数 */ function deepCopy(source){ let target=Array.isArray(source) ? [] : {}; for (const key in source) { if (source.hasOwnProperty(key)) { if(typeof source[key] === 'object' && source !== null){ target[key] = deepCopy(source[key]); }else{ target[key] = source[key]; } } } return target; } module.exports = { deepCopy } ``` ### 八、请简述TypeScript与JavaScript之间的关系 TypeScript是JavaScript的一个超集,TypeScript 可以在任何一个JavaScript中的运行环境中都支持 TypeScript中包含:JavaScript 类型系统 ES6+。TypeScript最终会编译成JavaScript ### 九、TypeScript的优缺点 TypeScript优点: - TypeScript解决JavaScript类型系统的问题 - TypeScript大大提高代码的可靠程度 - TypeScript 可以在任何一个JavaScript中的运行环境中都支持 TypeScript缺点: - 语言本身多了很多概念 - 项目初期,会增加一些成本 ### 十、引用计数的工作原理与优缺点 引用计数算法的原理: 设置引用数,判断当前引用数是否为0,引用关系改变时就会改变引用数字,比如有一个内存空间,有一个变量指向它引用计数就会加一,如果这个变量不再指向它引用计数就会减一,当这个空间引用数为0就会立即回收。 引用计数算法的优点: - 发现垃圾时立即回收 - 最大限度减少程序暂停(应用程序在执行的过程中会对内存进行消耗,内存是有限制的,当内存将要爆满的时候引用计数就会立即找到引用数0的内存空间立即释放) 引用计数算法的缺点: - 无法回收循环引用的对象 ```javascript function fn1(){ const obj1 = {} const obj2 = {} //但是obj2的一个属性是指向了 obj1的两者之间还存在引用 引用计数并不是为0的 obj1.name = obj2; obj2.name = obj1; return ''; } fn1(); ``` ### 十一、标记整理算法的工作流程 标记整理算法的工作原理: - 第一个阶段:遍历所有对象找标记活动对象(活动对象:可达对象) - 第二个阶段:遍历所有对象清除没有标记对象,并且抹掉第一个阶段的标记,便于下一次的标记清除正常工作 - 清除阶段会先执行整理,移动对象位置 ### 十二、V8新生代存储区的垃圾回收流程 - V8内存空间一分为二 - 小空间用于存储新生代对象(64位→32M | 32位→16M) - 新生代指的是存活时间较短的对象 (什么是存活时间较短的对象:当前的代码内有一个变量a在局部作用域,变量b在全局作用域,a的存活时间是比较短的) 新生代对象回收实现: - 回收过程采用复制算法 + 标记整理 - 新生代内存区分为二个等大小空间From 和 To - 使用空间为From,空闲空间为To - 活动对象存储于From空间 - 标记整理后将活动对象拷贝至To空间 From空间的活动对象就会有一个备份 - From与To交换空间完成释放 那么V8如何回收老生代呢? - 老生代64位→1.4G , 32位→ 700M - 老生代对象就是指存活时间较长的对象(如全局作用域下所存放的变量、闭包的情况下所存储的变量数据) - 主要采用:标记清除、标记整理、增量标记算法 - 首先使用标记清除完成垃圾空间的回收 - 采用标记整理进行空间优化(当新生代区域内容移动至老生代区域,而且老生代的存储空间不足以存储新生代所移动过来的对象,就会执行标记整理优化空间) - 采用增量标记进行效率优化 细节对比: - 新生代区域垃圾回收使用空间换时间 - 老生代区域垃圾回收不适合复制算法 ### 十三、描述增量标记算法在何时使用及工作原理 老生代区域:会采用增量标记进行效率优化 增量标记算法工作原理: 分为两个部分一个是程序的执行一个是垃圾回收,当执行垃圾回收操作会停止程序的执行,将一整段的垃圾回收操作组合的完成垃圾回收,垃圾回收与程序执行交替执行这样所带来的时间消耗会合理一些,程序执行一会标记一轮,最后标记操作完成操作后就进行垃圾回收操作,当垃圾回收操作完成之后程序继续执行操作。以前的垃圾回收会进行一整段操作,也会使程序停顿很长的一段时间.