ES6入门教程

E6入门教程

let和const命令

let命令

  • let声明的变量,只在其所在的代码块内有效。
  • 不存在变量提升
  • 暂时性死区:在代码块内,使用let命令声明变量之前,该变量都是不可用的。
  • 不允许重复声明

块级作用域

  • let为js新增了块级作用域
  • 内存作用域可以定义外层作用域的同名变量
  • es6块级作用域必须有大括号,如果没有大括号,js就认为不存在块级作用域。

const命令

const声明的是一个只读的常量,一旦声明,值就不能改变。
一旦声明常量,必须初始化,不能留到以后赋值。

  • 作用域与let命令相同
  • const实际上变量指向的那个内存地址所保存的数据不得改动。如果是复合类型数据,只能保证指针是固定的,数据结构无法控制。
  • ES6声明变量的6种方法
  1. var
  2. function
  3. let
  4. const
  5. import
  6. 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中新方法
  1. includes(),返回布尔值,表示是否找到了参数字符串。
  2. startsWith(),返回布尔值,表示参数字符串是否在原字符串首部。
  3. endsWith(),返回布尔值,表示参数字符串是否在原字符串尾部。

实例方法:repeat()

  • 返回一个新字符串,将原字符串重复n次

实例方法:padStart(),padEnd()

如果某个字符串不够指定长度,会在头部或尾部补全。

  • padStart()用于头部补全
  • padEnd()用于尾部补全
  • 接受两个参数
  1. 补全最大长度
  2. 用来补全的字符串
  • 用途
// 补全指定位数
'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规定只要函数参数使用了默认值、解构赋值、扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错。

  • 解决方法
  1. 设置全局严格模式
  2. 把函数包在一个无参数的立即执行函数里面。

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);
  • 使用注意点
  1. 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  2. 不可以当做构造函数,不可以使用new命令
  3. 不可以使用arguments对象,该对象在函数体内不存在。可以用rest参数代替。
  4. 不可以使用yield命令,箭头函数不能用作Generator函数。

在箭头函数中,this 是固定的。(外层代码块的this)
this指向的固定化,并不是箭头函数内部有绑定this的机制,实际是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。
正是因为它没有this,所以不能用作构造函数。

  • 不适合场合
  1. 定义对象的方法,且该方法内部包括this。
  2. 需要动态的this的时候,不应该使用箭头函数。

对象的属性 建议使用传统的写法定义,不要使用箭头函数定义。

尾调用优化

原理:指某个函数的最后一步是调用另一个函数

function f(x){
	return g(x);
}
  • 尾调用不一定出现在函数尾部,只要是最后一步操作即可。
  • 尾调用优化:只保留内层函数的调用帧。

尾递归:尾调用自身。

  • 递归非常耗费内层,需要同时保存成千上百个调用帧,容易发生“栈溢出”错误。
  • 对于尾递归,只存在一个调用帧,不会发生“栈溢出”错误。

函数式编程有一个概念,柯里化:将多参数的函数转换成单参数的函数。

  • 一旦使用递归,就最好使用尾递归。
  • 尾调用仅在严格模式下生效。

函数参数的尾逗号

函数参数,最后一个参数后面出现逗号。

toString()

返回一模一样的原始代码。

catch命令的参数省略

允许catch语句省略参数

try{

}catch{

}

数组的扩展

扩展运算符

扩展运算符是三个点(…),好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。

  • 该运算符主要用于函数调用
  • 扩展运算符将一个数组,变为参数序列。
  • 只有在函数调用时,扩展运算符才可以放在圆括号中,否则会报错。
  • 扩展运算符的应用
  1. 复制数组
  2. 合并数组
  3. 与解构赋值结合(如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错)
  4. 字符串 转成真正的数组,Array.from 将类数组转为真正的数组。
  5. Map和Set结构,Generator函数
  • 扩展运算符内部调用的事数据结构的Iterator(迭代器)接口,因此只要具有Iterator接口的对象,都可以使用扩展运算符,比如Map结构。
  • 如果没有Iterator接口的对象,使用扩展运算符,会报错。

Array.from()

  • Array.from方法用于将两类对象转为真正的数组:
  1. 类数组对象(array-like-object)
  2. 可遍历对象(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)

  • 接受三个参数
  1. target(必需):从该位置开始替换数据
  2. start(可选):从该位置开始读取数据,默认为0;
  3. end(可选):到该位置停止读取数据,默认等于数组长度。

数组实例的find()和findIndex()

  • find 找出第一个符合条件的数组成员,并返回该成员。参数是一个回调函数。
  • findIndex 找出第一个符合条件的数组成员,并返回该成员的位置。参数是一个回调函数。

数组实例的fill()

fill方法使用给定值,填充一个数组。

数组实例的entries()、keys()和values()

三个方法用于遍历数组,都返回一个遍历器对象,可以用for…of循环进行遍历
唯一区别:keys()是对键名遍历,values()是对键值遍历,entries()是对键值对的遍历。

数组实例的includes()

返回一个布尔值,表示某个数组是否包含给定的值。

  • indexOf有两个缺点
  1. 不够语义化,要比较是否不等于-1,表达不够直观
  2. 内部使用严格相等,导致对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,某些操作会忽略当前属性。
  1. for…in循环:只遍历对象自身的和继承的可枚举的属性。
  2. Object.keys():返回自身可枚举的属性键名。
  3. JSON.stringify():自身可枚举属性
  4. Object.assign():只拷贝自身可枚举属性。

引入可枚举目的:让某些属性可以规避掉for…in操作,不然所有内部属性和方法都会被遍历到。
所有Class的原型方法都是不可枚举的。
大多数我们只关心对象自身的属性,所以尽量不要用for…in循环,用Object.keys()代替。

