ES6(专属学习)
# ES6(专属学习)
# ECMAScript 6
# ECMAScript 6 简介
ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
# ECMAScript与JS的关系
要讲清楚这个问题,需要回顾历史。1996 年 11 月,JavaScript 的创造者 Netscape 公司,决定将 JavaScript 提交给标准化组织 ECMA,希望这种语言能够成为国际标准。次年,ECMA 发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript,这个版本就是 1.0 版。
该标准从一开始就是针对 JavaScript 语言制定的,但是之所以不叫 JavaScript,有两个原因。一是商标,Java 是 Sun 公司的商标,根据授权协议,只有 Netscape 公司可以合法地使用 JavaScript 这个名字,且 JavaScript 本身也已经被 Netscape 公司注册为商标。二是想体现这门语言的制定者是 ECMA,不是 Netscape,这样有利于保证这门语言的开放性和中立性。
因此,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的一种实现(另外的 ECMAScript 方言还有 JScript 和 ActionScript)。日常场合,这两个词是可以互换的。
# Babel 转码器
Babel是一个广泛使用的 ES6 转码器,可以将 ES6 代码转为 ES5 代码,从而在老版本的浏览器执行。这意味着,你可以用 ES6 的方式编写程序,又不用担心现有环境是否支持。下面是一个例子。
上面的原始代码用了箭头函数,Babel 将其转为普通函数,就能在不支持箭头函数的 JavaScript 环境执行了。
# let和const命令
# let的定义
ES6 新增了let
命令,用来声明变量,它的用法类似于var
# let 和var的不同
# 1、不存在变量提升
console.log(a);
var a='a';
console.log(b);
let b='b';
# 2、同一个作用域内不能重复定义同一个名称
var a = 10;
var a = 999;
console.log(a);
let b = 1;
let b = 2;
console.log(b);
# 3、有着严格的作用域
function test() {
var str='123';
if(true){
var str='456';
};
console.log(str);
};
test();
function test2() {
let str='123';
if(true){
let str='456';
};
console.log(str);
};
test2();
# 4、块级作用域的重要性
for(var i=0; i<5; i++){};
console.log(i); //结果 5
for(let i=0; i<5; i++){};
console.log(i); //i is not defined
var arr = [];
for(let i=0; i<5; i++){
arr[i] = function(){
console.log(i)
}
};
arr[4](); //var 结果 5 let 结果 4
# 暂时性死区
function f3(i){
let i;
console.log(i)
};
f3(10)
let x = y,y = 10;
function f2(){
console.log(x,y)
};
# const 的定义
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
保持let的不存在变量提升,同一个作用域内不能重复定义同一个名称,有着严格的作用域;
# 修改常量的值会报错
修改常量的值会报错
const x = 12345;
x = 999;
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const y; //error
只声明不赋值,就会报错
var a = 10;
let b = 100;
const a = 999; //error
const b = 9999; //error
# const的坑
const
实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const
只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
常量obj储存的是一个地址,这个地址指向一个对象。不可变的只是这个地址,即不能把obj指向另一个地址,
但对象本身是可变的,所以依然可以为其添加新属性。
const obj = {};
obj.name = "amy";
obj['age'] = 18;
obj = {id:1};//不能把obj指向另一个地址
常量arr是一个数组,这个数组本身是可写的,但是如果将另一个数组赋值给arr,就会报错。
const arr = [];
arr.push(1);
arr[1] = 2;
arr = ['a'] //但是如果将另一个数组赋值给arr,就会报错
# 解构
# 什么是解构
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)。
解构是ES6的新特性, 比ES5代码简洁,清晰,减少代码量
ES5中的为变量赋值,只能直接指定值
var a = 1;
var b = 2;
var c = 3;
# 数组解构
# es6匹配模式写法
var [a,b,c] = [1,2,3];
这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值
var x = 1;
var y = [1,2];
var z = 3
var [x,y,z] = [1,[1,2],3];//数组
var [x,y,z] = [1,{'name':'amy'},3]; //对象
如果解构不成功,变量的值就等于undefined。
let [a] = [];
let [a,b] = [1];
# 解构赋值允许指定默认值
var [x=1,y=2] = [10,20];// x = 10 ,y = 20;
var [x=1,y=2] = [10];// x = 10 ,y = 2;
var [x= 10,y = 20] = [];//x=10,y=20;
var [x,y='b']= ['a']; //x='a',y='b';
var [x=1,y] = [10,20]; //x = 10,y=20;
var [x = 1] = [null]; //x= null
只有当一个数组成员严格等于undefined,默认值才会生效
var [x = 1]=[undefined]; //x=1;
var [x,y='b'] =['a',undefined];////x='a',y='b';
# 惰性求值
如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值
function f(){
return '12345'
}
var [x = f()] = [1]; //x=1
var [x = f()] = []; //x='12345'
var [x = f()] = [undefined]; //x='12345'
# 对象解构
对象的解构与数组有一个重要的不同。数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
# 对象解构的写法
对象解构的写法:
let {a,b} = {b:1,a:2};
# 实际的运用
var obj = {
id:1,
name:'abc',
sex:'男',
friends:['x','y','z'],
o:{id:2,msg:'hello'}
};
es5从对象中取值的方式
var _id = obj.id;
var f = obj.friends;
es6通过解构更简洁,清晰
var {id,name,sex,o} = obj;
# 别名
变量名与属性名不一致,name是匹配模式 ,n是变量
var {name:n,age:a} = {name:'123',age:18};
# 解构赋值允许指定默认值
默认值生效的条件是,对象的属性值严格等于undefined
let {x,y = 5} = {x:10}; //x=10,y=5
let {x:y = 5} ={x:50}; //y=50
let {x:a = 10,y:b=20} = {x:undefined}; //a=10,b=20
# 注意
如果要将一个已经声明的变量用于解构赋值,必须非常小心。
错误的写法
let x;
{x} = {x: 1};
报错的原因:因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题
正确的写法
let x;
({x} = {x: 1});
将整个解构赋值语句,放在一个圆括号里面,就可以正确执行
# 字符串解构
let [a,b,c,d,e,f] ='nodejs';
# 函数参数解构
解构在函数中具体运用
数组解构
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
对象解构
function move({x, y}) {
return x +y;
}
move({x: 3, y: 8});
参数默认值的定义
function fun([x=0,y=0]) {
return x+y;
};
fun([2,4]);
function move({x = 0, y = 0}) {
return x +y;
}
move({x: 3, y: 5});
当实参为空时,不会报错
function fun([x=0,y=0]) {
return x+y;
};
fun([2]);
function fun([x=0,y=0]) {
return x+y;
};
fun([,4]);
function move({x = 0, y = 0}) {
return x +y;
}
move({x: 3});
function move({x = 0, y = 0}) {
return x +y;
}
move({y: 5});
扩展题
function move({x = 0, y = 0} = {}) {
return [x, y];
}
move({x: 3, y: 8});
move({x: 3});
move({});
move();
function move({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
move({x: 3, y: 8});
move({x: 3});
move({});
move();
# 扩展
# 字符串的扩展
字符串常用方法
charAt(); 返回指定索引位置的字符
indexOf(); 返回字符串中检索指定字符第一次出现的位置
lastIndexOf(); 返回字符串中检索指定字符最后一次出现的位置
slice() 提取字符串的片断,并在新的字符串中返回被提取的部分
split() 把字符串分割为子字符串数组
toLowerCase() 把字符串转换为小写
toUpperCase() 把字符串转换为大写
substr() 从起始索引号提取字符串中指定数目的字符
substring() 提取字符串中两个指定的索引号之间的字符
ES6扩展
includes(): 返回布尔值,表示是否找到了参数字符串。
startsWith(): 返回布尔值,表示参数字符串是否在原字符串的头部。
endsWith(): 返回布尔值,表示参数字符串是否在原字符串的尾部
repeat(): 返回一个新字符串,表示将原字符串重复n次。
padStart(): 用于头部补全
padEnd(): 用于尾部补全。
# 函数的扩展
# 函数默认参数
ES5
function fun(x,y){
y = y || '10';
console.log(x,y);
};
fun(123);
ES6
function fun(x,y ="10"){
console.log(x,y);
};
fun(123);
# rest参数
ES6 引入 rest 参数(形式为...变量名
),用于获取函数的多余参数,这样就不需要使用arguments
对象了。
rest 参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function fun(...values){
console.log(values); //[1, 2, 3, 4, 5]
};
fun(1, 2, 3, 4, 5);
获取具体参数值可以通过values[2]
注意,rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
function fun(a,...b){
console.log(a); //1
console.log(b); // [2, 3, 4]
}
fun(1,2,3,4)
报错
function fun(a,...b,c){}
fun(1,2,3,4)
# 箭头函数
# 基本用法
ES6 允许使用“箭头”(=>
)定义函数。
var f = v => v;
// 等同于
var f = function (v) {
return v;
};
参数为空
let f = () => '123';
多个参数
let f = (n1,n2) => n1+n2;
返回对象
let f = (n1,n2) => ({name:n1,age:n2})
# 定义默认值
var f = (x,y ="10")=>{
console.log(x,y);
};
f(123);
# 解构中的运用
let f = ({x = 0, y = 0} = {}) => {
return [x, y];
}
f({x:3,y:3})
let f = ({x = 0, y = 0} = {}) => {
return [x, y];
}
f()
# rest参数
const fun = (...item) => [item];
fun(1, 2, 3, 4, 5);
# 箭头函数有几个使用注意点
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
1、箭头函数不能当作构造函数, 不可以使用new 命令
//箭头函数中,this指向固定化,本身是没有自己的this,所以不能用作构造函数
var Fun = ()=>{
this.name = '1'
};
var f1 = new Fun() //Fun is not a constructor
2、箭头函数没有原型 对象
function fun(){}
fun.prototype;
var fun2 =()=>{}
fun2.prototype;
3、不可以使用arguments对象 该对象在函数体内不存在 替代 rest
var fun = ()=>{
console.log(arguments[1]) //arguments is not defined
};
fun(1,2,3);
替代rest
var fun = (...item)=>{
console.log(item[1])
};
fun(1,2,3);
4、this指向 由于箭头函数不绑定this,它会捕获其所在上下文的this的值,作为自己的this值
var str = 'global';
var obj = {
str:'private',
getStr:function(){
console.log(this.str)
}
};
obj.getStr(); //运行者(调用者)为obj, this指向obj
var a = obj.getStr;
a();
var str = 'global';
var obj = {
str:'private',
getStr:()=>{
console.log(this.str)
}
};
obj.getStr();
绑定的是定义者,箭头函数的表现为getStr的value,它在obj对象中,obj的执行上下文就是window,this.str 实际就是window.str 输出‘global’
5、call() apply() bind() 对于this毫无影响
var n = 10;
function f(){
return this.n
};
var obj ={
n:8,
f2:f
};
var obj2 = {
n:99
}
console.log(obj.f2.call(obj2)); //99
//箭头函数
var n = 10;
var f=()=> this.n;
var obj ={
n:8,
f2:f
};
var obj2 = {
n:99
}
console.log(obj.f2.call(obj2)); //10
# 扩展运算符
扩展运算符(spread)是三个点(...)。
它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。
该运算符主要用于函数调用。
主要运用
# 数组的合并
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
let arr3 = [...arr1,...arr2]
或者直接arr1.push(...arr2);
# 参数
function add(x, y) {
return x + y;
}
const numbers = [4, 5];
add(...numbers)
# 扩展运算符与正常的函数参数可以结合使用,非常灵活
function f(...items) {
console.log(items);
console.log(...items);
}
const args = [0, 1];
f(-1, ...args, 2, ...[3]);
# 扩展运算符可以与解构赋值结合起来,用于生成数组
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest // [2, 3, 4, 5]
# 如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错
const [...butLast, last] = [1, 2, 3, 4, 5];// 报错
const [first, ...middle, last] = [1, 2, 3, 4, 5];// 报错
# 在字符串中的应用
扩展运算符还可以将字符串转为真正的数组
ES5
var s1 = "hello";
console.log(s1.split(''));
ES6的用法
[...'hello']
# 对象中的运用
let obj1 = {id:1};
let obj2 = {name:2};
let obj3 = {...obj1,...obj2}
对象中合并函数
let fun = () => 123;
let obj4 = {...obj3,fun};
# 数值的扩展
数值
parseInt() 函数可解析一个字符串,并返回一个整数。
parseFloat() 函数可解析一个字符串,并返回一个浮点数
Number.isInteger()用来判断一个数值是否为整数。
Math.ceil() 返回大于或等于一个给定数字的最小整数
Math.floor() 返回小于或等于一个给定数字的最大整数
Math.round() 返回一个数字四舍五入后最接近的整数
Math.trunc() 用于去除一个数的小数部分,返回整数部分。
Math.sign() 方法用来判断一个数到底是正数、负数、还是零。对于非数值,会先将其转换为数值。
# 数组的扩展
# Array.of()
定义:用于将一组值,转换为数组
Array.of(); //[]
Array.of(1); //[1]
Array.of(1,2,3); //[1, 2, 3]
Array.of(4,5).length; //2
# copyWithin()
定义:将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组(会修改当前数组)
接受三个参数copyWithin(target, start , end)
target(必需):从该位置开始替换数据。如果为负值,表示倒数。
start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。
[1, 2, 3, 4, 5].copyWithin(0, 3) ; //[4, 5, 3, 4, 5]
将从3号位直到数组结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2。
[1, 2, 3, 4, 5].copyWithin(0, 3, 4) //[4, 2, 3, 4, 5]
将从3号位开始4号位结束的成员,复制到从0号位开始的位置,结果覆盖了原来的1。
[1, 2, 3, 4, 5, 6, 7].copyWithin(0, 3, 5) //[4, 5, 3, 4, 5, 6, 7]
将从3号位开始5号位结束的成员(4和5),复制到从0号位开始的位置,结果覆盖了原来的1和2。
[1, 2, 3, 4, 5].copyWithin(0, -2, -1) //[4, 2, 3, 4, 5]
-2相当于3号位,-1相当于4号位,代码等同于[1, 2, 3, 4, 5].copyWithin(0, 3, 4)
# find()
find()用于找出第一个符合条件的数组成员。它的参数是一个回调函数,,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
find方法的回调函数可以接受三个参数,依次为当前的值、当前的位置和原数组
var arr = [3,4,7,9];
var a = arr.find(function(item,index,arr){ //用于找出第一个符合条件的数组成员
return item >5
});
console.log(a);
另一种写法
[1, 11, 12, 10].find(n => n > 5) //11
# findIndex()
findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
var arr = [3,4,7,9];
var a = arr.find(function(item,index,arr){ //返回第一个符合条件的数组成员的位置
return item >5
});
console.log(a); //2
# fill()
定义:用于将一个固定值替换数组的元素。
array.fill(value, start, end)
value(必需):填充的值。
start(可选):开始填充位置。
end(可选):停止填充位置 (默认为 array.length)。
var arr = ["blue", "Orange", "red", "green"];
arr.fill("abc", 2, 4); //["blue", "Orange", "abc", "abc"]
# includes()
定义:方法返回一个布尔值,表示某个数组是否包含给定的值,与字符串的includes
方法类似
[1, 2, 3].includes(2) // true
[1, 2, 3].includes(4) // false
var arr = ["blue", "Orange", "red", "green"];
arr.includes('blue'); //true
该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
# flat()
定义:用于将嵌套的数组“拉平”,变成一维的数组。
[1, 2, [3, 4, 6],[7, 8]].flat(); //[1, 2, 3, 4, 6, 7, 8]
该方法返回一个新数组,对原数据没有影响。
var arr = [1, 2, [3, 4, 6],[7, 8]];
var a = arr.flat();
console.log(arr); //[1, 2, [3, 4, 6],[7, 8]];
console.log(a); // [1, 2, 3, 4, 6, 7, 8]
默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1。
flat()的参数为2,表示要“拉平”两层的嵌套数组
[1, 2, [3, [4, 6]],[7, 8]].flat(2);//[1, 2, 3, 4, 6, 7, 8]
[1, 2, [3, [4, [6]]],[7, 8]].flat(2); //[1, 2, 3, 4, [6], 7, 8]
如果不管有多少层嵌套,都要转成一维数组,可以用Infinity关键字作为参数。
[1, 2, [3, [4, [6]]],[7, 8]].flat(Infinity); //[1, 2, 3, 4, 6, 7, 8]
# 对象的扩展
# 属性名表达式
JavaScript 定义对象的属性,有两种方法。
var obj = {};
obj.id = 1;
obj['name'] = 'abc';
obj['get'+'name'] = 'xyz';
另一种写法
var a = 'name';
let obj2 = {
[a]: 'abc',
['get' + a]: 'xyz'
};
# 链判断运算符
编程实务中,如果读取对象内部的某个属性,往往需要判断一下该对象是否存在。比如,要读取message.body.user.firstName,安全的写法是写成下面这样。
// 错误的写法
const firstName = message.body.user.firstName;
// 正确的写法
const firstName = (message
&& message.body
&& message.body.user
&& message.body.user.firstName) || 'default';
这样的层层判断非常麻烦,引入了“链判断运算符” ?.,简化上面的写法。
const firstName = message?.body?.user?.firstName || 'default';
?.运算符常见形式,以及不使用该运算符时的等价形式。
a?.b
// 等同于
a == null ? undefined : a.b
a?.[x]
// 等同于
a == null ? undefined : a[x]
a?.b()
// 等同于
a == null ? undefined : a.b()
a?.()
// 等同于
a == null ? undefined : a()
# set
# 定义
ES6 提供了新的数据结构 Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。
Set
本身是一个构造函数,用来生成 Set 数据结构。
const s = new Set();
const set = new Set();
set.add(1);
set.add(2);
set.add(1).add(2).add(3).add(3);
另一种方式
const s2 = new Set([1,2,4,2,4,23,4,'2',2,3,5])
# Set 实例的属性和方法
1、add(value) 添加某个值,返回 Set 结构本身。
const set = new Set();
set.add(1).add(2).add(3).add(3);
2、delete(value) 删除某个值,返回一个布尔值,表示删除是否成功。
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.delete(2)
3、clear() 清除所有成员,没有返回值。
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.clear()
4、has(value); 返回一个布尔值,表示该值是否为Set
的成员。
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.has(3);
5、size属性:返回 set结构的成员总数
const set = new Set();
set.add(1).add(2).add(3).add(3);
set.size; //4
# 与数组结构类型转换
const set = new Set([1, 2, 3, 4, 5]);
let arr = Array.from(set);
扩展运算符
let arr2 = [...new Set(set)];
# 遍历方法
const set = new Set([2,3,52,3,4,43,3]);
for(let v of set){
console.log(v)
}
set.forEach((v,i)=>{
console.log(v)
});
# 扩展题
1、求出大于10的数据且去重处理
var arr = [10,23,42,23,14,23,42,23,10,55,23,43,42,55,67,42,20,20];
2、取二组数据的并集,交集,差集并去重
var a1 = [2,3,4,2,2,5,3,4];
var a2 = [7,8,3,2,3,5];
# map
# 定义
JavaScript 的对象(Object),本质上是键值对的集合,但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
const m = new Map();
m.set('name','value').set('age',18).set('a','a');
var a = {id:2};
m.set(a,'value');
# Map实例的属性和方法
1、size属性
返回 Map 结构的成员总数
const m = new Map();
m.set('a','a').set('b','b');
m.size //2
2、set(key, value)
const m = new Map();
m.set('qq', '2429462491') // 键是字符串
m.set(100, 'jindu') // 键是数值
m.set(undefined, 'value') // 键是 undefined
3、get(key) 读取key
对应的键值,如果找不到key
,返回undefined
。
const m = new Map();
var a = {id:2};
m.set(a,'value');
m.set('name','jindu');
m.get(a); //'value' 通过变量a来获取
m.get('name'); //jindu 通过'name'属性获取
4、has(key) 返回一个布尔值,表示某个键是否在当前 Map 对象之中。
const m = new Map();
var a = {id:2};
m.set(a,'value');
m.set('name','jindu');
m.has('name') //true
m.has('a') //false
m.has(a) //true
5、delete(key) 删除某个键,返回true
。如果删除失败,返回false
。
const m = new Map();
var a = {id:2};
m.set(a,'value');
m.set('name','jindu');
m.delete('name')
m.has('name') //false
6、clear() 清除所有成员,没有返回值。
const m = new Map();
var a = {id:2};
m.set(a,'value');
m.set('name','jindu');
m.clear();
# Map遍历方法
const m1 = new Map([['a',1],['b',2],['c',3]]);
for(let k of m1.keys()){
console.log(k) // a,b,c
}
for(let v of m1.values()){
console.log(v) // 1,2,3
}
for(let [k,v] of m1){
console.log(k,v)
};
# 与其它结构类型转换
const m1 = new Map([['a',1],['b',2],['c',3]]);
1、map类型转为数组
[...m1.keys()];
[...m1.values()];
[...m1];
2、map类型转为object
var o = {};
for (let [k,v] of m1){
o[k]=v;
};
3、object转为map
let obj = {"name":"jindu", "qq":"2429462491"};
const m1 = new Map(Object.entries(obj));
另一种方式
const m2 = new Map();
for(let k in obj){
m2.set(k,obj[k])
};
# for...of循环
ES6 借鉴 C++、Java、C# 和 Python 语言,引入了for...of
循环,作为遍历所有数据结构的统一的方法。
for...of
循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments
对象、DOM NodeList 对象)、后文的 Generator 对象,以及字符串。
const arr = ['red', 'green', 'blue'];
for(let v of arr) {
console.log(v);
}
for...of循环可以代替数组实例的forEach方法
arr.forEach(function (item,index) {
console.log(item); // red green blue
console.log(index); // 0 1 2
});
# set中的运用
var set = new Set([1,2,3,4,5]);
for(let v of set){
console.log(v) //1,2,3,4,5
};
# map中的运用
var m = new Map();
m.set('name','value').set('age',18).set('a','a');
for(let [k,v] of m){
console.log(k) //name,age,a
console.log(v) //value,18,'a'
};
# 对象中的运用
对于普通的对象,for...of结构不能直接使用,会报错
var obj = {
id:1,
name:'abc'
};
for (let k in obj) {
console.log(k);
console.log(obj[k]);
}
for (let k of obj) {
console.log(k);
}
// TypeError: es6[Symbol.iterator] is not a function
解决的办法
for (let [k,v] of Object.entries(obj)) {
console.log(k); //获取键
console.log(v); //获取值
}
# 与其它遍历的比较
1、最原始的写法就是for循环
var arr = [1,2,3,4,5];
for (var i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
2、forEach方法
这种写法的问题在于,无法中途跳出forEach循环,break命令或return命令都不能奏效。
arr.forEach(function (item,index) {
console.log(item);
});
3、for...in
for (let i in arr) {
console.log(i); // 0 1 2 3 获取索引
console.log(arr[i]) // 'a', 'b', 'c', 'd' 获取键名
}
4、for...of
for (let v of arr) {
if(v == 3){
break; //用于跳出循环,会继续执行该循环之后的代码(如果有的话)
//continue; //如果指定了条件,中断当前的迭代,然后继续循环中的下一个迭代。
}
console.log(v);
}
# symbol
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值
解决问题:如果添加的属性名出现重复,如何保证属性名的唯一性,解决属性名的冲突
凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突
var obj = {id:1,id:3,name:'abc',name:'jindu'};
console.log(obj)
获取结果:{id: 3, name: "jindu"}
# 声明的方式
let s = Symbol();
# 独一无二
let s1 = Symbol();
let s2 = Symbol();
console.log(s1==s2) //false
# 查看类型
let s = Symbol();
typeof s; // "symbol"
Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述
主要是为了在控制台显示,或者转为字符串时,比较容易区分
let s1 = Symbol('abc');
let s2 = Symbol('xyz');
注意,Symbol函数的参数只是表示对当前 Symbol 值的描述,因此相同参数的Symbol函数的返回值是不相等的。
let s3 = Symbol('jindu');
let s4 = Symbol('jindu');
s3 === s4 // false
# 类型转换
1、转成布尔值
let s = Symbol();
Boolean(s) //true
2、不能转成数值
Number(s) //Cannot convert a Symbol value to a number
# 获取描述信息
let s = Symbol('jindu');
s.description //jindu
# 实际的运用
注意 symbol作为对象属性名,不能用点运算符
var id = Symbol();
var o = {
[id] :'hello world',
id:'abc'
}
o.id //'abc'
o['id'] //'abc'
o[id] //'hello world';
注意 如果用symbol作用属性名后,Object.keys()、 for in 不能输出symbol类型的属性名
Object.keys(o) //["id"]
for(let v in o){
console.log(v) //id
}
Object.getOwnPropertyNames(o) //["id"]
# Symbol.for()
由于symbol()每次调用都会返回一个不同的值,希望重新使用同一个 Symbol 值,Symbol.for()
方法可以做到这一点.
let s1 = Symbol.for('jindu');
let s2 = Symbol.for('jindu');
s1 === s2 // true
s1和s2都是 Symbol 值,但是它们都是由同样参数的Symbol.for
方法生成的,所以实际上是同一个值。
# Symbol.for()与Symbol()的区别:
Symbol.for()与Symbol()这两种写法,都会生成新的 Symbol。区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的 Symbol 类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for("cat")30 次,每次都会返回同一个 Symbol 值,但是调用Symbol("cat")`30 次,会返回 30 个不同的 Symbol 值。
var o = {};
var a = Symbol('a');
o[a] = 123;
var a = Symbol('a');
o[a] = 456;
console.log(o);
返回的结果:{Symbol(a): 123, Symbol(a): 456}
var o = {};
var a = Symbol.for('a');
o[a] = 123;
var a = Symbol.for('a');
o[a] = 456;
console.log(o);
返回的结果:{Symbol(a): 456}
# Symbol.keyFor()
定义:返回一个已登记的 Symbol 类型值的key
var s = Symbol.for('abc');
Symbol.keyFor(s); //abc
# Promise
# 定义
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。
所谓Promise
,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。
解决 JS中多个异步回调难以维护和控制的问题
# 回调陷阱
$.ajax({
url:'',
data:{},
success:function(data){
//......
$.ajax({
url:'',
data:{},
success:function(data){
//......
$.ajax({
url:'',
data:{},
success:function(data){
//......
}
});
}
});
}
});
Promise
对象有以下两个特点。
(1)对象的状态不受外界影响。Promise
对象代表一个异步操作,有三种状态:pending
(进行中)、fulfilled
(已成功)和rejected
(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise
这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。
(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise
对象的状态改变,只有两种可能:从pending
变为fulfilled
和从pending
变为rejected
。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。如果改变已经发生了,你再对Promise
对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。
# 基本用法
Promise
对象是一个构造函数,用来生成Promise
实例。
var p = new Promise(function(resolve,reject){
if(true){ //请求成功
resolve('ok')
}else {
reject('error')
}
});
p.then(function(res){
console.log(res)
},function(res){
console.log(res)
})
Promise
构造函数接受一个函数作为参数,该函数的两个参数分别是resolve
和reject
。它们是两个函数,由 JavaScript 引擎提供,不用自己部署。
resolve
函数的作用是,将Promise
对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject
函数的作用是,将Promise
对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数。
then
方法可以接受两个回调函数作为参数。第一个回调函数是Promise
对象的状态变为resolved
时调用,第二个回调函数是Promise
对象的状态变为rejected
时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise
对象传出的值作为参数。
# 解决回调陷阱
let p = new Promise(function(resolve, reject){
if (true){
resolve("成功");
} else {
reject("失败");
}
})
p.then(function(v){
return new Promise(function(resolve, reject){
if (true){
resolve("成功");
} else {
reject("失败");
}
})
}, function(v){
console.log(v)
}).then(function(){
console.log(4)
}, function(){
console.log(5)
})
# 用Promise对象实现的 Ajax
const getJSON = function(url,type, data) {
const promise = new Promise(function(resolve, reject){
const xmlHttp = new XMLHttpRequest();
xmlHttp.open(type, url);
if(type =='get'){
xmlHttp.send();
}else {
xmlHttp.setRequestHeader("Content-Type", "application/json");
xmlHttp.send(JSON.stringify(data));
};
xmlHttp.responseType = "json";
xmlHttp.onreadystatechange =function(){
if (xmlHttp.readyState !== 4) return;
if (xmlHttp.status === 200) {
resolve(xmlHttp.response);
} else {
reject(new Error(xmlHttp.statusText));
}
};
});
return promise;
};
# Promise.all()
Promise.all()
方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。
const p = Promise.all([p1, p2, p3]);
Promise.all()
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise 实例,如果不是,就会先调用下面讲到的Promise.resolve
方法,将参数转为 Promise 实例,再进一步处理。另外,Promise.all()
方法的参数可以不是数组,但必须具有 Iterator 接口,且返回的每个成员都是 Promise 实例。
p
的状态由p1
、p2
、p3
决定,分成两种情况。
(1)只有p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数。
(2)只要p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数。
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('1111')
},1000)
});
var p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('22222')
},4000)
});
Promise.all([p1,p2])
.then(([d1,d2])=>{
console.log(d1,d2);
},err=>{
});
# Promise.race()
const p = Promise.race([p1, p2, p3]);
只要p1
、p2
、p3
之中有一个实例率先改变状态,p
的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p
的回调函数。
Promise.race()
方法的参数与Promise.all()
方法一样,如果不是 Promise 实例,就会先调用下面讲到的Promise.resolve()
方法,将参数转为 Promise 实例,再进一步处理。
var p1 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('1111')
},1000)
});
var p2 = new Promise((resolve,reject)=>{
setTimeout(()=>{
resolve('22222')
},4000)
});
Promise.race([p1,p2])
.then(d =>{
console.log(d);
},err=>{
});
# async-await
经常会看到有了 async-await、promise 还有必要学习吗、async await优于promise的几个特点,接收了这些信息后,就蒙圈了。
async-await是promise和generator的语法糖。只是为了让我们书写代码时更加流畅,当然也增强了代码的可读性。
简单来说:async-await 是建立在 promise机制之上的,并不能取代其地位。
具体理解:
# async
async function timeout() {
return 'hello world'
}
timeout(); //为什么没有执行'hello world'
async function timeout() {
return 'hello world'
}
console.log(timeout()); //Promise {<resolved>: "hello world"}
async 函数返回的是一个promise 对象,如果要获取到promise 返回值,应该用then 方法
async function fn(){
return 100;
}
console.log(fn()); //Promise {<resolved>: 100}
let f = fn();
f.then(function(d){
console.log(d) //100
})
# await
await操作符用于等待一个Promise对象,它只能在异步函数async function内部使用。
返回值: 返回promise对象的处理结果,如果待等的不是promise对象,则返回该值本身
如果一个promise被传递给一个await操作符,await将等待promise正常处理完成并返回其处理结果
function f2(){
console.log(4)
}
async function f(){
await f2();
console.log(2)
}
f();
console.log(3);
正常情况下,await命令后面是一个promise对象,它也可以是其它值,如字符串,布尔值,数值以及普通函数。
console.log(2)
async function fn(){
console.log(3)
await 100;
console.log(1)
}
fn()
console.log(4)
await命令后面是一个promise对象
function p(){
return new Promise(resolve=>{
resolve();
console.log(1)
});
};
async function fn(){
console.log(2)
await p();
console.log(3)
}
fn()
# 微任务和宏任务
# 微任务与宏任务的区别
就像去银行办业务一样,先要取号进行排号。
一般上边都会印着类似:“您的号码为XX,前边还有XX人。”之类的字样。
由于柜员只能处理一个来办理业务的客户,这时每一个来办理业务的人就可以认为是银行柜员的一个宏任务来存在的;
当柜员处理完当前客户的问题以后,选择接待下一位,广播报号,也就是下一个宏任务的开始。
所以多个宏任务合在一起就可以认为有一个任务队列在这,里边是当前银行中所有排号的客户。
如果叫到你的时候你不在,那么你当前的号牌就作废了,柜员会选择直接跳过进行下一个客户的业务处理,等你回来以后还需要重新取号
而且一个宏任务在执行的过程中,是可以添加一些微任务的,就像在柜台办理业务,你前边的一位老大爷可能在存款,结果在存款这个业务办理完以后,柜员会问老大爷还有没有其他需要办理的业务,这时老大爷想了一下:“想选择稳一些的理财”,这时候柜员肯定不能告诉老大爷说:“您再上后边取个号去,重新排队”。
所以本来快轮到你来办理业务,会因为老大爷临时添加的“理财业务”而往后推。
也许老大爷在办完理财以后还想 再办一个信用卡?或者 再买点儿纪念币?
无论是什么需求,只要是柜员能够帮她办理的,都会在处理你的业务之前来做这些事情,这些都可以认为是微任务。
在当前的微任务没有执行完成时,是不会执行下一个宏任务的。
# promise是同步还是异步?
Promise是ES6提出的解决异步编程导致陷入回调地狱问题的,那么Promise是同步的还是异步的?可以确定的是,Promise本身是同步的
console.log(1)
let a = new Promise((res,rej) => {
console.log(2);
});
console.log(3);
let b = new Promise((res,rej) => {
console.log(4);
});
console.log(5);
拿到的结果是 1 2 3 4 5
Promise本身是同步的,他的then方法和catch方法是异步的
let a = new Promise((res, rej) => { //同步
console.log(1);
res(3)
});
a.then((res) => { //微任务
console.log(res);
});
console.log(2); //同步
//拿到的结果是 1 2 3
扩展实例
console.log(1)
let a = new Promise((res,rej) => {
res();
console.log(2);
});
a.then(() => {
console.log(3)
})
console.log(4);
let b = new Promise((res,rej) => {
res();
console.log(5);
});
b.then(() => {
console.log(6)
})
console.log(7);
# proxy
proxy
在目标对象的外层搭建了一层拦截,外界对目标对象的某些操作,必须通过这层拦截
var proxy = new Proxy(target, handler);
# 基本用法
new Proxy()
表示生成一个Proxy
实例,target
参数表示所要拦截的目标对象,handler
参数也是一个对象,用来定制拦截行为
var target = {
name: 'jindu'
};
var handler = {
get: function(target, key) {
console.log(`${key} 被读取`);
return target[key];
},
set: function(target, key, value) {
console.log(`${key} 被设置为 ${value}`);
target[key] = value;
}
}
var a = new Proxy(target, handler);
a.name; // 控制台输出:name 被读取
a.name = 'abc'; // 控制台输出:name 被设置为 abc
console.log(target.name); // 控制台输出: abc
a读取属性的值时,实际上执行的是 handler.get:在控制台输出信息,并且读取被代理对象 target
的属性。
在 a 设置属性值时,实际上执行的是 handler.set:在控制台输出信息,并且设置被代理对象 target
的属性的值
# Proxy的作用
对于代理模式
Proxy
的作用主要体现在三个方面
- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
# Proxy所能代理的范围--handler
实际上 handler
本身就是ES6
所新设计的一个对象.它的作用就是用来 自定义代理对象的各种可代理操作 。它本身一共有13
中方法,每种方法都可以代理一种操作.其13
种方法如下
get(target, propKey, receiver):
拦截对象属性的读取,比如proxy.foo和proxy['foo']。
set(target, propKey, value, receiver):
拦截对象属性的设置,比如proxy.foo = v或proxy['foo'] = v,返回一个布尔值。
has(target, propKey):
拦截propKey in proxy的操作,返回一个布尔值。
deleteProperty(target, propKey):
拦截delete proxy[propKey]的操作,返回一个布尔值。
ownKeys(target):
拦截Object.getOwnPropertyNames(proxy)、Object.getOwnPropertySymbols(proxy)、Object.keys(proxy)、for...in循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()的返回结果仅包括目标对象自身的可遍历属性。
getOwnPropertyDescriptor(target, propKey):
拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象。
defineProperty(target, propKey, propDesc):
拦截Object.defineProperty(proxy, propKey, propDesc)、Object.defineProperties(proxy, propDescs),返回一个布尔值。
preventExtensions(target):
拦截Object.preventExtensions(proxy),返回一个布尔值。
getPrototypeOf(target):
拦截Object.getPrototypeOf(proxy),返回一个对象。
isExtensible(target):
拦截Object.isExtensible(proxy),返回一个布尔值。
setPrototypeOf(target, proto):
拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
apply(target, object, args):
拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)、proxy.call(object, ...args)、proxy.apply(...)。
construct(target, args):
拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)。
# Proxy场景
1)实现私有变量,想获取angelababy,由于添加了拦截获取的只能是18
var target = {
name: 'angelababy',
_age: 30
}
var handler = {
get: function(target,key){
if(key.startsWith('_')){
console.log('私有变量age不能被访问')
return 18
}
return target[key];
},
set: function(target, key, value) {
if(key.startsWith('_')){
console.log('私有变量age不能被修改')
return 18
}
target[key] = value;
}
}
var a = new Proxy(target, handler);
a._age //18 私有变量age不能被访问
a._age = 20 //私有变量age不能被修改
2)抽离校验模块
var num = {
count: 0,
id: 1234,
total: 14
};
var handler = {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("只能输入数字");
}
return Reflect.set(target, key, value, proxy);
}
};
var p = new Proxy(num, handler);
// 抛出错误,因为 "123" 不是数值型
p.count = "123";
// 赋值成功
p.count = 333;
# Object.defineProperty()与Proxy的异同
Object.defineProperty()
优点:可以更好的拦截
缺点:无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应(虽说对常用的方法进行了处理,但然存在局限性);只能劫持对象的属性,因此我们需要对每个对象的每个属性进行遍历。
proxy
优点:可以劫持整个对象,并返回一个新对象;有13种劫持操作
缺点:兼容性不好
var Obj = {}
Object.defineProperty(Obj, 'a', {
get: function () {
console.log('get');
return v
},
set: function (val) {
console.log('set');
v = val
}
});
Obj.a = [] // set
Obj.a.push('1') // get
Obj.a[0] = 1 // get
Obj.a.pop(1) // get
Obj.a = [1, 2, 3] //
var arr = [];
var p = new Proxy(arr, {
get: (target, key) => {
console.log('get')
return key in target ? target[key] : undefined
},
set: (target, key, value) => {
console.log('set')
target[key] = value
return true
}
})
p.push(1);
get // 获取数组arr的push方法
get // 获取数组arr的length属性
set // 设置arr[0] = 1
set // 设置数组arr长度为1
# class
# class的基本用法
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类
ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到
ES5中定义类
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return this.x
};
var p = new Point(2,4);
ES6
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return this.x;
}
}
var p = new Point(3,5);
上面代码定义了一个“类”,可以看到里面有一个constructor
方法,这就是构造方法,而this
关键字则代表实例对象。也就是说,ES5 的构造函数Point
,对应 ES6 的Point
类的构造方法。
Point
类除了构造方法,还定义了一个toString
方法。注意,定义“类”的方法的时候,前面不需要加上function
这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
ES6 的类,完全可以看作构造函数的另一种写法
class Point {
// ...
}
typeof Point // "function"
Point === Point.prototype.constructor // true
# constructor方法
constructor
方法是类的默认方法,通过new
命令生成对象实例时,自动调用该方法。一个类必须有constructor
方法,如果没有显式定义,一个空的constructor
方法会被默认添加。
class Point {
}
// 等同于
class Point {
constructor() {}
}
# 类的实例
生成类的实例的写法,与 ES5 完全一样,也是使用new
命令。
class Point {
// ...
}
// 报错
var point = Point(2, 3);
// 正确
var point = new Point(2, 3);
# class的继承
Class 可以通过extends
关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
class Point {
constructor() {
this.x = 'x';
this.y = 'y';
}
toString() {
return this.x;
}
}
class ColorPoint extends Point {
constructor(){
super();
}
}
var c = new ColorPoint();
c.x //'x'
c.toString() //'x'
上面代码定义了一个ColorPoint
类,该类通过extends
关键字,继承了Point
类的所有属性和方法
constructor
方法和toString
方法之中,都出现了super
关键字,它在这里表示父类的构造函数,用来新建父类的this
对象
子类必须在constructor
方法中调用super
方法,否则新建实例时会报错。这是因为子类自己的this
对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super
方法,子类就得不到this
对象。
# super关键字
super
这个关键字,既可以当作函数使用,也可以当作对象使用
super
作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super
函数。
class A {}
class B extends A {
constructor() {
super();
}
}
子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错
注意,super
虽然代表了父类A
的构造函数,但是返回的是子类B
的实例,即super
内部的this
指的是B
的实例,因此super()
在这里相当于A.prototype.constructor.call(this)
。
super
作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。
class A {
p() {
return 2;
}
}
class B extends A {
constructor() {
super();
console.log(super.p()); // 2
}
}
let b = new B();