JavaScript设计模式(三):结构型设计模式-外观模式、适配器模式、代理模式、装饰者模式、桥接模式、组合模式、享元模式

套餐服务-外观模式

外观模式(Facade)

为一组复杂的子系统接口提供一个更高级的统一接口,通过这个接口使得对子系统接口访问更容易

在JavaScript中有时也会用于对底层结构兼容性统一封装简化用户使用

需求:为指定按钮添加一个事件

document.onclick = function (e) {
    
    
    if (e.target === document.getElementById('btn01')) {
    
    
        alert('btn01 的点击事件');
    }
};
  • ⚠ 注意:以上方法的缺点:
    • document 绑定了 onclick 事件,但 onclickDOM0级事件,也就是说这种方式绑定的事件相当于为元素绑定一个事件方法,所以如果团队中有人再次通过这种方式为 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) {
    
    
        // 无法获取返回的数据
    }
});
  • 引发跨域问题的条件:
    1. 同一域名不同的端口号
      • 如: http://www.baidu.com:8001http://www.baidu.com:8002
    2. 同一域名不同协议
      • 如: http://www.baidu.comhttps://www.baidu.com
    3. 域名和域名对应的IP
      • 如: http://www.baidu.comhttp://61.135.169.125
    4. 主域与子域
      • 如: http://www.baidu.comhttp://b.a.com
    5. 子域与子域
      • 如: http://tieba.baidu.comhttp://fanyi.baidu.com

JSONP

当使用 script 标签的时候不会出现跨域问题,可以采用 script 进行解决

imgsrc 也有同样效果,但由于是单向的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)

不改变原对象的基础上,通过对其进行包装拓展(添加属性或者方法)使原有对象可以满足用户的更复杂需求。

  • 需求:
    1. 当输入框获取到焦点时,定义并显示提示文案;
    2. 追加需求:当输入框获取焦点时,把输入框边框变成红色;
<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;
}

享元模式特点

主要还是对其数据、方法共享分离,它将数据方法分成内部数据内部方法外部数据外部方法

内部方法与内部数据指的是相似或者共有的数据和方法,所以将这一部分提取出来减少开销,以提高性能。

就像城市里人们每天上班或者学生上学都要走一定的路。他们有的人开私家车,有的坐公交,当然我们是很容易看出坐公交在花费上要比私家车的开销少很多,主要是因为它让更多的人共有这一交通工具。

猜你喜欢

转载自blog.csdn.net/weixin_43526371/article/details/127470432