# fed-e-task-01-01 **Repository Path**: lightnoway/fed-e-task-01-01 ## Basic Information - **Project Name**: fed-e-task-01-01 - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-05-20 - **Last Updated**: 2021-03-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 简答题 #### 1 请说出下列最终的执行结果,并解释为什么? - 执行结果为:`10` - 解释如下: - 循环体执行了 10次 - 对`a[6]`值进行分析: - 函数创建时 ,i为6 ; - i 通过 var 创建, 其作用域是全局作用域或所在函数作用域,设`scope1`代表此作用域 - var 声明变量会变量提升,即 i的作用域与 a作用域相同 - a[6]执行时分v析: - 执行时,其内i直接指向`scope1`,此时`scope1`内i 在for执行结束后其值为`10` - 所以 `console.log(i)` 会输出 `10` - 扩展,如果想输出 函数创建时的i的值可以通过以下2种方式: ```js //1使用闭包:a[6]中i指向一个函数闭包内的形参; //形参i与scope1中i虽然同名,但实际上是2个变量,形参i在执行时拷贝了scope1中i当时的值 for(var i = 0;i < 10;i++){ a[i] = (function(i){ return function(){ console.log(i); } })(i); } ``` ```js //2使用let //在`for (let声明表达式...){循环体}`中: // 循环体内的i 与 let 表达式中的 i 是不同的变量, 循环体内i 值为 `for()` 部分执行时其内i的值 for(let i = 0;i < 10;i++){ a[i] = function(){ console.log(i); } }; } ``` #### 2 请说出下列最终的执行结果,并解释为什么? - 最终结果: 抛出异常 - 解释: - 由于使用了 let 关键字, `console.log(tmp)`中tmp 作用域为 `if(true){ ...}`中花括号所在块级作用域 - let 声明声明的变量 没有`变量提升`,即在 声明前,作用域范围内不能使用该变量(使用就抛出异常) #### 3 结合新语法,用最简单的方式找出数组的最小值 - 有2种方式如下 ``` Math.min(...arr); //arr length较少时可用;length较大时 ,可能会 有传参数量超过限制问题 ``` ``` arr.reduce((a,b)=>a>b?b:a,arr[0]) //arr length 应该大于0 ``` #### 4 请详细说明 var,let,const 三种声明变量的方式之间的具体差别? - 这3个关键字都用来声明变量, 从变量的作用域,变量提升,赋值方面比较如下: | | 支持作用域 | 变量提升 | 赋值 | | ----- | -------------- | -------------------------------------------------------------------------- | ------------------------- | | var | 全局,函数 | 变量声明提升到当前作用域开始处
遇到第一个赋值语句前,变量值为undefined | 声明时可不赋值,可多次赋值 | | let | 全局,函数,块级 | 无
作用域内声明前不允许使用改变量 | 声明时可不赋值,可多次赋值 | | const | 全局,函数,块级 | 无
作用域内声明前不允许使用改变量 | 声明时赋值,只能赋值一次 | - 备注: - 表格中 没有提到模块作用域, 认为 模块作用域 即函数作用域 - 变量在其作用域范围内独占,无二义: 不会发生 一个变量在 作用域前半段指向父级作用域的变量,后半段指向当前作用域变量 #### 5 请说出下列最终的执行结果,并解释为什么? - 最终结果是:20 - 解释: - `setTimeout`执行的函数(设为`fn1`)使用的`()=>` 语法,该函数创建时会绑定其创建时的`this` - `obj.fn()` 执行时,其内`this` 指向 `obj` - 所以 `fn1` 内 this.a 即 obj.a ,输出20 #### 6 简述 `Symbol` 类型的用途? - 原理上,返回一个唯一id - 应用如下: - 由于其唯一性,可以用来做namespace,避免不同模块之间产生重名问题 - 作为obj的key使用 - 可以对该key 成员的访问性做不严格的限制: - 拿到key可以访问该成员 - 遍历 obj 的Symbol拿到key #### 7 说说什么是浅拷贝,什么是深拷贝 - 拷贝即内存复制 - 变量存值: - 值本身分为值类型和引用类型 - 值类型 - 比如值为数字1,本身就是一段代表数字1的二进制 - 变量存1时,该变量分配的内存中存储 代表数字1的二进制 - 引用类型 - 比如 对象`{a:1}`, 需要单独分配一块内存来存储 `{a:1}` - 变量存引用类型的值时, 该变量分配的内存中存储的不是 代表`{a:1}`的二进制,而是指向其分配内存的地址 - 不同于值类型的原因是 ,引用类型的值 其所需内存较大(拷贝开销可能较大),或长度可变(比分配定长内存复杂),存定长的地址就很简单,快速 - 浅拷贝: - 变量值为值类型时,复制代表其值的二进制; - 变量值为引用类型时,复制其值存储的内存的所在地址 - js 中 `a=b` 采用的 浅拷贝策略 - 深拷贝 - 深拷贝与浅拷贝的差异在于 当值为引用类型时的拷贝目标不同 - 浅拷贝拷贝的内存地址 - 深拷贝会先根据内存地址找到其二进制所在内存, 然后分配一块内存对其进行拷贝, 并存储新分配的内存的地址 - js中深拷贝示例如下: ```js const a = {a:1}; const deepCopyOfA = deepClone(a); function deepClone(val){ // 此代码用来演示深拷贝, 非生产代码,比如没有考虑 function 类型判断, proxy,objectOptions ... if(typeof(val)!=='object'){ return val; } const res = {};//新分配内存 for(const [key,v] of Object.entries(val)){ res[key] = deepClone(v); } return res; } ``` ### 8 谈谈你是如何理解 JS 异步编程的, Event Loop 是做什么的,什么是宏任务,什么是微任务? - JS异步编程理解: 同步编程代码时连续执行的, 异步编程则是将连续的代码拆分成`小块`,一块一块执行 - 每`小块`通常由异步api分割,使用回调形式来包装(一个回调是一个`小块`) - 异步编程业务 应该考虑清楚 各`小块`的执行顺序 - 在js的执行过程上下文中 解释 EventLoop,宏任务,微任务 - js 执行过程: - js代码是单线程执行的,不会同时执行2段js代码; 称js代码交给`js执行器`来执行 - js代码以`任务`形式 填充到 `Queue`中, 先填充的`任务`先执行 - EventLoop 负责在 `js执行器`执行完代码时,从 `Queue` 中取出下一个`任务`交给 `js执行器`来执行 - `宏任务`,`微任务`都是一段 `任务`,差别在于 `排队`方式不同 - `宏任务`创建后, 插入到 `Queue`队尾,并在`Queue`前面的任务都执行完毕后,由 EventLoop从 `Queue`中取出交给`js执行器执行` - api:setTimeout - `微任务`创建后 会在 `js执行器`执行当前代码后排队, 当前代码执行完毕,依次执行`微任务`; - 可以说`微任务`会插队了, 会在下一个宏任务之前执行 - api:process.nextTick,MutationObserver ### 9 将下面异步代码用Promise 改进 ```js var scope = {}; new Promise(resolve => { setTimeout(() => { scope.a = "hello"; resolve(); }, 10); }).then(() => new Promise(resolve => { setTimeout(() => { scope.b = "lagou"; resolve(); }, 10); })).then(() => setTimeout(() => { scope.c = "I love U"; console.log(scope.a + scope.b + scope.c); }, 10)); ``` - 封装setTimeout ```js function setTimeoutPromise(delay,cb){ return new Promise(resolve=>{ setTimeout(()=>{ cb(); resolve(); },delay); }); } var scope = {}; setTimeoutPromise(10,()=>{ scope.a = "hello"; }).then(()=>setTimeoutPromise(10,()=>{ scope.b = "lagou"; })).then(() => setTimeout(() => { scope.c = "I love U"; console.log(scope.a + scope.b + scope.c); }, 10)); ``` ### 10 请简述 Typescript 与 JavaScript 之间的关系 - Typescript 是 Javascript 的超集 - Typescript 对js 语法进行了扩展,还支持类型相关语法: 类型标注,类型声明,接口,抽象类,修饰符... - ts代码可以转换成js - ts转换前需要编译, 在编译过程中: - 检查类型, 不符合类型约束的会输出错误信息 - 侵入性:ts 中类型标注等于转换后的js 存在映射关系 - 非侵入: - 通常ts 中的类型声明 在转换成js 后,会丢掉 - `const enum ...`,`abstract class ...` - 侵入性:`enum ...` ### 11 请谈谈你所认为的Typescript 优缺点 - ts 对js进行了扩展,有以下优点: - 强类型,符合代码多,周期长的大项目开发要求: - 在编译时检查类型 - 支撑ide 提供 智能提示, 重构标注功能 ~提升开发效率,减少单词拼写错误,解决弱类型语言重构难以定位引用问题 - 编译后显示错误提示 ~ 不必等到运行时,甚至代码没覆盖的情况埋隐患 - 类型声明 ~ 代码可读性高,易于维护 - 类型声明能在语法层面限制类型 ~ 不需要写额外的类型检查代码 - 强类型~避免变量隐式类型转换带来的一些容易忽视的错误 - 渐进式~ 易于上手,学到什么用什么 - es2015... 特性支持 - 微软背书,开发 - 生态繁荣: - 流行框架 react, vue,angular 都有使用ts - 社区能贡献常见模块的声明,如 `@types/lodash` - 缺点 - 小项目中,额外开销来写类型声明 - 受限于 '伪编译', 代码转换成js是个动态弱类型语言,代码在运行时约束有限 - 内容丰富,需要额外时间上手