ES6 | 一文扫清知识盲点
var、let和const区别
var是es6之前
用来声明变量的关键字,let和const是es6新增的声明变量的关键字。
var声明的变量属于函数作用域
,而let和const声明的变量属于块级作用域
。
var存在变量提升
,而let和const不存在
,变量提升就是可以在声明前访问它。
var在全局作用域下声明的变量会被挂载到window对象
上,而let和const不会。
var可以重复声明
,而let 重复声明会报错
。
var和let声明时可以不赋值,但是const声明的是一个常量,必须赋值,且初始值不可以改变。
const如果初始值是一个引用类型,那边非第一层级的属性值依旧可以改变,因为const强调得是指针地址的引用,所以它只能保证当前引用的变量不变。如果想定义一个完全不可更改得对象,可以使用Object.freeze()递归遍历对象属性。
对象
新增扩展:
- 属性简写,当对象键名和对应值名相等时,可以进行简写
- 扩展运算符=>解构赋值
-
属性遍历
- for...in遍历对象自身和原型上可枚举的属性
- Object.keys(obj) 遍历对象自身的可枚举属性
- Object.getOwnPropertyNames(obj) 遍历对象自身属性(含可枚举、不含Symbol)
- Object.getOwnPropertyNames(obj)对象自身所有Symbol属性的键名
- Reflect.ownKeys(obj)遍历对象自身所有属性(含可枚举、Symbol)
-
新增的方法
- Object.is()
- Object.assign()
- Object.getOwnPropertyDescriptors()返回对象自身所有自身属性(非继承)的描述对象
- Object.setPrototypeOf()设置对象原型对象、Object.getPrototypeOf()获取对象原型对象
- Object.keys()、Object.values()、Object.entries()
**数组
**新增扩展:
- 扩展运算符
- 构造函数Array.from 和 Array.of
-
实例新增方法
- copyWithin(target,start,end) [1,2,3,4,5].copyWithin(0,3)=>[4,5,3,4,5]将index=3位置到结束复制到index=0,覆盖原来得1,2
- find(cb(v,i,arr))返回第一个满足条件的数组成员
- findIndex(cb(v,i,arr))返回第一个满足条件的数组成员下标
- fill(v,start,end)填充数组,不提供第二、三参数,填充整个数组
- entries()、keys()、values()
- includes(v)
- flat()
- flatMap(cb)对原数组的每个成员执行回调函数,相当于
Array.prototype.map()
,再对数组进行flat()
,返回新数组
**函数
**新增扩展:
- 参数:es6允许为函数的参数
设置默认值
,注意:函数的形参不能使用let和const再次声明,否则报错
-
属性
- length属性:
(function(a,b,c=2){}).length
,结果2,返回没有指定默认值的参数个数(必须是尾参数,比如(function(a=2,b,c){}).length
),结果0 - name属性
-
var f = funtion(){} f.name//"f" var f = function n(){} f.name//"n" (new Function()).name//'anonymous' (function x(){}).bind({}).name//"bound x" bind都加上"bound前缀"
- length属性:
- 函数设置默认值时出现暂时作作用域
let x = 1;
function f(y=x){
//等同于let y = x;
let x = 3;
console.log(y)//1
}
f()
- 开启严格模式, 函数使用了默认值解构赋值\或者扩展运算符,都会报错
-
箭头函数(箭头函数和普通函数的区别)
this指向上一层级的作用域
, 在声明时作用域已经确定不能使用new操作符,
即不能做构造函数- 函数内
没有arguments参数
不能使用yield命令,不能作为Generator函数
**Set Map
**两种数据结构如何理解?
Set是es6新增的数据结构, 类似于数组,
是一种集合
的数据结构,但是set中的成员必须是唯一的
,无序无关联的
Set本身是一个构造函数,实例方法有: add() delete() has() clear() 遍历器keys() values() entries() forEach()
应用: 求交集 并集和差集
let a = new Set([1,2,3])
let b = new Set([4,3,2])
//并集
let union = new Set([...a,...b])
//交集
let intersect = new Set([...a].filter(v=>b.has(v)));
//差集
let difference = new Set([...a].filter(v=>!b.has(v)));
Map是键值对的有序列表, 类似于对象
,是一种字典的数据结构,其键值可以是任意类型
Map本身是构造方法, 实例方法有: size set() get() has() delete() clear() 遍历器 keys() values() entries() forEach()
Map和Object的区别: Map的
key可以是任意类型
,而Object的key只能是字符串类型或Symbol类型
WeakSet和WeakMap相对于Set Map的区别:
WeakSet:
- WeakSet中的成员
必须是一个具有Iterable接口的对象
,即只能是引用类型,不能是其他类型
- WeakSet中对对象的引用
是弱引用
,也就是当其他对象不再有对该对象的引用,垃圾回收机制就会销毁并会后该对象占有的内存 , 不会考虑它在WeakMap中的引用.
- 所以WeakMap没有遍历操作的API,也没有size属性
WeakMap:
- WeakMap中的
键必须是对象,不能是其他类型
- WeackMap中对对象的引用是弱引用
- 没有遍历操作,也没有clear操作方法
难
手写Set
方法: has clear add delete values
class Set{
constructor(){
this.list = {}
this.size = 0;
}
has(value){
return value in this.list
}
add(value){
if(!this.has(value)){
this.list[value] = value;
this.size++;
}
return this;
}
delete(value){
if(this.has(value)){
delete this.list[value];
this.size--;
}
return this;
}
clear(){
this.list = {};
this.size = 0;
}
values(){
let result = []
for(let key in this.list){
if(this.list.hasOwnProperty(key)){
result.push(key);
}
}
return result;
}
}
手写map
set() get() delete() clear() keys() values()
function defaultToString(key){//对key的类型处理
if(key === null){
return 'NULL'
}else if(key === undefined){
return 'UNDEFINED'
}else if(Object.prototype.toString.call(key) === '[object Object]' || Object.prototype.toString.call(key)==='[object Array]')
{
return JSON.stringify(key);
}
return key.toString()
}
class Map(){
constructor(){
this.list = {};
this.size = 0;
}
has(key){
return this.list[defaultToString(key)] !=== undefined;
}
set(key,value){
if(!this.has(key)){
this.list[defaultToString(key)] = value;
this.size++;
}
return this;
}
get(key){
return this.list[defaultToString(key)]
}
detele(key){
if(this.has(key)){
delete this.list[defaultToString(key)];
}
return this;
}
clear(){
this.list = {};
this.size();
}
keys(){
let result = [];
for(let key in this.list){
if(this.has(key)){
result.push(key)
}
}
return result;
}
values(){
let result = [];
for(let key in this.list){
if(this.has(key)){
result.push(this.list[key])
}
}
return result;
}
}
**Promise
**理解与应用场景
Promise译为"承诺",是异步编程的一种解决方案, 它的提出是为了解决传统请求存在的"回调地狱"问题
, Promise通过链式调用降低了代码的层级编写(难度),代码的可读性更高.
Promise有三种状态:
pending(进行中)
fullfilled(已成功)
rejected(已失败)
特点:
对象状态不受外界的影响
, 只有异步操作的结果,可以决定当前是哪种状态
- 一旦状态改变(
penging
变成fullfilled
和pending
变成rejected
), 就不会发生改变
实例方法
then(resolved=>{},rejected=>{})
- catch发生错误返回 , Promise的错误具有"冒泡"性质,一直向后传递,知道被捕获为止
- finnally 不管Promise对象最后状态如何,都会执行的操作.
构造函数
all()
传入一个由promise请求构成的数组,当所有的promise都resolve才返回最终结果
, 如果其中任何一个promise发生了rejected,那么就返回的结果就直接rejected
race()
传入一个由promise请求构成的数组,将第一个返回值作为返回值
, 其他的promise依旧会进行,没有被打断
allSettled()
传入一个由promise请求构成的数组, 只有等到所有promise参数实例都返回结果, 不管是rejected还是fullfilled, 才会返回对应的结果数组
resolve()
将传入的参数封装成Promise对象返回,状态为 rejected
reject()
将传入的参数封装成Promise对象返回,状态为fullfilled
难
手写一个简易的Promise
const PENDING = "pending";
const REJECTED = "rejected";
const FULLFILLED ="fullfilled";
class MyPromise{//https://zhuanlan.zhihu.com/p/183801144
constructor(executor){
this.status = PENDING;
this.value = null;
this.reason = null;
this.resolvedCallbacks = [];
this.rejectedCallbacks = [];
let resolve = (value)=>{
if(this.status === PENDING){
this.status = FULLFILLED;
this.value = value;
this.resolvedCallbacks.forEach(fn=>fn())
}
}
let reject = (reason)=>{
if(this.status == PENDING){
this.status = REJECTED;
this.reason = reason;
this.rejectedCallbacks.forEach(fn=>fn())
}
}
}
try{
executor(resolve,reject)//封装执行器
}catch(err){
reject(err)
}
then(onFullfilled,onRejected){
onFullfilled = typeof onFullfilled === "function"
?onFullfilled:function(value){
return value
};
onRejected = typeof onRejected === "function"?
onRejected:function(error)[
throw error;
}
if(this.status === PENDING){
this.resolvedCallbacks.push(onFullfilled)
this.rejectedCallbacks.push(onRejected);
}
if(this.status === FULLFILLED ){
onFullfilled(this.value)
}
if(this.status === REJECTED){
onRejected(this.value);
}
}
}
let p = new MyPromise((resolve,reject)=>{//未实现链式调用值穿透
resolve(2)//reject(3)
}).then(res=>{
},err=>{
})
手写Promise.all()
Promise.all = function(promises){
return new Promise((resolve,reject)=>{
let count = 0;
let len = promises.length;
let result = [];
if(len === 0) resolve([])
promises.forEach((p,i)=>{
Promise.resolve(p).then(res=>{
count +=1;
result[i] = res;
if(count === len) resolve(result)
}).catch(err=>{
reject(err)
})
})
})
}
手写Promise.race()
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
promises.forEach(p=>{
Promise.resolve(p).then(resolve).catch(reject)
})
})
}
手写allSettled()
Promise.allSettled = function(promises){
return new Promise((resolve,reject)=>{
let result = [];
let count = 0;
let len = promises.length;
if(len === 0) resolve([])
promises.forEach((p,i)=>{
Promise.resolve(p).then(res=>{
count +=1;
result[i] = {
value:res,
status:"fullfilled"
};
if(count === len ) resolve(result)
}).catch(err){
result[i] = {
value:err,
status:"rejected"
};
if(count === len ) resolve(result)
}
})
})
}
手写Promise.resolve()
Promise.resolve = function(value){
if(value instanceOf Promise){
return value;
}
return new Promise((resolve,_)=>resolve)
}
手写Promise.reject()
Promise.reject = function(value){
return new Promise((_,reject)=>reject)
}
**Proxy
**理解,使用场景有哪些?
Proxy用于创建一个对象的代理
,从而实现基本操作的拦截和定义
(如属性查找\赋值\枚举\函数调用)
Proxy本身为构造函数,new Proxy(target,handler)
target: 所有拦截的目标对象
handler: 通常以函数作为属性的对象, 执行代理行为
handler对象的属性:
- get(target,propKey,receiver)
- set(target,propKey,value,receiver)
- has(target,propKey)拦截
propKey in proxy
- deleteProperty(target,propKey)
- ownKeys(target)
- defineProperty(target,propKey,propDesc)
- getPrototypeOf(target)
- setPrototypeOf(target,proto)
- apply(target,Object,args)
- construct(target,args)
Reflect
需要在内部调用对象的默认行为, 建议使用Reflect
特点:
- Proxy对象具有的代理方法,
Reflect
全部具有,以静态方法的形式存在
- 修改某些
Object
方法的返回结果, 让其变得合理
- 让
Object
操作都变成函数行为
作用
- Object中一些明显的语言内部行为, 比如Object.defineProperty(),放到Reflect对象上,未来一些方法也只会部署到Reflect对象上
- Object上存在的一些命令式操作可以使用Reflect修改成函数操作,比如name in obj 或delete obj[name] 分别替换成Reflect.delete(obj,name) Reflect.has(obj,name)
- 修改Object中一些函数的返回结果,使它更加合理,比如Object.definePropety无法定义对象的属性时,抛出一个错误, 使用Reflect返回false
- Reflect对象上的方法与Proxy上的一一对象,可以为Proxy提供修改对象的默认行为
使用场景
- 拦截和监视外部对对象得访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
使用Proxy保障数据类型的准确性
let target = {count:0,amount:1234}
let proxy = new Proxy(target,{
set(target,key,value,proxy){
if(typeof value !== 'number'){
throw Error('属性只能是number')
}
return Reflect.set(target,key,value)
}
})
target.count ='foo'//Error:'属性只能是number'
target.count =23
使用Proxy实现观察者模式
观察者模式是指函数自动观察数据对象,一旦对象有变化,函数就会自动执行
function set(target,key,value,receiver){
const result = Reflect.set(target,key,value,receiver);
queueObservers.forEach(observer => observer())//set触发执行observe的print
return result;
}
const queueObservers = new Set();
const observe = fn=>queueObservers.add(fn);
const observable = obj => new Proxy(obj,{set})
const person = observable({
name:'远方',
age:18
})
function print(){
console.log(`${person.name},${person.age}`)
}
observe(print)
person.name = '星星'//触发print自动执行
**Generator函数
**和其使用场景
Generator函数为异步编程提供了一种方案, 其特点是
function
关键字与函数名之间有一个星号
- 函数体内部使用
yield
表达式,定义不同的内部状态
Generator函数返回一个遍历器对象, 即具有Symbol.iterator属性, {value:23,done:true/false}
结构 . 通过yield
关键字可以暂停generator函数返回的遍历器状态
使用场景:
async/await实际上是Generator函数实现异步编程的语法糖
function* gen(){
const res1 = yield fn(1);
const res2 = yield fn(res1);
const res3 = yield fn(res1);
return res3
}
function generatorAuto(generatorFn){
return function(){
return new Promise((resolve,reject)=>{
const gen = generatorFn();
const next1 = gen.next();//返回值next1:{Promise:{},done:false}
next1.value.then(res1=>{
const next2 = gen.next(res1);//传入结果res1
next2.value.then(res2=>{
const next3 = gen.next();//传入结果res2
next3.value.then(res3=>{
resolve(g.next(res3).value)
})
})
})
})
}
}
const asyncFn = generatorAuto(gen)
asyncFn().then(res=>console.log(res))//3秒后输出8
module模块化
commonJS与ES6模块化的区别:
- CommonJS模块输出的是一个
值得拷贝
,而ES6模块输出的是一个值的引用
. 一个值的拷贝:也就是说一旦输出一个值,模块内部的变化不会引起整个值的变化. 而ES6模块在js引擎对脚本进行静态分析时,遇到import命令, 会生成一个只读引用,等到脚本真正执行时,才会根据值得引用去加载模块中的取值
- CommJS
是同步加载
,而ES6是编译时加载.
引入CommonJS加载是一个对象(module.exports属性), 该对象只有在脚本运行完才会生成; 而ES6模块不是对象,它的对外接口是export xx的静态定义,在代码静态解析阶段就会生成.
转载自:https://juejin.cn/post/7144690682865647630