ES6被认为是 下一代 javasciprt 的标准。
本质:变量指向的内存地址不得改动。
块级作用域内声明函数的行为类似于var 声明。
function f() { console.log('I am outside!') }
(function() {
if(false) {
function f() { console.log('I am inside!'); }
}
f();
}());
ES5中,顶层对象的属性与全局变量是等价的。
ES6中,var 命令和 function 命令声明的全局变量依旧是顶层对象的属性;而 let 命令、const 命令、class 命令声明的全局变量不属于顶层对象的属性。
解构赋值本质:“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
只要某种数据结构具有 Iterator接口,都可以采用数组形式的解构赋值。
includes() startsWith() endsWith()
padStart() padEnd()
新增 u 修饰符,含义为 Unicode 模式;用于正确处理 UTF-16 编码
新增 y 修饰符,叫作“粘粘”修饰符
正则表达式的分组
Number.isInteger()
javascipt 能准确表示的整数范围在-2^53 到 2^53 之间(不含两个端点),超过这个范围就无法精确表示
Math.trunc():除去小数部分
Math.sign():判断正负零
Math.cbrt():计算立方根
Math.log10():log10(x)
Math.log2():log2(x)
指数运算符: 2 ** 3 === 8
只有当传入的参数 强等于 undefined,才会触发默认值。
// 写法一
function m1({x = 0,y = 0} = {}) {
return [x,y];
}
// 写法二
function m2({x, y} = {x: 0, y: 0}) {
return [x,y];
}
通常情况下,默认值的参数应该是函数的尾参数。如果非尾参数的参数设置默认值,实际上这个参数无法省略。
function f(x = 1,y) {
return [x,y];
}
f(); // [1,undefined]
f(2); // [2,undefined]
f(,1); // 报错
f(undefined,1);// [undefined,1]
参数的默认值不是在定义时执行,而是在运行时执行。
注意事项:
某个函数的最后一步是调用另一个函数
尾调用自身。可是由于只存在一个调用帧,所以永远不会发生“栈溢出”错误。
// 阶乘函数
function factorial(n) {
if(n === 1) return 1;
return n * factorial(n - 1);
}
// 改造为 尾递归
function factorial(n,total) {
if(n === 1) return total;
return factorial(n - 1,n * total);
}
将多参数的函数转换成单参数的形式。
找出数组中的最大元素
Math.max(...[14,3,77]);
// 等同于
Math.max(14,3,77);
合并数组
// es5
arr1.concat(arr2,arr3);
// es6
[...arr1,...arr2,...arr3];
与解构赋值结合,生成数组
如果扩展运算符用于数组赋值,则只能将其放在参数的最后一位,否则报错。
函数的返回值
将字符串转换成数组
// es5
const arr = str.split('');
// es6
[...'hello'];
// ['h','e','l','l','o'];
实现了 Iterator 接口的对象都可以用扩展运算符转为真正的数组
Map和Set结构,Generator函数 也可以使用扩展运算符
Array.from()
方法用于将两类对象转换为真正的数组:
类数组对象(含 length 属性的对象)
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
// es5 的写法
var arr1 = [].slice.call(arrayLike);// ['a','b','c'];
// es6 的写法
let arr2 = Array.from(arrayLike);
可遍历对象(实现了接口 iterable)
参数列表:还接收第二个参数,作用类似于数组的map方法;第三个参数,绑定this
Array.from(arrayLike,function(item) {
// map
});
将一组值转换为数组;为了弥补数组构造函数Array()的不足
模拟实现:
function ArrayOf() {
return [].slice.call(arguments);
}
都返回一个遍历器对象,可用 for…of遍历;
还可以手动调用遍历器对象的 next 方法进行遍历。
在某种情况下,可以用于替换 indexOf ;indexOf 其内部用严格相等运算符,会导致 NaN 的误判
而 includes 使用的是不一样的判断算法,就没有这个问题
属性名表示法 和 简洁表示法
行为与 === 基本一样;不同之处只有两个:
执行的浅复制,而不是深复制。遇到同名属性,后面的属性替换前面的属性。
为对象添加属性
class Point {
constructor(x, y) {
Object.assign(this,{x,y});
}
}
相当于将x属性和y属性添加到Point类的对象实例中。
为对象添加方法
Object.assign(SomeClass,prototype, {
someMethod(arg1, arg2) {
},
anotherMethod() {
}
});
克隆对象
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign({},assign);
}
合并多个对象
为属性指定默认值
const options = Object.assign({},DEFAULTS,options);
由于Object.assign() 为浅复制,故会替换引用类型属性值,而不是拼接
对象的每一个属性都有一个描述对象,用于控制该属性的行为
Object.getOwnPropertyDescriptor
方法可以获取该属性的描述对象。如:
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj,'foo');
// 输出结果
{
value: 123,
writable: true,
enumerable: true,
configurable: true
}
其中 enumerable 属性称为“可枚举性”,如果为false,则有的操作会忽略当前属性
从上述描述中可以看出:在遍历对象自身的属性时,尽量不用使用 for…in 循环,而用 Object.keys() 代替。
_proto_
属性此处叫作隐式原型,本质上是一个内部属性,而不是一个正式的对外的API。
只有浏览器必须部署这个属性,其他运行环境不一定要部署。
因此,无论从语义的角度,还是从兼容性的角度,都不要使用这个属性。取而代之用Object.setPrototypeOf()
写操作、Object.getPrototypeOf()
读操作或 Object.create()
生成操作。
Object.setPrototypeOf(object, prototype);
// 相当于
function (obj, prototype) {
obj.__proto__ = prototype;
return obj;
}
Object.keys() 返回 不含继承的,自身的,所有可遍历的 属性的键名
Object.values() 返回 对象自身的,过滤属性名为Symbol 的可遍历属性
Object.entries() 返回 对象自身的,不含继承的所有可遍历的属性的键值对数组
此方法的另一个用处是将对象转为真正的Map 结构
解构赋值的复制是浅复制
解构赋值必须是最后一个参数,否则会报错。
用于合并两个对象:b 会覆盖 a 的同名属性
let ab = { ...a, ...b };
// 等同于
let ab = Object.assgin({},a,b);
如果用户自定义的属性放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖。
let aWithOverrides = { ...a, x: 1, y: 2};
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值
let aWithDefaults = { x: 1,y: 2, ...a};
// 等同于
let aWithDefaults = Object.assign({},{ x: 1, y: 2}, a);
ES2017引入该方法,返回指定对象所有属性(非继承)的描述对象
const firstName = message?.body?.user?.firstName || 'default';
成员值唯一,没有重复。
Set 函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
去除数组重复成员的方法:
[...new Set(array)]
注意:向Set加入值时不会发生类型转换。类似于精确相等运算符(===),主要区别是NaN 等于自身。
使用Set 可以很容易地实现并集(Union)、交集(Interset)和差集(Differrence)。
// 集合
let a = new Set([1,2,3]);
let b = new Set([4,3,2]);
// 并集
let union = new Set([...a,...b]);
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
规定WeakSet 不可遍历
Map 的键的范围不限于字符串,各种类型的值(包括对象)都可以当作键。
Map 的键实际上是和内存地址绑定的,只要内存地址不同,就视为两个键。
注意:WeakMap 弱引用的只是键名而不是键值。键值依然是正常引用的。
ES6中的 class 可以看做只是一个语法糖
类的数据类型就是函数,类本身指向构造函数
**注意:**类的内部定义的所有方法都是不可枚举的
类必须用 new 来调用,否则会报错。这是它跟普通构造函数的一个主要区别,后者不用 new 也可以执行。
使用 class 表达式,可以写出立即执行的Class
类不存在变量提升,这点与ES5完全不同
私有方法虽是常见需求,但是ES6 不提供。
提案:在属性面前加 # 号
类的 name 属性总是返回紧跟在 class 关键字后面的类名。
在类的内部可以使用 get 和 set 关键字对某个属性设置getter 和 setter ,拦截该属性的存取行为。
存值函数和取值函数是设置在属性的 Descriptor 对象上的。
在方法前添加 static 关键字
父类的静态方法可以被子类继承
静态方法也可以从super 对象上调用
提案:Class 内部只有静态方法,没有静态属性。
提案:实例属性用等式写入类的定义之中。
目前如果要给类加入实例属性,需要在构造函数中往 this 挂载属性
new 是从构造函数生成实例的命令。
返回new 命令所作用的构造函数。如果不是通过 new 命令调用的,则返回 undefined。
通过这个属性,可以知道一个函数是作为构造函数使用了,还是作为普通函数调用了。
子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。
这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工。如果不调用 super 方法,子类就得不到 this 对象。
ES5 和 ES6 的继承实质:
Parent.apply(this)
基于ES6的原理;只有调用 super 之后才可以使用 this 关键字
此方法可以从子类上获取父类,因此可以使用这个方法判断一个类是否继承了另一个类。
Object.getPrototypeOf(ColorPoint) === Point;
super 关键字既可以当函数使用,也可以当对象使用。
super 作为函数调用时代表父类的构造函数。
**注意:**super 虽然代表了父类A 的构造函数,但是返回的是子类B 的实例,即 super 内部的 this 指向的是B ,因此 super() 在这里相当于 A.prototype.constructor.call(this)。
**注意:**作为函数时,super() 只能用在子类的构造函数之中,用在其他地方就会报错。
super 作为对象时在普通方法中指向父类的原型对象;在静态方法中指向父类。
**注意:**ES6规定,通过 super 调用父类的方法时,super 会绑定子类的 this。
__proto__
属性Class 作为构造函数的语法糖,同时有 prototype 属性和 __proto__
属性,因此同时存在两条继承链。
__proto__
属性表示构造函数的继承,总是指向父类。__proto__
属性表示方法的继承,总是指向父类的 prototype 属性。class A {
}
class B extends A {
}
B.__proto__ === A; // true
B.prototype.__proto__ === A.prototype; // true
类的继承是按照如下模式实现的:
// B 的实例继承 A 的实例
Object.setPrototypeOf(B.prototype, A.prototype);
// B 的实例继承 A 的静态属性
Object.setPrototypeOf(B, A);
而上述代码等同于:
Object.setPrototypeOf(B.prototype, A.prototype);
// 等同于
B.prototype.__proto__ = A.prototype;
Object.setPrototypeOf(B, A);
// 等同于
B.__proto__ = A;
有三种特殊情况需要注意:
子类继承 Object 类
class A extends Object {
}
A.__proto__ === Object; // true
A.prototype.__proto__ === Object.prototype; // true
不存在任何继承
class A {
}
A.__proto__ === Function.prototype; // true
A.prototype.__proto__ === Object.prototype; // true
这种情况下,A作为一个基类(即不存在任何继承)就是一个普通函数,所以直接继承 Function.prototype。但是,A调用后返回一个空对象,即const a = new A(); a instanceof Object; //true
,所以
A.prototype.__proto__
指向构造函数(Object)的prototype 属性。
子类继承 null
class A extends null {
}
A.__proto__ === Function.prototype; // true
A.prototype.__proto__ === undefined; // true
ECMAScript 的原生构造函数大致有:
在ES5的继承中,这些原生构造函数是无法继承的。
这是因为:原生构造函数会忽略 apply 方法传入的 this,也就是说,原生构造函数的 this 无法绑定,导致拿不到内部属性。
可是ES6的继承可以自定义原生数据结构(比如Array、String等)的子类,这是ES5无法做到的。
如下是使用ES6实现的带版本功能的数组:
class VersionedArray extends Array {
constructor() {
super();
this.history = [];
}
commit() {
this.history.push(this.slice());
}
revert() {
this.splice(0,this.length,...this.history[this.history.length - 1]);
}
}
const x = new VersionedArray();
x.push(1);
x.push(2);
x // [1, 2]
x.history // [[]]
x.commit();
x.history // [[], [1, 2]]
x.push(3);
x // [1, 2, 3]
x.revert();
x // [1, 2]
但是需要注意的是,继承Object的子类有一个行为差异
这是因为ES6改变了 Object 构造函数的行为,一旦发现Object 方法不是通过 new Object() 这种形式调用,ES6规定 Object构造函数会忽略参数。
Mixin 模式:将多个类的接口“混入”另一个类,ES6实现如下:
function mix(...mixins) {
class Mix {}
for(let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
function copyProperties(target, source) {
for(let key of Reflect.ownKeys(source)) {
if(key !== 'constructor' && key !== 'prototype' && key !=='name') {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
class A {
a() {
console.log('A.a()');
}
b() {
console.log('A.b()');
}
}
class B {
b() {
console.log('B.b()');
}
}
class AA extends A {
aa() {
console.log('AA.aa()');
}
}
// A 将 覆盖 B的 同名方法
// 且只能 mix 子类的方法, 父类的方法无法获取
class C extends mix(B,AA) {
}
const c = new C();
c.a(); // 报错