# fed-e-task-01-02 **Repository Path**: feilian/fed-e-task-01-02 ## Basic Information - **Project Name**: fed-e-task-01-02 - **Description**: 拉钩前端part1 模块二作业 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-08-09 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # fed-e-task-01-02 ## 模块二:ES新特性与TypeScript、JS性能优化 ### 简答题 #### 一、请说出下列最总的执行结果,并解释为什么。 ``` javascript var a = []; for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i) } } a[6]() ``` #### 解答: 最终执行结果为 10 分析: 1. var 声明的变量不存在块级作用域 2. 每次循环结束变量i的值都会替换 3. 所有函数中输出的i的值都是全局变量 i 4. 所以不管调用数组中那个元素对应的函数,输出结果都是for循环结束后i的最终计算结果 #### 二、请说出下列最终的执行结果,并解释为什么。 ``` javascript var tmp = 123; if (true) { console.log(tmp) let tmp } ``` #### 解答: 最终执行结果 Error:Uncaught ReferenceError: Cannot access 'tmp' before initialization 分析: 1. 首先错误的输出是在语义分析阶段 2. 块级作用中首先要输出了tmp 但是又有 let tmp 的声明语句,所以先从块级作用域进行语义分析 3. let 声明的变量不存在变量提升 4. js 引擎在分析语义的时候就认为发生了未声明先使用的错误 #### 三、结合ES6新语法,用最简单的方式找出数组中最小值。 ``` javascript var arr = [12, 34, 32, 89, 4] ``` #### 解答: ``` javascript var arr = [12, 34, 32, 89, 4] var min = Math.min(...arr) ``` #### 四、请详细说明var, let, const 三种声明变量的方式之间的具体差别。 #### 解答: 1. let、const 所声明的变量只在代码块儿内有效 ``` javascript { let a = 1 const b = 2 var b = 3 } a // ReferenceError: a is not defined. b // ReferenceError: b is not defined. c // 3 ``` 2. let、const 不存在变量提升 var 命令回发生“变量提升现象”,即变量可以在声明之前使用,值为undefined。 ``` javascript // var 的情况 console.log(foo) // 输出undefined var foo = 2 // let 的情况 console.log(bar) // 报错ReferenceError Cannot access 'bar' before initialization console.log(baz) // 报错ReferenceError Cannot access 'baz' before initialization let bar = 2 const baz = 3 ``` 3. let、const 不允许重复声明 ``` javascript var a = 1; var a = 2; console.log(a) // 2 let b = 2; let b = 3; // Uncaught SyntaxError: Identifier 'b' has already been declared ``` 4. const 行为与 let、var的区别 1. const 声明一个只读的常量,一旦声明就不能改变,这里的改变指的的常量在内存所指向的地址。 2. const 只声明不赋值就会报错。 #### 五、请说出下列代码最总输出的结果,并解释为什么。 ``` javascript var a = 10; var obj = { a: 20, fn () { setTimeout(() => { console.log(this.a) }) } } obj.fn(); ``` #### 解答: 最终的输出结果为 20 分析: 1. fn 是由obj对象来调用的 2. 箭头函数内没有 this arguments 3. setTimeout 的箭头函数内的this 指向的是 fn的执行上下文 4. fn 函数体的内部this 指向调用者 也就是 obj 5. 所以 this.a === obj.a === 20 #### 六、简述symbol类型的用途。 #### 解答: 1. 最主要的用途是为了对象添加独一无二的属性名 2. 定义一组常量值,保证这组常量的值都是不相等的 ``` javascript log.levels = { DEBUG: Symbol('debug'), INFO: Symbol('info'), WARN: Symbol('warn') } ``` 3. 消除魔术字符串 #### 七、说说什么是浅拷贝,什么是深拷贝? #### 解答: 1. 浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。 也就是说不同的变量名指向相同的内存地址 2. 深拷贝是指源对象与拷贝对象相互独立,其中任何一个对象的改动都不会对另外一个对象造成影响。指向不同的地址。 3. js如何实现深拷贝 deepClone 1. 如果不考虑RegExp, Function, Symbol, 循环引用的问题,那么使用JSON ``` javascript var target = { name: 'fedaily', age: 1, topics: ['react', 'vue', 'angular'] } var copyTarget = JSON.parse(JSON.stringify(target)); ``` 2. 但是如果需要考虑各种引用类型,以及循环引用等问题 ``` javascript const regexpTag = '[object RegExp]' function deepClone(value, stack = new WeakMap()) { if (!isObject(value)) { return value; } let result = Array.isArray(value) ? [] : {} // 函数直接返回 if (typeof value === 'function') { return value } // 处理引用类型的拷贝 result = initCloneByTag(value, getTag(value)) // 处理循环引用 if (stack.has(value)) { return stack.get(value) } stack.set(value, result) // 这里没有处理key是Symbol的情况 // for in 不会枚举Symbol的key // 可以通过Object.getOwnPropertySymbols获取所有Symbol的key for (let key in value) { resutl[key] = deepClone(value[key], stack) } return result } function isObject (value) { const type = typeof value return value !== null && (type === 'object' || type === 'function') } function getTag(value) { if (value == null) { return value === undefined ? '[object Undefined]' : '[object Null]' } return Object.prototype.toString.call(value) } function cloneRegExp(regexp) { const result = new regexp.constructor(regexp.source, /\w*$/.exec(regexp)) result.lastIndex = regexp.lastIndex return result } function initCloneByTag(object, tag) { const Ctor = object.constructor // 可以在这里处理 // arrayBuffer, int32array, dataview等情况 switch (tag) { case regexpTag: return cloneRegExp(object) default: return {} } } ``` #### 八、请简述TypeScript与JavaScript之间的关系 #### 解答: 1. TypeScript是JavaScript的超集 typescript: javascript es6+ 类型系统 => 编译 javascript 2. 通过 typescript 直接转换 es6 可以最低编译为es3的代码 3. 任何一种javascript运行环境都支持 #### 九、请谈谈你所认为的TypeScript优缺点。 #### 解答: 1. 优点: 1. Typescript 属于 [渐进式] 2. TypeScript功能更为强大,生态也更健全、更完善 3. angular / vue.js 3.0 应用了TypeScript, Typescript - 前端领域中的第二语言 4. 适合长期维护的大型项目 2. 缺点: 1. 语言本身多了很多概念 接口 泛型 枚举 2. 项目初期,TypeScript 会增加一些成本 #### 十、描述引用计数的工作原理和优缺点。 #### 解答: 1. 引用计数算法实现原理 1. 核心思想:设置引用数,判断当前引用数是否为0 2. 引用计数器 3. 引用关系改变是修改引用数字 4. 引用数字为0的时候立即回收 2. 引用计数算法的优点 1. 发现垃圾时立即回收 可以及时回收垃圾对象 2. 最大限度减少程序暂停 (内存有占满的时候, 立即对计数为0的进行回收) 减少程序的卡顿时间 3. 缺点 1. 无法回收循环引用的对象 2. 时间开销大 (当前的引用计数需要维护一个引用数值的变化,需要时刻监听数值的修改,需要消耗时间)资源消耗大(引用计数器) #### 十一、描述标记整理算法的工作流程。 #### 解答: 1. 标记整理算法实现原理 1. 核心思想:分标记和清除俩个阶段完成 2. 遍历所有对象找标记活动对象 3. 标记阶段会先执行整理,移动对象位置 4. 遍历所有对象清除没有标记对象 5. 回收相应的空间 标记整理可以看做是标记清除的增强,标记阶段的操作和标记清除一致 2. 优缺点 1. 减少碎片化空间 2. 不会立即回收垃圾对象 #### 十二、描述V8中新生代存储区垃圾回收的流程。 #### 解答: 1. 基本概念: 1. v8 内存空间一分为二 2. 小空间用于存储新生代对象 (32M | 16M) 3. 新生代指的是存活时间较短的对象 4. 如何鉴定 有一个局部作用域 其他地方有全局变量 2. 流程: 1. 新生代对象回收实现 2. 回收过程采用复制算法 + 标记整理 3. 新生代内存区分为俩个等大小空间 from to 4. 使用空间 FROM, 空闲空间为to 5. 活动对象存储于From 对象 6. 标记整理后将活动对象拷贝至To 将对象的内置连续 7. From 与 To 交换空间完成释放 3. 回收细节说明 1. 拷贝过程中可能出现晋升 2. 晋升就是将新生代对象移动至老生代 3. 一轮GC还存活的新生代需要晋升 4. To空间的使用率超过25% (需要将活动对象移动到老生代中) 为什么是25% To 超过一定使用率新进来的对象的存储空间不够用 #### 十三、描述增量标记算法在何时使用及工作原理。 #### 解答: 1. 基本概念: 1. 老生代对象存放在右侧老生代区域 2. 64位操作系统1.4G, 32位操作系统 700M 3. 老生代对象就是指存活时间较长的对象 (全局对象下存放的变量,闭包中存放的变量数据) 2. 老生代对象回收实现过程使用增量标记 1. 主要采用标记清除、标记整理、增量标记算法 2. 首先采用标记清除完成垃圾空间的回收 (v8引擎主要采用标记清除的算法,相对于空间碎片,速度上的提升很明显) 3. 采用标记整理进行空间优化(如果发现新生代的内容往老生代晋升的时候,而且这个时间节点老生代的剩余空间不足以存放新生代存储区移动过来的对象时触发) 4. 采用增量标记进行效率优化 3. 增量标记工作原理: 1. 将一整段垃圾回收拆分成多个小部,组合的去完成整个垃圾回收,从而去替代整个垃圾回收操作 2. 好处:可以实现与程序执行交替的完成,时间消耗更加合理