ES6学习笔记
1. 变量的解构赋值
- 数组解构
let [a, b, c] = [1, 2, 3] // a = 1, b = 2, c = 3
- 对象解构
let {foo, bar} = {foo: 'foo', bar: 'bar'}; // foo = 'foo', bar = 'bar'
2. 箭头函数,rest参数
- 箭头函数
var sum = (num1, num2) => num1 + num2;
var getObj = id => ({id: id, name: 'bob'}) // 如果返回的是个对象,需要用圆括号包起来
- rest 参数
function add(...values) {
let sum = 0;
values.forEach(x => sum+= x);
return sum;
}
add(1, 2, 3); // 6
3. 数组:
-
扩展运算符 …[] 将一个数组转换成用逗号分隔的参数序列。
如...[1,2,3] // 1,2,3
- Array.from() 将类似于数组的对象转换成数组。
-
Array.of() 将一组值转换成数组,
如Array.of(1,2,3) // [1,2,3]
- filter/find/findIndex 找出符合条件的数组成员。
-
includes() 判断数组是否包含某个值
兼容写法:
const contains = (() => Array.prototype.includes ? (arr, value) => arr.includes(value) : arr.some(el => el === value))()
- keys/values/entries 供 for of 遍历
4. 对象
- Object.assign() 将源对象(source)的所有可枚举属性复制到目标对象(target)。用法:
var target = {
a: 1
};
var source1 = {
b: 2
};
var source2 = {
b: 3
}
Object.assign(target, source1, source2) // {a:1,b:2,c:3}
- Object.keys() // 返回自身所有可枚举的属性
- Object.values()
- Object.entries()
- keys/values/entries
- Object.setPrototypeOf() 用来设置一个对象的prototype对象
- Object.setPrototypeOf(obj, prototype)
- Object.getPrototypeOf() 用来读取对象的prototype对象
5. Symbol 一种新的原始数据类型,表示独一无二的值。每个Symbol的值都不相等。(Symbol值不能与其他类型的值进行运算)
var s = Symbol(’s’) 可接收一个参数,用于描述。
- 作为属性名的Symbol,保证不会出现同名的属性。
var mySymbol = Symbol();
// 第一种写法
var a = {};
a[mySymbol] = 'hello';
// 第二种写法
var b = {
[mySymbol]: 'hello'
}
// 第三种写法
var c = {};
Object.defineProperties(c, mySymbol, {
value: 'hello'
});
// 注意:Symbol值作为对象属性名时,不能使用点运算符
6. Set和Map数据结构
- Set——类似于数组,成员没有重复
数组去重:
[...new Set([1,2,3,4,4])] // [1,2,3,4]
或者 Array.from(new Set([1,2,2,3,3]))
;
- Set实例的属性和方法
- add(value): 添加某个值,返回Set本身。
- delete(value):删除某个值
- has(value):返回一个布尔值,表示参数是否为Set的成员。
- clear():清除所有成员
let mySet = new Set();
mySet.add(1).add(2).add(2);
s.size // 2
s.has(1) // true
s.has(2) // false
s.delete(2);
s.has(2); // false
数组的map和filter也适用于Set,因此使用Set可以很容易的实现并集(Union)、交集(Intersect)、差集(Difference)。
let a = new Set([1,2,3]);
let b = new Set([2,3,4]);
// 并集
let union = new Set([...a,...b]); //set {1,2,3,4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x))); //set {2,3};
// 差集
let differenceA = new Set([...a].filter(x => !b.has(x))); // set{1}
let differenceB = new Set([...b].filter(x => !a.has(x))); // set{4};
let difference = new Set([...differenceA,...differenceB]); // set{1,4};
- Map——类似于对象,也是键值对的集合,各种类型的值都可以当做键,即“值——值”。
- Map实例的属性和方法
- set(key, value)
- get(key)
- delete(key):删除某个值
- has(key):返回一个布尔值,表示参数是否为Map的成员。
- clear():清除所有成员
Map结构转为数组,可使用拓展运算符 ... ,如
let map = new Map([
[1,'one'],
[2,'two'],
[3, 'three']
])
[...map.keys()] // [1,2,3]
[...map.values()] // ['one','two','three']
[...map] // [[1,'one'],[2,'two'],[3,'three']]
结合数组的map方法、filter方法,可以实现Map的遍历和过滤。
[...map].filter(([k, v]) => k < 2); // map {1 => 'a'}
7. Proxy-代理、拦截
Proxy可以理解成为目标对象前架设一个“拦截”层,外界对改对象的访问都必须先通过这层拦截,可以对外界的访问进行过滤和改写。
let proxy = new Proxy(target, handler)
target参数表示所要拦截的目标对象,handler参数也是一个对象,用来定制拦截行为。
var proxy = new Proxy({}, {
get(target, property) { // target 表示 目标对象,property所要访问的属性
return 35;
}
})
proxy.time // 35
proxy.title // 35
注意:
要使Proxy起作用,必须针对proxy实例。
- Proxy实例的方法
- get():用于拦截某个属性的读取操作
var person = {
name: '张三'
}
var personProxy = new Proxy(person, {
get(target, property){
if(property in target){
return target[property];
}else{
throw new Error('...');
}
}
})
personProxy.name // 张三
personProxy.age // 报错
- set():用于拦截某个属性的赋值操作
let validator = {
set(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
obj[prop] = value;
}
}
};
let person = new Proxy({}, validator);
person.age = 100; // 100
person.age = 'young'; // 报错
person.age = 300; // 报错
8. Promise对象——异步编程的解决方案
var getJson = function(url) {
var promise = new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open('get', url);
xhr.responseType = 'json';
xhr.setRequestHeader('Accept', 'application/json');
xhr.send();
xhr.onreadystatechange = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
return promise;
});
};
getJson('data.json')
.then(json => {
console.log('json' + json);
})
.catch(e => {
console.log(e);
});
注:promise先记录个简单用法,后期再深入学习。
9.Class
es5通过构造函数定义并生产对象,如:
function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function() {
return `(${this.x}, ${this.y})`;
};
ES6引入了Class(类)的概念作为对象的模板,通过class关键字定义类,通过class改写上面的Point类。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
let p = new Point(1, 2); // 类必须使用new来调用
类的所有方法都定义在类的prototype属性上。
10.Class的继承
Class通过extends实现继承。
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return `(${this.x}, ${this.y})`;
}
}
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y); // 调用父类的constructor(x, y)
this.color = color;
}
toString() {
return `${this.color} ${super.toString()}`; // 调用父类的toString()
}
}
let colorPoint = new ColorPoint(2, 3, 'red');
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。在子类的构造函数中,只有调用了super之后才可以使用this关键字。
ES5与ES6的继承
- ES5的继承实质是先创建子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。
- ES6实质是先创建父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
super虽然代表了父类的构造函数,但是返回的是子类的实例,即super内部的this指的是子类,因此super()在此相当于 父类.prototype.constructor.call(this)。
super关键字
- 作为函数使用,子类的构造函数必须执行一次super函数。
- 作为对象在普通方法中指向父类的原型对象。
由于绑定子类的this,因此如果通过super对某个属性赋值,这是super就是this,赋值的属性会变成子类实例的属性。
class A {
constructor() {
this.x = 1;
}
}
class B extends A {
constructor(){
super();
this.x = 2;
super.x = 3; // super.x 等同于 this.x
console.log(super.x); // super.x 等同于 A.prototype.x undefined
console.log(this.x); // 3
}
}
11.Module语法
ES6模块是编译时加载或者静态加载,CommonJS模块是‘运行时加载’,因此ES6模块效率更高。
- export
// profile.js
let firstName = 'Michael';
let lastName = 'Jackson';
let year = 1958;
function getName(firstName, lastName) {
return `${firstName} ${lastName}`;
}
function v1(year) {
return `${year}-01-01`;
}
export { firstName, lastName, year, getName, v1 as getDate };
通常情况下,export输出的变量就是本身的名字,但也可以使用as关键字重命名。
- import
import {
firstName,
lastName,
year
} from './profile';
function setName(ele) {
ele.textContent = `${firstName} ${lastName}`;
}
由于import是静态执行,所以不能使用表达式。如果多次重复执行同一句import语句,只会执行一次。
- 模块的整体加载
除了指定加载某个输出值,还可以使用整体加载(*)来指定一个对象,所有输出值都加载在这个对象上。
// circle.js
function area(radius) {
return Math.PI * radius * radius;
}
function circumference(radius) {
return 2 * Math.PI * radius;
}
export {
area,
circumference
}
// 指定加载某个值
import {area, circumference} from './circle';
console.log(area(4));
// 整体加载一个对象(* 重命名 circle)
import * as circle from './circle';
console.log(circle.area(4));
- export default 默认输出
本质上,export default 就是输出了一个叫做default的变量或方法,将该命令后面的值赋给default然后再默认,然后系统允许我们为他取名字。
对比默认输出和正常输出
- 默认输出:export default
export default function foo(){
console.log('foo');
} // 函数名foo在模块外无效,加载时视同匿名函数
import fn from './foo.js'
- 正常输出:export
function foo() {
console.log('foo');
}
export {
foo
}
from './foo.js'
- export 与impor的复合用法
export {foo, bar} from './my_module';
// 等同于
import {foo, bar} from './my_module';
export {foo, bar};
- import():完成动态加载,运行时执行。
import能接受什么参数,import()就可以接受什么参数。
import()函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。
import()返回一个Promise对象:加载模块成功之后,会作为一个对象当做then的参数。
import('./myModule.js')
.then({export1, export2} => {
// ...
})
.catch(e => {
console.log(e)
});
import()的适用场景
- 按需加载:import()可以在需要的时候加载某个模块。
button.addEventListener('click', event => {
import('./dialoge.js').then(dialoge => {
dialoge.open();
}).catch(e => {
console.log(e);
})
})
- 条件加载
if(condition){
import('moduleA').then()
}else{
import('moduleB').then()
}
12.编码风格
-
在let和const之间,建议优先使用const。
const优于let有一下原因:- 提醒阅读程序的人,此变量不可改变。
- javascript编译器会对const 进行优化。
-
优先使用解构赋值。
-
注意区分Object和Map,只有模拟实体对象时才使用Object。如果只是需要key:value的数据结构,则使用Map。因为Map有内建的遍历机制。
-
用Class取代需要prototype的操作。因为Class的写法更简洁。使用extends实现继承,更简单。
-
Module语法是Javascript模块的标准写法。使用import取代require,使用export取代mudule.exports。