傲娇大少之——【面试总问的ES6】

最近心里只有一句话: 减肥真特么难!虽然我已经收了十多斤了。但是看着还是不瘦,程序员是不是都有发胖的苦恼?

细说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+的新特性吧!

那些常用的,简单的就一笔带过了,毕竟五年过去了,该用的早就用烂了。

  1. let 和 const
  2. Number对象
    新增Number.isFinite()、Number.isNaN()、Number.isInteger();
    全局方法parseInt()和parseFloat()移植为Number.parseInt(), Number.parseFloat();
  3. Math对象
    Math.hypot()
  4. 指数运算符 (**)
  5. 数组
    扩展运算符(…):将一个数组转为用逗号分隔的参数序列
    Array.from():将类对象转为真正的数组
    Array.of():将一组值,转换为数组
    copyWithin():在当前数组内部,将指定位置的成员复制(覆盖)到其他位置,然后返回当前数组
    find():用于找出第一个符合条件的数组成员
    findIndex():返回第一个符合条件的数组成员的位置,否则返回-1
    fill():使用给定值,填充一个数组
    includes():布尔值,表示某个数组是否包含给定的值
    flat(): 用于将嵌套的数组“拉平”,变成一维的数组
    flatMap():对原数组的每个成员执行一个函数,然后对返回值组成的数组执行flat()方法
  6. 函数
    设置函数参数的默认值:(function say(name = ‘xuxi’){})
    增加函数的length属性:返回没有指定默认值的参数个数((function (a, b, c = 5) {}).length // 2)
    函数作用域:一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域
    箭头函数: (()=>{})
  7. 对象
    扩展运算符:({…a})Object.assign()用于取出参数对象的所有可遍历属性,拷贝到当前对象之中;
    Object.is():用来比较两个值是否严格相等
    Object.assign():用于对象的合并,将源对象的所有可枚举属性,复制到目标对象
    Object.keys():返回一个数组,成员是参数对象自身的所有可遍历属性的键名
    Object.values():返回一个数组,成员是参数对象自身的所有可遍历属性的键值
    Object.entries():返回一个数组,成员是参数对象自身的所有可遍历属性的键值对数组
    Object.fromEntries():用于将一个键值对数组转为对象,entries的逆操作
  8. symbol:ES6 引入了一种新的原始数据类型Symbol,表示唯一的值。
    Symbol.for():接受一个字符串作为参数,然后搜索并返回以该参数作为名称的 Symbol 值
    Symbol.keyFor():返回一个已登记的 Symbol 类型值的key
  9. Set
    ES6提供了新的数据结构Set,类似于数组,但是成员的值都是唯一的,没有重复的值。Set本身是一个构造函数,用来生成Set数据结构。
  10. Map
    类似于对象,也是键值对的集合,各种类型的值(包括对象)都可以当作键。Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。
  11. Proxy
  12. Reflect
  13. Promise
  14. async函数
  15. class
  16. 修饰器Decorator
  17. module:(export 和 import)
  18. 模板字符串:(`)

……等等

由于我比较懒,就不一一介绍和描述了,所以咱们从网上抄了一些面试题来了解ES6吧!哈哈,我真是聪明~

准备!面试开始啦!

  1. var、let 和 const 定义变量的区别?
  2. 箭头函数和function定义函数的区别?什么时候不能用箭头函数?
  3. ES6的模板字符串有哪些新特性?并实现一个类模板字符串的功能?
  4. 介绍下Set、WeakSet、Map、WeakMap的区别?
  5. calss继承和原生JS继承的区别?
  6. Promise构造函数时同步执行还是异步执行,那么then方法呢?
  7. setTimeout、Promise、Async/Await的区别?
  8. promise有几种状态,什么时候会进入catch?
  9. 使用class手写一个Promise
  10. 使用解构赋值,实现两个变量的值的交换。
  11. 设计一个对象,键名的类型至少包含一个symbol类型,并且实现遍历所有key?
  12. 如何使用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];
    }
  14. 理解async/await以及对Generator的优势?
  15. forEach、for in、for of 三者区别?
  16. 说一下es6的导入导出模块?
  17. 下面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继承的区别?

答:

  1. 写法上:ES5通过构造函数的原型完成,ES6通过类的extends完成继承。
  2. 规范上:ES6类的定义,以及类体的写法规范更为严格。
  3. 继承机制:
    ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
    ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
    只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。
  4. 继承链:
    ES5,一条继承链: son.proto_ ⇒ parent.prototype ⇒ parent.prototype.construtor ⇒ parent
    ES6,两条继承链:
    son.proto_ ⇒ parent // 继承属性
    son.prototype.proto ⇒ parent.prototype // 继承方法

关于继承,我之前写过一篇比较详细的博客,有兴趣的戳这个:https://blog.csdn.net/qq_35855343/article/details/105405780

6. Promise构造函数时同步执行还是异步执行,那么then方法呢?

答:
Promise是同步执行的,then是异步的。

解析:
这个我之前也写过一篇博客,哈哈,戳一下这个:https://blog.csdn.net/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.csdn.net/qq_35855343/article/details/103315353

8. promise有几种状态,什么时候会进入catch?

答:
promise有三种状态:pending,fulfilled,rejected。
pending:正在执行中
fulfilled:执行成功
rejected:执行失败

当promise的状态从pending变为rejected时,会进入catch。

解析:
跟第6题一样哈,这里有:https://blog.csdn.net/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.csdn.net/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.csdn.net/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的长度,所以命名参数的值没有被同步。

在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值