ES6规范
let & const ☆☆☆☆☆
- let 是对局部变量的补充设计
- 并且向着大型应用语言的方向去要求,
(1)没有类似var
的变量声明提升(可以在声明之前引用这个变量),必须先声明,后使用
// 被称为暂时性死区 还是遵循 先声明后引用的条件
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
(2)不允许多次声明
(3)在if
swtich
for
等代码块中也起作用
- const 弥补了javascript中没有常量定义的设计。
字符串 ☆☆☆☆☆
- 字符串模板语法 ,目前已经非常常用了
- 字符串新增方法
//实例方法 xxx.trimStart // 删除掉字符串头部的空格 xxx.trimEnd // 删除掉字符串尾部的空格 xxx.padStart xxx.padEnd(maxLength, fillingString) //第一个参数指定 最终的字符串的最大长度 第二个参数 指定重复添加的字符串 xxx.includes // 检测字符串中是否包含 指定的字符串 xxx.startsWith xxx.endsWith xxx.repeat // 重复当前字符串 n次
解构赋值: ☆☆☆☆☆
- 数组解构赋值
- 对象解构赋值
- 函数参数的解构
function say({name, age}) {
console.log(`${name}, ${age}`)
}
let o = {name: "ngnice", age: 10}
say(o);
数值扩展 ☆☆
- 二进制和八进制的表示法
(1)二进制 以 0B 或者 0b开头
0b101 === 5 // true
(2)以0o 开头表示八进制
0o101 === 65 // true
-
Number
增加静态方法
// 是否有限, 对于非数字和Infinity 都返回false
Number.isFinite(1) // true
Number.isFinite(Infinity) // false
Number.isFinite("1") //false
// Number.isNaN 判断是否是一个非数字
// 如果参数类型不是NaN,Number.isNaN一律返回false。
Number.isNaN(NaN) // true
Number.isNaN(9)// false
Number.isNaN(null) //false
// 将parseInt 和 parseFloat 移植到了Number类上
Number.parseInt(90.2) //90
- Math对象新增了一些数学函数方法
Math.trunc()
正则扩展 ☆
数组扩展 ☆☆☆☆☆
- 扩展运算符 更像 rest的逆运算
const arr = [1,3,4,4];
console.log(...arr);
// 可以用于重新组合数组
let newArr = [1,22,...arr];
-
Array.from
将其他类数组对象转换成数组
Array.from(arguments)
Array.from(document.querySelectorAll('xxx'))
Array.from(new Set([1,2,3,34]))
注意点:
(1)和扩展运算符区别:Array.from 能将任何类似数组的结构转换成数组(即用于length属性)
(2)扩展运算符本质上是调用被转换对象的iterator接口,所以只有部署了这个接口的对象才能使用。
-
Array.of
将一组值转换成数组
Array.of(1,2,3,43) // [1, 2, 3, 43]
- 其他的一些方法
arr.find(callback) // 返回符合条件的第一个元素
arr.findIndex // 类似 arr.indexOf
xxx.includes()// 是否包含某个值
xxx.keys xxx.values xxx.entries
arr.fill(value, start, end) // 使用指定的值 填充指定的范围,范围不包含结束位置
- flat 拍平数组 参数拍平多少层:1,2,3, ...Infinity
[1, [2, [3]]].flat(Infinity) //[1, 2, 3]
对象扩展 ☆☆☆☆☆
- 属性简写和方法简写
// es6 之前
var name = "ngnce";
const obj = {
name: name,
log: function () {
console.log(this.name)
}
}
// es6之后
var name = "ngnce";
const obj = {
name,
log () {
console.log(this.name)
}
}
- 属性名表达式
// 对象属性声明的两种方式
var obj = {};
obj.name = "aaa";
// or
var property = "name"
obj["last"+property] = "ngnice";
es6之前,针对对象字面量这种声明方式 无法使用第二种,现在我们就可以了
var obj = {
// 属性可以使用第二种方法声明
[["last"+property]: "ngnice",
log() {
}
}
- 扩展运算符,解构赋值
- ES2020 新增的链判断运算符,类似dart中的语法
var a = obj?.other?.age||'default';
-
==
和===
以及Object.is
(1)== 非严格相等 判断两个值相等: 会进行类型自动转换
(2)=== 严格相等: 不会进行类型转换,对于基础类型只判断其值是否相等,对于引用类型 判断其内存地址是否相等,了NaN=== NaN 不成立
(3)Object.js 在=== 基础上修正了两点:Object.is(NaN, NaN)//true Object.is(+0, -0)//false
-
Object.assign
对象合并
(1) 浅拷贝
(2)使用对象的扩展运算符 能达到类似的效果
var a = {name: "ngnice"};
var b = {age: 10}
var c = Object.assign({}, a,b);
// or
var c = {...a, ...b}
函数扩展 ☆☆☆☆☆
- 函数参数默认值
// es6 之前
function log(msg) {
msg = msg || "默认值";
console.log(msg)
}
// es6 之后 更加清晰 简洁
function log(msg = "默认值") {
console.log(msg)
}
注意点:
(1)参数变量是默认声明的,所以不能用let或const再次声明。
(2)参数默认值是惰性求值的。
(3)参数默认值 一般设置 最后一个参数 否则 无法省略
- rest参数
可以用来替换 arguments , 而且 arguments是一个类数组对象,不能直接使用数组的实例方法, 但是 rest参数就是一个数组
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(1,2,3)// 6
- 箭头函数
var log = (msg="aaa")=>console.log(msg);
箭头函数有几个使用注意点。
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。
(4)不可以使用yield命令,因此箭头函数不能用作 Generator 函数。
错误使用
const cat = {
lives: 9,
jumps: () => {
this.lives--;
}
}
cat.jumps();
- 函数的name属性 标准化
注意点:
(1)bind返回的函数,name属性值会加上bound前缀。
function foo() {};
foo.bind({}).name // "bound foo"
(function(){}).bind({}).name // "bound "
- 尾调用优化
新数据结构 Set
Map
☆☆☆☆☆
Set
集合,是一种值不重复的数组WeakSet
(1)只能存储引用类型的值 不能存储基础数据类型
(2)弱引用 无法遍历 可以防止内存泄露Map
和Object
类似 区别在于Map
的键 可以是对象类型WeakMap
的键只能是对象类型,并且不计入垃圾回收机制
类 ☆☆☆☆☆
- 定义对象
es6之前一直使用构造函数来定义类, 看起来和普通的函数无任何的区别
function Person() {
}
(1) 必须先定义 后使用,不存在声明提升
es6提供了class关键字来定义一个类,和传统的OOP 语言 更加接近。
- 属性和方法声明
(1)属性声明
class foo {
// 写在类中的顶层
bar = 'hello';
baz = 'world';
constructor(age) {
// 可以放到这里
this.name = "ngnice";
this.age = age;
}
}
(2)静态方法 static
,静态属性 Person.staticProp
, 新提案中提出用static关键字
(3)私有属性和方法:使用_命名方式(没有代码实际效果,只是约定编码规则),使用Symble实现
- 构造函数
(1)必须和new一起使用 -
super
关键字
(1)super
函数 一般用于子类继承父类,在子类中的构造函数中调用;ES6 规定 必须在子类中的构造方法中调用super方法 否则就会在new的时候 报错。
(2)在子类的方法中调用 指向父类的原型对象, 所以无法直接通过super.prop
访问父类的属性,调用的父类方法中的this 指向当前子类实例对象。
class N {
name = "ngnice";
age = 10;
say() {
console.log(this.name);
}
}
class NN extends N {
name = "new name";
log() {
console.log(super.age)
}
log1() {
super.say();
}
}
var n = new NN();
n.log();// 输出undefined
// log1中的this 指向当前子类实例对象 所以输出的是 new name
n.log1();// 输出 new name
(3)在子类的静态方法中调用 指向父类对象
-
__proto__
属性和prototype
属性
(1)对象的__proto__
属性 指向 构造函数的原型对象(初始化这个对象的构造函数的原型对象)
class Animal{
}
let cat = new Animal();
cat.__proto__ === Animal.prototype //true
// 普通字面量
var a = 2;
a.__proto__ == Number.prototype
(2)实例对象(通过new 关键字创建的对象)没有prototype
属性, 只有函数 或者是 通过class关键字定义的类才有这个属性 指向当前类的原型对象。
(3)可以通过instanceof
来判断是否是某个类的实例对象
总结来说:
针对实例对象来说(不论是通过new 命令创建的还是通过字面量创建)__proto__
都是指向构造类的原型对象prototype
对于类来说 如果是通过extends
关键字实现继承的 子类的__proto__
指向的是父类,如果是通过ES5方式实现继承的话,还是指向 构造函数的原型Function.prototype
- ES6中的继承
(1) ES6中的继承 通过关键字extends
实现。
(2)和ES5中的继承对比,ES5中的继承本质上是先创建子类实例对象this
,然后将父类的属性和方法添加到新的子类上
function People(name, age) {
this.age = age;
this.name = name;
}
People.prototype.say =function () {
console.log(`${this.name} is ${this.age}`);
}
// 定义子类 继承 父类
function Xiaoming(name, age, sex) {
// this 已经存在
this.sex = 1;
// 实例属性通过下面的方法 添加到子类实例对象上
People.apply(this, [name, age]);
}
// 通过原型拷贝的方式 给子类的原型上添加父类的方法 实现继承
Xiaoming.prototype = Object.create(People.prototype)
// 对子类对象的构造函数属性进行修正
Xiaoming.prototype.constructor = Xiaoming
可以参考:Object.create实现继承
es6中实现继承就比较简单了(代码摘自阮一峰老师的教程)
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class ColorPoint extends Point {
// 子类中如果显式的声明 constructor 构造函数
//必须在构造函数中添加 super调用,并且在此调用之前无法获取到`this`的引用。
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正确
}
}
(3)通过Object.getPrototype
可以判断是否继承了另外一个类
对于ES6 class关键字声明的类
Object.getPrototype(ColorPoint)
对于ES5语法 声明的类 需要判断的是其prototype
Object.getPrototype(Xiaoming.prototype)=== People.prototype
本质上是 原型对象上的__proto__
指向的原型对象
ColorPoint.__proto__ === Point
Xiaoming.prototype.__proto__ === People.prototype
模块 ☆☆☆☆☆
模块化的意义: 针对中大型编程中而言,我们实际上有两个述求,
(1) 能使用第三方模块提供通用的功能实现(不需要重复造轮子)可能有同学说重复造轮子难道不好吗,如果针对业务需求开发,你会发现如果通用的功能模块都有优秀的第三方实现,我们只需要开发业务逻辑部分的开发是多么的幸福;
(2) 则是针对业务逻辑通用代码或者是没有开源的第三方模块,需要我们自己去实现,在组织我们大量的业务逻辑代码的时候,模块化的机制是多么的重要,方便协作的同事和自己后续维护。
(1)封装、解耦、分治
(2)防止全局污染
(3)代码组织方式
总结
到最后还是能总结到软件编程中的两个永恒的话题:效率和质量。
-
javascript
中的模块化之路
(1)commonjs
(2)amd/cmd
(3)es6中的模块化
ES6的模块 和 commonjs模块
(1)ES6 模块是引用
(2)commonjs 模块是拷贝
promise ☆☆☆☆☆
前端异步编程的解决方案之一,改写了callback
模式
Iterator和 for of
遍历器概念
javascript中目前存在的数据集合 Set
Map
Array
Object
- 为了给数据集合提供统一的访问接口,
ES6
提供了Iterator
接口,和for of
-
ES6
规定Iterator
接口部署在数据集合的Symbol.iterator
属性上,Iterator
是一个函数 返回一个对象,包含一个next
的的方法,用来移动内部指针,遍历数据集合。 -
ES6
中的有些数据结构原生部署了Iterator
接口
Array
Map
Set
String
TypedArray
函数的 arguments 对象
NodeList 对象
以数组做例子:
var arr = [1,2,2];
var iterator = arr[Symbol.iterator]()
iterator.next();// {value: 1, done: false}
iterator.next();//{value: 2, done: false}
iterator.next();//{value: 2, done: false}
iterator.next();//{value: undefined, done: false}
- 有些场合默认会调用
Iterator
接口
(1)解构赋值
(2)扩展运算符
(3)yield* 跟上部署了遍历器接口的数据结构
(4)其他一些应用
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
Promise.all()
Promise.race()
-
Iterator
接口除了必须设置实现的next方法,还可以部署return
throw
方法
var obj = {
num: 10,
[Symbol.iterator]() {
return {
next() {
if(obj.num -- == 0) {
return {done: true, value: obj.num}
} else {
return {done: false, value: obj.num}
}
},
return() {
// logic code
console.log('return function')
// 返回值 必须是一个对象
return {
done: true,
value: "ngnice"
}
}
}
}
}
// break可以触发 return
for(let a of obj) {
console.log(a);
if(a < 4) {
break;
}
}
// throw error也可
for(let a of obj) {
console.log(a);
if(a < 4) {
throw new Error('ngnice');
}
}
for of
就是用来消费部署了Iterator
接口的数据集合。
var set = new Set([1,2,3,4]);
for( let item of set ) {
console.log(item)
}
// 注意map
var m = new Map()
m.set('name', 'ngnice')
m.set('age', 10)
for(let item of m) {
console.log(item)
}
// ["name", "ngnice"]
// ["age", 10]
generator ☆☆☆☆☆
generator 函数
-
generator
书写上和普通函数不同的地方就是 函数名和function
关键字之间需要添加一个*
号, 并且函数体内部可以通过yield
定义多个状态(或者是任务)。 - 在执行效果上,和普通普通函数不同的地方就是可以暂停当前函数的执行。
- 是
ES6
中提供解决javascript
中异步编程解决方案的方案之一。
示例代码:
function *gen(x) {
var a = yield x+1;
yield a+x;
return 11;
}
// 调用之后 返回的是一个部署了iterator 接口的对象 而不是函数的返回值
var g = gen(10);
// 需要调用遍历器的next方法
g.next();// {value: 11, done: false}
g.next(10);// {value: 20, done: false}
g.next(); // {value: 11, done: true}
g.next(); // {value: undefined, done: true}
注意点:
-
generator
函数执行之后 不会返回函数的返回值,而是返回一个部署了可迭代协议的对象,因此可以使用for of
遍历。 - 通过调用
next
方法执行yield
后面的表达式,而yield
并不会有返回值,或者说是默认返回undefined
,但是可以通过next
方法传入参数,将作为上一个yield
表达式的返回值。 - 每次执行
next
方法,都会依次执行yield
后面的表达式,并且暂停在哪里,直到下一个next的调用, - 如果后面没有
yield
语句,就将函数的返回值作为value
值 返回done
属性为true
. -
yield
命令只能使用在generator
方法中。
generator
的异常处理
我们在进行异常捕获的时候 使用try catch
语句块,但是只能捕获try
语句块中的代码异常。
但是 Generator
因为它可以暂停执行,交出控制权,所以允许我们捕获代码块之外的异常。
function *gen() {
try {
yield a;
console.log('10');
yield 11;
console.log('11')
} catch(err) {
console.log(err);
}
}
var g = gen();
g.next();
g.throw('outer error');
Generator
的return
方法
类似普通函数的return 作用,它可以结束Generator
的执行,提前将done
属性改成 true
,如果return
提供了入参的话,将作为value
值。
如果存在try finally
则会直接跳到finally中执行完成。
function *gen() {
yield "ngnice";
yield console.log("a");
try {
console.log(yield 'nice');
} finally{
console.log('finally');
yield 1;
yield 2;
}
}
var g = gen();
g.next();
g.return("return value");
对比:
function *gen() {
yield "ngnice";
yield console.log("a");
try {
console.log(yield 'nice');
} finally{
// 如果finally代码块中还存在yield语句 将不会结束
console.log('finally');
yield 1;
yield 2;
}
}
var g = gen();
g.next();
g.next();
g.next();
g.return("return value");
yield * 表达式 可以获取generator
函数生成对象的内部值。
function* gena () {
yield 1;
yield 0;
yield 2;
yield 3;
}
function *genB() {
yield 'a';
yield* gena();
yield 'last';
}
var g = genB()
g.next();
g.next();
g.next();
generator
的应用
异步操作的同步化表达
-
javascript
中thunk
函数将多参数函数转换成 接受一个回调函数的参数形态。 - 通过在回调函数中 递归的调用
generator
生成的对象的next方法实现了 自动化运行。 - 那么出了回调函数这种方式,也可以通过
promise
的这种方案实现(本质上还是回调实现) - 通过
next
方法 可以将数据从回调中 传递出来
代码具体实现, 我们可以参考TJ大神的co函数库
async
一句话 就是 async 就是 generator在异步编程应用的语法糖