# 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是个动态弱类型语言,代码在运行时约束有限
- 内容丰富,需要额外时间上手