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),然后系统允许你为它取任意名字。所以它后面不能跟变量声明语句。
参考:
关于箭头函数的普通函数
promise的错误处理