# fed-e-task-01-01 **Repository Path**: jiailing/fed-e-task-01-01 ## Basic Information - **Project Name**: fed-e-task-01-01 - **Description**: ES6+、异步编程、TypeScript - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-05-13 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 简答题 #### 1. 请说出下列最终的执行结果,并解释为什么? ```js var a = [] for (var i = 0; i < 10; i++) { a[i] = function () { console.log(i) } } a[6]() // 10 ``` 答:输出结果为10。因为var是全局变量,当执行a[6]()的时候,函数体中的变量i,JS引擎会根据作用域去查到,在函数作用域里查不到i的定义,就会往上层作用域查找,直到在全局作用域中查到i,此时i已经变成了10。 如果希望调用a[i]()的结果输出i,可以将for循环中的i的声明由var改为let。因为let会产生块级作用域。执行函数时函数内的变量i可以在for循环里面每次产生的块级作用域里查到i的值。 ```js var a = [] for (let i = 0; i < 10; i++) { a[i] = function () { console.log(i) } } a[6]() // 6 ``` 除此之外,还可以通过闭包的形式,用函数作用域摆脱全局作用域的影响。将每一轮的变量i放到闭包中,执行函数的时候,JS引擎根据作用域查找时,在当前闭包中就可以查到当前的i的值,而当前i的值就是每轮循环时的i。 ```js var a = [] for (let i = 0; i < 10; i++) { a[i] = (function(i){ return function () { console.log(i) } })(i) } a[6]() // 6 ``` --- #### 2. 请说出下列最终的执行结果,并解释为什么? ```js var tmp = 123 if (true) { console.log(tmp) let tmp } ``` 答:会在console.log(tmp)时报错:ReferenceError: Cannot access 'tmp' before initialization 因为在ES6中,let定义的变量会产生块级作用域,虽然在全局用var tmp = 123 定义了tmp,但是在进入if里面后,又用let重新定义了tmp,此时的if作用域的tmp都是let变量声明的块级变量,全局变量tmp已经被屏蔽了。当执行console.log(tmp)时,根据作用域的查找原则,JS引擎会先在代码执行处的最内层的块级作用域中查找,如果查找不到才会往上级作用域查找。而在if块级作用域中有tmp的声明,但是console.log(tmp)在执行的时候,tmp还未被初始化,所以会报错。 --- #### 3. 结合ES6新语法,用最简单的方式找出数组中的最小值 ```js var arr = [12, 34, 32, 89, 4] const minValue = Math.min(...arr) console.log(minValue) // 4 ``` --- #### 4. 请详细说明var、let、const三种声明变量的方式之间的具体差别? 答:let const都是块级作用域,let是变量,const是常量。var定义的变量在创建和初始化的过程会提升。let和const定义的变量创建过程会提升,但初始化过程没有提升。而const只有创建和初始化过程,没有赋值过程,不可以被重新赋值。 ```js console.log(t) // undefined var t ``` ```js console.log(t) // ReferenceError: Cannot access 't' before initialization let t ``` --- #### 5. 请说出下列代码最终输出的结果,并解释为什么? ```js var a = 10 var obj = { a: 20, fn() { setTimeout(() => { console.log(this.a) }) } } obj.fn() ``` 答:输出20。setTimeout中的函数是箭头函数,箭头函数中的this指向的是上层作用域中的this,也就是普通函数fn(){}函数体的this。而fn()是对象obj的成员方法,在调用时fn内的this指向函数的调用者,也就是obj,所以this.a就是obj.a,所以输出20。 --- #### 6. 简述Symbol类型的用途? 答:Symbol是ES6新增的一种基础数据类型,最主要的作用就是为对象添加独一无二的属性名。console.log(Symbol() === Symbol()) // false ```js const obj = { [Symbol()]: 11 } ``` 此时obj的这个Symbol属性不可被枚举。 --- #### 7. 说说什么是浅拷贝,什么是深拷贝? 答:JS的数据类型分为值类型和引用类型。值类型变量是存放在栈中,引用类型变量是将内存地址放在栈中,而栈中的地址指向了堆中的一块区域,堆中这个地址存放的内容才是这个引用类型变量的真正的内容。 在浅拷贝时,值类型会直接拷贝,而引用类型只拷贝了变量的地址,新的变量和原变量还会指向同一个堆中的区域。新变量的改变会对原变量产生影响,因为内存地址相同,其中一个变量的改变,其实改变的是同一个内存中的东西。 ```js let a = 1 let b = a b = 2 // 值类型拷贝新变量不会对原变量产生影响。 console.log(a === b, a, b) // false 1 2 let c = { name: 'cathy' } let d = c d.name = 'jal' // 引用类型拷贝新变量会对原变量产生影响。 console.log(c === d, c, d) // true { name: 'jal' } { name: 'jal' } ``` 而在深拷贝时,遇到值类型会直接拷贝,遇到引用类型时,会进行递归拷贝,如果这个引用类型是数组/对象,则判断数组的每一个元素/属性值,如果是值类型,则直接拷贝,如果是引用类型,则再对这个元素/属性值进行判断,递归下去,直到拷贝完整个对象为止。 这是我之前写的深拷贝案例: ```js /** * 深拷贝 * @param {Object} obj 要拷贝的对象 */ function deepClone(obj) { if (typeof obj !== 'object' || obj === null) return obj let newObj if (obj instanceof Array) { newObj = [] } else { newObj = {} } for (let key in obj) { if (obj.hasOwnProperty(key)) newObj[key] = deepClone(obj[key]) } return newObj } ``` --- #### 8. 谈谈你是如何理解JS异步编程的,Event Loop是做什么的,什么是宏任务,什么是微任务? JS是单线程语言,指JS执行环境中那个负责执行代码的线程只有一个。但是JS的运行环境以及JS的某些API在运行时是可以开启新的线程的。当JS代码的回调栈中进行某些耗时任务时,会将这些耗时任务放到WebAPI中,等到可以执行的时候,就会进入消息队列中。当回调栈空的时候,会被Event Loop监听到,Event Loop会从消息队列中取出第一个任务放到回调栈中让主任务执行。 ![notes/img/IMG_0159.jpeg](notes/img/IMG_0159.jpeg) JS回调队列中的任务称之为【宏任务】,而宏任务执行过程中可以临时加上一些额外需求,可以选择作为一个新的宏任务进到队列中排队(如setTimeout),也可以作为当前任务的【微任务】,直接在当前任务结束后立即执行。 微任务的目的是为了提高整体的响应能力,目前绝大多数异步调用都是作为宏任务执行,Promise 、MutationObserver、process.nextTick 是作为微任务在本轮调用的末尾执行。 --- #### 9. 将下面异步代码使用Promise改进? ```js setTimeout(function() { var a = 'hello' setTimeout(function () { var b = 'lagou' setTimeout(function () { var c = 'I love U' console.log(a + b + c) }, 10) }, 10) }, 10) ``` 改为Promise后: ```js new Promise((resolve, reject) => { setTimeout(() => { var a = 'hello' resolve(a) }, 10); }) .then(res=>{ return new Promise((resolve, reject) => { setTimeout(() => { var b = ' lagou' resolve(res + b) }, 10); }) }) .then(res => { return new Promise((resolve, reject) => { setTimeout(() => { var c = ' I love U' resolve(res + c) }, 10); }) }) .then(res => { console.log(res) }) .catch(err=>{ console.log(err) }) ``` --- #### 10. 请简述TypeScript和JavaScript之间的联系 答:TypeScript是JavaScript的超集,是基于JavaScript之上的编程语言,它解决了JavaScript类型系统的问题。TypeScript的语法上对JavaScript兼容,JavaScript代码完全可以当做TypeScript代码运行。不过TypeScript在JavaScript基础上,有了更严格的类型要求。如果有错误的类型问题,JavaScript在运行时才会暴露出来,而TypeScript则会在项目编译初期就会报错。 --- #### 11. 请谈谈你所认为的TypeScript优缺点 答: 优点: + JavaScript的超集/扩展集 + 任何一种JavaScript运行环境都支持 + 功能更为强大,生态也更健全、更完善 缺点: + 语言本身多了很多概念 + 项目初期,TypeScript会增加一些成本