ES6 平时开发中经常使用,但是部分基础定义时常被忽略。借此,用总计面试题的方式记录一下,若有不对的地方,还请大家指出,谢谢!
let const var 的区别
变量的作用域,指的是,在编写的算法函数中,我们能访问变量(在使用函数作用域时,也可以是一个函数)的地方. 有局部变量和全局变量两种。
let const var 的区别
let和const定义的的变量会常量是具有块级作用域的。var定义的变量没有块级作用域的概念,但是会有变量提升,即 可在变量声明之前使用变量,let和const则不行。- 被
const和let声明的常量或变量不能再被重复声明(在同一个作用域内),var可以重复声明变量,以最后定义的变量覆盖之前的原则定义。 const定义常量,定义的时候就要赋初值,否则报错。let,var初始化时可以不赋初值。const定义的基本数据类型的常量不能被修改值,定义引用数据类型的常量引用数据的内部属性值是可以修改值的。
扩展问题 1 : JS有哪些作用域?什么是块级作用域?
JS中作用域采用了此法作用域机制。 包含有 全局作用域, 函数作用域 和块级作用域
全局作用域
即 window 全局作用域
函数作用域
一个 function就是一个作用与空间页脚局部作用域,局部作用于内的变量不予许在外部访问。
function中的嵌套结构当嵌套之后在当前作用域中无法找到某个变量的时候,编译引擎就会在外层嵌套作用域中继续查找(父级作用域指的是定义时的父级作用域并非运行时),知道找到这个变量。或是抵达最外层的作用域(也就是全局作用域)为止。
这个逐层向上查找机制就是
作用域链的查找规则。
如果在最外层也没找到该变量, 严格模式下会抛出异常, 非严格模式会在最外层(全局)定义该变量
块级作用域
使用let ,const 定义的变量或常量。会形成块级作用域。
块级作用域 指的是 在一个作用域空间中通过let ,const 声明的变量在该作用域外是不可见的。
常见的 function ,if , for , try/catch 都会形成一个作用域块级作用域。
注意: for 的小括号也是一个独立的作用域空间。
扩展问题 2 :什么是变量提升?
个人理解的是变量或function声明之前就能使用的现象。
举个例子:
// 变量定义
console.log(a); // undefined
console.log(b); // Uncaught ReferenceError: b is not defined
console.log(c); // Cannot access 'x1' before initialization
var a = 1;
let b = 2;
const c = 3;
var 定义的变量会变量提升,let ,const 进行的声明则不会
// 函数提升
console.log(fn1); // undefined
console.log(fn2); // ƒ fn2() {}
var fn1 = function() {};
function fn2() {}
由上可知,通过 function 关键字顶一个函数会被提升,但是函数表达式不会被提升
var num = 1;
function fn3() {
console.log(num); // undefined
var num = 0;
}
fn3();
console.log(num); // 1
每个作用域里面都有提升操作,声明会被提升到所在作用域的顶部
console.log(g) // function g(){}
var g = 1
function g(){}
console.log(g) // 1
console.log(g) // function g(){}
function g(){}
var g = 1
console.log(g) // 1
函数function关键字提升的优先级要高于变量var关键字。
所以第一个console.log(g)打印出了g()方法,当代码继续向下运行遇到function g(){},g()函数已经声明过了不做任何处理,而是被 g=1 的赋值操作给覆盖,所以后面一个console.log(g)打印出了1。
如果变量或函数有重复声明会以最后一次声明为主。
所以,变量和函数提升的特点是:
- 通过
var定义的变量会提升,而let和const进行的声明不会提升。 - 通过
function关键字定义的函数会被提升,而函数表达式则不会提升。 var声明本身会被提升,但包括函数表达式在内的赋值操作并不会提升。- 每个作用域都会进行提升操作,
声明会被提升到所在作用域的顶部。 - 函数
function关键字提升的优先级要高于变量var关键字。
参考: JS的作用域
Set、Map、WeakSet和WeakMap的区别
Set、Map、WeakSet和WeakMap的详细说明以及使用和 Set 和 Map 数据结构
Set
- 是一种
类数组的数据结构,成员的值都是唯一的,没有重复值 - 接受一个数组(或类似数组的对象)作为参数
- 可以遍历,遍历顺序就是插入顺序
WeakSet 结构类似Set, 也是不重复的值的集合。
WeakSet 和 Set 的区别
WeakSet的成员只能是对象,而不能是其他类型的值WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的,也没有size属性。
WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。
Map
- 是一种
类似对象的数据结构,也是键值对的集合,“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。 - 可以接受一个数组作为参数,该数组的成员是一个个表示
键值对的数组。 Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。- 如果
Map的键是一个简单数据类型的值(数字,布尔,字符串),则只要两个值严格相等,Map将其视为一个键 Map的遍历顺序就是插入顺序
WeakMap 与Map 的区别
WeakMap只接受对象作为键名(null除外)- 键名是对象的弱引用(垃圾回收机制不将该引用考虑在内),所以其所对应的对象可能
会被自动回收。 - 是没有遍历操作(即没有
key()、values()和entries()方法),也没有size属性; - 是无法清空,即不支持
clear方法。这与WeakMap的键不被计入引用、被垃圾回收机制忽略有关。因此,WeakMap只有四个方法可用:get()、set()、has()、delete()。
基本上,WeakMap的专用场合就是,它的键所对应的对象,可能会在将来消失。WeakMap结构有助于防止内存泄漏。
说一说箭头函数和普通函数的区别?
- 箭头函数在语法上比普通函数更简洁
- 箭头函数没有
prototype(原型),所以箭头函数本身没有this - 箭头函数的
this指向在函数定义时就继承自外层第一个普通函数的this,所以箭头函数的this指向在定义的时候就已经确定,且之后永远不会改变。 - 使用
call,apply,bind都不能改变箭头函数中的this指向 - 因为箭头函数没有子集的
this且不会改变,所以箭头函数不能用定义构造函数,否则使用new关键字会报错 - 箭头函数内部没有
arguments,而是rest参数(...)代替arguments对象来访问箭头函数的参数列表 - 箭头函数不能用作
Generator函数,不能使用yield关键字
针对 arguments 的实例:
// 普通函数
function A(a){
console.log(arguments);
}
A(1,2,3,4); // [1, 2, 3, 4, callee: ƒ, Symbol(Symbol.iterator): ƒ]
// 箭头函数
let B = (b)=>{
console.log(arguments);
}
B(2,92,32); // Uncaught ReferenceError: arguments is not defined
// rest参数...
let C = (...c) => {
console.log(c);
}
C(3,82,32); // [3, 82, 32,]
说一说你对Promise理解
Promise,可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。Promise对象提供统一的接口,使得控制异步操作更加容易
Promise的缺点:
- 首先,无法取消
Promise,一旦新建它就会立即执行,无法中途取消。 - 其次,如果不设置回调函数,
Promise内部抛出的错误,不会反应到外部。 - 第三,当处于
pending状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。
Promise的状态
参考 ECMAScript 6 入门 ,Promise有三种状态:
- 等待中(pending)
- 完成了(resolved)
- 拒绝了(rejected)
Promise 解决了什么问题
Promise 的出现解决了 之前的回调地狱问题,并且Promise 实现了链式调用,也就是说每次调用 then 之后返回的都是一个 Promise , 并且是一个全新的Promise 。是因为Promise 的状态不可变。如果你在then中使用了return ,那么 return 的值会被 Promise .resolve 包装。
Promise怎么拦截错误
- .catch
使用
catch方法 捕捉错误,但是catch方法只能捕捉到同步错误,异步错误捕捉不到。 - 使用
reject抛出错误,错误会被不停的返回到下一个,必须在每一个then里面使用thow将错误抛出去,不然不能被catch捕捉到,其实也可以不用再次thow错误,在promise正常catch就好,在异步中reject一下在最后就能catch到。
Promise 中的错误不会影响到外层的运行,window.onerror 也是无法检测到的。
关于 Promise的错误拦截详细描述请移步到 关于 Promise的错误拦截
模块化开发
将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。
模块功能主要有两个命令构成 : export和 import。export命令用于规定模块的对外接口,import命令用于输入其他模块提供的功能。
export 命令
一个模块就是一个独立的文件。该文件内部的所有变量,外部无法获取。如果你希望外部能够读取模块内部的某个变量,就必须使用export关键字输出该变量。
使用:
// 用法 1
export var name = 'lisa'
// 用法2
var name= 'lisa';
var age= 18;
export { name, age};
// 用法 3
function fn1() { ... }
function fn2() { ... }
export {
fn1 as checkNanme1,
fn2 as checkNanme2
} // 使用 as 重命名
如果处于块级作用域内,就会报错,import命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了 ES6 模块的设计初衷。
import 命令
使用 import 命令加载模块
注意:
import命令输入的变量都是只读的,因为它的本质是输入接口。也就是说,不允许在加载模块的脚本里面,改写接口(如果导入的是对象,改写对象的属性是可以的,但是不建议轻易修改它的值)。
export default 命令
为模块指定默认输出
export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能唯一对应export default命令。
本质上,export default就是输出一个叫做default的变量或方法(指定对外接口为default),然后系统允许你为它取任意名字。所以它后面不能跟变量声明语句。
参考: