# fed-e-task-01-02 **Repository Path**: lorixiang/fed-e-task-01-02 ## Basic Information - **Project Name**: fed-e-task-01-02 - **Description**: Part 1 : JavaScript 深度剖析 模块二:ES 新特性与 TypeScript、JS 性能优化 - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-12-04 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # fed-e-task-01-02 #### 介绍 Part 1 : JavaScript 深度剖析 模块二:ES 新特性与 TypeScript、JS 性能优化 #### 简答题答案以及说明 #### 一、请说出下列最终的执行结果,并解释为什么。 ```js var a = [] for(var i = 0; i < 10; i++){ a[i] = function() { console.log(i) } } a[6]() ``` 最终打印结果是 10。 var 声明的变量 i 是全局变量,a 作为数组是一个引用类型,在循环体中,对 i 进行了累加,对数组 a 中的每个元素赋值了一个函数,即 a 的每个元素都指向了一个函数堆地址,循环结束后,全局变量 i 的值变为 10,执行任意一个 a 数组里的函数时,都打印的是全局作用域下的 i,即打印结果都是 10。 如果需要解决这个问题,可以使用闭包的方式,或用 let 代替 var 定义变量 i。 ```javascript // 闭包 var a = [] for(let i = 0; i < 10; i++){ a[i] = (function(i) { return function () { console.log(i) } })(i) } a[6]() // 用 let 代替 var,建立局部作用域 var a = [] for(let i = 0; i < 10; i++){ a[i] = function() { console.log(i) } } a[6]() ``` #### 二、请说出下列最终的执行结果,并解释为什么。 ```js var tmp = 123 if(true) { console.log(tmp) let tmp } ``` 最终执行结果会报引用错误,因为没有初始化 tmp。 在 ES6 新特性中,if 语句会建立一个新的块级作用域,通过 let 声明的变量只能在块级作用域中被访问,且 let 关键字声明的变量不存在声明提升,所以在声明前打印 tmp,会认为 tmp 没有被声明,访问报错。 #### 三、结合ES6新语法,用最简单的方式找出数组的最小值。 ```js var arr = [12, 34, 32, 89, 4] ``` 利用内置对象 Math 的 min 静态方法,和 ... 展开操作符求数组最小值: ``` console.log(Math.min(...arr)); // 4 ``` #### 四、请详细说明var,let,const三种声明变量的方式之间的具体差别。 在 ES2015 标准出来之前,JS 声明变量关键字只有 var,此时还只存在 **全局作用域** 和 **函数作用域**,且变量声明存在提升现象,即通过 var 声明的变量会提升至作用域顶部初始化为 undefined。 在新标准出台后,变量声明增加了 let 和 const 关键字,两者的相同点是: 1. 有 let 或 const 存在的块(if 语句,或 for 循环语句),其声明的变量都是 **块级作用域** 内的变量,外界不能访问; 2. 都可以声明新的变量,且不允许重复声明,如果已存在则报错; 3. 都不存在变量提升 不同点是: 1. const 声明变量后,变量不能再被修改(注意是不能修改声明时的内存指向地址,即可以修改声明的对象的属性); 2. const 声明时必须设置初始值,否则会报错 总之,在新标准下的最佳实践是,不用 var,主要用 const,配合使用 let。 #### 五、请说出下列代码最终输出的结果,并解释为什么。 ```js var a = 10 var obj = { a: 20, fn() { setTimeout(() => { console.log(this.a) }) } } obj.fn() ``` 最终打印结果为 20。 这道题考查知识点为 this 指向值。当函数作为某个对象的方法调用时,此时这个对象就是函数的上下文对象,这时候 this 会指向这个对象。 > 借题整理一下 this 的绑定规则: > > this 是一个关键字,在所有函数作用域中被自动定义,它的指向值在动态创建执行上下文时被确定,即和函数定义的位置无关,而和函数被调用时的绑定规则相关,以下按照优先级**从低到高**来看一下各个绑定规则的异同: > > 1. **默认绑定** > > 当使用不带任何修饰的函数引用进行调用时,只能使用默认绑定,而不能使用其他绑定规则。注意严格模式的默认规则下 this 指向 undefined。 > > 2. **隐式绑定** > > 当函数作为某个对象的方法调用时,此时这个对象就是函数的上下文对象,这时候 this 会指向这个对象。此时注意,如果在全局执行上下文中用变量 foo 保存了该对象的方法,并执行了 foo() 时,该方法内部的 this 实际上指向的是 window,这就是**隐式丢失**。 > > 3. **显示绑定** > > [call](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/call),[apply](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/apply),[bind](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) > > 4. **new操作符绑定、箭头函数** > > new 操作符:当函数作为构造函数执行`new`的过程中,`this`指向了最终创建的实例。 > > - 创建一个新对象,将this绑定到新创建的对象 > - 使用传入的参数调用构造函数 > - 将创建的对象的_proto__指向构造函数的prototype > - 如果构造函数没有显式返回一个对象,则返回创建的新对象,否则返回显式返回的对象(即手动返回的对象) > > 箭头函数:箭头函数中的`this`比较特殊,它的指向值不是动态决定的,而是由函数定义时作用域中包含的`this`值确定的。 > > [引用](https://juejin.cn/post/6901856557634420749) #### 六、简述symbol类型的用途。 Symbol 是 ES2015 标准中新的原始数据类型,目的是为了创建一个独一无二的值。主要用途有: 1. 避免对象属性名重复产生的问题; 2. 创建对象的私有成员,注意新创建的私有成员用 for in , Object.keys, JSON.stringify 都无法访问,可以用过 ES6 新标准中的Object.getOwnProptertySymbols 来进行访问。 #### 七、说说什么是浅拷贝,什么是深拷贝? 浅拷贝和深拷贝都可以实现对对象的复制,但两者之间在堆内存上对复杂(引用)数据类型的保存的表现是不一样的。 浅拷贝前后的对象的引用数据类型成员共享同一块内存。即只是把引用数据类型在栈中保存的地址复制了一份,浅拷贝前后的对象中的引用类型指向了同一个地址,改变其中任何一个对象的引用类型成员就会影响到另一个对象的数据。JS的原生不支持深拷贝,`Object.assign` 和 `{...obj}`都属于浅拷贝 深拷贝会在堆内存中开辟一个新的内存区域存放拷贝后的对象,不再是对复杂类型成员地址的引用,而是会被原对象的子成员进行递归拷贝,改变任何一个对象的任何成员不会对另一个对象产生影响。 实现深拷贝的方式有,JSON.stringify 和 parse (注意此方法会忽略对象中值为 undefined 的成员,且不能复制 function 正则 和 Symbol 等,诸多缺点,不提倡)、实现递归拷贝、或引用成熟库中的方法(如 lodash 中的 deepCopy)等。 #### 八、请简述TypeScript 与 JavaScript之间的关系。 一般认为 TypsScript 是 JavaScript 的超集,它包含 JavaScript、类型系统、ES6+ 等,在运行环境中通过编译成原始 Javascript来执行,任何一个支持 JS 的运行环境都支持。 #### 九、请谈谈你所认为的TypeScript 优缺点。 优点: 1. TS 自动转换新特性,功能更强大,生态更健全完善; 2. TS 作为一个强类型语言,代码错误更早暴露,而不是需要等到运行时才能发现异常; 3. 减少了代码层面的不必要的类型判断,提高编码效率; 4. 使得项目代码重构更可靠。 5. 是一门渐进式的语言,它全面兼容 JS,学习和项目迁移的难度降低。 缺点: 1. 语言本身新增概念多,有一定的学习难度; 2. 不适用于周期型的小项目,会增加成本。 总之适应了前端项目工程化的背景。 #### 十、描述引用计数的工作原理和优缺点。 引用计数算法的核心思想是,通过引用计数器维护对象的引用数,设置引用数,引用关系改变时会修改引用数字,判断当前引用数是否为0,当其为0时,被当作垃圾回收。 优点:1. 发现垃圾时立即回收;2. 最大限度减少程序卡顿时间:内存将满时,GC找到引用计数为 0 的空间释放。 缺点:1. 无法回收循环引用的对象(比如两个对象相互引用对方的内存空间);2. 时间开销大,资源消耗大。 #### 十一、描述标记整理算法的工作流程。 标记整理算法的核心思想是,将垃圾回收分标记和清除两个阶段完成。 标记阶段的流程如下: 1. 遍历所有对象,找活动对象(可达对象)进行标记 2. 遍历所有对象,清除没有被标记的对象,也会把第一个阶段的标记都抹掉 清除阶段流程如下: 1. 先执行整理,移动对象位置——解决碎片化问题(回收的垃圾对象在地址上本身是不连续的,在回收后可能形成空间碎片,浪费空间) 2. 回收相应空间 #### 十二、描述V8中新生代存储区垃圾回收的流程。 V8 采用了分代回收垃圾的策略,内存一分为二,分为新生代区域和老生代区域,其中新生代对象(存活时间较短的对象,如局部作用域变量)存储在小空间(64位系统为 32M,32位系统为 16M),回收过程采用 **空间复制 和 标记整理** 算法。新生代内存区分为 2 个相等空间,先假设使用空间为 From, 空闲空间为 To,回收流程如下: 1. 活动对象存储于 From 空间 2. From 空间利用到一定程度后,触发 GC机制,标记整理后将活动对象拷贝至 To 备份 3. From 与 To 交换空间完成释放 #### 十三、描述增量标记算法在何时使用及工作原理。 增量标记算法在 V8 引擎进行老生代存储区垃圾回收时,优化垃圾回收操作,提高垃圾回收的效率。 当垃圾回收操作时会暂停程序执行,增量标记算法会将垃圾回收的标记和清除操作操作拆成小步,组合成整个回收过程。 可以使垃圾回收和程序执行交替执行,使时间间断更合理,对用户操作来说更友好。