# fed-e-task-01-02 **Repository Path**: cuitzhy/fed-e-task-01-02 ## Basic Information - **Project Name**: fed-e-task-01-02 - **Description**: 拉勾大前端课程 Part 1 模块 2 作业 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-08-03 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # fed-e-task-01-01 拉勾大前端课程 Part 1 模块 2 作业 ## 简答题运行环境说明 本次作业的相关代码基于 node v12.18.1 编写和测试 使用 vscode 开发并配合 prettier 进行代码格式化,若格式化没有效果应该首先检查 vscode 有没有安装 prettier 插件 部分代码题会使用到第三方依赖库,可以使用以下命令安装这些库 ``` npm i ``` 或 ``` yarn ``` ## 各题作答 ### 简答题一 最终的执行结果会在控制台输出 10. 这是因为 var 声明的变量不会有块级作用域,它的作用域是它当前的执行上下文.变量 i 处于全局作用域中,当 for 循环执行完毕后,i 的值已经变为 10 了,并且无论是执行 a\[6\]() 还是 a\[7\]() ,最终都会输出 10. 具体来说,在 es5 中有两种作用域,分别是全局作用域和函数作用域,而在 es6 中才新增了块级作用域.如果要声明一个在块级作用域中的变量需要使用 let 或 const 关键字,在块级作用域中定义的变量,只在块的内部起作用,在块的外部是无法访问的. > 特别的,在 for 循环的圆括号中,可以使用 var 或 let 关键字声明新的变量,使用 var 声明的变量不是该循环的局部变量,而是与 for 循环处在同样的作用域中.用 let 声明的变量是语句的局部变量.参考[for 语句](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/for) 在该例中,如果想在使用 var 声明变量的时候,得到期望的输出(如输出 6.),可以使用闭包也 在 [code/code1.js](code/code1.js) 中包含相关源代码,可以通过以下命令验证执行结果 ``` node code/code1.js ``` ### 简答题二 最终执行结果会在控制台输出 ReferenceError 错误. 这是因为与 var 声明变量不同,使用 let 声明的变量不会出现提升的现象.let 从语法层面要求必须先声明变量再使用变量. 现在看来变量声明提升应该是一个 JS 官方的 bug,但 let 出现的解决了这个 bug. 在 [code/code2.js](code/code2.js) 中包含相关源代码,可以通过以下命令验证执行结果 ``` node code/code2.js ``` ### 代码题三 在解答该题的过程中,主要运用了 let、const、展开数组和箭头函数等 es6 的知识 可以在项目根目录下运行以下命令查看运行结果,或直接打开 [code/code3.js](code/code3.js) 查看源代码 ``` node code/code3.js ``` ### 简答题四 var 是 JS 中 es6 之前声明变量的方式,let 和 const 是 es6 中新增的声明变量的方式.它们主要有以下区别: **作用域不同** - es6 之前只有全局作用域和函数作用域,在 es6 中又增加了一个非常关键的块级作用域 - 块是指花括号包裹起来的语句,例如 for 语句和 if 语句 - 使用 var 声明的变量不会存在于块级作用域中,let 和 const 会 - 在块中用 var 定义的变量在块的外部也可以访问到 - 在块中用 let 或 const 定义的变量在块的外部访问不到 - 在 es6 之前为了达到 let 声明变量的效果(如 for 循环中),经常使用闭包来创建一个函数作用域 **变量提升的问题** - 使用 var 声明的变量会有变量声明提升的现象,而 let 和 const 没有 - let 和 const 不存在这种现象,并且还要求必须先声明再使用 - const 更进一步要求声明的时候必须同时赋值,并且一旦赋值就不能再进行修改(指不能修指向的内存地址) **重复声明的问题** - let 和 const 重复声明会报错,var 不会 **全局属性的问题** - 在浏览器中,let 和 const 不会在全局声明时(在最顶部的范围)创建 window 对象的属性,而 var 会 ### 简答题五 最终输出结果为 20 简单的说,因为该题 setTimeout 的回调函数是一个箭头函数,所以回调中的 this 指向函数声明时的执行上下文,即 obj 本身,这是因为箭头函数不会改变 this 的指向 如果该例中使用 function 声明 setTimeout 的回调函数,那么将输出 undefined 在 [code/code5.js](code/code5.js) 中包含相关源代码,可以通过以下命令验证执行结果 ``` node code/code5.js ``` ### 简答题六 Symbol 是在 es6 中引入的,是一种全新的数据类型,Symbol()函数返回的每一个值都是独一无二的,它主要有以下用途: **应用一:为对象添加独一无二的属性值** - 对于一个对象 obj,如果不知道它已经有一个属性叫做 x,此时再对 obj.x 复制将会覆盖之前的值 - 解决上述问题可以靠约定,比如程序员 a 定义的属性以 a\_开头,程序员 b\_ 定义的属性以 b\*开头 - 由于 Symbol 值的独特性,刚好可以解决这个问题(?????)(都是私有属性????) **应用二:toStringTag** - 重写自定义对象的 toString 的方法, 见[code/code6.js](code/code6.js) 在 [code/code6.js](code/code6.js) 中包含相关源代码,可以打开查看 ### 简答题七 浅拷贝和深拷贝主要涉及到 JS 中复制对象的知识 浅拷贝一个对象,创建的新对象中拥有着源对象属性值的一份精确拷贝.如果源对象的属性值是一个基本类型,新对象将会拷贝这个基本类型的值,如果源对象的属性值是引用类型,那么新对象将会拷贝这个属性值的内存地址,如果其中一个对象改变了这块内存空间的东西,将会影响到另一个对象. 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。但简单的深拷贝(JSON.parse(JSON.stringify(obj1)))也有着一些不能处理的情况,比如循环引用、正则、函数、undefined 等等,使用一些工具库(比如 fp.deepClone)提供的方法可以避免其中的一些情况 深拷贝有着不同的解决方案. 综上,如果使用浅拷贝,两个对象之间可能存在相互影响.深拷贝则不会. 赋值示例(不是拷贝) ```js const obj1 = { a: 1, } const obj2 = obj1 // obj2和obj1指向了同一块内存空间 ``` 浅拷贝示例 ```js const target = { a: 1, b: 2, d: { d1: 6 } } const source = { b: 4, c: 5 } const returnedTarget = Object.assign(target, source) returnedTarget.d.d1 = 7 console.log(target, source, returnedTarget) // 如果不想影响target对象 // const returnedTarget = Object.assign({},target, source) // es6扩展运算符拷贝 // const returnedTarget = { ...target, ...source } ``` 简单的深拷贝示例 ```js obj1 = { a: 0, b: { c: 0 } } let obj3 = JSON.parse(JSON.stringify(obj1)) obj1.a = 4 obj1.b.c = 4 log(JSON.stringify(obj3)) ``` 在 [code/code7.js](code/code7.js) 中包含相关源代码,可以打开查看 ### 简答题八 TypeScript 是基于 JavaScript 的,即使没有学过 TypeScript,也完全可以按照 JavaScript 的语法开发 TypeScript 应用. TS 可以看做 JS 的一个超集. TS 主要解决了 JS 类型系统的不足.因为 TS 有一个类型系统. 一般来说,TS 代码最终要编译 JS 代码,才能在生产环境运行 ### 简答题九 **TS 的优点** - 类型系统,把大多数 bug 暴露在开发阶段 - 立即使用 ES 新特性 - 开发工具(如 vscode)支持度好 - 适合长周期大项目 **TS 的缺点** - 在运行时没有了开发时那种类型系统,如果不经过额外处理,还是会遇到 a.b.c(读取 undefined 的属性)的错误,特别是处理后台接口数据的时候 - 语言本身多了很多概念 - 对于小型项目成本会有一点高,因为要维护类型声明 ### 简答题十 **引用计数的工作原理** - 核心思想:设置引用数,判断当前引用数是否为 0 - 引用计数器 - 引用关系改变时修改引用数字,增减一个引用关系,计数器就加 1,减少一个引用关系,计数器就减一 - 引用数字为 0 时立即回收 **引用计数的优点** - 发现垃圾时立即回收 - 最大限度减少程序暂停(因为立即回收) **引用计数的缺点** - 无法回收循环引用对象 - 时间开销大(引用计数器数值的变化需要时间) ### 简答题十一 **标记整理算法的实现原理** - 标记整理可以看做标记清除的增强 - 标记阶段的操作和标记清除一样 - 清除阶段会先执行整理,移动对象,以最大化利用连续空间 ### 简答题十二 **V8 新生代存储区垃圾回收的流程** - 回收过程采用复制算法+标记整理 - 新生代内存区分为两个等大小的空间 - 使用空间叫做 From,空闲空间叫做 To - 活动对象存储于 From 空间 - 标记整理后将活动对象拷贝至 To - From 与 To 交换空间完成释放 ### 简答题十三 **编辑增量算法何时使用** 在 V8 中,优化 GC 效率的时候使用 **编辑增量算法工作原理** - 垃圾回收程序执行的会阻塞当前 JS 程序执行 - 将一长段垃圾回收的拆分几小段,和 JS 程序交替执行,可以减少每次垃圾回收执行(阻塞)时间 - 垃圾回收程序可以先找到第一层可达对象,然后等待 JS 执行后,再去标记更深层次的可达对象,如此交替执行,等标记完成再执行清除操作,最后就完成了整个 GC 操作了