ECMAScript 6 常用特性整理
说明
看了 阮一峰 老师的 ECMAScript 6 入门,决定将之前一直使用到的 ES6 重新整理一遍。
1. let 和 const
用法类似
var
用来声明变量,但是声明的变量只在命令所在的代码块中有效不存在变量提升
暂时性死区, 在变量用 let 声明前的代码中 只要使用到变量,就会报错
不允许重复声明变量
const 声明的是一个只读的常量,声明后值不可以改变
对于对象和数组来说,变量指向内存地址,保存的只是一个指针,const 声明的变量可以保证指针不变,但是指向的数据结构发生变化就是不可控的
2. 块级作用域
let 和 const 声明的变量存在块级作用于, ES6 允许块级作用域的任意嵌套,外层的作用域无法读取内层作用域的变量
应该避免在块级作用域内声明函数,如果需要声明函数,要使用函数表达式
3. 变量的解构赋值
解构赋值等号的右边需要时可遍历结构
1. 数组的解构赋值
let [a, [b], d] = [1, [2, 3], 4];
console.log(a, b, d); // 1, 2, 4
// 等号右边需要时可遍历结构
let [f] = 121212;
console.log(f); // 报错
// 指定默认值
let [foo = true] = [];
console.log(foo); // true
// ES6 内部使用严格相等 === 判断一个位置是否有值,=== undefined 才会使用默认值
let [x = 1] = [undefined];
let [y = 1] = [null];
console.log(x, y); // 1 null
// 惰性求值
function f () { // 未执行,只有 x 取不到值的时候才会执行
console.log('aaa');
}
let [x = f()] = [1];
console.log(x); // 1
// 默认值引用解构赋值的其他变量
let [x = 1, y = x] = [];
console.log(x, y); // 1 1
let [x, y = x] = [1];
console.log(x, y); // 1 1
let [x = y, y = 1] = [];
console.log(x, y); // 报错
2. 对象的解构赋值
let {a: aa} = {a: 1, b: 2}; // 想找到同名的属性 'a' 然后复制给变量 'aa'
console.log(aa); // 1
// 变量的解构赋值是变量声明和赋值一体的,所以要赋值的变量不能提前声明
let a = 12;
let [a] = [1, 2, 3];
console.log(a); // 报错
let a = 12;
let b;
[a] = [1, 2, 3];
({b} = {b: 1}); // 需要放在'()'里 否则会把 {b} 解析为代码块
console.log(a, b); // 成功 1 1
// 数组是特殊的对象
let {0: fir, 1: sec} = [1, 2, 3];
console.log(fir, sec); // 1 2
3. 字符串的解构赋值
// 字符串会被转换成类数组对象,含有 length 属性
let [a, b] = 'hello';
console.log(a, b); // h e
let {length: len} = 'hello';
console.log(len); // 5
4. 函数参数的解构赋值
function f([a,b]) {
return a + b;
}
console.log(f([1, 2])); // 3
// 使用默认值
function f ({x = 'x', y = 'y'} = {}) {
console.log([x, y]);
}
f(); // ["x", "y"] 不传入参数 参数按照默认值解构 {x = 'x', y = 'y'} = {} ({},是参数中的)
f({}); // ["x", "y"] 传入参数 参数按照传入的值解构 {x = 'x', y = 'y'} = {} ({}, 是传入的 {})
f({x: 1}); // [1, "y"] 传入参数 参数按照传入的值解构 {x = 'x', y = 'y'} = {x: 1}
// 另外一种默认值的写法
function f ({x, y} = {x: 'x', y: 'y'}) {
console.log([x, y]);
}
f(); // ["x", "y"] 不传入参数 参数按照默认值解构 {x, y} = {x: 'x', y: 'y'}
f({}); // [undefined, undefined] 传入参数 参数按照传入的值解构 {x, y} = {}
f({x: 1}); // [1, undefined] 传入参数 参数按照传入的值解构 {x, y} = {x: 1}
4. 字符串扩展
模板字符串
// 一般写法
`hello hello`
// 多行
`
hello hello
hello
`
// 变量
`hello ${name} hello`
更多字符串方法 javascript字符串方法汇总
5. 数值的扩展
1. Number.isFinite() 是否为有限的
Number.isFinite(1); // true
Number.isFinite(NaN); // false
Number.isFinite(Infinity); // false
Number.isFinite(true); // false
2. Number.isNaN() 是否为 NaN
Number.isNaN(NaN); // true
Number.isNaN(15); // false
3. Number.parseInt(), Number.parseFloat() 移植到 Number 上了
4. Number.isInteger() 判断值是否为整数
Number.isInteger(3.3); // false
Number.isInteger(3.0); // true
5. Math.trunc() 除去数字的小数部分,返回整数部分 (内部使用 Number 方法装换为数值)
Math.trunc(123.22); // 123
Math.trunc('123.22'); // 123
Math.trunc(true); // 1
Math.trunc(null); 0
Math.trunc(undefined); // NaN
Math.trunc({}); // NaN
Math.trunc([]); // 0
Math.trunc('a123'); // NaN
6. 数组的扩展
1. Array.from() 将类数组对象转换为真正的数组
let obj = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
}
const arr = Array.from(obj);
console.log(arr); // ["a", "b", "c"]
function f () {
const args = Array.from(arguments, (x) => x + '--add');
console.log(args);
}
f('a', 'c'); //["a--add", "c--add"]
2. Array.of() 将一组数值转化为数组
const arr = Array.of(12,12,12);
console.log(arr); // [12, 12, 12]
3. find() findIndex() 找到第一个符合条件的数组成员,参数是一个回调函数,数组成员依次执行回调,直到第一个返回值为true的成员,然后返回此成员或者所在index,没有则返回undefined
const member = [22, 323, 424, -12, 0].find((value, index) => {
return value < 0;
})
console.log(member); // -12
const member = [22, 323, 424, -12, 0].findIndex((value, index) => {
return value < 0;
})
console.log(member); // 3
更多数组方法,在 Javascript 数组方法汇总 中
7. 函数的扩展
ES6 中的函数参数可以设置默认值,函数进行声明初始化的时候,参数会形成一个单独的作用域,初始化结束后这个作用域就会消失,这种行为在不设置参数默认值时是不存在的
// 参数中 y 默认等于 x 变量,调用函数是,参数新城单独作用域,y 指向参数 x 而不是全局的 x
var x = 1;
function f(x, y = x) {
console.log(y);
}
f(2) // 2
// foo 在 bar 函数的参数作用域中没有定义,所以指向全局的 foo
let foo = 'outer';
function bar(func = x => foo) {
let foo = 'inner';
console.log(func());
}
bar(); // outer
1. rest 参数, 有点类似逆向的扩展运算符
function f (...args) {
console.log(args); // [1, 2, Array(2), function]
}
f(1, 2, [11, 22], function () {
console.log('in');
});
2. 扩展运算符, 将一个数组转为用逗号分隔的参数序列。
// 函数使用
function f (a, b) {
console.log(a, b);
}
const arr = [123, 456];
f(...arr); // 123 456
// 合并数组
const arr1 = ['a', 'b', 'c'];
const arr2 = [1, 2, 3];
const arr = [...arr1, ...arr2];
console.log(arr); // ["a", "b", "c", 1, 2, 3]
// 结合解构赋值
const arr1 = ['a', 'b', 'c'];
const [fir, ...sec] = arr1;
console.log(fir, sec); // a ["b", "c"]
// 结构赋值时,要放在最后,否则报错
const arr1 = ['a', 'b', 'c'];
const [...fir, sec] = arr1;
console.log(fir, sec); // 报错
// 可以用来将字符串转换成数组
const arr = [...'hello'];
console.log(arr); // ["h", "e", "l", "l", "o"]
3. 严格模式
ES6 中的 严格模式可以设置成全局的,但是在函数中的严格模式需要函数参数不包含 默认值、解构赋值、扩展运算符,否则会报错
4. 箭头函数, 简化回调函数
- 函数体内的 this 对象 就是定义时所在的对象,不是使用时的对象
- 不可以当做构造函数,也就是不也已使用 new 命令
- 不可以使用 arguments 对象,不存在,可以用 rest 参数 代替
- 不可以使用 yield 命令,因此箭头函数不能用作 Generator 函数
// 定义函数, 一个参数
let f = x => x;
// 没有参数 用 ()
let f = () => 'test';
// 多个参数 (x, y)
let f = (a, b) => a + b;
// 代码块多于一行
let f = (a, b) => {
let num = a + b;
return num;
}
8. 对象的扩展
// 对象的一般写法
let foo = 'foo';
let obj = {foo: foo};
// 简洁的写法
let obj = {foo}; // 属性名 = 变量名
// 方法的简写
const obj = {
method(){
console.log('method');
}
}
1. ES6 允许表达式作为对象的属性名或者方法名
// 属性名
let prop = 'foo';
let obj = {
[prop]: true,
['a' + 'b']: 123
};
console.log(obj); // {foo: true, ab: 123}
// 方法名
let prop = 'foo';
let obj = {
[prop]() {
console.log('obj.foo function');
}
}
obj.foo(); // obj.foo function
// 属性名表达式与简洁表示法不能同时用,会报错
let foo = 'bar';
let bar = 'abc';
let obj = {
[foo] // 报错
}
2. Object.is() 比较两个值是否相等,基本与 === 差不多
// 与 === 不一样的地方
NaN === NaN // false
-0 === +0 // true
Object.is(-0, +0) // false
Object.is(NaN, NaN) // true
3. Object.assign() 实现浅拷贝,用于合并对象,将源对象的所有可枚举属性,复制到目标对象
// 第一个参数是目标对象,后面的参数是源对象,有同名的属性,后边的会覆盖前边的
let target = {a: 1, b: 2};
let source1 = {b: 4, c: 8};
let source2 = {c: 6};
const a = Object.assign(target, source1, source2);
console.log(a); // {a: 1, b: 4, c: 6}
console.log(target); // {a: 1, b: 4, c: 6}
// 指定默认值
const defaultVal = {
level: 1,
type: 'html'
}
function f (options) {
options = Object.assign({}, defaultVal, options);
return options;
}
console .log(f({level: 2})); // {level: 2, type: "html"}
4. 对象的扩展运算符
// 解构赋值, ... 必须在最后,并且 = 右边需要是一个对象
let {x, y, ...d} = {x: 'x', p: 'p', y: 'y', c: 'c'};
console.log(d); // {p: "p", c: "c"}
// 作为扩展,可以有多个 ...
let a = {x: '1212', y: '1222'};
let b = {z: 'zzz', y: '12ed'};
let ab = {...a, ...b};
console.log(ab); // {x: "1212", y: "12ed", z: "zzz"}
9. Set
- Set 数据结构类似数组,但是成员的值都是唯一的,没有重复的值,可以用来数组去重
// 初始化
const set = new Set([1, 2, 1, 3 ,2 , 5]);
console.log([...set]); // [1, 2, 3, 5]
// 添加
set.add(123);
set.add(2);
console.log([...set]); // [1, 2, 3, 5]
// 数组去重, 向Set 中加入值的时候不会发生类型转换
let arr = [1, 2, 1, 3 ,2 , 5, '5'];
const output = [...new Set(arr)];
console.log(output); // [1, 2, 3, 5, "5"]
// NaN 与 NaN 比较算是重复的
let arr = [1, NaN];
let set = new Set(arr);
console.log(set.size); // 2
set.add(NaN);
const output = [...set];
console.log(output); // [1, NaN]
- set 实例的方法
let set = new Set([1, 2, 3, 2, 5]);
console.log(set.size); // 4 返回 Set 实例的成员总数
set.add(8); // SetIterator {1, 2, 3, 5, 8} 添加值
set.delete(5); // SetIterator {1, 2, 3, 8} 删除值
set.has(8); // true 是否含有某个值
set.clear(); // [] 清除所有值,没有返回值
for (let item of set.keys()) {
console.log(item); // 1 2 3 5 返回键名,Set 没有键名就返回键值,跟 set.values一样
}
for (let item of set.values()) {
console.log(item); // 1 2 3 5 返回键值
}
for (let item of set.entries()) {
console.log(item); // [1, 1] [2, 2] [3, 3] [5, 5] 返回键值对
}
set.forEach((item, i) => {
console.log(item, i); // 遍历
});
- set 实现并集、交集、差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 2, 3]);
// 并集
const union = new Set([...a, ...b]);
console.log(union); // Set(4) {1, 2, 3, 4}
// 交集
const intersect = new Set([...a].filter(x => b.has(x)));
console.log(intersect); // Set(2) {2, 3}
// 差集
const diff = new Set([...a].filter(x => !b.has(x)));
console.log(diff); // Set(1) {1}
10. Map
Object 对象是键值对的集合,但是传统上只能用 字符串做键,而 Map 结构上类似 Object 也是键值对的集合,但是键的范围不限制于字符串,各种类型的值都可以作为键,如果键的值是引用类型则,键实际上是指针指向的地址
const a = new Map();
const o1 = {name: 'xiaoming'};
const o2 = {name: 'xiaoming'};
// set 方法用于设置键值对
a.set(o1, 'this is xiaoming');
// has 方法用于判断是否含有值
console.log(a.has(o1)); // true
// o1 和 o2 是值相同但是指向不同内存地址的对象
console.log(a.get(o1)); // this is xiaoming
console.log(a.get(o2)); // undefined
// o1 作为键内存地址不改变但是值变了,Map 依然可以通过 get 方法取到值,证明 键实际上是指针的指向地址
o1.age = 23;
// get 方法用于获取值
console.log(a.get(o1)); // this is xiaoming 不变
// delete 方法用作删除
a.delete(o1);
console.log(a.has(o1)); // false
// 设置多个键值对
const b = new Map([
['name', 'xiaoming'],
['age', 90]
])
console.log(b.has('name')); // true
console.log(b.has('age')); // true
// size 属性 返回 map 成员总数
console.log(b.size); // 2
// clear 方法清除所有成员
b.clear();
console.log(b.has('name')); // false
const b = new Map([
[{name: 'firstName'}, 'xiaoming'],
['age', 90]
])
// 返回键名
for (let key of b.keys()) {
console.log(key); // {name: "firstName"} age
}
// 返回键值
for (let value of b.values()) {
console.log(value); // xiaoming 90
}
// 返回所有成员
for (let item of b.entries()) {
console.log(item); // [{name: "firstName"}, "xiaoming"] ["age", 90]
}
// 遍历所有成员
b.forEach(function (value, key, map) {
console.log(value, key);
// xiaoming {name: "firstName"}
// 90 "age"
})
11. Class
javascript 中,生成实例对象的传统方法是通过构造函数,es6 引入 Class 作为对象的模板,通过 Class 定义类,但是 es6 的 Class 只是一个语法糖,他只是在写法上更像面向对象编程的语法而已,es6 的类可以看做是构造函数的另外一种写法
class Parent {
/*
通过 new 生成实例对象时,自动调用该方法,
一个类必须有此方法,如果没有显示定义,空的constructor会被自动添加
constructor 方法默认返回实例对象 即 this
*/
constructor(x, y) {
/*
这里的 this 指向实例化后的对象,
*/
this.attrs = {
a: 'a'
};
}
// 定义实例属性 constructor 中也可以通过 this 定义
state = {
name: 'state name'
};
/*
方法之间不能有逗号,
定义的方法都挂载在 Parent.prototype 上了
类的方法中如果有 this 默认指向类的实例,但是如果将方法提取出来单独使用,this 会指向该方法所在的运行环境,
这个时候如果想通过 this 调用 类中定义的方法或属性就会找不到,解决办法是手动绑定 `.bind(this)`
*/
show() {
}
// 静态属性 由类直接调用
static classAttr = 'class attr';
// 静态方法 由类直接调用
static classMethod() {
console.log('static');
}
}
/*
类 必须要通过 new 调用否则会报错(普通构造函数可以直接使用)
不存在变量提升,子类一定要在父类后定义
*/
Parent.classMethod(); // static
console.log(Parent.classAttr); // class attr
const parent = new Parent();
console.log(Parent.name); // Parent
console.log(parent.state); // {name: "state name"}
console.log(parent.attrs); // {a: "a"}
console.log(parent.__proto__ === Parent.prototype); // true
console.log(parent.__proto__.constructor === Parent); // true
console.log(Parent.prototype); // {constructor: function, show: function}
/*
Class 表达式方式定义
me 只用作函数内使用, 不使用的话可以省略 函数外类叫做 MyClass
*/
const MyClass = class me {
getClassName() {
return me.name
}
}
const a = new MyClass();
console.log(a.getClassName()); // me
类的继承,(extends)
- es5 的继承,实质上是先创建子类的实例对象 this ,然后将父类的方法添加到 this 上
- es6 的继承机制完全不同,实质上是先创建父类的实例对象 this (所以必须先调用super方法),然后再用子类的构造函数修改 this,子类中只有调用了 super 方法之后 才可以使用 this 关键字
class Parent {
constructor(name) {
this.name = name;
}
logName(newName) {
console.log(newName);
}
}
// 子类 通过 extends 关键字继承父类
class Child extends Parent {
constructor(age, name) {
/*
super 表示父类的构造函数(constructor(name))
子类必须在 constructor 方法中调用 super 否则会因为子类没有自己的 this 对象 又没有通过 super 继承父类的 this 而报错
*/
super(name);
this.age = age;
}
logAge() {
console.log(this.age)
}
logName() {
console.log('this is in Child');
}
logInfo() {
// 调用 logName 方法,子类覆盖了父类
this.logName(this.name);
// 直接调用父类 的 logName 方法
super.logName(this.name);
this.logAge();
}
}
console.log(Child.prototype.constructor === Child); // true
const child = new Child(20, 'xiaobai');
child.logInfo(); // this is in Child | xiaobai | 20