JavaScript设计模式 - 结构型设计模式
套餐服务-外观模式
外观模式(Facade)
为一组
复杂的子系统接口
提供一个更高级的统一接口
,通过这个接口使得对子系统接口
的访问更容易
。在JavaScript中有时也会用于对底层结构
兼容性
做统一封装
来简化用户使用
。
需求:为指定按钮添加一个事件
document.onclick = function (e) {
if (e.target === document.getElementById('btn01')) {
alert('btn01 的点击事件');
}
};
- ⚠ 注意:
以上方法的缺点:
- 为
document
绑定了onclick
事件,但onclick
是DOM0级事件
,也就是说这种方式绑定的事件相当于为元素绑定一个事件方法,所以如果团队中有人再次通过这种方式为document
绑定click
事件时,就相当于重复定义了一个方法,会将你定义的click
事件方法覆盖,如下列程序:
document.onclick = function (e) { if (e.target === document.getElementById('btn01')) { alert('btn01 的点击事件'); } }; document.onclick = function (e){ console.log('document事件覆盖掉了上边的事件'); }; ```
- 为
外观模式
实现优化:应使用 DOM2级事件 处理程序提供的方法 addEventListener 来实现
// 外观模式实现 function addEvent(dom, type, fn) { // 对于支持DOM2级事件处理程序addEventListener方法的浏览器 if (dom.addEventListener) { dom.addEventListener(type, fn, false); // 对于不支持addEventListener方法但支持attachEvent方法的浏览器 } else if (dom.attachEvent) { dom.attachEvent('on' + type, fn); // 对于不支持addEventListener方法也不支持attachEvent方法,但支持on+'事件名'的浏览器 } else { dom['on' + type] = fn; } } addEvent(document.getElementById('btn01'), 'click', function (e) { alert('btn01 的点击事件'); }); addEvent(document, 'click', function (e) { console.log('document事件覆盖掉了上边的事件'); });
特点:
规整复杂接口,解决兼容问题
水管弯弯-适配器模式
适配器模式(Adapter)
将一个类(对象)的接口(方法或者属性)转化成另外一个接口,以满足用户需求,使类(对象)之间接口的不兼容问题通过适配器得以解决。
适配异类框架
团队内存在一个和jQuery类似的A代码库,但是现在要代码库中的方法适配到jQuery上,该如何实现?
- A代码库
// 团队内部创建的A代码库 const A = { // 通过ID获取元素 g(id) { return document.getElementById(id) }, // 为元素绑定事件 on(id, type, fn) { // 如果传递参数是字符串则以id处理,否则以元素对象处理 var dom = typeof id === 'string' ? this.g(id) : id; // 标准DOM2级添加事件方式 if (dom.addEventListener) { dom.addEventListener(type, fn, false); // IE DOM2级添加事件方式 } else if (dom.attachEvent) { dom.attachEvent('on' + type, fn); // 简易添加事件方式 } else { dom['on' + type] = fn; } } }; // 窗口加载完成事件 A.on(window, 'load', function () { // 按钮点击事件 A.on('btn01', 'click', function (e) { // do something console.log('AAA'); }); });
- 适配实现:
const A = { // 通过ID获取元素 g(id) { // 通过jQuery获取jQuery对象,然后返回第一个成员 return $(id).get(0); }, // 为元素绑定事件 on(id, type, fn) { // 如果传递参数是字符串则以id处理,否则以元素对象处理 var dom = typeof id === 'string' ? $('#' + id) : $(id); dom.on(type, fn); } }; // 窗口加载完成事件 A.on(window, 'load', function () { // 按钮点击事件 A.on('btn01', 'click', function (e) { // do something console.log('AAA'); }); });
参数适配
当一个方法的传参有固定的顺序是我们很难进行适配,因为第三方的框架可能不能完美的契合我们写的传参顺序方法,解决方法如下:
通过传入对象的方式进行参数的适配 (数组格式的数据我们一般也会处理成对象的格式在进行传参)
function funA({
name, age }) {
console.log(name, age);
}
let data1 = {
name: 'Lee',
age: 18
};
let data2 = {
age: 18,
name: 'Lee'
};
// 无所谓对象内部的顺序,打印结果一样
funA(data1); // Lee 18
funA(data2); // Lee 18
牛郎织女-代理模式
代理模式(Proxy)
由于一个对象不能直接引用另一个对象,所以需要通过代理对象在这两个对象之间起到中介的作用。
代理模式解决数据接口请求跨域问题
$.ajax({
url: 'http://www.baidu.com/',
success: function (res) {
// 无法获取返回的数据
}
});
- 引发跨域问题的条件:
- 同一域名不同的端口号
- 如:
http://www.baidu.com:8001
与http://www.baidu.com:8002
- 如:
- 同一域名不同协议
- 如:
http://www.baidu.com
与https://www.baidu.com
- 如:
- 域名和域名对应的IP
- 如:
http://www.baidu.com
与http://61.135.169.125
- 如:
- 主域与子域
- 如:
http://www.baidu.com
与http://b.a.com
- 如:
- 子域与子域
- 如:
http://tieba.baidu.com
与http://fanyi.baidu.com
- 如:
- 同一域名不同的端口号
JSONP
当使用
script
标签的时候不会出现跨域问题,可以采用script
进行解决
img
的src
也有同样效果,但由于是单向的GET请求,所以在这里并不适用
- 客户端:
<body> <script> /** * 请求成功回调 * @param {string} status 请求状态 * @param {object} res 请求结果 */ function jsonpCallBack(status, res) { console.log(status, res); // success {name: 'Lee', age: 18} } </script> <script src="http://127.0.0.1:8000?callback=jsonpCallBack&name=Lee"></script> </body>
- 服务端:
const http = require('http'); const url = require('url'); http.createServer(function (req, res) { let { query: q } = url.parse(req.url, true); let _callback = q['callback']; let data = { name: q['name'], age: 18 }; res.end(`${ _callback}('success', ${ JSON.stringify(data)})`); }).listen(8000);
特点:
- 这种方式,你可以想象成河里面的一只小船,通过小船将你的请求发送给对岸,然后对岸的人们将数据放在小船里为你带回来
代理模板
利用表单传参,服务端重定向回调实现接收后端返回的数据
http://127.0.0.1:5500/
域的文件index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>牛郎织女-代理模式-代理模板-A页面</title> </head> <body> <h1>A页面</h1> <iframe name="proxyIframe" id="proxyIframe" src=""></iframe> <form action="http://127.0.0.1:8000/" method="GET" target="proxyIframe"> <label>回调函数名称:<input type="text" name="callback" value="callback"></label> <br /> <label>重定向地址: <input type="text" name="proxy" value="http://127.0.0.1:5500/demo.html"></label> <br /> <input type="submit" value="提交"> </form> <script> function callback(data) { console.log('成功接收到后端返回的数据:', data); } </script> </body> </html> ``` + `demo.html` ```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>牛郎织女-代理模式-代理模板-B页面</title> </head> <body> <h1>B页面</h1> <script> // 获取地址栏传参 function getUrlParam(url, paramname) { var reg = new RegExp("(^|&)" + paramname + "=([^&]*)(&|$)"); // 查询匹配 substr(1)删除? match()匹配 var s = url.substr(1).match(reg); if (s != null) { return unescape(s[2]); // unescape() 函数可对通过 escape() 编码的字符串进行解码。 } return null; } window.onload = function () { let realURL = decodeURIComponent(location.search); let _callback = getUrlParam(realURL, 'callback'); let _data = getUrlParam(realURL, 'data'); // 如果不在 index.html(A页面)中,那么不执行 if (top == self) return; eval(`top.${ _callback}(${ _data})`); // 成功接收到后端返回的数据: {name: 'Lee', age: 18} }; </script> </body> </html> ```
http://127.0.0.1:8000/
域的文件(服务端)const http = require('http'); const url = require('url'); http.createServer(function (req, res) { // 接收参数 let { query: { callback: _callback, proxy: _proxy } } = url.parse(req.url, true); // 传参 let data = { name: 'Lee', age: 18 }; // 重定向地址 let redirectURL = `${ _proxy}?callback=${ _callback}&data=${ JSON.stringify(data)}`; res.statusCode = 302; res.setHeader('Location', redirectURL); // 结束 res.end(); }).listen(8000);
房子装修-装饰者模式
装饰者模式(Decorator)
在
不改变原对象
的基础上,通过对其进行包装拓展(添加属性或者方法)
使原有对象可以满足用户的更复杂需求。
- 需求:
- 当输入框获取到焦点时,定义并显示提示文案;
- 追加需求:当输入框获取焦点时,把输入框边框变成红色;
<input id="input-val" type="text">
<span id="warn-text" style="color: red;"></span>
需求一:当输入框获取到焦点时,定义并显示提示文案;
/**
* 需求1:当输入框获取到焦点时,显示提示文案
*/
const input = document.getElementById('input-val');
// 获取焦点
input.onfocus = function (e) {
console.log('旧', e);
showWarn('warn-text', '请输入不大于10个字符的名字!')
};
/**
* 显示警告提示
* @param {string} id DOM id
* @param {string} text 提示内容
*/
const showWarn = function (id, text) {
const el = document.getElementById(id);
el.innerText = text;
el.style.display = 'inline';
};
需求二:追加需求:当输入框获取焦点时,把输入框边框变成红色;
- 方式一:
直接进行历史代码修改
const input = document.getElementById('input-val'); // 获取焦点 input.onfocus = function (e) { e.target.style.border = '1px solid red'; showWarn('warn-text', '请输入不大于10个字符的名字!') }; /** * 显示警告提示 * @param {string} id DOM id * @param {string} text 提示内容 */ const showWarn = function (id, text) { const el = document.getElementById(id); el.innerText = text; el.style.display = 'inline'; };
- 方式二:
装饰者模式进行需求追加修改
/** * 定义装饰者 * @param {string} id DOM id * @param {function} fn 追加方法 */ const decorator = function (id, fn) { // 获取事件源 const el = document.getElementById(id); // 判断事件源是否已经绑定了事件(未定义事件时el.onfocus为null) if (typeof el.onfocus === 'function') { // 缓存事件源事件(保留历史事件) const oldEventFn = el.onfocus; // 追加新的事件 el.onfocus = function (e) { // 执行历史事件 oldEventFn(e); // 执行追加的事件 fn(e); }; } else { // 事件源未绑定事件,直接为事件源添加新增回调函数 el.onfocus = fn; } }; decorator('input-val', (e) => { console.log('新', e); e.target.style.border = '1px solid red'; });
适配器模式和装饰者模式区别
适配器模式
- 对原有对象适配,添加的方法与原有方法功能上大致相似
- 使用适配器时我们新增的方法是要调用原来的方法
装饰者模式
- 装饰者提供的方法与原来的方法功能项是有一定区别的
- 在装饰者模式中,不需要了解对象原有的功能,并且对象原有的方法照样可以原封不动地使用
城市间的公路-桥接模式
桥接模式(Bridge)
在系统沿着多个维度变化的同时,又不增加其复杂度并已达到
解耦
。
需求:实现tabs划过的不通效果
<style>
ul>li {
list-style: none;
float: left;
margin: 10px;
padding: 5px 10px;
border: 1px solid #333;
}
</style>
<ul>
<li>ProsperLee</li>
<li>等级:<span>Lv10</span></li>
<li>消息:<span>99+</span></li>
</ul>
- 方式一:
const tabs = document.getElementsByTagName('li'); tabs[0].onmouseover = function (e) { this.style.backgroundColor = 'red'; }; tabs[0].onmouseout = function (e) { this.style.backgroundColor = 'transparent'; }; tabs[1].onmouseover = function (e) { this.getElementsByTagName('span')[0].style.color = 'blue'; }; tabs[1].onmouseout = function (e) { this.getElementsByTagName('span')[0].style.color = 'black'; }; tabs[2].onmouseover = function (e) { this.getElementsByTagName('span')[0].style['font-weight'] = 'bolder'; }; tabs[2].onmouseout = function (e) { this.getElementsByTagName('span')[0].style['font-weight'] = 'normal'; };
- 方式二:(
桥接模式
- 提取共同点)const tabs = document.getElementsByTagName('li'); /** * 桥接函数 - 解耦抽象出共同点方法 * @param {HTMLElement} el DOM * @param {string} attr 样式属性 * @param {string} value 样式值 */ const setStyle = function (el, attr, value) { el.style[attr] = value; }; tabs[0].onmouseover = function (e) { // 这个匿名函数为桥接模式的函数,作为onmouseover和setStyle之间的桥梁 setStyle(this, 'backgroundColor', 'red'); }; tabs[0].onmouseout = function (e) { // 这个匿名函数为桥接模式的函数,作为onmouseover和setStyle之间的桥梁 setStyle(this, 'backgroundColor', 'transparent'); }; tabs[1].onmouseover = function (e) { // 这个匿名函数为桥接模式的函数,作为onmouseover和setStyle之间的桥梁 setStyle(this.getElementsByTagName('span')[0], 'color', 'blue'); }; tabs[1].onmouseout = function (e) { // 这个匿名函数为桥接模式的函数,作为onmouseover和setStyle之间的桥梁 setStyle(this.getElementsByTagName('span')[0], 'color', 'black'); }; tabs[2].onmouseover = function (e) { // 这个匿名函数为桥接模式的函数,作为onmouseover和setStyle之间的桥梁 setStyle(this.getElementsByTagName('span')[0], 'font-weight', 'bolder'); }; tabs[2].onmouseout = function (e) { // 这个匿名函数为桥接模式的函数,作为onmouseover和setStyle之间的桥梁 setStyle(this.getElementsByTagName('span')[0], 'font-weight', 'normal'); };
多元化对象
多维变量类/共同点类
/**
* 运动单元 - 多维变量类/共同点类
* @param {number} x
* @param {number} y
*/
function Speed(x, y) {
this.x = x;
this.y = y;
}
Speed.prototype.run = function () {
console.log('运动起来');
}
/**
* 着色单元 - 多维变量类/共同点类
* @param {Color} color
*/
function Color(color) {
this.color = color;
}
Color.prototype.draw = function () {
console.log('绘制色彩');
}
/**
* 变形单元 - 多维变量类/共同点类
* @param {string} shape
*/
function Shape(shape) {
this.shape = shape;
}
Shape.prototype.change = function () {
console.log('改变形状');
}
/**
* 说话单元 - 多维变量类/共同点类
* @param {string} word
*/
function Speek(word) {
this.word = word;
}
Speek.prototype.say = function () {
console.log('书写字体');
}
桥接使用共同点类以达到解耦作用
/**
* 定义一个有颜色的运动小球
* @param {number} x
* @param {number} y
* @param {Color} c
*/
function Ball(x, y, c) {
// 实现运动单元
this.speed = new Speed(x, y);
// 实现着色单元
this.color = new Color(c);
}
Ball.prototype.init = function () {
// 实现运动
this.speed.run();
// 实现着色
this.color.draw();
}
var ball = new Ball(10, 12, 'red');
ball.init();
/**
* 定义一个人类
* @param {number} x
* @param {number} y
* @param {string} f
*/
function People(x, y, f) {
this.speed = new Speed(x, y);
this.font = new Speek(f);
}
People.prototype.init = function () {
// 实现运动
this.speed.run();
// 实现说话
this.font.say();
}
var people = new People(10, 12, 16);
people.init();
/**
* 定义一个精灵
* @param {number} x
* @param {number} y
* @param {Color} c
* @param {string} s
*/
function Sprite(x, y, c, s) {
this.speed = new Speed(x, y);
this.color = new Color(c);
this.shape = new Shape(s);
}
Sprite.prototype.init = function () {
this.speed.run();
this.color.draw();
this.shape.change();
}
var sprite = new Sprite(10, 12, 'red', '矩形');
sprite.init();
桥接模式最主要的特点:
将实现层(如元素绑定的事件)与抽象层(如修饰页面UI逻辑)解耦分离,使两部分可以独立变化,桥接模式主要是对结构之间的解构。
超值午餐-组合模式
组合模式(Composite)
又称
部分-整体模式
,将对象组合成树形结构
以表示“部分整体”的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
需求:新闻列表组件
- 新闻列表组件要求:
- 文字新闻,例:
80岁老翁一夜暴富
- 图片文字新闻,例:
(img)90后父母给儿子取名为“张总”
- 图标文字新闻-类似图片文字新闻,例:
(icon)100岁老头儿半夜上吊
- 图标新闻 和 文字新闻 在一行中,例:
(icon)已逝之人30年后再现 | 面对黑暗得用火
- 分类文字新闻,例:
[type]他也曾拿着手术刀在夜半凝视着我的脖颈
- 文字新闻,例:
实现:
- 定义虚拟类,用于标记开发者需要创建怎样的类作为树上的节点(
节点模板
)/** * 新闻虚拟类 */ const News = function () { // 子组件容器 this.children = []; // 当前组件元素 this.element = null; }; News.prototype = { // 初始化DOM init: function () { throw new Error("请重写你的方法"); }, // 添加子DOM add: function () { throw new Error("请重写你的方法"); }, // 获取当前DOM getElement: function () { throw new Error("请重写你的方法"); } };
- 定义继承模式(
这里使用的是寄生组合设计模式
)/** * 寄生组合式继承 - 寄生式继承,继承原型 */ // 原型式继承 function inheritObject(o) { // 声明一个 过渡函数对象 function F() { } // 过渡对象 的 原型 继承 父对象 F.prototype = o; // 返回过度对象的一个实例,该实例的原型继承了父对象 return new F(); } /** * 寄生组合式继承 - 寄生式继承,继承原型 * @param {class} subClass 子类 * @param {class} superClass 父类 */ function inheritPrototype(subClass, superClass) { // 赋值一份父类的原型副本保存在变量中 let p = inheritObject(superClass.prototype); // 修正因为重写子类原型导致子类的constructor属性被修改 p.constructor = subClass; // 设置子类的原型 subClass.prototype = p; }
- 定义一个新闻容器(ul)存在新闻列表(
继承自虚拟类
)/** * 创建一个新闻列表组件容器(ul) * @param {string} id DOM id * @param {HTMLElement} parent 父容器(要把ul放到哪) */ const Container = function (id, parent) { // 构造函数继承父类 News.call(this); // 模块id this.id = id; // 模块的父容器 this.parent = parent; // 构建方法 this.init(); }; // 寄生式继承父类原型方法 inheritPrototype(Container, News); // 构建方法 Container.prototype.init = function () { this.element = document.createElement('ul'); this.element.id = this.id; this.element.className = 'news-container'; }; // 添加子元素方法(li) Container.prototype.add = function (child) { // 在子元素容器中插入子元素 this.children.push(child); // 插入当前组件元素树中 this.element.appendChild(child.getElement()); return this; }; // 获取当前元素方法 Container.prototype.getElement = function () { return this.element; }; // 显示方法 Container.prototype.show = function () { this.parent.appendChild(this.element); return this; };
let newsContainer = new Container('news-id', document.body); newsContainer.show(); // <ul id="news-id" class="news-container"></ul>
- 定义新闻内容的父容器(
li
)/** * 创建新闻条目(li) * @param {string} className class样式选择器 */ const NewsRow = function (className) { News.call(this); this.className = className; this.init(); }; inheritPrototype(NewsRow, News); // 初始化条目DOM NewsRow.prototype.init = function () { this.element = document.createElement('li'); this.element.className = this.className; } NewsRow.prototype.add = function (child) { // 在子元素容器中插入子元素 this.children.push(child); // 插入当前组件元素树中 this.element.appendChild(child.getElement()); return this; } NewsRow.prototype.getElement = function () { return this.element; }
- 定义各类型的新闻
文字新闻
/** * 文字新闻 * @param {string} text 新闻标题 * @param {string} href 新闻链接 */ const TextNews = function (text, href) { News.call(this); this.text = text || ''; this.href = href || ''; this.init(); }; inheritPrototype(TextNews, News); TextNews.prototype.init = function () { this.element = document.createElement('a'); this.element.innerText = this.text; this.element.href = this.href; }; TextNews.prototype.add = function (child) { // 在子元素容器中插入子元素 this.children.push(child); // 插入当前组件元素树中 this.element.appendChild(child.getElement()); return this; }; TextNews.prototype.getElement = function () { return this.element; }; ``` + `图片/图标 文字新闻` ```js /** * 图片/图标 文字新闻 * @param {string} url 图片地址 * @param {string} text 新闻标题 * @param {string} href 新闻链接 */ const ImageNews = function (url, text, href) { News.call(this); this.url = url || '默认图片地址'; this.text = text || ''; this.href = href || ''; this.init(); }; inheritPrototype(ImageNews, News); ImageNews.prototype.init = function () { // 创建a标签 this.element = document.createElement('a'); this.element.href = this.href; // 创建图片标签 let img = new Image(); img.src = this.url; this.element.appendChild(img); // 创建文字标签 let span = document.createElement('span'); span.innerText = this.text; this.element.appendChild(span); }; ImageNews.prototype.add = function (child) { // 在子元素容器中插入子元素 this.children.push(child); // 插入当前组件元素树中 this.element.appendChild(child.getElement()); return this; }; ImageNews.prototype.getElement = function () { return this.element; }; ``` + `分类文字新闻` ```js /** * 分类文字新闻 * @param {string} type 新闻分类 * @param {string} text 新闻标题 * @param {string} href 新闻链接 */ const TypeNews = function (type, text, href) { News.call(this); this.type = type || '[未分类] '; this.text = text || ''; this.href = href || ''; this.init(); }; inheritPrototype(TypeNews, News); TypeNews.prototype.init = function () { this.element = document.createElement('a'); this.element.href = this.href; let iEle = document.createElement('i'); iEle.innerText = `[${ this.type}] `; this.element.appendChild(iEle); let span = document.createElement('span'); span.innerText = this.text; this.element.appendChild(span); }; TypeNews.prototype.add = function (child) { // 在子元素容器中插入子元素 this.children.push(child); // 插入当前组件元素树中 this.element.appendChild(child.getElement()); return this; }; TypeNews.prototype.getElement = function () { return this.element; }; ```
- 实现新闻列表组件:
let newsContainer = new Container('news-id', document.body); newsContainer.add( new NewsRow('news-text').add( // 文字新闻,例:`80岁老翁一夜暴富` new TextNews('80岁老翁一夜暴富', '#') ) ).add( new NewsRow('news-img').add( // 图片文字新闻,例:`(img)90后父母给儿子取名为“张总”` new ImageNews('https://dummyimage.com/200x100/000/fff', '90后父母给儿子取名为“张总”', '#') ) ).add( new NewsRow('news-icon').add( // 图标文字新闻,例:`(icon)100岁老头儿半夜上吊` new ImageNews('https://dummyimage.com/16x16/00f/fff', '100岁老头儿半夜上吊', '#') ) ).add( new NewsRow('news-text').add( // 图标新闻和文字新闻在一行中,例:`(icon)已逝之人30年后再现 | 面对黑暗得用火` new ImageNews('https://dummyimage.com/16x16/00f/fff', '已逝之人30年后再现 | ', '#') ).add( // 图标新闻和文字新闻在一行中,例:`(icon)已逝之人30年后再现 | 面对黑暗得用火` new TextNews('面对黑暗得用火', '#') ) ).add( new NewsRow('news-type').add( // 带分类新闻,例:`[type]他也曾拿着手术刀在夜半凝视着我的脖颈` new TypeNews('深夜惊悚', '他也曾拿着手术刀在夜半凝视着我的脖颈', '#') ) ).show(); console.log(newsContainer);
最终的树型数据结构
特点:
组合模式能够给我们提供一个清晰的组成结构(整体-部分,树型-节点)。
城市公交车-享元模式
享元模式(Flyweight)
运用共享技术有效地支持大量的细粒度的对象,避免对象间拥有相同内容造成多余的开销。
分页功能:类似公交车向前一站一站的传递
<ul id="container"></ul>
<button id="next_page">下一页</button>
/**
* 享元模式
*/
var Flyweight = (function () {
// 存储已创建的元素
var created = [];
// 创建一个列表容器
function create() {
const container = document.getElementById('container');
const item = document.createElement('li');
container.appendChild(item);
// 缓存新创建的元素
created.push(item);
// 返回创建的新元素
return item;
}
return {
created,
// 通过数据渲染DOM
renderDomByData(pageSize) {
// 如果数据总量小于等于传入的单页最大数据量pageSize,那么创建总数居量条数据
// 默认created为空数组小于单页数据量,自动创建li
if (created.length < pageSize) {
return create();
}
// 当 created 存满 pageSize 条数据量后不再新增DOM,而是将第一个DOM放到最后一个的位置
else {
// 删除数组的第一个元素,并返回删除的元素
var li = created.shift();
created.push(li);
return li;
}
},
}
}());
// 全部数据
let list = new Array(18).fill(null).map((_t, i) => i + 1);
// 单页条数
const pageSize = 5;
// 当前页码
let pageNum = 1;
console.log(`------------第${
pageNum}页 渲染流程 开始--------------`);
// 初始化的渲染
for (let i = 0; i < pageSize; i++) {
// 通过享元类获取创建的元素并写入内容
Flyweight.renderDomByData(pageSize).innerText = list[i];
console.log(Flyweight.created.map(el => el.innerText));
}
console.log(`------------第${
pageNum}页 渲染流程 结束--------------`);
document.getElementById('next_page').onclick = function () {
if (list.length <= pageSize) return;
console.log(`------------第${
pageNum + 1}页 渲染流程 开始--------------`);
for (let i = 0; i < pageSize; i++) {
// 通过享元类获取创建的元素并写入内容
Flyweight.renderDomByData(pageSize).innerText = list[pageNum * pageSize + i] || '';
console.log(Flyweight.created.map(el => el.innerText));
}
console.log(`------------第${
pageNum + 1}页 渲染流程 结束--------------`);
pageNum++;
};
享元动作
让继承者享受元类对象上提供的属性和方法
// 享元类 - 让继承者享受元类对象上提供的属性和方法
const Movement = {
moveX: function (x) {
this.x = x;
},
moveY: function (y) {
this.y = y;
}
};
// 玩家继承运动对象
let Player = function (x, y, color) {
this.x = x;
this.y = y;
this.color = color;
}
Player.prototype = Movement;
Player.prototype.changeColor = function (color) {
this.color = color;
};
// 精灵继承运动对象
let Sprite = function(x, y, radius){
this.x = x;
this.y = y;
this.radius = radius;
}
Sprite.prototype = Movement;
Sprite.prototype.changeRadius = function(radius){
this.radius = radius;
}
享元模式特点
主要还是
对其数据、方法共享分离
,它将数据
和方法
分成内部数据
、内部方法
和外部数据
、外部方法
。内部方法与内部数据指的是相似或者共有的数据和方法,所以将这一部分提取出来减少开销,以提高性能。
就像城市里人们每天上班或者学生上学都要走一定的路。他们有的人开私家车,有的坐公交,当然我们是很容易看出坐公交在花费上要比私家车的开销少很多,主要是因为它让更多的人共有这一交通工具。