JavaScript 设计模式 张容铭
第十二章 房子装修--装饰者模式 (102页)
装饰者模式(Decorator)
在不改变原对象的基础上,通过对其进行包装拓展(添加属性或者方法)使原有对象可以满足用户的更复杂需求。

//装饰者
var decorator = function(input, fn) {
// 获取事件源
var input = document.getElementById(input);
// 若事件源已经绑定事件
if(typeof input.onclick === 'function') {
// 缓存事件源原有回调函数
var oldClickFn = input.onclick;
// 为事件源定义新事件
input.onclick = function() {
oldClickFn();
fn();
}
} else {
// 事件源未绑定事件,直接为事件源添加新增回调函数
input.onclick = fn();
}
}
装饰器模式
// 电话输入框功能装饰
decorator('tel_input', function(){
document.getElementById('tel_demo_text').style.display = 'none';
});
// 姓名输入框功能装饰
decorator('name_input', function(){
document.getElementById('name_demo_text').style.display = 'none';
});
// 地址输入框功能装饰
decorator('name_input', function(){
document.getElementById('address_demo_text').style.display = 'none';
});
ES7中怎么用
装饰器(Decorator)是ES7中的一个新语法,使用可参考阮一峰的文章。正如其字面意思而言,它可以对类、方法、属性进行修饰,从而进行一些相关功能定制。它的写法与Java的注解(Annotation)非常相似,但是功能还是有很大区别。
看一个简单的例子
const log = (target, name, descriptor) => {
console.log('target');
console.log(target);
console.log('name');
console.log(name);
console.log('descriptor');
console.log(descriptor);
var oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);
return oldValue.apply(this, arguments);
};
return descriptor;
}
export default class Math {
@log // Decorator
plus(a, b) {
return a + b;
}
}
// 调用页面
// 引用
import Math from '@common/decoratorTest';
// 在类中引用
const math = new Math();
console.log(math.plus(3,4));
看看antd pro中使用
@connect(({ list, loading }) => ({
list,
loading: loading.models.list,
}))
@Form.create({
onValuesChange({ dispatch }, changedValues, allValues) {
// 表单项变化时请求数据
// eslint-disable-next-line
console.log(changedValues, allValues);
// 模拟查询表单生效
dispatch({
type: 'list/fetch',
payload: {
count: 8,
},
});
},
})
ES7的语法糖怎么做才能引用到项目中
1、安装
npm install babel-plugin-transform-decorators-legacy --save-dev
2、.babelrc
"plugins": [
"transform-runtime",
"transform-decorators-legacy",
]
它的原理是什么
JS中的装饰器本质也是一个函数,利用的是JS中object的descriptor,这个函数会接收三个参数:
/**
* 装饰器函数
* @param {Object} target 被装饰器的类的原型
* @param {string} name 被装饰的类、属性、方法的名字
* @param {Object} descriptor 被装饰的类、属性、方法的descriptor
*/
function Decorator(target, name, descriptor) {
// 以此可以获取实例化的时候此属性的默认值
let v = descriptor.initializer && descriptor.initializer.call(this);
// 返回一个新的描述对象作为被修饰对象的descriptor,或者直接修改 descriptor 也可以
return {
enumerable: true,
configurable: true,
get() {
return v;
},
set(c) {
v = c;
},
};
}
// USE
class Fudao{
@Decorator
title = "企鹅辅导“
}
当然装饰器也可以接受参数:
// decorator 外部可以包装一个函数,函数可以带参数
function Decorator(type) {
/**
* 装饰器函数
* @param {Object} target 被装饰器的类的原型
* @param {string} name 被装饰的类、属性、方法的名字
* @param {Object} descriptor 被装饰的类、属性、方法的descriptor
*/
return (target, name, descriptor) => {
// 以此可以获取实例化的时候此属性的默认值
let v = descriptor.initializer && descriptor.initializer.call(this);
// 返回一个新的描述对象作为被修饰对象的descriptor,或者直接修改 descriptor 也可以
return {
enumerable: true,
configurable: true,
get() {
return v + type;
},
set(c) {
v = c;
},
};
}
}
// USE
class Fudao{
@Decorator('string')
title = "企鹅辅导“
}
在React中使用
import React, { Component } from 'react';
import { Chart, Geom, Coord, Shape } from 'bizcharts';
import DataSet from '@antv/data-set';
import Debounce from 'lodash-decorators/debounce';
import Bind from 'lodash-decorators/bind';
import classNames from 'classnames';
import autoHeight from '../autoHeight';
import styles from './index.less';
/* eslint no-underscore-dangle: 0 */
/* eslint no-param-reassign: 0 */
const imgUrl = 'https://gw.alipayobjects.com/zos/rmsportal/gWyeGLCdFFRavBGIDzWk.png';
@autoHeight()
class TagCloud extends Component {
state = {
dv: null,
};
componentDidMount() {
requestAnimationFrame(() => {
this.initTagCloud();
this.renderChart();
});
window.addEventListener('resize', this.resize, { passive: true });
}
componentDidUpdate(preProps) {
const { data } = this.props;
if (JSON.stringify(preProps.data) !== JSON.stringify(data)) {
this.renderChart(this.props);
}
}
componentWillUnmount() {
this.isUnmount = true;
window.cancelAnimationFrame(this.requestRef);
window.removeEventListener('resize', this.resize);
}
resize = () => {
this.requestRef = requestAnimationFrame(() => {
this.renderChart();
});
};
saveRootRef = node => {
this.root = node;
};
initTagCloud = () => {
function getTextAttrs(cfg) {
return Object.assign(
{},
{
fillOpacity: cfg.opacity,
fontSize: cfg.origin._origin.size,
rotate: cfg.origin._origin.rotate,
text: cfg.origin._origin.text,
textAlign: 'center',
fontFamily: cfg.origin._origin.font,
fill: cfg.color,
textBaseline: 'Alphabetic',
},
cfg.style
);
}
// 给point注册一个词云的shape
Shape.registerShape('point', 'cloud', {
drawShape(cfg, container) {
const attrs = getTextAttrs(cfg);
return container.addShape('text', {
attrs: Object.assign(attrs, {
x: cfg.x,
y: cfg.y,
}),
});
},
});
};
@Bind()
@Debounce(500)
renderChart(nextProps) {
// const colors = ['#1890FF', '#41D9C7', '#2FC25B', '#FACC14', '#9AE65C'];
const { data, height } = nextProps || this.props;
if (data.length < 1 || !this.root) {
return;
}
const h = height * 4;
const w = this.root.offsetWidth * 4;
const onload = () => {
const dv = new DataSet.View().source(data);
const range = dv.range('value');
const [min, max] = range;
dv.transform({
type: 'tag-cloud',
fields: ['name', 'value'],
imageMask: this.imageMask,
font: 'Verdana',
size: [w, h], // 宽高设置最好根据 imageMask 做调整
padding: 5,
timeInterval: 5000, // max execute time
rotate() {
return 0;
},
fontSize(d) {
// eslint-disable-next-line
return Math.pow((d.value - min) / (max - min), 2) * (70 - 20) + 20;
},
});
if (this.isUnmount) {
return;
}
this.setState({
dv,
w,
h,
});
};
if (!this.imageMask) {
this.imageMask = new Image();
this.imageMask.crossOrigin = '';
this.imageMask.src = imgUrl;
this.imageMask.onload = onload;
} else {
onload();
}
}
render() {
const { className, height } = this.props;
const { dv, w, h } = this.state;
return (
<div
className={classNames(styles.tagCloud, className)}
style={{ width: '100%', height }}
ref={this.saveRootRef}
>
{dv && (
<Chart
width={w}
height={h}
data={dv}
padding={0}
scale={{
x: { nice: false },
y: { nice: false },
}}
>
<Coord reflect="y" />
<Geom type="point" position="x*y" color="text" shape="cloud" />
</Chart>
)}
</div>
);
}
}
export default TagCloud;
autoHeight 装饰器是怎么设计的
/* eslint eqeqeq: 0 */
import React from 'react';
function computeHeight(node) {
const totalHeight = parseInt(getComputedStyle(node).height, 10);
const padding =
parseInt(getComputedStyle(node).paddingTop, 10) +
parseInt(getComputedStyle(node).paddingBottom, 10);
return totalHeight - padding;
}
function getAutoHeight(n) {
if (!n) {
return 0;
}
let node = n;
let height = computeHeight(node);
while (!height) {
node = node.parentNode;
if (node) {
height = computeHeight(node);
} else {
break;
}
}
return height;
}
const autoHeight = () => WrappedComponent =>
class extends React.Component {
state = {
computedHeight: 0,
};
componentDidMount() {
const { height } = this.props;
if (!height) {
const h = getAutoHeight(this.root);
// eslint-disable-next-line
this.setState({ computedHeight: h });
}
}
handleRoot = node => {
this.root = node;
};
render() {
const { height } = this.props;
const { computedHeight } = this.state;
const h = height || computedHeight;
return (
<div ref={this.handleRoot}>{h > 0 && <WrappedComponent {...this.props} height={h} />}</div>
);
}
};
export default autoHeight;