ES6 - ES11
ES6
怎么理解ES6 中的Promise;使用场景
- 介绍
1 | /** |
- 状态
1 | promise 有3种状态 |
- 特点
1 | 1.对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态 |
- 流程
- 用法
1 | const promise = new Promise(function(resolve, reject) {}); |
实例方法
Promise
构建出来的实例存在以下方法:- then()
1
2
3
4
5
6
7
8/**
then()是实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数
then方法返回的是一个新的Promise实例,也就是promise能链式书写的原因 */
getJSON("/posts.json").then(function(json) {
return json.post;
}).then(function(post) {
// ...
});- catch()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21/**
catch()方法是.then(null, rejection)或.then(undefined, rejection)的别名,用于指定发生错误时的回调函数 */
getJSON('/posts.json').then(function(posts) {
// ...
}).catch(function(error) {
// 处理 getJSON 和 前一个回调函数运行时发生的错误
console.log('发生错误!', error);
});
//Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止
getJSON('/post/1.json').then(function(post) {
return getJSON(post.commentURL);
}).then(function(comments) {
// some code
}).catch(function(error) {
// 处理前面三个Promise产生的错误
});
/**
一般来说,使用catch方法代替then()第二个参数
Promise对象抛出的错误不会传递到外层代码,即不会有任何反应 */- finally()
1
2
3
4
5//finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
构造函数方法
- all()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17//Promise.all()方法用于将多个 Promise实例,包装成一个新的 Promise实例
const p1 = new Promise((resolve, reject) => {
resolve('hello');
})
.then(result => result)
.catch(e => e);
const p2 = new Promise((resolve, reject) => {
throw new Error('报错了');
})
.then(result => result)
.catch(e => e);
Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
//如果p2没有自己的catch方法,就会调用Promise.all()的catch方法- race()
1
2
3
4
5
6
7
8
9
10
11//将多个 Promise 实例,包装成一个新的 Promise 实例;只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变
const p = Promise.race([
fetch('/resource-that-may-take-a-while'),
new Promise(function (resolve, reject) {
setTimeout(() => reject(new Error('request timeout')), 5000)
})
]);
p
.then(console.log)
.catch(console.error);- allSettled()
1
2//Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例
//只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束- resolve()
1
2
3
4//将现有对象转为 Promise对象
Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))- reject()
- try()
使用场景
- 将图片的加载写成一个
Promise
,一旦加载完成,Promise
的状态就发生变化
1
2
3
4
5
6
7
8const preloadImage = function (path) {
return new Promise(function (resolve, reject) {
const image = new Image();
image.onload = resolve;
image.onerror = reject;
image.src = path;
});
};- 通过链式操作,将多个渲染数据分别给个
then
,让其各司其职。或当下个异步请求依赖上个请求结果的时候,我们也能够通过链式操作友好解决问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/ 各司其职
getInfo().then(res=>{
let { bannerList } = res
//渲染轮播图
console.log(bannerList)
return res
}).then(res=>{
let { storeList } = res
//渲染店铺列表
console.log(storeList)
return res
}).then(res=>{
let { categoryList } = res
console.log(categoryList)
//渲染分类列表
return res
})- 通过
all()
实现多个请求合并在一起,汇总所有请求结果,只需设置一个loading
即可
1
2
3
4
5
6
7
8
9
10
11
12function initLoad(){
loading.show() //加载loading
Promise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{
console.log(res)
loading.hide() //关闭loading
}).catch(err=>{
console.log(err)
loading.hide()//关闭loading
})
}
//数据初始化
initLoad()- 通过
race
可以设置图片请求超时
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//请求某个图片资源
function requestImg(){
var p = new Promise(function(resolve, reject){
var img = new Image();
img.onload = function(){
resolve(img);
}
//img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg"; 正确的
img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1";
});
return p;
}
//延时函数,用于给请求计时
function timeout(){
var p = new Promise(function(resolve, reject){
setTimeout(function(){
reject('图片请求超时');
}, 5000);
});
return p;
}
Promise
.race([requestImg(), timeout()])
.then(function(results){
console.log(results);
})
.catch(function(reason){
console.log(reason);
});- 将图片的加载写成一个
如何理解ES6中的Porxy; 使用场景
- 介绍
1 | Proxy 亦是如此,用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等) |
用法
Proxy
为 构造函数,用来生成Proxy
实例
1
2
3
4
5
6var proxy = new Proxy(target, handler)
/**
参数
target表示所要拦截的目标对象(任何类型的对象,包括原生数组,函数,甚至另一个代理))
handler通常以函数作为属性的对象,各属性中的函数分别定义了在执行各种操作时代理 p 的行为
*/- 参数 handler 解析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15关于handler拦截属性,有如下:
get(target,propKey,receiver):拦截对象属性的读取
set(target,propKey,value,receiver):拦截对象属性的设置
has(target,propKey):拦截propKey in proxy的操作,返回一个布尔值
deleteProperty(target,propKey):拦截delete proxy[propKey]的操作,返回一个布尔值
ownKeys(target):拦截Object.keys(proxy)、for...in等循环,返回一个数组
getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey),返回属性的描述对象
defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc),返回一个布尔值
preventExtensions(target):拦截Object.preventExtensions(proxy),返回一个布尔值
getPrototypeOf(target):拦截Object.getPrototypeOf(proxy),返回一个对象
isExtensible(target):拦截Object.isExtensible(proxy),返回一个布尔值
setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto),返回一个布尔值
apply(target, object, args):拦截 Proxy 实例作为函数调用的操作
construct(target, args):拦截 Proxy 实例作为构造函数调用的操作- Reflect
1
2
3
4
5
6若需要在Proxy内部调用对象的默认行为,建议使用Reflect,其是ES6中操作对象而提供的新 API
基本特点:
只要Proxy对象具有的代理方法,Reflect对象全部具有,以静态方法的形式存在
修改某些Object方法的返回结果,让其变得更合理(定义不存在属性行为的时候不报错而是返回false)
让Object操作都变成函数行为一些参数用法
- get() get接受三个参数,依次为目标对象、属性名和 proxy 实例本身,最后一个参数可选
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45var person = {
name: "张三"
};
var proxy = new Proxy(person, {
get: function(target, propKey) {
return Reflect.get(target,propKey)
}
});
proxy.name // "张三"
//get能够对数组增删改查进行拦截,下面是试下你数组读取负数的索引
function createArray(...elements) {
let handler = {
get(target, propKey, receiver) {
let index = Number(propKey);
if (index < 0) {
propKey = String(target.length + index);
}
return Reflect.get(target, propKey, receiver);
}
};
let target = [];
target.push(...elements);
return new Proxy(target, handler);
}
let arr = createArray('a', 'b', 'c');
arr[-1] // c
//如果一个属性不可配置(configurable)且不可写(writable),则 Proxy 不能修改该属性,否则会报错
const target = Object.defineProperties({}, {
foo: {
value: 123,
writable: false,
configurable: false
},
});
const handler = {
get(target, propKey) {
return 'abc';
}
};
const proxy = new Proxy(target, handler);
proxy.foo
// TypeError: Invariant check failed- set() 拦截某个属性的赋值操作,可以接受四个参数,依次为目标对象、属性名、属性值和
Proxy
实例本身
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53let validator = {
set: function(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');
}
}
// 对于满足条件的 age 属性以及其他属性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错
//如果目标对象自身的某个属性,不可写且不可配置,那么set方法将不起作用
const obj = {};
Object.defineProperty(obj, 'foo', {
value: 'bar',
writable: false,
});
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = 'baz';
}
};
const proxy = new Proxy(obj, handler);
proxy.foo = 'baz';
proxy.foo // "bar"
//严格模式下,set代理如果没有返回true,就会报错
;
const handler = {
set: function(obj, prop, value, receiver) {
obj[prop] = receiver;
// 无论有没有下面这一行,都会报错
return false;
}
};
const proxy = new Proxy({}, handler);
proxy.foo = 'bar';
// TypeError: 'set' on proxy: trap returned falsish for property 'foo'- deleteProperty()
deleteProperty
方法用于拦截delete
操作,如果这个方法抛出错误或者返回false
,当前属性就无法被delete
命令删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19var handler = {
deleteProperty (target, key) {
invariant(key, 'delete');
Reflect.deleteProperty(target,key)
return true;
}
};
function invariant (key, action) {
if (key[0] === '_') {
throw new Error(`无法删除私有属性`);
}
}
var target = { _prop: 'foo' };
var proxy = new Proxy(target, handler);
delete proxy._prop
// Error: 无法删除私有属性
//目标对象自身的不可配置(configurable)的属性,不能被deleteProperty方法删除,否则报错- 取消代理
1
Proxy.revocable(target, handler);
使用场景
Proxy
其功能非常类似于设计模式中的代理模式,常用功能如下:- 拦截和监视外部对对象的访问
- 降低函数或类的复杂度
- 在复杂操作前对操作进行校验或对所需资源进行管理
- 使用
Proxy
保障数据类型的准确性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15let numericDataStore = { count: 0, amount: 1234, total: 14 };
numericDataStore = new Proxy(numericDataStore, {
set(target, key, value, proxy) {
if (typeof value !== 'number') {
throw Error("属性只能是number类型");
}
return Reflect.set(target, key, value, proxy);
}
});
numericDataStore.count = "foo"
// Error: 属性只能是number类型
numericDataStore.count = 333
// 赋值成功- 声明了一个私有的
apiKey
,便于api
这个对象内部的方法调用,但不希望从外部也能够访问api._apiKey
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23let api = {
_apiKey: '123abc456def',
getUsers: function(){ },
getUser: function(userId){ },
setUser: function(userId, config){ }
};
const RESTRICTED = ['_apiKey'];
api = new Proxy(api, {
get(target, key, proxy) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} 不可访问.`);
} return Reflect.get(target, key, proxy);
},
set(target, key, value, proxy) {
if(RESTRICTED.indexOf(key) > -1) {
throw Error(`${key} 不可修改`);
} return Reflect.get(target, key, value, proxy);
}
});
console.log(api._apiKey)
api._apiKey = '987654321'
// 上述都抛出错误- 观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行; observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数
1
2
3
4
5
6
7
8
9
10
11const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
//观察者函数都放进Set集合,当修改obj的值,在会set函数中拦截,自动执行Set所有的观察者
怎么理解ES6中Generator;使用场景
介绍
执行
Generator
函数会返回一个遍历器对象,可以依次遍历Generator
函数内部的每一个状态形式上,
Generator
函数是一个普通函数,但是有两个特征:function
关键字与函数名之间有一个星号- 函数体内部使用
yield
表达式,定义不同的内部状态
1
2
3
4
5function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}使用
Generator
函数会返回一个遍历器对象,即具有Symbol.iterator
属性,并且返回给自己
1
2
3
4
5
6
7
8function* gen(){
// some code
}
var g = gen();
g[Symbol.iterator]() === g
// true- 通过
yield
关键字可以暂停generator
函数返回的遍历器对象的状态
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
/**
上述存在三个状态:hello、world、return
通过next方法才会遍历到下一个内部状态,其运行逻辑如下:
1.遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
2.下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式
3.如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
4.如果该函数没有return语句,则返回的对象的value属性值为undefined
*/
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
/**
done用来判断是否存在下个状态,value对应状态值
yield表达式本身没有返回值,或者说总是返回undefined
通过调用next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值
*/
function* foo(x) {
var y = 2 * (yield (x + 1));
var z = yield (y / 3);
return (x + y + z);
}
var a = foo(5);
a.next() // Object{value:6, done:false}
a.next() // Object{value:NaN, done:false}
a.next() // Object{value:NaN, done:true}
var b = foo(5);
b.next() // { value:6, done:false }
b.next(12) // { value:8, done:false }
b.next(13) // { value:42, done:true }
//正因为Generator函数返回Iterator对象,因此我们还可以通过for...of进行遍历
function* foo() {
yield 1;
yield 2;
yield 3;
yield 4;
yield 5;
return 6;
}
for (let v of foo()) {
console.log(v);
}
// 1 2 3 4 5
//原生对象没有遍历接口,通过Generator函数为它加上这个接口,就能使用for...of进行遍历了
function* objectEntries(obj) {
let propKeys = Reflect.ownKeys(obj);
for (let propKey of propKeys) {
yield [propKey, obj[propKey]];
}
}
let jane = { first: 'Jane', last: 'Doe' };
for (let [key, value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// first: Jane
// last: Doe异步解决方案
- 回调函数
1
2
3
4
5
6
7
8
9
10//回调函数,就是把任务的第二段单独写在一个函数里面,等到重新执行这个任务的时候,再调用这个函数
fs.readFile('/etc/fstab', function (err, data) {
if (err) throw err;
console.log(data);
fs.readFile('/etc/shells', function (err, data) {
if (err) throw err;
console.log(data);
});
});
//readFile函数的第三个参数,就是回调函数,等到操作系统返回了/etc/passwd这个文件以后,回调函数才会执行- Promise 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18//Promise就是为了解决回调地狱而产生的,将回调函数的嵌套,改成链式调用
const fs = require('fs');
const readFile = function (fileName) {
return new Promise(function (resolve, reject) {
fs.readFile(fileName, function(error, data) {
if (error) return reject(error);
resolve(data);
});
});
};
readFile('/etc/fstab').then(data =>{
console.log(data)
return readFile('/etc/shells')
}).then(data => {
console.log(data)
})
//这种链式操作形式,使异步任务的两段执行更清楚了,但是也存在了很明显的问题,代码变得冗杂了,语义化并不强- generator 函数
1
2
3
4
5
6
7//yield表达式可以暂停函数执行,next方法用于恢复函数执行,这使得Generator函数非常适合将异步任务同步化
const gen = function* () {
const f1 = yield readFile('/etc/fstab');
const f2 = yield readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};- async/await
1
2
3
4
5
6
7//将上面Generator函数改成async/await形式,更为简洁,语义化更强了
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};区别:
1 | 通过上述代码进行分析,将promise、Generator、async/await进行比较: |
使用场景
Generator
是异步解决的一种方案,最大特点则是将异步操作同步化表达出来
1
2
3
4
5
6
7
8
9
10
11function* loadUI() {
showLoadingScreen();
yield loadUIDataAsynchronously();
hideLoadingScreen();
}
var loader = loadUI();
// 加载UI
loader.next()
// 卸载UI
loader.next()- 包括
redux-saga
中间件也充分利用了Generator
特性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21import { call, put, takeEvery, takeLatest } from 'redux-saga/effects'
import Api from '...'
function* fetchUser(action) {
try {
const user = yield call(Api.fetchUser, action.payload.userId);
yield put({type: "USER_FETCH_SUCCEEDED", user: user});
} catch (e) {
yield put({type: "USER_FETCH_FAILED", message: e.message});
}
}
function* mySaga() {
yield takeEvery("USER_FETCH_REQUESTED", fetchUser);
}
function* mySaga() {
yield takeLatest("USER_FETCH_REQUESTED", fetchUser);
}
export default mySaga;- 还能利用
Generator
函数,在对象上实现Iterator
接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function* iterEntries(obj) {
let keys = Object.keys(obj);
for (let i=0; i < keys.length; i++) {
let key = keys[i];
yield [key, obj[key]];
}
}
let myObj = { foo: 3, bar: 7 };
for (let [key, value] of iterEntries(myObj)) {
console.log(key, value);
}
// foo 3
// bar 7
怎么理解ES6中Module; 使用场景
介绍
模块,(Module),是能够单独命名并独立地完成一定功能的程序语句的集合(即程序代码和数据结构的集合体)。
两个基本的特征:外部特征和内部特征
- 外部特征是指模块跟外部环境联系的接口(即其他模块或程序调用该模块的方式,包括有输入输出参数、引用的全局变量)和模块的功能
- 内部特征是指模块的内部环境具有的特点(即该模块的局部数据和程序代码
为什么需要模块化
- 代码抽象
- 代码封装
- 代码复用
- 依赖管理
使用
ES6
模块内部自动采用了严格模式,这里就不展开严格模式的限制,毕竟这是ES5
之前就已经规定好模块功能主要由两个命令构成:
export
:用于规定模块的对外接口import
:用于输入其他模块提供的功能
- export
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// profile.js
export var firstName = 'Michael';
export var lastName = 'Jackson';
export var year = 1958;
//或
// 建议使用下面写法,这样能瞬间确定输出了哪些变量
var firstName = 'Michael';
var lastName = 'Jackson';
var year = 1958;
export { firstName, lastName, year };
//输出函数或类
export function multiply(x, y) {
return x * y;
};
//通过as可以进行输出变量的重命名
function v1() { ... }
function v2() { ... }
export {
v1 as streamV1,
v2 as streamV2,
v2 as streamLatestVersion
};- import
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
32
33
34
35
36
37//使用export命令定义了模块的对外接口以后,其他 JS 文件就可以通过import命令加载这个模块
// main.js
import { firstName, lastName, year } from './profile.js';
function setName(element) {
element.textContent = firstName + ' ' + lastName;
}
//同样如果想要输入变量起别名,通过as关键字
import { lastName as surname } from './profile.js';
//当加载整个模块的时候,需要用到星号*
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
// main.js
import * as circle from './circle';
console.log(circle) // {area:area,circumference:circumference}
//输入的变量都是只读的,不允许修改,但是如果是对象,允许修改属性
//如果不需要知道变量名或函数就完成加载,就要用到export default命令,为模块指定默认输出
//加载该模块的时候,import命令可以为该函数指定任意名字
// export-default.js
export default function () {
console.log('foo');
}
// import-default.js
import customName from './export-default';
customName(); // 'foo'动态加载
1 | /** |
怎么理解Decorator (装饰器); 使用场景
介绍
即装饰器,在不改变原类和使用继承的情况下,动态地扩展对象功能
优点:
- 代码可读性变强了,装饰器命名相当于一个注释
- 在不改变原有代码情况下,对原来功能进行扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//定义一个类,什么属性值,方法也没有
class soldier{
}
//定义一个方法,作为装饰器
function strong(target){
target.AK = true
}
//将装饰器装饰在类上
@strong
class soldier{
}
//调用类的AK属性
soldier.AK // true用法
Docorator
修饰对象为下面两种:- 类的装饰
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
32
33
34
35
36
37
38
39
40/**
类的装饰
当对类本身进行装饰的时候,能够接受一个参数,即类本身
将装饰器行为进行分解,大家能够有个更深入的了解
*/
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
//下面@testable就是一个装饰器,target就是传入的类,即MyTestableClass,实现了为类添加静态属性
@testable
class MyTestableClass {
// ...
}
function testable(target) {
target.isTestable = true;
}
MyTestableClass.isTestable // true
//如果想要传递参数,可以在装饰器外层再封装一层函数
function testable(isTestable) {
return function(target) {
target.isTestable = isTestable;
}
}
@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true
@testable(false)
class MyClass {}
MyClass.isTestable // false- 类属性的装饰
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
32
33
34
35
36
37/**
类属性的装饰
当对类属性进行装饰的时候,能够接受三个参数:
1.类的原型对象
2.需要装饰的属性名
3.装饰属性名的描述对象
*/
//首先定义一个readonly装饰器
function readonly(target, name, descriptor){
descriptor.writable = false; // 将可写属性设为false
return descriptor;
}
//使用readonly装饰类的name方法
class Person {
@readonly
name() { return `${this.first} ${this.last}` }
}
//相当于以下调用
readonly(Person.prototype, 'name', descriptor);
//如果一个方法有多个装饰器,就像洋葱一样,先从外到内进入,再由内到外执行
function dec(id){
console.log('evaluated', id);
return (target, property, descriptor) =>console.log('executed', id);
}
class Example {
@dec(1)
@dec(2)
method(){}
}
// evaluated 1
// evaluated 2
// executed 2
// executed 1
//外层装饰器@dec(1)先进入,但是内层装饰器@dec(2)先执行注意项
装饰器不能用于修饰函数,因为函数存在变量声明情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23var counter = 0;
var add = function () {
counter++;
};
@add
function foo() {
}
//编译阶段,变成下面
var counter;
var add;
@add
function foo() {
}
counter = 0;
add = function () {
counter++;
};
//意图是执行后counter等于 1,但是实际上结果是counter等于 0使用场景
基于
Decorator
强大的作用,我们能够完成各种场景的需求,下面简单列举几种:- 使用
react-redux
的时候,如果写成下面这种形式,既不雅观也很麻烦
1
2
3
4
5class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
//通过装饰器就变得简洁多了
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}- 将
mixins
,也可以写成装饰器,让使用更为简洁了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function mixins(...list) {
return function (target) {
Object.assign(target.prototype, ...list);
};
}
// 使用
const Foo = {
foo() { console.log('foo') }
};
@mixins(Foo)
class MyClass {}
let obj = new MyClass();
obj.foo() // "foo"- 讲讲
core-decorators.js
几个常见的装饰器
- 使用
1 | //@antobind |
ES6 class
class 没有声明提升
类方法 constructor
这个方法类似于其他语言,类的init方法,每次new实例时都会调用
关键字 extends (继承)
1 | class Son_of_Bullshit extends Bullshit{} |
关键字 super
1 | // 当我们获取父类属性时,需要用到super进行获取 |
关键字 static
在类里,静态属性、方法是属于类自身的,当调用时直接使用类调用即可
静态方法里的this是类而非实例对象,所以静态方法里面可以调用另外一个静态方法
关键字setter getter (设置属性和获取属性)
class实现单例模式
1 | // |