属性的遍历

  • 5种方法可以遍历对象的属性
序号方法名作用返回
1for…in循环遍历对象自身的和继承的可枚举属性(不含Symbol属性)
2Object.keys()自身的所有可枚举属性的键名(不含Symbol属性)数组
3Object.getOwnPropertyNames(obj)自身所有属性的键名(不含Symbol属性)数组
4Object.getOwnPropertySymbols(obj)自身所有Symbol属性的键名数组
5Reflect.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函数生成。
  • 对象属性名现在可以有两种类型
  1. 原来的字符串
  2. 新增的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实例的属性和方法

属性

  1. constructor 构造函数,默认就是Set函数
  2. size 返回Set实例的成员总数

方法

  1. 操作方法

    • add(),添加某个值,返回Set结构本身。
    • delete(), 删除某个值,返回一个布尔值,表示删除是否成功。
    • has(),返回一个布尔值,表示是否为Set的成员。
    • clear(),清除所有成员,没有返回值。
  2. 遍历操作

    • keys(),返回键名的遍历器
    • values(),返回键值的遍历器
    • entries(),返回键值对的遍历器
    • forEach(),使用回调函数遍历每个成员

Set遍历顺序就是插入顺序。
可以使用for…of遍历Set

WeakSet

WeakSet结构与Set类似,也是不能重复值的集合。
成员只能是对象,不能是其他类型的值
WeakSet中的对象都是弱引用。
是一个构造函数,可以使用new命令

  1. 方法
    • add(),添加一个新成员
    • delete(),清除指定成员
    • has(),表示某个值是否存在。

Map

js对象,本质上是键值对的集合,但是只能用字符串当做键,给它的使用带来了很大的限制。
Map结构提供了‘值-值’的对应,是一种更完善的Hash结构实现。如果你需要‘键值对’的数据结构,Map比Object更合适。

  1. 属性

    • size属性
    • set(),返回的是当前Map对象,可以采用链式写法
    • get(),读取对应的key,如果找不到key,返回undefined
    • has(),返回一个布尔值,表示某个键是否在当前的Map对象中
    • delete(key),删除某个键,返回true。
    • clear(),清除所有成员,没有返回值。
  2. 遍历方法

    • 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种

  1. get
  2. set
  3. has
  4. deleteProperty
  5. ownKeys
  6. getOwnPropertyDescriptor
  7. defineProperty
  8. preventExtensions
  9. getPrototypeOf
  10. isExtensible
  11. setPrototypeOf
  12. apply //拦截Proxy实例作为函数调用的操作
  13. construct //拦截Proxy实例作为构造函数调用的操作。

Proxy.revocable(),返回一个可取消的Proxy实例。

  • Web服务的客户端

可以拦截目标对象的任意属性,很适合用来写Web服务的客户端。

Reflect

Reflect对象与Proxy对象一样,也是为了操作对象而提供的新API。

  • 将Object对象的一些属于语言内部的方法,放到Reflect对象上。
  • 修改Object方法的返回结果
  • 让Object操作都编程函数行为。
  • Reflect对象的方法与Proxy对象的方法一一对应。

观察者模式

指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。

Promise对象

Promise是异步编程的一种解决方案

  • 特点
    1. 对象的状态不受外界影响,有三种状态 pending fulfilled rejected,只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
    2. 状态一旦改变,就不会再变。

基本用法

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接口,就可以完成遍历操作(依次处理该数据结构的所有成员)

作用:

  1. 为各种数据结构,提供一个统一的、简便的访问接口;
  2. 使得数据结构的成员,能够按照某种次序排列
  3. ES6新的遍历命令 for…of 循环,遍历器接口主要供for…of消费

默认Iterator接口

当使用for…of循环遍历某种数据结构时,该循环会自动去寻找Iterator接口

  • 一种数据结构只要部署了Iterator接口,这种数据结构是可遍历的。
  • 一个数据结构只要具有Symbol.iterator属性,就可以认为是可遍历的。
  • ES6有些数据结构原生具备Iterator接口,不用任何处理,就可以被for…of循环遍历。因为这些数据结构原生部署了Symbol.iterator属性。
  • 对象没有默认部署Iterator接口,因为对象的属性顺序不确定

调用Iterator接口的场合

  1. 解构赋值 对数组和Set结构进行解构赋值时,会默认调用Symbol.iterator方法。
  2. 扩展运算符(…)调用默认的Iterator接口
  3. yield*
  4. 数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,都调用了遍历器接口。

字符串的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函数是一个普通函数,有两个特征:
    1. function关键字与函数名之间有一个星号
    2. 函数体内部使用yield表达式,定义不同的内部状态。

async函数

async 使得异步操作变得更加方便
是Generator函数的语法糖

  • 特点
    1. 内置执行器,调用了函数,就会自动执行。
    2. 更好的语义
    3. 更广的适用性 await后面,可以是Promise对象和原始类型的值。
    4. 返回值是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当做函数使用
  1. super虽然代表父类的构造函数,但是返回子类的实例,子类调用super()相当于A.prototype.constructor.call(this);
  2. 作为函数,super()只能用在子类的构造函数之中,用在其他地方会报错。
  • super当做对象使用
  1. 在普通方法中,指向父类的原型对象
  2. 在静态方法中,指向父类。

在子类普通方法中通过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’

  • 严格模式有一下限制
  1. 变量必须先声明
  2. 函数的参数不能有同名属性
  3. 不能使用with语句
  4. 不能对只读属性赋值,否则报错。
  5. 禁止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对象,可以用在任何地方
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值