JavaScript 23 种设计模式之 2 原型模式

JavaScript 23 种设计模式之 2 原型模式

  • 概念与特点
  • 结构与实现
  • 应用场景
  • 应用实例
  • 总结

概念与特点

概念:
用一个已经创建的实例作为原型,通过复制该实例对象来创建许多和原型相同或相似的新对象。

特点:

  1. 高效,无需知道对象创建的细节。
  2. 不用 new 很多对象,节省空间。

结构与实现

原型模式中需要提供一个实例对象作为原型,然后提供一个复制(克隆)该原型的方法。这里的复制又分为浅复制和深复制。在 JavaScript 中主要通过 Object.create 来实现对象clone。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <title>React App</title>
</head>
<body>
<script>
    function Person(name) {
        this.name = name;
        this.clone = function (param) {
            return Object.create(this,{name:{value:param||name}});
        }
    }
    var obj1 = new Person("jack");
    var obj2 = obj1.clone();
    var obj3 = obj1.clone("rose");
    console.log(obj1);
    console.log(obj2);
    console.log(obj3);
    console.log("obj1==obj2?"+(obj1==obj2));
</script>
</body>
</html>

应用场景

  1. 对象相同或相似(只有几个属性不同),比喻孙悟空可以变出好多小猴子。
  2. 对象的创建过程比较麻烦,但是复制比较简单,比如做雪花场景,要创建一个雪花可以比较复杂,大片的雪花直接复制就行。

应用实例

实现一个虚拟 DOM 批量制造雪花。
简单说一下虚拟DOM创建过程。

  1. 根据参数通过 createElement 方法递归创建树形结构的元素对象。
  2. 根据元素对象的 type 值判断属于文本元素还是普通的 DOM 元素,然后分别 new 构造函数。
  3. 通过 mountComponent 方法拼接虚拟 DOM 节点。
  4. 将节点挂载到根元素。

以下案例中用到原型模式的地方就是第 2 步。在构造函数中添加了 clone 方法。所以批量创建雪花的时候不用批量 new 虚拟 DOM 对象。而是直接通过 clone 方法实现,并传入不同的样式实现满屏大小不一,位置不一的雪花效果。

    //获取 DOM 元素
    function ReactDOMComponent(element) {
        // 存下当前的element对象引用
        this._currentElement = element;
        this._rootNodeID = null;
        //这里是原型模式的重点
        this.clone = function (style) {
            //1.深复制一份对象。
            var str = JSON.stringify(this);
            var newObj = JSON.parse(str);
            //2.把原型最新 this,可以调用 prototype 上的方法。
            newObj.__proto__ = this;
            if(style){
                newObj._currentElement.props.style = style;
            }
           return  newObj;
        }
    }
  //根据元素类型生成不同的组件
    var componentInstance = instantiateReactComponent(element) //通过vDom生成Component
    //调用拼接元素
    var html = componentInstance.mountComponent(0);
    //以后就是通过克隆的方式批量创建元素
    for(var i=0;i<100;i++){
        var el= componentInstance.clone(
            "left:"+parseInt(Math.random() * window.innerWidth)+"px;"+
            "top:"+parseInt(Math.random() * (window.innerHeight-0))+"px;"+
            "font-size:"+parseInt(Math.random() * (50))+"px"
        );
        var markup1 = el.mountComponent(0);
        html+=markup1;
    }
    root.innerHTML = html;

完整代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8"/>
    <title>React App</title>
</head>
<style>
    .snow{
        color:#fff;
        position: absolute;
        opacity: 0.9;
    }
