E6入门教程
let和const命令
let命令
- let声明的变量,只在其所在的代码块内有效。
- 不存在变量提升
- 暂时性死区:在代码块内,使用let命令声明变量之前,该变量都是不可用的。
- 不允许重复声明
块级作用域
- let为js新增了块级作用域
- 内存作用域可以定义外层作用域的同名变量
- es6块级作用域必须有大括号,如果没有大括号,js就认为不存在块级作用域。
const命令
const声明的是一个只读的常量,一旦声明,值就不能改变。
一旦声明常量,必须初始化,不能留到以后赋值。
- 作用域与let命令相同
- const实际上变量指向的那个内存地址所保存的数据不得改动。如果是复合类型数据,只能保证指针是固定的,数据结构无法控制。
- ES6声明变量的6种方法
- var
- function
- let
- const
- import
- class
顶层对象的属性
顶层对象,在浏览器环境指的是window对象,在Node指的是global对象。
- ES6开始,全局变量将逐步与顶层对象的属性脱钩。
globalThis对象
变量的解构赋值
数组的解构赋值
- ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,被称为解构
- 本质上属于“模式匹配”
- 如果等号右边不是数组(不可遍历的解构),将会报错。
- 对于Set结构,也可以使用数组的解构赋值。
- 事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。
默认值
- 解构赋值允许指定默认值。
let [foo = true] = [];
foo // true
- ES6内部使用全等,判断一个位置是否有值,所以只有成员严格等于undefined,默认值才会生效。
- 默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
对象的解构赋值
对象与数组解构的不同:数组的元素必须按次序排列,变量值由位置决定;对象的属性没有次序,变量必须与属性同名,才能取到值。
- 如果解构失败,变量的值等于undefined
- 可以方便地将现有对象的方法,赋值给某个变量
对象解构的内部机制:先找到同名属性,然后赋给对应的变量,被赋值的事后者,而不是前者。
解构可以用于嵌套解构的对象。
默认值
- 对象的解构也可以指定默认值
var {x = 3} = {}
x // 3
- 默认值生效的条件是:对象的属性值严格等于undefined。null和undefined不严格相等。
字符串的解构赋值
字符串也可以解构赋值,因为此时,字符串被转换成了一个类数组的对象。
数值和布尔值的解构赋值
数值和布尔值的包装对象都有toString属性
- 解构赋值的规则是:只要等号右边的值不是对象或数组,就先将其转换为对象。
- undefined和null无法转换为对象,对其解构赋值都会报错。
函数参数的解构赋值
function add([x, y]){
return x + y;
}
add([1, 2]); // 3
函数add的参数表面上是一个数组,但是传入参数的那一刻,数组参数就被解构为变量x、y,对于函数内部的代码来说,能感受到的参数就是x、y。
- undefined会触发函数参数的默认值。
圆括号问题
只要有可能导致解构的歧义,就不得使用圆括号
用途
变量的解构赋值用途很多
交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
从函数返回多个值
//返回一个数组
function example(){
return [1, 2, 3];
}
let [a, b, c] = example();
function example(){
return {
foo: 1,
bar: 2,
}
}
let {foo, bar} = example();
函数参数的定义
解构赋值可以方便地将一组参数与变量名对应起来。
提取JSON数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
}
let { id, status, data: number } = jsonData;
console.log(id, status, number);
// 42, "OK", [867, 5309]
函数参数的默认值
遍历Map结构
任何部署了Iterator接口的对象,都可以用for…of遍历循环。
Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值非常方便
输入模块的指定方法
字符串的扩展
字符的Unicode表示法
字符串的遍历器接口
ES6为字符串添加了遍历器接口,使得字符串可以被for…of循环遍历
直接输入U+2028和U+2029
JSON.stringify()的改造
模板字符串
模板字符串时增强版的字符串,用反引号(`)标识。
- 如果在模板字符串中需要使用反引号,则前面要用反斜杠转义。\`
- 如果使用模板字符串表示多行字符串,所有的空格和缩进都会被保留在输出中。
- 模板字符串嵌入变量,需要将变量名写在${}中。大括号可以放入任意js表达式,可以进行运算,以及引用对象属性。
- 模板字符串还能调用函数。
- 如果变量没有声明,则报错。
实例:模板编译
标签模板
可以紧跟在一个函数名后面,该函数将被调用来处理这个模板字符串。被称为“标签模板”
标签模板其实不是模板,而是函数调用的一种特殊形式。
标签指的就是函数,紧跟在后面的模板字符串就是它的参数。
模板字符串的限制
字符串的新增方法
String.fromCodePoint()
- ES5提供了String.fromCharCode(),但这个方法不能识别码点大于0xFFFF的字符。
String.raw()
- 该方法返回一个斜杠都被转义的字符串,用于模板字符串的处理方法。
codePointAt()
normalize()
实例方法:includes()、startsWith()、endsWith()
传统上,js只有indexOf方法,用来确定一个字符串是否包含另一个字符串中。
- ES6提供了3中新方法
- includes(),返回布尔值,表示是否找到了参数字符串。
- startsWith(),返回布尔值,表示参数字符串是否在原字符串首部。
- endsWith(),返回布尔值,表示参数字符串是否在原字符串尾部。
实例方法:repeat()
- 返回一个新字符串,将原字符串重复n次
实例方法:padStart(),padEnd()
如果某个字符串不够指定长度,会在头部或尾部补全。
- padStart()用于头部补全
- padEnd()用于尾部补全
- 接受两个参数
- 补全最大长度
- 用来补全的字符串
- 用途
// 补全指定位数
'1'.padStart(10, '0'); // "0000000001"
// 提示字符串格式
'12'.padStart(10, 'YYYY-MM-DD'); // "YYYY-MM-12"
"09-12".padStart(10, "YYYY-MM-DD"); // "YYYY-09-12"
实例方法:trimStart()、trimEnd()
返回新字符串,不会修改原字符串
实例方法:matchAll()
实例方法:replaceAll()
返回新字符串,不会修改原字符串
正则的扩展
数值的扩展
二进制和八进制表达式
- 如果要将0b和0o的字符串转为十进制,要使用Number方法
Number.isFinite()、Number.isNaN()
- Number.isFinite()用来检查一个数值是否为有限的(finite),即不是Infinity。
- 如果参数不是数值,Number.isFinite()一律返回false。
- Number.isNaN用来检查一个值是否为NaN。
- 如果参数类型不是NaN,Number.isNaN()一律返回false
Number.parseInt()、Number.parseFloat()
ES6将全局方法 parseInt()和parseFloat(),移植到Number对象上,行为完全保持不变。
这样做的目的,逐步减少全局性方法,使得语言逐步模块化。
Number.isInteger()
如果对于数据精度的要求较高,不建议使用该方法判断一个数值是否为整数
- Number.isInteger()用来判断一个数值是否为整数。
- 整数和浮点数采用的是同样的存储方法,所以25和25.0被视为同一个值。
Number.EPSILON
Number.isSafeInteger()
Math对象的扩展
ES6在Math对象上新增了17个与数学相关的方法。
所有这些方法都是静态方法,只能在Math对象上调用。
- Math.trunc():用于去除一个数的小数部分,返回整数部分。
- Math.sign():用来判断一个数是正数、负数、还是零。无法转换为数值的值,返回NaN。
- Math.cbrt():计算一个数的立方根。
- Math.clz32()
- Math.imul()
- Math.fround()
- Math.hypot()
指数运算符
新增了一个指数运算符(**),特点是右结合。
BigInt数据类型
第八种数据类型,bigint只用来表示整数,没有位数的限制。
函数的扩展
函数参数的默认值
- 参数是默认声明的,所以不能用let或const再次声明。
- 使用默认参数,不能有同名参数。
参数默认值可以与解构赋值的默认值,结合起来使用。
//写法一
function m1({x=0,y=0} = {}){
return [x, y];
}
//写法二
function m2({x, y} = {}){
return [x, y]
}
/
*
*上面两种写法都对函数的参数设定了默认值
*写法一的函数参数的默认值是一个空对象,并设置了对象解构赋值的默认值
*写法二的函数参数的默认值是一个具有属性的对象,没有设置对象解构赋值的默认值。
/
//没有参数
m1() //[0, 0]
m2() //[0, 0]
//有参数
m1({x:3, y: 8}) //[3, 8]
m2({x:3, y: 8}) //[3, 8]
//x有值,y无值
m1({x: 3}) //[3, 0]
m2({x: 3}) //[3, undefined]
//x和y都无值
m1({}) //[0, 0]
m2({}) //[undefined, undefined]
m1({z: 3}) //[0, 0]
m2({z: 3}) //[undefined, undefined]
参数默认值的位置
定义了默认值的参数,应该是函数的尾参数。
如果值为undefined,触发默认值,undefined不全等null,所以null参数不会触发默认值。
函数的length属性
- 指定了默认值之后,函数的length属性,将返回没有指定默认值的参数个数。
- 指定了默认值,函数的length属性将失真。
利用参数默认值,可以指定某一个参数不得省略,如果省略就抛出一个错误。
rest参数
ES6引入rest参数(形式为…变量名),用于获取函数的多余函数,这样就不用arguments对象了
rest参数搭配的变量是一个数组,将多余的参数放入数组中。
- arguments对象不是数组,而是一个类似数组的对象。
- rest参数是一个真正的数组。
- rest参数之后不能再有其他参数,否则会报错
- 函数的length属性,不包括rest参数
严格模式
ES6规定只要函数参数使用了默认值、解构赋值、扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。
- 解决方法
- 设置全局严格模式
- 把函数包在一个无参数的立即执行函数里面。
name属性
箭头函数
var f = v => v;
//等于
var f = function(v){
return v;
}
箭头函数使得表达式更加简洁
- 简化回调函数
//正常函数写法
[1,2,3].map(function(x){
return x*x;
});
//箭头函数写法
[1,2,3].map((x)=>x*x);
- 使用注意点
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
- 不可以当做构造函数,不可以使用new命令
- 不可以使用arguments对象,该对象在函数体内不存在。可以用rest参数代替。
- 不可以使用yield命令,箭头函数不能用作Generator函数。
在箭头函数中,this 是固定的。(外层代码块的this)
this指向的固定化,并不是箭头函数内部有绑定this的机制,实际是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。
正是因为它没有this,所以不能用作构造函数。
- 不适合场合
- 定义对象的方法,且该方法内部包括this。
- 需要动态的this的时候,不应该使用箭头函数。
对象的属性 建议使用传统的写法定义,不要使用箭头函数定义。
尾调用优化
原理:指某个函数的最后一步是调用另一个函数
function f(x){
return g(x);
}
- 尾调用不一定出现在函数尾部,只要是最后一步操作即可。
- 尾调用优化:只保留内层函数的调用帧。
尾递归:尾调用自身。
- 递归非常耗费内层,需要同时保存成千上百个调用帧,容易发生“栈溢出”错误。
- 对于尾递归,只存在一个调用帧,不会发生“栈溢出”错误。
函数式编程有一个概念,柯里化:将多参数的函数转换成单参数的函数。
- 一旦使用递归,就最好使用尾递归。
- 尾调用仅在严格模式下生效。
函数参数的尾逗号
函数参数,最后一个参数后面出现逗号。
toString()
返回一模一样的原始代码。
catch命令的参数省略
允许catch语句省略参数
try{
}catch{
}
数组的扩展
扩展运算符
扩展运算符是三个点(…),好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。
- 该运算符主要用于函数调用
- 扩展运算符将一个数组,变为参数序列。
- 只有在函数调用时,扩展运算符才可以放在圆括号中,否则会报错。
- 扩展运算符的应用
- 复制数组
- 合并数组
- 与解构赋值结合(如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错)
- 字符串 转成真正的数组,Array.from 将类数组转为真正的数组。
- Map和Set结构,Generator函数
- 扩展运算符内部调用的事数据结构的Iterator(迭代器)接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。
- 如果没有Iterator接口的对象,使用扩展运算符,会报错。
Array.from()
- Array.from方法用于将两类对象转为真正的数组:
- 类数组对象(array-like-object)
- 可遍历对象(iterable)对象(包括Set和Map)
- 只要是部署了Iterator接口的数据结构,Array.from都能将其转为数组
- 如果参数是一个真正的数组,Array.from会返回一个一模一样的新数组。
类数组对象,本质特征有一点,必须有length属性。
任何有length属性的对象,都可以通过Array.from方法转为数组,而扩展运算符就无法转换。
- Array.from 接受第二个参数,作用类似于数组的map方法,用来对每个元素进行处理,将处理后的值放入返回的数组。
- Array.from另一个应用是,将字符串转为数组,然后返回字符串的长度。可以正确处理各种Unicode字符,避免bug。
Array.of()
Array.of()方法用于将一组值,转换为数组。
目的:弥补数组构造函数Array()的不足。
- Array.of()可以用来替代Array()或new Array(),行为非常统一。
- 如果没有参数,就返回一个空数组。
数组实例的copyWithin()
会修改当前数组。
copyWithin(target, start, end)
- 接受三个参数
- target(必需):从该位置开始替换数据
- start(可选):从该位置开始读取数据,默认为0;
- end(可选):到该位置停止读取数据,默认等于数组长度。
数组实例的find()和findIndex()
- find 找出第一个符合条件的数组成员,并返回该成员。参数是一个回调函数。
- findIndex 找出第一个符合条件的数组成员,并返回该成员的位置。参数是一个回调函数。
数组实例的fill()
fill方法使用给定值,填充一个数组。
数组实例的entries()、keys()和values()
三个方法用于遍历数组,都返回一个遍历器对象,可以用for…of循环进行遍历
唯一区别:keys()是对键名遍历,values()是对键值遍历,entries()是对键值对的遍历。
数组实例的includes()
返回一个布尔值,表示某个数组是否包含给定的值。
- indexOf有两个缺点
- 不够语义化,要比较是否不等于-1,表达不够直观
- 内部使用严格相等,导致对NaN的误判。
数组实例的flat(),flatMap()
flat()用于将嵌套的数组拉平,变成一维数组,返回新数组,对原数组无影响。
flatMap() 对原数组每个成员执行一个函数,相当于执行map(),返回一个新数组。
数组的空位
Array.prototype.sort()的排序稳定性
对象的扩展
属性的简洁表示法
ES6允许大括号里面直接写入变量和函数,作为对象的属性和方法。
简写的对象方法不能用作构造函数,会报错。
属性名表达式
- js定义对象的属性,有两种方法
//方法一
obj.foo = true;
//方法二
obj['a'] = 123;
- ES6允许字面量定义对象时,用方法二(表达式)作为对象的属性名,即把表达式放在括号内。
let str = 'foo';
let obj = {
[str]: true,
[str + 'hello'](){
return 'hi';
}
}
方法的name属性
属性的可枚举性和遍历
可枚举性
- 对象的每个属性都有一个描述对象,用来控制该属性的行为。
- Object.getOwnPropertyDescriptor可以获取该属性的描述对象。
- enumerable:可枚举性,如果该属性为false,某些操作会忽略当前属性。
- for…in循环:只遍历对象自身的和继承的可枚举的属性。
- Object.keys():返回自身可枚举的属性键名。
- JSON.stringify():自身可枚举属性
- Object.assign():只拷贝自身可枚举属性。
引入可枚举目的:让某些属性可以规避掉for…in操作,不然所有内部属性和方法都会被遍历到。
所有Class的原型方法都是不可枚举的。
大多数我们只关心对象自身的属性,所以尽量不要用for…in循环,用Object.keys()代替。
属性的遍历
- 5种方法可以遍历对象的属性
序号 | 方法名 | 作用 | 返回 |
---|---|---|---|
1 | for…in | 循环遍历对象自身的和继承的可枚举属性(不含Symbol属性) | 无 |
2 | Object.keys() | 自身的所有可枚举属性的键名(不含Symbol属性) | 数组 |
3 | Object.getOwnPropertyNames(obj) | 自身所有属性的键名(不含Symbol属性) | 数组 |
4 | Object.getOwnPropertySymbols(obj) | 自身所有Symbol属性的键名 | 数组 |
5 | Reflect.ownKeys(obj) | 自身的所有属性的键名,包括Symbol和不可枚举 | 数组 |
super关键字
this关键字指向函数所在的当前对象
ES6又新增了super关键字,指向当前对象的原型对象。
- super关键字只能用在对象的方法中,用在其他地方会报错
- 只有对象简写方法可以让js引擎确认,是对象的方法。
const obj = {
find(){
return super.foo;
}
}
对象的扩展运算符
解构赋值
解构赋值必须是最后一个参数,否则会报错
let {x, y, ...z} = {x:1, y:2, a:3, b: 4};
z // {a: 3, b: 4}
扩展运算符
用于取出参数对象的所有可遍历属性,拷贝到当前对象中。
如果扩展运算符后面是个空对象,没有任何效果。
如果后面不是对象,自动将其转为对象。
扩展运算符等同于使用Object.assign();
可以用于合并两个对象
let ab = {...a, ...b};
//等同于
let ab = Object.assign({}, a, b);
- 扩展运算符的参数对象中,如果有取值函数get,这个函数是会执行的。
链判断运算符
如果读取对象某个属性,往往需要判断一下该对象是否存在。
?. 运算符,直接在链式调用的时候判断,左侧的对象是否为null或undefined。如果是的,返回undefined。
使用?. 运算符的场合,不应该使用圆括号。
Null判断运算符
读取对象属性,如果某个属性的值是null或undefined,需要为他们指定默认值。常见做法通过||指定默认值。
但是||运算符,左侧如果为空字符串或false或0,默认值也会生效。
- Null判断运算符??,行为类似||,但是只有左侧值为null或undefined时,才会返回右侧的值。
该运算符目的:跟链判断运算符?. 配合使用,为null或undefined的值设置默认值。
- 如果多个逻辑运算符一起使用,必须使用括号表明优先级,否则报错。
对象的新增方法
Object.is()
ES5比较两个值相等,只有两个运算符:相等和全等
Object.is比较两个值是否严格相等,与全等行为基本一致
Object.is('foo', 'foo'); //true
Object.is({}, {}); //false
- 不同之处:+0 不等于-0,NaN等于自身
Object.assign()
Object.assign()用于对象的合并,将源对象的所有可枚举属性,复制到目标对象。
const target = { a: 1 }
const source1 = { b: 2 }
const source2 = { c: 3 }
Object.assign(target, source1, source2);
target // {a: 1, b: 2, c: 3}
如果源对象有同名属性,则后面的属性会覆盖前面的属性。
如果undefined和null不在首参数,不会报错
Object.assign()拷贝的属性:只拷贝源对象的自身属性,包括Symbol属性,不拷贝继承属性,不拷贝不可枚举属性(enumerable: false)
注意点
浅拷贝
- Object.assign()实行的事浅拷贝
同名属性的替换
- 一旦遇到同名属性,是替换而不是添加。
数组的处理
- 会把数组视为对象。
Object.assign([1, 2, 3], [4, 5]);
// [4, 5, 3]
取值函数的处理
- 如果要复制的是一个取值函数,那么将求值后再复制。
常见用途
为对象添加属性
为对象添加方法
克隆对象
合并多个对象
- 同值属性,后面的会覆盖前面的
Object.getOwnPropertyDescriptors()
- ES5的 Object.getOwnPropertyDescriptor() 返回某个对象属性的描述对象
- ES2017 Object.getOwnPropertyDescriptor(),返回指定对象的所有自身属性(非继承)的描述对象。
proto,Object.setPrototypeOf()、Object.getPrototypeof()
proto
用来读取或设置当前对象的原型对象(prototype)
//ES5写法
const obj = {
method:function(){}
}
obj.__proto__ = OtherObj;
//ES6
var obj = Object.create(OtherObj);
obj.method = function(){}
- 该属性没有写入ES6正文,不是一个正式对外的API,不建议使用该属性。
- Object.setPrototypeOf()
- Object.getPrototypeOf()
- Object.create()
Object.setPrototypeOf()
用来设置一个对象的原型对象(prototype),返回参数对象本身。
第一个参数为undefined或null,无法转为对象,会报错。
Object.getPrototypeOf()
用于读取一个对象的原型对象。
Object.keys(),Object.values(),Object.entries()
Object.keys() 返回一个数组,成员是参数自身的所有可遍历属性的键名。
Object.values() 返回一个数组,成员是参数自身的所有可遍历属性的键值。
Object.entries() 返回一个数组,成员是参数自身的所有可遍历属性的键值对数组。
- 供for…of使用 遍历对象的属性
Object.fromEntries()
是Object.entries()的逆操作,用于将一个键值对数组转为对象。
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// {foo: 'bar', baz: 42}
该方法的主要目的,是将键值对的数据结构还原为对象,因此特别适合将Map结构转为对象。
const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
Object.fromEntries(entries);
// { foo: 'bar', baz: 42 }
Symbol
对象属性名,容易造成重名冲突,引入Symbol,保证每个属性名对一无二,从根本上防止属性名的冲突。
原始数据类型Symbol
undefined、null、Boolean、String、Number、Object
- Symbol值通过Symbol函数生成。
- 对象属性名现在可以有两种类型
- 原来的字符串
- 新增的Symbol类型
- 凡是属性名属于Symbol类型,都是独一无二的。
- 生成的Symbol是一个原始类型的值,不是对象,所以不能添加属性。
- Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述。为了容易区分。
- 即使参数相同,但是值也是不相等的。
- Symbol值可以转为字符串、布尔值,不能转为数值,不能运算。
- Symbol.description返回Symbol的描述。
作为属性名的Symbol
Symbol值作为对象属性名时,不能用点运算符。因为点运算符后面总是字符串,所以导致属性名实际是一个字符串,而不是一个Symbol值。
Symbol值作为属性名时,该属性还是公开属性,不是私有属性。
- 使用Symbol值定义属性时,Symbol值必须放在方括号之中。
let s = Symbol();
let obj = {
[s]: function(arg){}
}
obj[s](123)
实例:消除魔术字符串
魔术字符串:在代码中多次出现,与代码形成强耦合的某一个具体的字符串或数值。
尽量消除魔术字符串,改由含义清晰的变量代替。
属性名的遍历
- Object.getOwnPropertySymbols(),可以获取对象的所有Symbol属性名,返回一个数组。
- Reflect.ownKeys(),可以返回所有类型的键名
Symbol.for()、Symbol.keyFor()
重新使用同一个Symbol值,Symbol.for()方法,接受一个字符串作为参数,搜索以该参数作为名称的Symbol值,如果有,就返回这个值,否则就新建一个值,并注册到全局。
Symbol.keyFor()方法返回一个已登记的Symbol类型值得key。
let s1 = Symbol.for('foo');
Symbol.keyFor(s1); // 'foo'
let s2 = Symbol('foo');
Symbol.keyFor(s2); // undefined
- Symbol.for()为Symbol值登记的名字,是全局环境的,不管有没有在全局环境运行。
实例:模块的Singleton模式
内置的Symbol值
Set和Map数据结构
Set
新的数据结构 Set,类似于数组,但是成员的值都是唯一的,没有重复的值。
Set 本身是一个构造函数,用来生成Set数据结构
const s = new Set();
[2,3,4,4,5,6,6,1,2].forEach((x)=> s.add(x));
for(let i of s){
console.log(i);
}
//1 2 3 4 5 6
- 通过add() 方法向Set结构加入成员,不会添加重复值
- Set函数可以接受一个数组,或者具有iterable接口的其他数据结构,作为参数,用来初始化。
- 去除数组重复成员的方法
[...new Set(array)]
//去除字符串重复字符
[...new Set('abbccc')].join('');
- Set 认为NaN等于自身,两个对象总是不相等。
Set实例的属性和方法
属性
- constructor 构造函数,默认就是Set函数
- size 返回Set实例的成员总数
方法
-
操作方法
- add(),添加某个值,返回Set结构本身。
- delete(), 删除某个值,返回一个布尔值,表示删除是否成功。
- has(),返回一个布尔值,表示是否为Set的成员。
- clear(),清除所有成员,没有返回值。
-
遍历操作
- keys(),返回键名的遍历器
- values(),返回键值的遍历器
- entries(),返回键值对的遍历器
- forEach(),使用回调函数遍历每个成员
Set遍历顺序就是插入顺序。
可以使用for…of遍历Set
WeakSet
WeakSet结构与Set类似,也是不能重复值的集合。
成员只能是对象,不能是其他类型的值
WeakSet中的对象都是弱引用。
是一个构造函数,可以使用new命令
- 方法
- add(),添加一个新成员
- delete(),清除指定成员
- has(),表示某个值是否存在。
Map
js对象,本质上是键值对的集合,但是只能用字符串当做键,给它的使用带来了很大的限制。
Map结构提供了‘值-值’的对应,是一种更完善的Hash结构实现。如果你需要‘键值对’的数据结构,Map比Object更合适。
-
属性
- size属性
- set(),返回的是当前Map对象,可以采用链式写法
- get(),读取对应的key,如果找不到key,返回undefined
- has(),返回一个布尔值,表示某个键是否在当前的Map对象中
- delete(key),删除某个键,返回true。
- clear(),清除所有成员,没有返回值。
-
遍历方法
- keys()
- values()
- entries()
- forEach()
WeakMap
结构与Map结构类似,也是用于生成键值对的集合。
WeakMap只接受对象作为键名,不接受其他类型
WeakMap键名所指向的对象,不计入垃圾回收机制。键名所引用的对象,都是弱引用。
Proxy
用于修改某些操作的默认行为,等同于在语言层面做出修改,属于一种‘元编程’,既对编程语言进行编程。
在目标对象之前架设一层拦截,外界对该对象的访问,都必须通过这层拦截。
对外界的访问进行过滤和改写,代理某些操作,译为代理器。
- ES6原生提供了Proxy构造函数,用来生成Proxy实例。
var proxy = new Proxy(target, handler);
- Proxy对象所有用法,都是上面这种形式。不同的只是handler参数的写法。
- 要是的Proxy起作用,必须针对Proxy实例进行操作。
- 如果handler没有设置任何拦截,就等同于直接通向原对象。
Proxy支持的拦截操作 一共13种
- get
- set
- has
- deleteProperty
- ownKeys
- getOwnPropertyDescriptor
- defineProperty
- preventExtensions
- getPrototypeOf
- isExtensible
- setPrototypeOf
- apply //拦截Proxy实例作为函数调用的操作
- construct //拦截Proxy实例作为构造函数调用的操作。
Proxy.revocable(),返回一个可取消的Proxy实例。
- Web服务的客户端
可以拦截目标对象的任意属性,很适合用来写Web服务的客户端。
Reflect
Reflect对象与Proxy对象一样,也是为了操作对象而提供的新API。
- 将Object对象的一些属于语言内部的方法,放到Reflect对象上。
- 修改Object方法的返回结果
- 让Object操作都编程函数行为。
- Reflect对象的方法与Proxy对象的方法一一对应。
观察者模式
指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
Promise对象
Promise是异步编程的一种解决方案
- 特点
- 对象的状态不受外界影响,有三种状态 pending fulfilled rejected,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
- 状态一旦改变,就不会再变。
基本用法
Promise对象是一个构造函数,用来生成Promise实例
const promise = new Promise(function(resolve, reject){
});
Promise新建后就会立即执行。
resolved的Promise是在本轮事件循环的末尾执行,总是晚于本轮循环的同步任务。
then()
then方法返回的是一个新的Promise实例(不是原来那个Promise实例)。可以采用链式写法,then后面再跟一个then方法。
catch()
用于指定发生错误时的回调函数。
建议使用catch方法,而不是then方法的第二个参数。
如果没有使用catch方法指定错误处理的回调函数,Promise对象不会有任何反应。
finally()
不管Promise对象最后状态如何,都会执行的操作。
- .finally(()=>{ ··· });
- 不管promise最后的状态,在执行完then或catch以后,都会执行finally方法。
- findally不接受任何函数,没办法知道前面的状态,所以里面的操作,应该是与状态无关的。不依赖于Promise的执行结果。
all()
Promise.all()可以将多个Promise实例,包装成一个新的Promise实例
新的实例的状态,由多个参数Promise实例决定
- 如果参数实例没有自己的catch方法,就会调用all()的catch方法。
race()
参数实例中有一个实例率先改变状态,新的实例的状态就会跟着改变。
allSettled()
只有等参数Promise实例全部返回结果后,包装实例才会结束。
- 有时候不关心异步操作的结果,只关心这些操作有没有结束,Promise.allSettled()方法就很有用。
any()
接受一组Promise实例作为参数,包装成一个新的Promise实例返回。
只要有一个成功的状态,就会变成成功的状态
如果所有的参数实例都变成失败的状态,包装实例就会变成失败状态。
Promise.resolve()
Promise.reject()
有时需要将现有对象转为Promise对象
应用
- 加载图片
- Generator函数与Promise的结合
try()
模拟try代码块,捕获所有同步和异步的错误。
Iterator和for…of循环
遍历器(iterator)是一种接口,为各种不同的数据结构提供统一的访问机制。
任何数据结构只要部署Iterator接口,就可以完成遍历操作(依次处理该数据结构的所有成员)
作用:
- 为各种数据结构,提供一个统一的、简便的访问接口;
- 使得数据结构的成员,能够按照某种次序排列
- ES6新的遍历命令 for…of 循环,遍历器接口主要供for…of消费
默认Iterator接口
当使用for…of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口
- 一种数据结构只要部署了Iterator接口,这种数据结构是可遍历的。
- 一个数据结构只要具有Symbol.iterator属性,就可以认为是可遍历的。
- ES6有些数据结构原生具备Iterator接口,不用任何处理,就可以被for…of循环遍历。因为这些数据结构原生部署了Symbol.iterator属性。
- 对象没有默认部署Iterator接口,因为对象的属性顺序不确定
调用Iterator接口的场合
- 解构赋值 对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator方法。
- 扩展运算符(…)调用默认的Iterator接口
- yield*
- 数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,都调用了遍历器接口。
字符串的Iterator接口
字符串是一个类似数组的对象,原生具有Iterator接口。
Iterator接口与Generator函数
- Symbol.iterator()方法的最简单实现,使用Generator函数。几乎不用部署任何代码,只要用yield命令给出每一步的返回值即可。
for…of循环
for…of循环可以使用的范围包括 数组、Set和Map结构、类数组对象(arguments,DOM NodeList对象)、Generator对象、字符串。
与其他遍历语法的比较
- for循环 写法比较麻烦
- forEach 无法中途跳出,break return都不能奏效
- for…in 主要是为遍历对象而设计的,不适合遍历数组
- for…of 可以与break、continue、return 配合使用
- for…of提供了遍历所有数据结构的统一操作接口。
Generator函数的语法
Generator函数是ES6提供的一种异步编程解决方案。
- Generator函数是一个状态机,封装了多个内部状态。
- Generator函数返回一个遍历器对象,可以依次遍历Generator函数内部的每一个状态。
- 形式上:Generator函数是一个普通函数,有两个特征:
- function关键字与函数名之间有一个星号
- 函数体内部使用yield表达式,定义不同的内部状态。
async函数
async 使得异步操作变得更加方便
是Generator函数的语法糖
- 特点
- 内置执行器,调用了函数,就会自动执行。
- 更好的语义
- 更广的适用性 await后面,可以是Promise对象和原始类型的值。
- 返回值是Promise
基本语法
async函数返回一个Promise对象,可以使用then方法添加回调函数
语法
返回Promise对象
- async函数内部return语句返回的值,会成为then方法回调函数的参数。
- 内部错误,会导致返回的Promise对象变为reject状态,抛出的错误对象会被catch方法回调函数接收到。
Promise对象的状态变化
- async函数返回的Promise对象,必须等到内部所有await命令后面Promise对象执行完毕,才会发生状态改变。除非遇到return或者抛出错误。
await命令
- await后面是一个Promise对象,返回该对象的结果,如果不是Promise对象,就会直接返回对应的值。
- 任何一个await语句后面的Promise对象变为reject状态,整个async函数都会中断执行。
- 有时希望异步操作失败,不要中断后面的异步操作,可以将await放在try…catch结构里面,这样不管是否操作成功,后面的都会执行。
- 另一种方法是await后面的Promise对象再跟一个catch方法,处理可能出现的错误。
使用注意点
- await命令后面的Promise对象,最好放到try…catch代码块中
- 多个await命令后面的异步操作,如果不存在继发关系,最好同时触发。(写成继发关系,比较耗时)
- await命令只能用在async函数之中,如果在普通函数,就会报错。
async函数的实现原理
async函数的实现原理,就是将Generator函数和自动执行器,包装在一个函数里。
与其他异步处理方法的比较
最简洁、最符合语义,几乎没有语义不相关的代码。
按顺序完成异步操作
- 虽然map方法的参数是async函数,但它是并发执行的。
顶层await
顶层await命令有点像,交出代码的执行权给其他的模块加载,等异步操作完成后,再拿回执行权,继续向下执行。
Class的基本语法
js中生成实例对象的传统方法是通过构造函数
function Point(x, y){
this.x = x;
this.y = y;
}
Point.prototype.toString = function(){
return this.x + ',' + this.y;
}
这种写法与传统的面向对象语言差异很大。
ES6引入了Class这个概念,通过class关键字可以定义类。
class可以看做一个语法糖,绝大部分功能,ES5都能做到。
class Point{
constructor(x, y){
this.x = x;
this.y = y;
}
toString(){
return this.x + ',' + this.y;
}
}
- 使用的时候,直接对类使用new命令。跟构造函数的用法完全一致。
- 类的内部所有定义的方法,都是不可枚举的。
constructor
是类的默认方法,通过new命令生成对象实例时,自动调用该方法。
一个类必须有constructor()方法,如果没有显示定义,一个空的constructor()方法会被默认添加。
类必须使用new调用,和普通构造函数的重要区别。
类的实例
实例的属性除非显示定义在本身上(即定义在this对象上),否则都是定义在原型上(即定义在class上)。
类的所有实例都共享一个原型对象。
可以通过实例的__proto__属性为‘类’添加方法。不推荐使用,因为这会改变‘类’的原始定义,影响到所有实例。
取值函数(getter)和存值函数(setter)
在类内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
属性表达式
- 类的属性名,可以采用表达式
class表达式
- 与函数一样,类也可以使用表达式的形式定义。
注意点
- 严格模式:类和模块的内部,默认就是严格模式。
- 不存在变量提升,与ES5不同
- name属性
- Generator方法,如果某个方法之前加上星号(*),就表示该方法是一个Generator函数。
- this的指向:类的内部如果含有this,它默认指向类的实例。
静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
如果在一个方法前,加上static关键字,表示该方法不会被实例继承,而是直接通过类来调用,这就称为‘静态方法’。
class Foo{
static classMethod(){
return 'hello';
}
}
Foo.classMethod(); //hello
var foo = new Foo();
foo.classMethod(); //is not a function
- 如果静态方法包含this关键字,这个this指的就是类,而不是实例。
- 静态方法可以与非静态方法重名。
- 父类的静态方法,可以被子类继承。
- 静态方法也是可以从super对象上调用的。
静态属性
静态属性指的是Class本身的属性。
私有方法和私有属性
私有方法和私有属性,只能在类的内部访问的方法和属性,外部不能访问。有利于代码的封装。
new.target属性
该属性一般用在构造函数中,返回new命令作用于的那个构造函数。这个属性可以用来确定构造函数是怎么调用的。
如果构造函数不是通过new命令或者Reflect.construct()调用的,new.target就会返回undefined
- 子类继承父类时,new.target会返回子类
- 利用这个特点,可以写出不能独立使用,必须继承后才能使用的类。
class Shape{
constructor(){
if(new.target === Shape){
throw new Error('本类不能实例化');
}
}
}
class Rectangle extends Shape{
constructor(x, y){
super();
}
}
var x = new Shape(); //报错
var y = new Rectangle(3, 4); //正确
Class的继承
Class可以通过extends关键字实现继承,这比ES5通过修改原型链实现继承,要清晰和方便很多。
class Point{}
class ColorPoint extends Point{}
- super关键字,表示父类的构造函数,用来新建父类的this对象。
- 子类必须在constructor方法中调用super方法,否则新建实例时会报错。因为子类的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法。如果不调用super方法,子类就得不到this对象。
ES5继承
- 实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面
ES6继承
- 实质是先将父类实例对象的属性和方法加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。
- 如果子类没有定义constructor方法,这个方法会被默认添加。
在子类的构造函数中,只有调用super方法之后,才可以使用this关键字。因为子类实例的构建,基于父类实例,只有super方法才能调用父类实例。
Object.getPrototypeOf()
用来从子类上获取父类,可以使用这个方法判断,一个类是否继承了另一个类。
super关键字
super既可以当做函数使用,也可以当做对象使用。
- super当做函数使用
- super虽然代表父类的构造函数,但是返回子类的实例,子类调用super()相当于A.prototype.constructor.call(this);
- 作为函数,super()只能用在子类的构造函数之中,用在其他地方会报错。
- super当做对象使用
- 在普通方法中,指向父类的原型对象
- 在静态方法中,指向父类。
在子类普通方法中通过super调用父类方法,方法内部的this指向当前子类实例。
使用super的时候,必须显示的指定是作为函数、还是作为对象使用,否则会报错。
类的prototype属性和__proto__属性
ES5中,每个对象都有__proto__属性,指向对应的构造函数的prototype属性
Class作为构造函数的语法糖,同时又prototype属性和__proto__属性,因此同时存在两条继承链。
- 子类的__proto__属性,表示构造函数的继承,总是指向父类。
- 子类的prototype属性的__proto__属性,表示方法的继承,总是指向父类的prototype属性。
class A{}
class B extends A{}
B.__proto___ === A //true
B.prototype.__proto__ === A.prototype //true
实例的__proto__属性
- 子类实例的__proto__属性的__proto__属性,指向父类实例的__proto__属性。子类的原型的原型,是父类的原型。
原生构造函数的继承
Mixin模式的实现
Mixin指的是多个对象合成一个新的对象,新对象具有各个组成成员的解构。
const a = {
a: 'a'
}
const b = {
b: 'b'
}
const c = {...a, ...b}; // {a: 'a', b: 'b'}
Module的语法
ES6实现了模块功能,完全可以取代CommonJS和AMD规范,成为浏览器和服务器通用的模块解决方案。
- ES6模块尽量的静态化,使得编译时就能确定模块的依赖关系,一级输入和输出的变量。
- CommonJS和AMD模块,都是在运行时确定这些东西,输入时必须查找对象属性。
- ES6模块不是对象,而是通过export命令显示指定输出的代码,再通过import命令输入。
- ES6可以在编译时就完成模块加载,效率要比CommonJS模块的加载方式高。
严格模式
ES6模块自动采用严格模式,不管你有没有在模块头部加上‘use strict’
- 严格模式有一下限制
- 变量必须先声明
- 函数的参数不能有同名属性
- 不能使用with语句
- 不能对只读属性赋值,否则报错。
- 禁止this指向全局对象。
- 不应该在顶层代码使用this。
export命令
模块功能主要由两个命令构成:export和import
- export命令用于规定模块的对外接口
- import命令用于输入其他模块提供的功能。
- 一个模块就是一个独立的文件,该文件内部的所有变量,外部无法获取。
- 必须使用export关键字输出变量。
- export语句输出的接口,与其对应的值是动态绑定关系,即通过该接口,可以取到模块内部实时的值。
- export命令可以出现在模块的任何位置,只要处于模块顶层就可以。
import 命令
使用了export命令定义了模块对外的接口以后,其他js文件就可以通过import命令加载这个模块。
- import命令输入的变量都是只读的,因为本质是输入接口。不允许改写接口。
- import命令要使用as关键字,将输入的变量重命名。
import { lastName as surname } from './name.js'
- import命令具有提升效果,会提升到整个模块的头部,首先执行。import命令是在编译阶段执行的,在代码运行执行之前。
- 由于import是静态执行的,所以不能使用表达式或变量,只有在运行时才能得到结构的语法结构。
- 如果多次重复执行同一句import语句,name只会执行一次,而不会执行多次。
模块的整体加载
使用整体加载,用星号(*)执行一个对象,所有输出值都加载在这个对象上面。
export default命令
为模块指定默认输出
export default function(){
console.log('foo');
}
- 一个模块只能有一个默认输出,因此export default命令只能使用一次。
- export default也可以用来输出类。
export与import的复合写法
export { foo, bar } from 'name.js';
- 如果写成一行,接口并没有导入当前模块,只是相当于对外转发接口,导致当前模块不能直接使用接口。
模块的继承
跨模块常量
import()
import命令会被js引擎静态分析,先于模块内其他语句执行。
import和export命令只能在模块的顶层,不能在代码块之中。
import无法实现动态加载功能
- ES2020提案引入import()函数,支持动态加载模块。
- import()返回一个promise对象,可以用在任何地方