目录
演示范例——装饰器 @autobind 实现this指向原对象
装饰器模式的定义
装饰器模式:向一个现有的对象添加新的功能,同时又不改变其结构的设计模式
用途:为现有的对象添加新的功能
如果想在项目中大量使用装饰器,推荐看一下core-decorators
这个库,其中封装了很多常用的装饰器.
配置开发环境
因目前装饰器Decorator
的语法还只是一个提案,如果期望现在使用装饰器模式,需要安装配合babel
+ webpack
并结合插件实现。
- npm安装依赖
npm install babel-core babel-loader babel-plugin-transform-decorators babel-plugin-transform-decorators-legacy babel-preset-env
- 配置
.babelrc
文件
{
"presets": ["env"],
"plugins": ["transform-decorators-legacy"]
}
- 在
webpack.config.js
中添加babel-loader
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
],
}
如果你使用的IDE为Visual Studio Code,可能还需要在项目根目录下添加以下tsconfig.json
文件来组织一个ts检查的报错。
{
"compilerOptions": {
"experimentalDecorators": true,
"allowJs": true,
"lib": [
"es6"
],
}
}
演示范例——装饰器
@autobind
实现this
指向原对象
实现一个autobind
的函数,用来装饰getPerson
这个方法,实现this
永远指向Person
的实例。
function autobind(target, key, descriptor) {
var fn = descriptor.value;
var configurable = descriptor.configurable;
var enumerable = descriptor.enumerable;
// 返回descriptor
return {
configurable: configurable,
enumerable: enumerable,
get: function get() {
// 将该方法绑定this
var boundFn = fn.bind(this);
// 使用Object.defineProperty重新定义该方法
Object.defineProperty(this, key, {
configurable: true,
writable: true,
enumerable: false,
value: boundFn
})
return boundFn;
}
}
}
通过bind
实现了this
的绑定,并在get
中利用Object.defineProperty
重写了该方法,将value
定义为通过bind
绑定后的函数boundFn
,以此实现了this
永远指向实例。下面我们为getPerson
方法加上装饰并调用。
class Person {
@autobind
getPerson() {
return this;
}
}
let person = new Person();
let { getPerson } = person;
console.log(getPerson() === person); // true
演示范例——装饰器
@debounce
实现函数防抖
函数防抖(debounce)在前端项目中有着很多的应用,例如在resize
或scroll
等事件中操作DOM,或对用户输入实现实时ajax搜索等会被高频的触发,前者会对浏览器性能产生直观的影响,后者会对服务器产生较大的压力,我们期望这类高频连续触发的事件在触发结束后再做出响应,这就是函数防抖的应用。
class Editor {
constructor() {
this.content = '';
}
updateContent(content) {
console.log(content);
this.content = content;
// 后面有一些消耗性能的操作
}
}
const editor1 = new Editor();
editor1.updateContent(1);
setTimeout(() => { editor1.updateContent(2); }, 400);
const editor2= new Editor();
editor2.updateContent(3);
setTimeout(() => { editor2.updateContent(4); }, 600);
// 打印结果: 1 3 2 4
上面的代码中我们定义了Editor
这个类,其中updateContent
方法会在用户输入时执行并可能有一些消耗性能的DOM操作,这里我们在该方法内部打印了传入的参数以验证调用过程。可以看到4次的调用结果分别为1 3 2 4
。
下面我们实现一个debounce
函数,该方法传入一个数字类型的timeout
参数。
function debounce(timeout) {
const instanceMap = new Map(); // 创建一个Map的数据结构,将实例化对象作为key
return function (target, key, descriptor) {
return Object.assign({}, descriptor, {
value: function value() {
// 清除延时器
clearTimeout(instanceMap.get(this));
// 设置延时器
instanceMap.set(this, setTimeout(() => {
// 调用该方法
descriptor.value.apply(this, arguments);
// 将延时器设置为 null
instanceMap.set(this, null);
}, timeout));
}
})
}
}
上面的方法中,我们采用了ES6提供的Map
数据结构去实现实例化对象和延时器的映射。在函数的内部,首先清除延时器,接着设置延时执行函数,这是实现debounce
的通用方法,下面我们来测试一下debounce
装饰器。
class Editor {
constructor() {
this.content = '';
}
@debounce(500)
updateContent(content) {
console.log(content);
this.content = content;
}
}
const editor1 = new Editor();
editor1.updateContent(1);
setTimeout(() => { editor1.updateContent(2); }, 400);
const editor2= new Editor();
editor2.updateContent(3);
setTimeout(() => { editor2.updateContent(4); }, 600);
//打印结果: 3 2 4
上面调用了4次updateContent
方法,打印结果为3 2 4
。1
由于在400ms
内被重复调用而没有被打印,这符合我们的参数为500
的预期。
演示范例——装饰器
@deprecate
实现警告提示
在使用第三方库的过程中,我们会时不时的在控制台遇见一些警告,这些警告用来提醒开发者所调用的方法会在下个版本中被弃用。这样的一行打印信息也许我们的常规做法是在方法内部添加一行代码即可,这样其实在源码阅读上并不友好,也不符合单一职责原则。如果在需要抛出警告的方法前面加一个@deprecate
的装饰器来实现警告,会友好得多。
下面我们来实现一个@deprecate
的装饰器,其实这类的装饰器也可以扩展成为打印日志装饰器@log
,上报信息装饰器@fetchInfo
等。
function deprecate(deprecatedObj) {
return function(target, key, descriptor) {
const deprecatedInfo = deprecatedObj.info;
const deprecatedUrl = deprecatedObj.url;
// 警告信息
const txt = `DEPRECATION ${target.constructor.name}#${key}: ${deprecatedInfo}. ${deprecatedUrl ? 'See '+ deprecatedUrl + ' for more detail' : ''}`;
return Object.assign({}, descriptor, {
value: function value() {
// 打印警告信息
console.warn(txt);
descriptor.value.apply(this, arguments);
}
})
}
}
上面的deprecate
函数接受一个对象参数,该参数分别有info
和url
两个键值,其中info
填入警告信息,url
为选填的详情网页地址。下面我们来为一个名为MyLib
的库的deprecatedMethod
方法添加该装饰器吧!
class MyLib {
@deprecate({
info: 'The methods will be deprecated in next version',
url: 'http://www.baidu.com'
})
deprecatedMethod(txt) {
console.log(txt)
}
}
const lib = new MyLib();
lib.deprecatedMethod('调用了一个要在下个版本被移除的方法');
// DEPRECATION MyLib#deprecatedMethod: The methods will be deprecated in next version. See http://www.baidu.com for more detail
// 调用了一个要在下个版本被移除的方法
更多设计模式详见——js设计模式【详解】总目录
https://blog.csdn.net/weixin_41192489/article/details/116154815