es6一些语法
ES6
块状作用域的绑定
使用var声明的变量都会被当成当前作用域顶部声明的变量,这个就是变量提升(Hoisting)机制
- let 声明:let声明的变量不会提升,而且其作用域限制在函数内部以及块中(两个花括号之间)
- const 声明:同let,但是const声明的变量不能改变其值,故用const声明的同时要初始化
注意事项: - 被let或者const声明的变量,在同一作用域不能再被声明,否则会出现错误
- 使用let或者const声明的变量必须在声明之后才能使用(用typeof 操作符也不能),否则会出现错误
- 使用let或者const在全局作用域创建变量,不会添加为全局对象的属性,它只会遮蔽全局对象属性而不会覆盖
- let或const声明的变量在循环中的行为是每次迭代创建新的绑定
建议:默认使用const,实在需要改变值时使用let
字符串和正则
子字符串的识别
- include(),如果字符串中检查到指定文本则返回true,否则返回false
- startsWith(),如果字符串的起始部分检测到指定文本就返回true,否则返回false
- endsWith(),如果字符串的结束部分检测到指定文本则返回true,否则返回false
他们都支持第二参数(可选):指定开始搜索的索引值
字符串的重复
repeat()方法,接收一个number参数,表示重复的次数,返回重复之后的新字符串
正则表达式的复制
1 | let re1 = /ab/i; |
获取修饰符
1 | let re = /ab/g; |
模板字面量
模板字面量语法支持创建领域专用语法(DSL)
反撇号中所有的空白符都属于子字符串的一部分,要注意缩进
可以这样写:
1 | let html = ` |
可以在模板字面量显式使用\n来指明换行符
字符串占位符
在模板字面量中,可以把任何合法的Javascript表达式嵌入到占位符中并将其作为字符串的一部分输出到结果中。
占位符构成:${表达式}
如:
1 | let name = "Nicholas", |
可以把一个模板字面量通过${}
嵌入到另一个模板字面量中
标签模板
1 | let message = tag`hello,world`; |
tag是一个函数,执行模板字面量上的转换并返回最终的字符串值
标签函数使用不定参数来定义占位符,从而简化数据处理的过程
1 | function tag(literlas,...substitutions){ |
literals包含一个额外属性raw,是包含每一个字面值的原生等价信息的数组,如:literlas.raw[1]
literlas.length - 1 === substitution.length
使用原始值
使用String.raw()作为tag
1 | let message = String.raw`Multiline\nstring`; |
函数
函数默认参数值
声明函数时,可以为任意参数指定默认值,只有不为参数传入值或者主动传入undefined时才会使用默认值。
1 | function makeRequest(url,timeout = 2000,callback = function(){}){ |
null是一个合法值,故传入null时不会使用默认值
还可以使用函数来得到默认的参数值
1 | let value = 1; |
也可以引用前面参数的值
1 | function add(a,b = a){ |
arguments对象
在ES6下,arguments对象将不随着形式参数的变化而变化。
不定参数
在函数命名参数前添加三个点(…)就表明这是一个不定参数,该参数是一个数组,包含着自它之后传入的所有参数
1 | function add(a,...nums){ |
每一个函数最多只能声明一个不定参数,并且要放在所有参数的末尾
展开运算符
可以指定一个数组,将他们打散之后作为各自独立的参数传入函数
1 | let values = [25,50,75,100]; |
new.target
当通过new调用函数时,即调用函数的[[Construct]]方法,函数体内的new.target被赋值为new操作符的操作目标;当调用[[Call]]方法时,new.target的值为undefined。
块状函数
在块状作用域中声明的函数会被视为块状函数,但是使用function 操作符仍然会变量提升
箭头函数
- 箭头函数中this、super、arguments及new.target这些值由最近一层非箭头函数决定。
- 不能通过关键字new调用(没有[[Construct]]方法)
- 没有原型
- 不可以改变this值的绑定
- 不支持arguments对象
- 不支持重复的命名参数
1
2
3
4
5
6let getName = ()=>"name";
let sum = (a,b) => a+b;
let sum2 = (a,b) => {return a+b;};
let reflect = value => value;
let doNothing = () => {};
((name) => {console.log(name)})("name");//创建并立即调用
箭头函数this的值取决于最近一层非箭头函数的this;否则this值会被设置为undefined
尾调用优化
- 尾调用布冯问当前栈的变量(也就是说函数不是一个闭包)
- 在函数内部,尾调用时最后一条语句
- 尾调用的结果作为函数值返回
常见递归优化:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15const factorial = function(n){
if(n <= 1){
return 1;
} else{
return n*factorial(n-1); //无法优化
}
}
const factorial = function(n,res = 1){
if(n <= 1){
return 1*res;
} else{
res *= n;
return factorial(n-1,res);//可以优化
}
}
对象
对象类别
- 普通对象(Ordinary):具有JavaScript所有的默认内部行为对象
- 特异对象(Exotic):具有某些与默认行为不符的对象
- 标准对象(Standard):ES6中定义的对象,如Array,Date等。其既可以是普通对象,也可以是特异对象
- 内建对象:脚本执行时就存在于JavaScript执行环境中的对象,所有的标准对象都是内建对象
对象初始值的简写
当一个对象的属性与本地变量同名时,不必再写冒号和值,简单地只写属性名即可。1
2
3
4
5
6
7
8const Person = function(name,age){
return {
name,
age
};
};
let person1 = new Person("dengyf",22);
console.log(person1.name);//dengyf对象方法的简写
1
2
3
4
5
6let person = {
name: "Micholas",
sayname(){
console.log(this.name);
}
}
简写方法可以使用super关键字
可计算属性
1 | let suffix = "name"; |
新增方法
Object.is()
1 | Object.is(NaN,NaN);//true |
Object.assign()
接收任意对象的源对象,并按指定顺序将属性复制到接收对象中。如果多个源对象具有同名属性,则排位靠后的源对象会覆盖排位靠前的。
1 | let receiver = {}; |
Object.setPrototypeOf()
接收两个参数,第一个是要改变原型的对象,第二个为替代第一个参数的对象
1 | let human = { |
super
一般当对象中一些方法与原型中方法重名时,调用原型中方法:
1 | let person = { |
而super就是简化Object.getPrototypeOf(this).getName.call(this)
源代码变成:
1 | let friend = { |
super.getName()始终指向perosn.getName(),其引用不是动态变化的
ES6将一个方法定义为一个函数,它有一个内部的[[HomeObject]]属性来容纳这个方法从属的对象
super的引用第一步在其函数的[[HomeObject]]属性上调用Object.getPrototypeOf()方法检索原型,然后搜索原型找到同名函数,最后设置this绑定并且调用相应的方法。
解构
解构是一种打破数据结构,将其拆分成更小部分的过程。
对象解构
1 | let node = { |
使用let、const、var解构声明变量,必须提供初始化程序(等号右侧的值)
解构赋值
1 | ({type,name} = node) |
代码块语句不允许出现在赋值语句左侧,所以上面的括号是不可缺少的
设定默认值
1 | let node = { |
非同名变量赋值
1 | let node = { |
嵌套对象解构
1 | let node = { |
数组解构
1 | let colors = ["green",["red","light red"],"blue"], |
不定元素
1 | let colors = ["green",["red","light red"],"blue"]; |
高级应用
- 交换两个数值
1
2
3
4
5let a = "a",
b = "b";
[a,b] = [b,a];
console.log(a);//"b"
console.log(b);//"a" - 复制数组
1
2let colors = ["red","green"];
let [...colorsCopy] = colors;解构参数
设置默认值1
2
3function setCookie(name,value,{secure,path,domain,expires} = {}){ //define default
//do something
}1
2
3
4
5
6
7
8
9const cookieDefault = {
secure:false,
path:"/",
domain:"example.com",
expires:new Date()(Date.now() + 36000000)
};
function setCookie(name,value,{secure = cookieDefault.secure,path = cookieDefault.path,domain = cookieDefault.domain,expires = cookieDefault.expires} = cookieDefault){
//do something
}Symbol
ES6引入的第六种原始类型:symbol->创建非字符串名称。Symbol的使用
symbol还可以用在1
2
3
4
5
6let firstName = Symbol("first name"); //接收一个可选参数,添加一段文本描述
let person = {
[firstName] : "Nicholas"
};
console.log(person[firstName]);//"Nicholas"
console.log(firstName.toString());//"Symbol(first name)"Object.defineProperty()
和Object.defineProperties()
方法Symbol共享体系
Symbol.for()方法首先在全局Symbol注册表中搜索键为”iud”的Symbol是否存在,如果存在,直接返回已有的Symbol;否则,创建一个新的Symbol并使用这个键在Symbol全局注册表中注册,然后返回一个新创建的Symbol1
2
3
4
5let uid = Symbol.for("uid");
let o = {};
o[uid] = "123";
console.log(o[uid]);//"123"
console.log(uid);//"Symbol(uid)"
Set与Map集合
- Set集合是一种无重复元素的列表
- Map集合内含多组键值对
Set集合
1
2
3
4
5
6let set = new Set();
set.add(5);
set.add("5");
console.log(set.size);//2
let set1 = new Set([1,2]);
console.log(set.size);//2
Set集合不会对所存值进行强制的类型转换
如果后续传入的值已经存在,则此次操作会被忽略
通过has()方法检测Set集合是否存在某个值 —>使用Object.is()判断是否相等
1 | console.log(set.has(5));//true |
通过delete()方法移除Set集合中某一个元素,调用clear()方法移除集合中所有元素
1 | let set = new Set(); |
forEach()
forEach()方法回调函数接收三个参数:
- Set集合中下一个索引位置
- 与第一个参数一样的值
- 被遍历的Set集合本身
Set中第一个和第二个参数完全一样Set和数组互相转化
Set —> ArrayArray —> Set1
let set = new Set([1,2,3,4,5]);
1
let array = [...set];
Weak Set
Weak Set只存储对象的弱引用,并且不可以存储原始值;集合中的弱引用如果是对象唯一的引用,则会被回收并释放相应内存。 - WeakSet只有add、delete、has方法,不支持forEach、size、clear
- WeakSet不暴露任何迭代器(例如keys()、value()方法),所以无法通过程序检测里面的内容
- 通过add、delete、has传入非对象参数都会导致程序报错
- 不能用于for-of循环
Map集合
1
2
3
4
5
6
7let map = new Map();
map.set("title","ECMAScript 6");
console.log(map.get("title"));//"ECMAScript 6" 若不存在返回undefined
console.log(map.has("title"));//true
console.log(map.size);//1
map.delete("title");
map.clear();Map 与 Array
1
let map = new Map(["name","Nicholas"],["age",23]);
forEach()
回调函数接收三个参数 - Map集合中下一次索引位置 value
- 值对应的键名 key
- Map集合本身
WeakMap
列表的键名必须是非null对象,键名对应的值可以是任何类型
通过set、get、has、delete操作,不支持clear和forEach
Iterator and Generator
ES5创建一个迭代器方法
1 | let createIterator = function(items){ |
ES6通过生成器创建迭代器
1 | const createIterator = function *(items){ |
也可以这样:
1 | function *createIterator(){ |
或者
1 | let o = { |
- yield关键字可以返回任何值或者表达式
- yield只能用在生成器内部,不能用在函数外部或者内部的函数,否则会报错
- 箭头函数不能使用yield关键字
通过Symbol.iterator访问默认迭代器
也可以给自定义对象添加生成器1
2let values = [1,2,3,4];
let iterator = values[Symbol.iterator]();1
2
3
4
5
6
7
8let collection = {
items = [],
*[Symbol.iterator](){
for(let item of this.items){
yield item;
}
}
};内建迭代器
- entries() 返回一个迭代器,其值为多个键值对,以数组形式 –> Map集合默认迭代器
- values() 返回一个迭代器,其值为集合的值 –> 数组和Set集合默认迭代器
- keys() 返回一个迭代器,其值为集合中所有键名
使用可以使用解构语法1
2
3
4
5
6
7let colors = ["red","green","blue"];
for(let color of colors.entries()){
console.log(color);
}
for(let key of colors.keys()){
console.log(key);
}1
2
3
4
5
6
7let data = new Map();
data.set("name","Nicholas");
data.set("age",34);
for(let [key,value] of data){
console.log(key + "=" + value);
}高级迭代器功能
给迭代器传递参数
如果给迭代器的next()方法传递参数,则这个参数的值就会代替生成器内部上一条yield语句的返回值。
第一次调用next()方法时无论传入什么参数都会被丢弃
1 | function *createIterator(){ |
在迭代器抛出错误
1 | iterator.throw(new Error("Boom"));//错误抛出并阻止代码继续执行 |
生成器返回语句
在生成器中,return表示所有操作已经完成,done被设置为true。在return后面的语句不会运行。最后一次next()调用可以通过return返回一个特定值。
展开运算发与for-of循环会直接忽略指定的任意返回值,只要done变成true,就立即停止读取其他值
委托生成器
某些情况下,需要将两个迭代器合二为一,这时可以创建一个生成器,再给yield语句添加一个星号,可以将生成数据的过程委托给其他生成器
1 | function *createNumberIterator(){ |
**yield 可以直接用于字符串,此时使用字符串的默认迭代器。yield “hello”
异步任务执行
简单的任务执行器
1 | function run(taskDef){ |
JavaScript中的类
基本的类声明语法
1 | class Person{ |
不需要在类的各元素之间用逗号分隔,声明最后不需要分号
- 类声明与let声明类似,不能被提升
- 在类中,所有方法都是不可枚举
- 每一个类都有[[Construct]]内部方法,通过关键字调用,用new调用不含[[Construct]]其他方法会抛出错误
- 只能使用关键字new调用类,否则会抛出错误
- 类中修改类名会抛出错误
类表达式
1
2
3
4
5let Person = class {
constructor(name){
this.name = name;
}
};类命名表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32let Person = class PersonType{ //Person可以在外部或者内部使用,可是PersonType只能在内部使用
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
};
//等价于
let Person = (function(){
//构造函数
const PersonType = function(name){
if(new.target === undefined){
throw new Error("必须通过new关键字调用");
}
this.name = name;
};
Object.defineProperty(PersonType.prototype,"sayName",{
value:function(){
if(new.target !== undefined){
throw new Error("不可通过new调用");
}
console.log(this.name);},
enumerable:false,
writable: true,
configurable: true
});
return PersonType;
})();
let person = new Person("Nicholas");
person.sayName();访问器属性
1
2
3
4
5
6
7
8
9
10
11class CustomHTMLElement{
constructor(element){
this.element = element;
}
get html(){
return this.element.innerHTML + "hahaah";
}
set html(value){
this.element.innerHTML = value;
}
}生成器方法
1
2
3
4
5
6
7
8
9
10class MyClass{
*createIterator(){
yield 1;
yield 2;
}
}
let instance = new MyClass(),
iterator = instance.createIterator();
console.log(iterator.next());//1
console.log(iterator.next());//2静态成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14class Person{
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name);
}
static create(name){
return new Person(name);
}
}
let person = Person.create("Nicholas");
person.sayName();//"Nicholas"
console.log(typeof person.create);//undefined - 不能将static用在构造函数上
- 不可在实例上访问静态成员
继承与派生类
如果派生类不使用构造函数,则当创建新的实例的时候会自动调用super()并传入所有参数。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class Rectangle{
constructor(length,width){
this.length = length;
this.width = width;
}
getArea(){
return this.length * this.width;
}
}
class Square extends Rectangle{
constructor(length){
super(length,length);
}
}
console.log(Rectangle.isPrototypeOf(Square));//true
let square = new Square(4);
console.log(square.getArea());//16
console.log(square instanceof Square);//true
console.log(square instanceof Rectangle);//true - 使用extend的派生类构造函数一定要使用super,否则只能让构造函数返回一个对象。
派生类中可以继承基类的静态方法
派生自表达式的类
只要表达式可以被解析为一个函数并具有[[Construct]]属性和原型,那么就可以用extend进行派生1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Rectangle(length,width){
this.length = length;
this.width = width;
}
Rectangle.prototype.getArea = function(){
return this.length * this.width;
}
function getBase(){
return Rectangle;
}
class Square extends getBase(){ //使用表达式
constructor(length){
super(length,length);
}
}
let square = new Square(4);
console.log(square.getArea());内建对象的继承
1
2
3class MyArray extends Array{
}Symbol.species属性
改进数组功能
新增方法:Array.of()
和Array.from()
Array.from接收三个参数,第一个为类数组,第二个为映射函数,第三个为映射函数this的值
1 | ```fill()```接收三个参数:要填充的值、开始索引、结束索引(不包含) |
可以通过byteLength来查看缓冲区中的比特数量
1 | console.log(buffer.byteLength);//10 |
通过slice可以返回一个新的ArrayBuffer实例:传入开始索引和结束索引(不包括结束索引)
1 | let buffer2 = buffer.slice(4,6); |
通过视图操作数组缓冲区
DataView类型时一种通用的数组缓冲区视图,其支持所有8种数值数据类型
1 | let view = new DataView(buffer,5,2);//选择索引5和索引6的字节 -->可选参数 |
获取视图信息
- buffer 视图绑定的数组缓冲区
- byteOffset DataView构造函数第二参数,默认0
- byteLength DataView构造函数第三参数,默认缓冲区长度
读写数据
get方法接收两个参数:读取数据时偏移的字节数量;和一个可选的布尔值,表示是否按照小端序进行读取
set方法接收三个参数,写入数据时偏移的字节数量,介入的值和一个布尔值,表示是否按照小端序格式存储 - getInt8()
- setUint8()
1
2
3
4
5
6let buffer = new ArrayBuffer(2);
view = new DataView(buffer);
view.setInt8(0,5);
view.setInt8(1,-1);
console.log(view.getInt8(0));//5
console.log(view.getInt8(1))//1定型数组
定型数组实际上是用于数组缓冲区的特定类型的视图,可以强制使用特定的数据类型,而不是使用通用的DataView对象来操作数组缓冲区
构造函数(部分): - Int8Array()
- Uint32Array()
- Float64Array()
- Uint8ClampedArray() –>强制转换:小于0 => 0;大于225 => 225
创建定型数组
1
2
3let buffer = new ArrayBuffer(10),
view1 = new Int8Array(buffer,5,2);
console.log(view1.byteLength);//2可以将以下作为参数传入构造函数1
2
3
4
5
6let int = new Int16Array(2),
float = new Float32Array(5);
console.log(int.byteLength);//4
console.log(int.length);//2
console.log(float.byteLength);//20
console.log(float.length);//5 - 一个定型数组
- 一个可迭代对象
- 一个数组
- 一个类数组对象
1
2
3let int = new Int16Array([1,2,3]);
console.log(int.length);//3
console.log(int.byteLength);//6
定型数组不是Array实例
当给定型数组传入非法值时,会用0来代替
附加方法
1 | ``` subarray() ```接收两个参数,可选开始参数和可选结束参数 |
readFile()函数立即执行,当读取磁盘上的文件时会停止执行。此时console.log(“Hi”)会立即执行并输出”Hi”;当readFile()结束执行时,会向任务队列的末尾添加一个新任务,该任务包含回调函数及相应的参数。
Promise
Promise相当于异步操作结果的占位符,它不会去订阅一个事件,也不会传递一个回调函数给目标函数,而是让函数返回一个Promise。
1 | let promise = readFile("example.txt"); |
这段代码中,readFile()不会立即读取文件,函数会先返回一个表示异步读取操作的Promise对象,未来对这个对象的操作完全取决于Promise对象的声明周期。
Promise的生命周期
- 进行中状态(pending),此时操作尚未完成,所以也是未处理(unsettled)
- 异步操作执行结束,Promise变成已处理(settled)状态。包括以下两个状态:Fulfilled –> 操作完成 、 Rejected –> 操作未能完成
Promise状态改变时,通过then()方法采取特定行动创建Promise之后,执行器会立即执行,然后才执行后续代码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28另一个是当Promise状态变成rejected时调用的函数,与失败相关的数据都会传递到这个函数。
```catch()```相当于只传入rejected函数的then函数
#### 创建未完成的Promise
用Promise构造函数可以创建新的Promise,构造函数只接收一个参数:包含初始化Promise代码的执行器函数(executor)。执行器接收两个参数,分别是resolve()函数和reject()函数。执行器成功完成时调用resolve()函数,反之,失败时调用reject()函数
```JavaScript
const fs = require("fs");
function readFile(filename){
return new Promise(function(resolve,reject){
//触发异步操作
fs.readFile(filename,"utf-8",function(err,content){
//检查错误
if(err){
reject(err);
return;
}
//成功读取
resolve(content);
});
});
}
let promise = readFile("example.txt");
//监听fulfilled和rejected状态
promise.then(
(content)=>{console.log(content);},
(err)=>{console.error(err.message);}
);创建已处理的Promise
1
2
3```JavaScript
let promise = Promise(42);
promise.then((value)=>{console.log(value);});//421
2
3```JavaScript
let promise = Promise.reject(42);
promise.catch((value)=>{console.log(value);});//42
如果向Promise.resolve或者Promise.reject函数传入一个promise,那么这个promise会被直接返回
执行器错误
如果执行器内部抛出一个错误,则Promise的拒绝处理程序就会被调用
1 | let promise = new Promise(function(resolve,reject){ |
每一个执行器都隐含一个try catch块
全局的Promise拒绝处理
Node.js环境的拒绝处理
触发process对象上两个事件:
- unhandledRejection 事件处理程序接收错误对象和Promise作为参数
- rejectionHandled 事件处理程序接收被拒绝的Promise对象作为参数浏览器环境
1
2
3
4
5
6
7
8let rejected;
process.on("unhandledRejection",function(err,promise){
console.log(err.message);//"Explosion"
console.log(rejected === promise);//true
});
rejected = Promise.reject(new Error("Explosion"));
触发window对象两个事件,与Node环境完全等效
浏览器不同的是,两个事件都可以使用拒绝值,即第一个参数串联Promise
每次调用then()方法或者catch()方法时实际上创建并返回另一个Promise,只有当第一个Promise完成或被拒绝后,第二个才被解决1
2
3
4
5
6
7
8
9
10
11
12let p1 = new Promise((resolve,reject)=>{
resolve(42);
});
p1.then((value)=>{
console.log(value);
}).then(()=>{
console.log("finished");
});
// output:
// 42
//finished捕获错误
1
2
3
4
5
6
7
8
9
10
11
12
13let p1 = new Promise((resolve,reject)=>{
resolve(42);
});
p1.then((value)=>{
console.log(value);
throw new Error("explosion");
}).catch((err)=>{
console.log(err.message);
});
// output:
// 42
//explosionPromise链的返回值
即使是拒绝处理程序中return也可以返回值到下一个promise完成处理程序1
2
3
4
5
6
7
8
9let p1 = new Promise((resolve,reject)=>{
resolve(42);
});
p1.then((value)=>{
console.log(value);
return value + 1;//42
}).then((value)=>{
console.log(value);//43
})在Promise链中返回Promise
如果p2被拒绝,就会调用拒绝程序,如果p2被完成,那么调用完成程序1
2
3
4
5
6
7
8
9
10
11
12let p1 = new Promise((resolve,reject)=>{
resolve(42);
});
let p2 = new Promise((resolve,reject)=>{
resolve(43);
});
p1.then((value)=>{
console.log(value);//42
return p2;
}).then((value)=>{
console.log(value);//43
});响应多个Promise
Promise.all()
Promise.race()
都只接收一个参数并返回一个Promise,该参数是一个含有多个受监视Promise的可迭代对象。Promise继承
1
2
3
4
5
6
7
8
9class myPromise extends Promise{
//use default Constructor
success(resolve,reject){
this.then(resolve,reject);
}
failure(reject){
this.catch(reject);
}
}