最近心里只有一句话: 减肥真特么难!虽然我已经收了十多斤了。但是看着还是不瘦,程序员是不是都有发胖的苦恼?
细说ES6
追本溯源,谈下ES6啥意思
大家都知道ES6新增了很多功能,但是这么多年过去了,ES7,ES8却很少有人提及,面试的时候,总是问ES6的新特性,都新了这么多年了,还真的新么?所以你确定真的很了解ES6么?
1995年,JavaScript刚被Netscape公司的Brendan Eich开发出来的时候,叫做LiveScript。后来发布的时候,为了蹭Java的热度,就改名为JavaScript了,但其实跟Java啥关系都没有。然后它就真的火了。
然后,当时的巨头微软不乐意了,于是1996年,微软也开发了自己的浏览器为IE,使用的脚本语言为JScript。
浏览器的脚本语言多了,就得有个标准不是,不然怎么管理呢?
于是,1997年,以JavaScript1.1为蓝本的建议被提交给了欧洲计算机制造商协会(ECMA,European Computer Manufacturers Association)。于是ECMA忙了好几个月,终于制定出了一个名为ECMAScript的新脚本语言的标准——ECMA-262。
ECMA-262定义的ECMAScript与Web浏览器没有依赖关系。它主要规范了:语法,类型,语句,关键字,保留字,操作符,对象。所以其实JavaScript在实现了ECMAScript的基础上,比ECMAScript规定的意义要更多。
ECMAScript(读音:ek-ma-script)其实就是一个标准化的脚本语言,遵循的标准是ECMA-262。而JavaScript是按照ECMA-262标准实现的ECMAScript。到了2008年,五大主流浏览器(IE,火狐,谷歌,Safari和Opera)全部做到了与ECMA-262兼容。但是ECMA-262规范外的,各浏览器还是有各自的特性功能的,而这些才是我们在兼容浏览器需要注意的地方。
了解了着一历史,再来说说我们现在总说的ES几,其实就是ECMAScript所遵循的ECMA-262规范的第几个版本。
ES1即ECMA-262的第一个版本。同理ES2,ES3,ES4, ES5就是ECMA-262对应的第2,3,4,5个版本。
这时你会说了,那ES6就是ECMA-262的第6个版本呗。没错!就这么回事。但是这里有个插曲。因为ECMA-262的前几个标准都是隔了比较长的时间才更新的,比如ES5在2009年发布,距2015年发布的ES6相隔了六年!更新的太慢,于是组委会很嫌弃呀,就要求必须一年更新一版!
一年更新一版的话,怕就ES差数下去太大,也不好区分。所以ES6改名为ES2015。而之后的每一年发布的ECMA-262标准就变成了ES+年份,即ES2017,ES2018……
所以其实很多面试官问的ES6新特性,其实问的是2015年以后的新增特性,你去查ES2020也是有新增特性的。。
咱也捋一下ES6+ 新特性吧
说完了ES6的历史,是不是你也觉得再说ES6就感觉怪怪的了,哈哈。那就说ES6+的新特性吧!
那些常用的,简单的就一笔带过了,毕竟五年过去了,该用的早就用烂了。
- let 和 const
- Number对象
新增Number.isFinite()、Number.isNaN()、Number.isInteger();
全局方法parseInt()和parseFloat()移植为Number.parseInt(), Number.parseFloat(); - Math对象
Math.hypot() - 指数运算符 (**)
- 数组
扩展运算符(…):将一个数组转为用逗号分隔的参数序列
Array.from():将类对象转为真正的数组
Array.of():将一组值,转换为数组
copyWithin():在当前数组内部,将指定位置的成员复制(覆盖)到其他位置,然后返回当前数组
find():用于找出第一个符合条件的数组成员
findIndex():返回第一个符合条件的数组成员的位置,否则返回-1
fill():使用给定值,填充一个数组
includes():布尔值,表示某个数组是否包含给定的值
flat(): 用于将嵌套的数组“拉平”,变成一维的数组
flatMap():对原数组的每个成员执行一个函数,然后对返回值组成的数组执行flat()方法 - 函数
设置函数参数的默认值:(function say(name = ‘xuxi’){})
增加函数的length属性:返回没有指定默认值的参数个数((function (a, b, c = 5) {}).length // 2)
函数作用域:一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域
箭头函数: (()=>{}) - 对象
扩展运算符:({…a})Object.assign()用于取出参数对象的所有可遍历属性,拷贝到当前对象之中;
Object.is():用来比较两个值是否严格相等
Object.assign():用于对象的合并,将源对象的所有可枚举属性,复制到目标对象
Object.keys():返回一个数组,成员是参数对象自身的所有可遍历属性的键名
Object.values():返回一个数组,成员是参数对象自身的所有可遍历属性的键值
Object.entries():返回一个数组,成员是参数对象自身的所有可遍历属性的键值对数组
Object.fromEntries():用于将一个键值对数组转为对象,entries的逆操作 - symbol:ES6 引入了一种新的原始数据类型Symbol,表示唯一的值。
Symbol.for():接受一个字符串作为参数,然后搜索并返回以该参数作为名称的 Symbol 值
Symbol.keyFor():返回一个已登记的 Symbol 类型值的key - Set
ES6提供了新的数据结构Set,类似于数组,但是成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成Set数据结构。 - Map
类似于对象,也是键值对的集合,各种类型的值(包括对象)都可以当作键。Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。 - Proxy
- Reflect
- Promise
- async函数
- class
- 修饰器Decorator
- module:(export 和 import)
- 模板字符串:(`)
……等等
由于我比较懒,就不一一介绍和描述了,所以咱们从网上抄了一些面试题来了解ES6吧!哈哈,我真是聪明~
准备!面试开始啦!
- var、let 和 const 定义变量的区别?
- 箭头函数和function定义函数的区别?什么时候不能用箭头函数?
- ES6的模板字符串有哪些新特性?并实现一个类模板字符串的功能?
- 介绍下Set、WeakSet、Map、WeakMap的区别?
- calss继承和原生JS继承的区别?
- Promise构造函数时同步执行还是异步执行,那么then方法呢?
- setTimeout、Promise、Async/Await的区别?
- promise有几种状态,什么时候会进入catch?
- 使用class手写一个Promise
- 使用解构赋值,实现两个变量的值的交换。
- 设计一个对象,键名的类型至少包含一个symbol类型,并且实现遍历所有key?
- 如何使用Set去重?
- 将下面for循环改成for of 形式?
let arr = [11,22,33,44,55];
let sum = 0;
for(let i=0;i<arr.length;i++){
sum += arr[i];
} - 理解async/await以及对Generator的优势?
- forEach、for in、for of 三者区别?
- 说一下es6的导入导出模块?
- 下面Set结构,打印出的size值是多少
let s = new Set();
s.add([1]);
s.add([1]);
console.log(s.size);
懵逼没有,哈哈,检测下自己!
答案来啦!
1. var、let 和 const 定义变量的区别?
答:
var 声明的变量可以重复声明,而let和const不可以重复声明。
var 是不受限于块级作用域的,而let和const是受限于块级作用域的。
var 会与window相映射(会挂一个属性),而let和const不与window相映射。
var 可以在声明的上面访问变量,而let和const有暂存死区,在声明的上面访问变量会报错。
const声明的变量不得改变值,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
解析:
举例:执行下边这段代码就一目了然了。
alert('start!');
console.log(a); // undefined,说明var可以在声明的上面访问变量
// console.log(b); // 报错,说明let不可以在声明之前访问
// console.log(c); // 报错,说明const不可以在声明之前访问
var a = 1;
let b = 1;
const c = 1;
function show () {
console.log(this.a); // 1, 说明var可以与window像映射,可以在window对象上挂一个属性
console.log(this.b); // undefined,说明let不可以与window向映射
console.log(this.c); // undefined,说明const不可以与window向映射
}
show();
if (a === 1) {
var a1 = 2;
let b1 = 2;
const c1 = 2;
}
console.log(a1); // 2,说明var不受限于块级作用域
// console.log(b1); // 报错,说明let受限于块级作用域
// console.log(c1); // 报错,说明const受限于块级作用域
var a = 3;
// let b = 3; // 报错,说明let无法被重复声明
// const c = 3; // 报错,说明const无法被重复声明
console.log(a); // 3
// c = 4; // 报错,说明const定义的变量无法被赋值。
理解后,对于这个面试题的答案只需要记住这几个关键词就可以啦:重复声明、块级作用域、变量提升、window映射。
2. 箭头函数和function定义函数的区别?什么时候不能用箭头函数?
答:
写法不同,function(){}和()=>{},箭头函数写法更简单,所以箭头函数都是匿名函数,而function可以匿名也可以具名;
this的指向不同,箭头函数没有自己的this,它里面的this是继承所属上下文中的this,使用call与apply都无法改变;
构造函数,箭头函数不可以定义构造函数,function可以。所以箭头函数没有原型属性;
arguments对象,箭头函数不具有arguments对象,function有;
变量提升,箭头函数无法变量提升,function可以;
其它,箭头函数不能作为Generator函数,不具有super,new.target。
创建构造函数和Generator函数的时候不能用箭头函数!
解析:
理解后:只需要记住箭头函数是简化版的函数,所以很多function拥有的属性,箭头函数都没有。
所以记住这些,你就赢了:写法简单,没有名字,没有this,没有arguments,没有super,没有new.target,搞定!
3. ES6的模板字符串有哪些新特性?并实现一个类模板字符串的功能?
答:
使用两个反撇号把任何变量以及字符串都包含起来。
模板字符串可以使用 ${变量名} 引入变量
模板字符串可以通过 ${运算} 进行简单的运算
模板字符串可以进行嵌套,反撇号中再可用反撇号,单引号,双引号
模板字符串可以多行拼接,即模板字符串内部可以随意换行
let b = 123;
let a = `aabb
${b}加加减减
'sss'"llll"`;
console.log(a);
解析:
略!
以前偷看数学题答案的时候,看到这个字是不是很绝望啊,哈哈!不过这玩意儿确实没啥可解析的,就是好用!
4. 介绍下Set、WeakSet、Map、WeakMap的区别?
答:
- Set:
成员唯一不重复、无序;
只有键值,没有键名(键值和键名一致);
可以遍历,方法有:add、delete、has、clear、entries、forEach、keys、values
Set 也能用来保存 NaN 和 undefined, 如果有重复的 NaN, Set 会认为就一个 NaN(实际上 NaN!=NaN); - Map
本质上是键值对的集合,类似集合;
可以遍历,方法很多,可以跟各种数据格式转换。 - WeakSet
成员都是对象;
成员都是弱引用,可以被垃圾回收机制回收,可以用来保存 DOM 节点,不容易造成内存泄漏;
不能遍历,方法有 add、delete、has。 - WeakMap
只接受对象作为键名(null 除外),不接受其他类型的值作为键名;
键名是弱引用,键值可以是任意的,键名所指向的对象可以被垃圾回收,此时键名是无效的;
不能遍历,方法有 get、set、has、delete。
解析:
Set
~概念:
Set是ES6新增的一种数据结构,类似于数组,但是数值唯一,没有重复值。
Set本身也是一个构造函数,可以生成实例。(可以new Set())
~属性:
size
: 返回set实例的成员总个数。
~方法:
add()
:添加成员,返回set结构;
delete()
:删除某个成员,并返回一个布尔值,表示是否删除成功;
has()
:判断某值是否为Set的成员,并返回一个布尔值;
clear()
:清除所有成员,没有返回值;
keys()
:返回键名的遍历器;
values()
:返回键值的遍历器;
entries()
:返回键值对的遍历器;
forEach()
:使用回调函数遍历每个成员;
~举例:
let set = new Set();
set.add(1);
console.log(set.add('a')); // Set(2) {1, "a"}
console.log(set.size); // 2
console.log(set.delete(1)); // true
console.log(set.has(1)); // false
set.clear();
console.log(set); // Set(0) {}
set = new Set(['a', 'b', 'c']);
for (let item of set.keys()) { console.log(item); } // a b c
for (let item of set.values()) { console.log(item); } // a b c
for (let item of set.entries()) { console.log(item); } // ["a", "a"] ["b", "b"] ["c", "c"]
for (let val of set) { console.log(val); } // Set结构默认可遍历,等同于values。
set.forEach((value, key) => console.log(key + ':' + value)); // a:a b:b c:c
console.log([...set]); // ["a", "b", "c"] 注:这么写,是可以用于数组去重的
WeakSet
~概念:
WeakSet结构与Set类似,也是不重复的成员的集合。
WeakSet的成员只能是对象!
WeakSet不可遍历!
~属性:
WeakSet没有size属性哦,因为不可以遍历。
~方法:
add()
:添加成员,返回WeakSet结构;
delete()
:删除某个成员,并返回一个布尔值,表示是否删除成功;
has()
:判断某值是否为WeakSet的成员,并返回一个布尔值;
~理解:
从上边的描述可以看出WeakSet和Set的一切不同点都是因为weak。weak是虚弱的意思,由于WeakSet成员只能是对象,且WeakSet的成员对象又都是弱引用,即随时都可能被垃圾回收机制给回收掉。所以,WeakSet 的成员是不适合引用的,也不能遍历,因为成员随时都可能会消失。
但是它肯定是有专门的用处的,比如存储DOM节点,不用担心存储的DOM节点从文档中移除时,引发内存泄露。
~举例:
let weakSet = new WeakSet();
let obj = {a: 1};
console.log(weakSet.add(obj));
console.log(weakSet.add([1, 2]));
// console.log(weakSet.add(3)); // 报错
console.log(weakSet.has(obj));
console.log(weakSet.delete(obj));
console.log(weakSet.has(obj));
Map
~概念:
ES6新增的在Object的基础上,允许键名称为除了字符串之外的其它格式的一种数据结构。理解起来就是Object是这样的 {字符串:值} ,而Map是允许这样的 {值: 值}。
~属性:
size
返回Map 结构的成员总数。
~方法:
set()
:新增或修改键值对,返回当前map对象;
get()
:获取对应的键值,没找到则返回undefined;
delete()
:删除某个键,并返回一个布尔值,表示是否删除成功;
has()
:判断某键是否存在,并返回一个布尔值;
clear()
:清除所有成员,没有返回值;
keys()
:返回键名的遍历器;
values()
:返回键值的遍历器;
entries()
:返回键值对的遍历器;
forEach()
:使用回调函数遍历每个成员;
~举例:
let map = new Map();
let o = {a: 1};
map.set(123, 'eee');
map.set(undefined, 'uuuu');
map.set(o, '111');
console.log(map); // {123 => "num", undefined => "111", {…} => "111"}
console.log(map.size); // 3
console.log(map.get(o)); // 111
console.log(map.has(123)); // true
console.log(map.delete(123)); // true
console.log(map.has(123)); // false
for (let item of map.keys()) { console.log(item); } // undefined {a: 1}
for (let item of map.values()) { console.log(item); } // uuu 111
for (let item of map.entries()) { console.log(item); } // [undefined, "uuuu"] [{…}, "111"]
map.forEach((value, key) => console.log(key + ':' + value)); // undefined:uuuu [object Object]:111
WeakMap
~概念:
WeakMap结构与Map类似,但是只接受对象作为键名。WeakMap与WeakSet功能类似,WeakMap的键名对象都是弱引用,目的是为了防止内存泄露。
~属性:
WeakSet没有size属性哦,因为不可以遍历。
~方法:
set()
:新增或修改键值对,返回当前WeakMap对象;
get()
:获取对应的键值,没找到则返回undefined;
delete()
:删除某个键,并返回一个布尔值,表示是否删除成功;
has()
:判断某键是否存在,并返回一个布尔值;
~理解:
跟WeakMap一样,以弱引用对象作为键名,当对象被删除时,可以防止内存泄露。主要应用于以文档的DOM节点做键名的情况。
~举例:
let weakMap = new WeakMap();
let aDom = document.getElementById('aaa');
let bDom = document.getElementById('bbb');
weakMap.set(aDom, 'testA');
weakMap.set(bDom, 'testB');
5. calss继承和原生JS继承的区别?
答:
- 写法上:ES5通过构造函数的原型完成,ES6通过类的extends完成继承。
- 规范上:ES6类的定义,以及类体的写法规范更为严格。
- 继承机制:
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。 - 继承链:
ES5,一条继承链: son.proto_ ⇒ parent.prototype ⇒ parent.prototype.construtor ⇒ parent
ES6,两条继承链:
son.proto_ ⇒ parent // 继承属性
son.prototype.proto ⇒ parent.prototype // 继承方法
关于继承,我之前写过一篇比较详细的博客,有兴趣的戳这个:https://blog.youkuaiyun.com/qq_35855343/article/details/105405780
6. Promise构造函数时同步执行还是异步执行,那么then方法呢?
答:
Promise是同步执行的,then是异步的。
解析:
这个我之前也写过一篇博客,哈哈,戳一下这个:https://blog.youkuaiyun.com/qq_35855343/article/details/103353679
7. setTimeout、Promise、Async/Await的区别?
答:
除写法不同外:
setTimeout:定时器,异步延时触发,定时器结束后会在宏任务队列中等待调用栈调用执行。
Promise:promise是自执行函数,但是其中的then是异步的,所以promise内部程序执行完成后,会在微任务队列中等待调用栈执行then函数的内容完成回调。
Async/Await:async函数会返回个promise对象,相当于自执行函数,但是如果语句中含有await,await执行完成后面的表达式后,会让出线程,同时在微任务队列中等待调用栈回调后执行async函数后面的语句。
解析:
三者皆为异步处理,所以本题考的主要就是事件循环机制和任务队列。
巧的是这个我也写过,详解请看这里:https://blog.youkuaiyun.com/qq_35855343/article/details/103315353
8. promise有几种状态,什么时候会进入catch?
答:
promise有三种状态:pending,fulfilled,rejected。
pending:正在执行中
fulfilled:执行成功
rejected:执行失败
当promise的状态从pending变为rejected时,会进入catch。
解析:
跟第6题一样哈,这里有:https://blog.youkuaiyun.com/qq_35855343/article/details/103353679
9. 使用class手写一个Promise
答:
class myPromise {
constructor (fn) {
if (typeof fn !== 'function') {
throw new Error('参数只能为函数!');
}
this.status = 'pending'; // 默认初始状态pending
this.onFulfilledCallback = []; // 存储执行成果回调队列
this.onRejectedCallback = []; // 存储执行失败回调队列
this.resolveValue = undefined; // 存储执行成功的结果,即resolve的值
this.rejectVlaue = undefined; // 存储执行失败的结果,即reject的值
try {
fn(this.resolve.bind(this), this.reject.bind(this)); // 运行内部自执行函数
} catch (error) {
reject(error);
}
}
resolve (result) { // resolve只执行一次
if (this.status !== 'pending') return false;
this.status = 'fulfilled'; // 状态由pending => fulfilled表示执行成功
this.resolveValue = result; // 存储执行成功的结果。
this.onFulfilledCallback.forEach(fn => { // 执行成功的回调函数(then)
fn(result);
});
}
reject (result) {
if (this.status !== 'pending') return false;
this.status = 'rejected'; // 状态由pending => fulfilled表示执行成功
this.rejectVlaue = result; // 存储执行成功的结果。
this.onRejectedCallback.forEach(fn => { // 执行成功的回调函数(then)
fn(result);
});
}
then (onSuccess, onFailed) {
if (typeof onSuccess === 'function') {
this.onFulfilledCallback.push(onSuccess);
}
if (typeof onFailed === 'function') {
this.onFulfilledCallback.push(onFailed);
}
}
}
测试:
new myPromise((resolve) => {
setTimeout(() => {
console.log(888);
resolve(123);
}, 1000);
}).then(res => {
console.log(res);
});
运行结果:
解析:
这种题肯定就是考你class的用法,以及对promise的理解了,首先必须知道promise的状态由三种:pending,fulfilled,rejected。
10. 使用解构赋值,实现两个变量的值的交换。
答:
let a=5;
let b=3;
[a,b]=[b,a]
解析:
ES6增加了解构赋值,比ES5需要定义一个中间变量的方法要简便很多。
解构赋值:解构赋值是对赋值运算符的扩展。主要包括数组的解构赋值、对象的解构赋值、字符串的解构赋值、函数参数的解构赋值。
- 解构的源,解构赋值表达式的右边部分。
- 解构的目标,解构赋值表达式的左边部分。
举几个例子,一看就懂的那种:
let [a, b] = [1,2];
console.log(a, b);
console.log([b, a]);
let [q,w,e,r,t] = 'hello';
console.log(q,w,e,r,t);
let [a1,,b1] = [1, 2, 3];
console.log(a1, b1);
let [a2, ...b2] = [1, 2, 3];
console.log(a2, b2);
let [a3 = 1, b3] = []; // a = 1, b = undefined
console.log(a3, b3);
let { aaa, bbb } = { aaa: '111', bbb: '222' };
console.log(aaa, bbb);
let {a4 = 1, b4 = 2} = {a: 3};
console.log(a4, b4);
结果:
11. 设计一个对象,键名的类型至少包含一个symbol类型,并且实现遍历所有key?
答:
const name = Symbol();
let obj = {
[name]: 'ding',
'age': 12
};
Reflect.ownKeys(obj);
解析:
symbol是JavaScript中新增加的一种基本数据类型。
基本数据类型: Number,String,Null,Undefined,Boolean,Symbol。
Symbol基本用法:
let a = Symbol();
let b = Symbol('desc');
Symbol最重要的就是记住这个:每个Symbol实例都是唯一的。即无论你怎么定义,任意两个Symbol实例都不可能相等。
然后记住这个:如果一个对象中包含了Symbol对象做为key名时,是无法通过Object.keys()和for…in方法遍历到的。(有点像Java里面的protected属性,可以访问却不能遍历)如果想要遍历symbol的key,可以通过Reflect.ownKeys()方法(新增的反射API),或者通过Object.getOwnPropertySymbols()方法得到。
举例:
let obj = {
[Symbol('sy')]: 123,
'aa': 456,
'bb': 789
};
console.log(Object.keys(obj)); // ["aa", "bb"]
for (let key in obj) {
console.log(key);
} // aa bb
console.log(Object.getOwnPropertySymbols(obj)); // [Symbol(sy)]
console.log(Reflect.ownKeys(obj)); // ["aa", "bb", Symbol(sy)]
估计很多人跟我一样,不知道对这东西到底该用在什么地方吧。这里顺带提一嘴:
应用场景1:像上边例子中一样,当某对象需要设置私有唯一属性的时候可以用,不会被遍历到。
应用场景2:代替常量。正常情况下,我们定义常量,需要一个常量名,然后要给这个常量赋初始值。如果使用Symbol定义常量的话,就不需要赋初值了,还能保证唯一。
应用场景3:在class的构造器中(constructor)使用,可以定义私有属性。
还不懂的可以看这个大神的文章: https://www.jianshu.com/p/f40a77bbd74e
12. 如何使用Set去重?
答:
let arr = [2,5,2,3,1,5,2];
let set = new Set(arr);
console.log([...set]); // 利用扩展运算符将set转换为数组
console.log(Array.from(set)); // 利用Array.from将set转换为数组
解析:
Set本身的定义就是没有重复成员。所以直接创建一个Set实例就可以实现去重,然后将Set格式转换为数组即可。
13. 将下面for循环改成for of 形式?
let arr = [11,22,33,44,55];
let sum = 0;
for(let i=0;i<arr.length;i++){
sum += arr[i];
}
答:
let arr = [11,22,33,44,55];
let sum = 0;
for(value of arr){
sum += value;
}
解析:
for…of 语句创建一个循环来迭代可迭代的对象。
可以迭代的对象有: Arrays(数组), Strings(字符串), Maps(映射), Sets(集合),Arguments Object(参数对象),Generators(生成器)。这个就不举例子了,太好理解了。
普通对象是不可以迭代的。{a:1, b:2}这种不可以。
14. 理解async/await以及对Generator的优势?
答:
async函数是Generator函数的语法糖。
async函数加上await后才有效果,await会跳出进程,等后边异步执行完成后,才会继续执行函数体。
async函数会返回promise对象。
优势就是:
- 内置执行器。async函数await的异步执行完成后,会自动继续执行;Generator函数yield只能等待next()函数去触发继续执行;
- 更好的语义。async 和 await 相较于 * 和 yield 更加语义化
- 更广的适用性。yield命令后面只能是 Thunk 函数或 Promise对象,async函数的await后面可以是Promise也可以是原始类型的值。
- 返回值是 Promise。async 函数返回的是 Promise 对象,比Generator函数返回的Iterator对象方便,可以直接使用 then() 方法进行调用
解析:
略。不懂的看这个:https://blog.youkuaiyun.com/qq_35855343/article/details/103315353
15. forEach、for in、for of 三者区别?
答:
forEach更多的用来遍历数组
for in 一般常用来遍历对象或json
for of数组对象都可以遍历,遍历对象需要通过和Object.keys()
for in循环出的是key,for of循环出的是value
16. 说一下es6的导入导出模块?
es6导入导出模块使用的是关键字import和export。
关于import:
import 命令会提升到整个模块的头部,首先执行。
单例模式,即多次重复执行同一句 import 语句,那么只会执行一次,而不会执行多次。
静态执行特性,即import 是静态执行,所以不能使用表达式和变量。
如果模块定义的导出是export default,可以直接import。
如果模块定义的导出是export {},那么要import {}。
可以通过as给引入的模块重命名。
关于export:
可以export default,会暴露变量,可以被任意变量接收。
也可以export {} 导出某个或某几个变量,对象,函数
解析:
ES6的模块化特点:
- 在编译时就能确定模块的依赖关系,以及输入和输出的变量,在编译的时候加载模块。相比CommonJS等在运行时加载的方式,效率更高;
- 每一个模块只加载一次, 并只执行一次,重复加载同一文件,直接从内存中读取;
- 每个模块都有自己的上下文,每一个模块内声明的变量都是局部变量,不会污染全局作用域。
- 可以只加载引入的模块,而不加载模块文件本身;
- ES6 的模块自动开启严格模式,不管你有没有在模块头部加上 use strict。
- 模块中可以导入和导出各种类型的变量,如函数,对象,字符串,数字,布尔值,类等。
如果对模块化感兴趣的可以看小的写的这篇文章:https://blog.youkuaiyun.com/qq_35855343/article/details/105552587
17. 下面Set结构,打印出的size值是多少
let s = new Set();
s.add([1]);
s.add([1]);
console.log(s.size);
答: 2
解析:
这个问题类似于问这个:
let a = [1];
let b = [1];
console.log(a === b); // false
简单的说就是a和b是两个数组,两个引用对象,怎么可能相等呢?所以对于set来说,这俩不重复,所以是2!
拓展:
1. 小讲一下function的arguments。
这玩意儿很基本哈,估计大家开始学习js的时候就已经很清楚。这里写这个,主要是为了半路出家的人,尤其是学过java后转JS,会容易混乱,所以这里提及一下。
比如,我随手定义了一个函数:
function easy () {
console.log(123);
}
这个函数可以传参吗?可以的哈,估计学过Java的同学可能有点懵了,请暂时先忘记严格模式的形参he实参之类的概念,只需要记得,JavaScript非常的宽容!
ECMAScript规范定义了函数的参数在内部是用一个数组表示的,即不管你形参是如何定义的,内部保存的都是个数组,这个数组就是arguments对象。即使你没有设置形参,依旧是可以传值的,然后通过arguments带数组下标的格式获取参数。同理,即使你设置了多个形参,调用时不传参数也是可以的,都不会报错。
在ECMAScript中,形参(命名参数)只是为了提供便利,并不是必需的,举个例子,你就懂了。
function aaa () {
if (arguments.length > 0) {
console.log(arguments[0]);
}
}
function bbb (param1, param2) {
if (arguments.length === 1) {
console.log(arguments[0]);
} else if (arguments.length === 2) {
console.log(param1 + arguments[1]);
} else if (arguments.length > 2) {
console.log(arguments[0] + params + arguments[2]);
} else {
console.log('No params!')
}
}
aaa('lalalla'); // lalalla
bbb(); // No params!
bbb('aa', 'bb', 'cc'); // aabbcc
理解了之后,还有几个需要注意的点:
- 没有传递参数的命名参数将被自动赋予undefined值,就像定义了变量但未初始化一样,比如上例中bbb()调用时,如果打印param1,肯定是undefined。
- 命名参数虽然不是必须的,但是只要定义了,会为其分配独立的内存空间。
- arguments的值永远与对应命名参数的值保持同步。
- arguments的长度是由传入的参数个数决定的。
后边第二点挺好玩的,咱们还是举个例子说一下吧:
function bbb (param1, param2) {
arguments[0] = 'ee';
console.log(arguments.length);
console.log(arguments[0]);
console.log(param1);
}
bbb('aa'); // 1 ee ee ,说明了第二点,arguments的值与命名参数的值保持一直。
bbb(); // 0 ee undefined,说明了第三点,传入的参数个数决定了arguments的长度,所以命名参数的值没有被同步。