</style>
<body style="background: black">
<div id="root"></div>
<script>
    //递归创建虚拟 DOM 属性
    function createElement(type, config, children) {
        var props = {};
        var propName;
        config = config || {};
        var key = config.key || null;
        for (propName in config) {
            if (config.hasOwnProperty(propName) && propName !== "key") {
                props[propName] = config[propName];
            }
        }
        var childrenLength = arguments.length - 2;
        if (childrenLength === 1) {
            props.children = Array.isArray(children) ? children : [children];
        } else if (childrenLength > 1) {
            var childArray = [];
            for (var i = 0; i < childrenLength; i++) {
                childArray[i] = arguments[i + 2];
            }
            props.children = childArray;
        }
        return new ReactElement(type, key, props);
    }
    function ReactElement(type, key, props) {
        this.type = type;
        this.key = key;
        this.props = props;
    }
    //获取文本元素
    function ReactDOMTextComponent(text) {
        // 存下当前的字符串
        this._currentElement = "" + text;
    }
    //拼接文本元素
    ReactDOMTextComponent.prototype.mountComponent = function() {
        return this._currentElement;
    };
    //获取 DOM 元素
    function ReactDOMComponent(element) {
        // 存下当前的element对象引用
        this._currentElement = element;
        this._rootNodeID = null;
        //这里是原型模式的重点
        this.clone = function (style) {
            //1.深复制一份对象。
            var str = JSON.stringify(this);
            var newObj = JSON.parse(str);
            //2.把原型最新 this,可以调用 prototype 上的方法。
            newObj.__proto__ = this;
            if(style){
                newObj._currentElement.props.style = style;
            }
           return  newObj;
        }
    }
    //拼接 DOM 元素
    ReactDOMComponent.prototype.mountComponent = function() {
        var props = this._currentElement.props;
        // 外层标签
        var tagOpen = "<" + this._currentElement.type;
        var tagClose = " + this._currentElement.type + ">";
        // 拼接标签属性
        for (var propKey in props) {
            if (props[propKey] && propKey != "children" && !/^on[A-Za-z]/.test(propKey)) {
                tagOpen += " " + propKey + "=" + props[propKey];
            }
        }
        // 渲染子节点dom
        var content = "";
        var children = props && props.children || [];
        var childrenInstances = []; // 保存子节点component 实例
        children.forEach((child, key) => {
            var childComponentInstance = instantiateReactComponent(child);
            // 为子节点添加标记
            childComponentInstance._mountIndex = key;
            childrenInstances.push(childComponentInstance);
            // 得到子节点的渲染内容
            var childMarkup = childComponentInstance.mountComponent();
            // 拼接在一起
            content += " " + childMarkup;
        });
        // 保存component 实例
        this._renderedChildren = childrenInstances;
        // 拼出整个html内容
        return tagOpen + ">" + content + tagClose;
    };
    //初始化元素
    function instantiateReactComponent(node) {
        //文本节点的情况
        if (typeof node === "string" || typeof node === "number") {
            return new ReactDOMTextComponent(node);
        }
        //浏览器默认节点的情况
        if (typeof node === "object" && typeof node.type === "string") {
            //注意这里,使用了一种新的component
            return new ReactDOMComponent(node);
        }
    }
    //创建虚拟元素
    var element = createElement(
        'div',
        {class:'snow'},
        '❆'
    );
    //根据元素类型生成不同的组件
    var componentInstance = instantiateReactComponent(element) //通过vDom生成Component
    //调用拼接元素
    var html = componentInstance.mountComponent(0);
    //以后就是通过克隆的方式批量创建元素
    for(var i=0;i<100;i++){
        var at = componentInstance.clone(
            "left:"+parseInt(Math.random() * window.innerWidth)+"px;"+
            "top:"+parseInt(Math.random() * (window.innerHeight-0))+"px;"+
            "font-size:"+parseInt(Math.random() * (50))+"px"
        );
        var markup1 = at.mountComponent(0);
        html+=markup1;
    }
    root.innerHTML = html;
</script>
</body>
</html>

总结

以上案例中实现原型模式的两个关键步骤。

  1. 通过 JSON.stringify 和 JSON.parse 实现对象的深度复制。
  2. 通过改变对象的 __proto__ 来实现继承(可使用原型上的属性和方法)。

你可能感兴趣的:(设计模式)