简单的js模板引擎 | 模板字符串替换

方文锋  2023-08-23 19:53:49  711  首页学习JavaScript

最近编写了一个简单的js模板引擎(my_template),模板字符串替换(tpl_data_replace),对象或数组转换成元素节点(OA2Element) 的方法。

代码如下:

 /**
 * 判断是否是数组
 * @param argument
 * @param {number|boolean} opt 是否用严格模式
 * @return {*}
 */
function is_array(argument, opt) {
    if (opt === 1 || opt === true) {
        return Object.prototype.toString.call(argument) === "[object Array]";
    }
    return argument && (typeof argument === "object") && ("length" in argument) && (typeof argument.length === "number");
}

/**
 * 判断是否是对象
 * @param argument
 * @return {*}
 */
function is_object(argument) {
    if (argument && (typeof argument === "object") && ("length" in argument)) {
        return Object.prototype.toString.call(argument) === "[object Object]";
    }
    return argument && (typeof argument === "object") && !("length" in argument);
}

/**
 * 判断是否是函数
 * @param argument
 * @return {*|boolean}
 */
function is_function(argument) {
    return typeof argument === "function";
}

/**
 * 判断是否是字符串
 * @param argument
 * @return {*|boolean}
 */
function is_string(argument) {
    return typeof argument === "string";
}

/**
 * 判断是否是布尔值
 * @param argument
 * @return {boolean}
 */
function is_boolean(argument) {
    return typeof argument === "boolean";
}

/**
 * 判断是否是数字
 * @param argument
 * @return {boolean}
 */
function is_number(argument) {
    return typeof argument === "number";
}

/**
 * 判断是否是未定义
 * @param argument
 * @return {boolean}
 */
function is_undefined(argument) {
    return typeof argument === "undefined";
}

/**
 * 判断是否是空值
 * @param argument
 * @return {boolean}
 */
function is_null(argument) {
    return argument === null || argument === "";
}

/**
 * 是否存在[true存在,false不存在]
 * @param argument
 * @param {array} opt 这里面的值是代表“不存在”
 * @return {boolean}
 */
function is_exist(argument, opt) {
    opt || (opt = ["", null, false, undefined]);
    for (var k in opt) {
        if (opt[k] === argument) {
            return false;
        }
    }
    return true;
}

/**
 * 判断是否是整数
 * @param vars
 * @return {boolean}
 */
function is_int(vars) {
    return /^[-]{0,1}\d+$/i.test(vars + "");
}

/**
 * 判断是否是小数
 * @param vars
 * @return {boolean}
 */
function is_float(vars) {
    return /^[-]{0,1}[0-9]+\.{0,1}[0-9]*$/i.test(vars + "")
}

/**
 * 是否是html标签元素
 * @param argument
 * @returns {boolean}
 */
function is_element(argument) {
    var reg = /^(\[object HTML[A-Za-z\-_\.]*Element\])$/i;
    return reg.test(Object.prototype.toString.call(argument));
}

/**
 * 是否是 symbol 类型
 * @param argument
 * @return {boolean}
 */
function is_symbol(argument) {
    return typeof argument === "symbol";
}

/**
 * 是否是字符串(扩大范围)
 * @param {string|number} str
 * @returns {boolean|(*|boolean)}
 */
function y_string(str) {
    return is_exist(str) && (is_string(str) || is_number(str));
}

/**
 * 搜索数组中是否存在指定的值
 * @param {string} needle
 * @param {array} haystack
 * @param {boolean} argStrict
 * @return {boolean}
 */
function in_array(needle, haystack, argStrict) {
    var key = "", strict = !!argStrict;
    if (strict) {
        for (key in haystack) {
            if (haystack[key] === needle) {
                return true
            }
        }
    } else {
        for (key in haystack) {
            if (haystack[key] == needle) {
                return true
            }
        }
    }
    return false
}

/**
 * 遍历数组或对象(Object原型链上)
 * @param {function} fn 传入两个参数:key,value。返回值为0或false时停止循环遍历
 */
Object.prototype.foreach = function (fn) {
    if (typeof fn === "function") {
        var rv;
        if (is_array(this, 1)) {
            for (var i = 0, len = this.length; i < len; i++) {
                rv = fn.call(this, i, this[i]);
                if (rv === 0 || rv === false) {
                    break;
                }
            }
        }
        if (is_object(this) || is_function(this)) {
            for (var attr in this) {
                rv = fn.call(this, attr, this[attr]);
                if (rv === 0 || rv === false) {
                    break;
                }
            }
        }
    }
};

/**
 * 遍历对象属性(对象,数组,函数)
 * @param {function} fn
 */
Object.prototype.foreachAll = function (fn) {
    if (typeof fn === "function") {
        var list = ["[object Storage]", "[object Window]", "[object Function]"], rv;
        if (is_object(this) || is_array(this, 1) || is_function(this) || in_array(Object.prototype.toString.call(this), list, true)) {
            for (var attr in this) {
                rv = fn.call(this, attr, this[attr]);
                if (rv === 0 || rv === false) {
                    break;
                }
            }
        }
    }
};

/**
 * 设置对象属性
 * @param {object|array} obj
 * @param {object} data
 */
function set_obj_attr(obj, data) {
    if ((is_object(obj) || is_array(obj)) && is_object(data)) {
        for (var attr in data) {
            is_object(data[attr]) ? set_obj_attr(obj[attr] = is_object(obj[attr]) || is_array(obj[attr]) ? obj[attr] : {}, data[attr]) : obj[attr] = data[attr];
        }
    }
}

/**
 * 对象或数组转换成元素节点
 * @param {object|array|string} ele_data
 * @param {Element|object|array} container_ele
 * @returns {*}
 * @constructor
 */
function OA2Element(ele_data, container_ele) {
    if (is_object(ele_data)) {
        var tagName = ele_data.tagName, attr = ele_data.attr, child = ele_data.child;
        if (y_string(tagName)) {
            var E = document.createElement(tagName), k;
            for (k in ele_data) {
                if (ele_data.hasOwnProperty(k)) {
                    E[k] = ele_data[k];
                }
            }
            if (is_object(attr)) {
                for (var k1 in attr) {
                    if (attr.hasOwnProperty(k1) && y_string(attr[k1])) {
                        E.setAttribute(k1, attr[k1]);
                    }
                }
            } else if (is_array(attr)) {
                attr.foreach(function (k, v) {
                    if (is_object(v) && y_string(v.name) && y_string(v.value)) {
                        E.setAttribute(v.name, v.value);
                    } else if (is_array(v) && y_string(v[0]) && y_string(v[1])) {
                        E.setAttribute(v[0], v[1]);
                    }
                });
            }
            if (is_array(child)) {
                var Fragment = document.createDocumentFragment();
                child.foreach(function (index, v) {
                    Fragment.appendChild(OA2Element(v));
                    //Fragment.insertBefore()
                });
                E.appendChild(Fragment);
            }
            is_element(container_ele) && container_ele.appendChild(E);
            return E;
        }
    } else if (is_array(ele_data, 1)) {
        var Fragment = document.createDocumentFragment();
        ele_data.foreach(function (k, v) {
            Fragment.appendChild(OA2Element(v));
        });
        is_element(container_ele) && container_ele.appendChild(Fragment);
        return Fragment;
    } else {
        if (y_string(ele_data)) {
            var oE = {tagName: ele_data}, v3;
            for (var i = 1, len = arguments.length - 1; i < len; i++) {
                v3 = arguments[i];
                if (is_object(v3)) {
                    set_obj_attr(oE, v3);
                } else if (is_array(v3)) {
                    y_string(v3[0]) && (oE[v3[0]] = v3[1]);
                }
            }
            return OA2Element(oE, arguments[len]);
        }
    }
    return false;
}

/**
 * 模板字符串替换
 * @param {object|array} data
 * @param {string} tpl_string
 * @param {string|RegExp} mode_start
 * @param {string|RegExp} mode_end
 * @returns {*}
 */
function tpl_data_replace(data, tpl_string, mode_start, mode_end) {
    var escape=function(str){return str.replace(/([{}\[\]().+*?^$|\/])/g,"\\$1");}, type=Object.prototype.toString, rvFn=function(rv){is_array(rv)&&(rv="Array");is_object(rv)&&(rv="Object");is_function(rv)&&(rv="Function");return rv;};
    var get_obj_val = function (obj, str) {
        if (is_object(obj)) {
            if (is_array(str)) {
                for (var i = 0, len = str.length, attr, end_len = len - 1, rv; i < len; i++) {
                    attr = str[i];
                    rv = obj[attr];
                    if (is_object(rv) || is_array(rv, 1)) {
                        obj = obj[attr];
                        if (i === end_len) {
                            return rvFn(rv);
                        }
                    } else {
                        return rvFn(rv);
                    }
                }
            } else if (y_string(str)) {
                str = str.replace(/(^[.]+|[.]+$)/g, "");
                var list = str.split(/[.]+/);
                return get_obj_val(obj, list);
            }
        }
        return false;
    };
    var is_reg = "[object RegExp]", is_reg_mode_start = (type.call(mode_start) === is_reg), is_reg_mode_end = (type.call(mode_end) === is_reg);
    if (is_reg_mode_start || mode_start === 0) {
        (mode_start === 0) && (mode_start = /(@[A-Za-z0-9._\-]+@)/g);
        if (is_object(data) && y_string(tpl_string)) {
            is_reg_mode_end || (mode_end = /([A-Za-z0-9._\-])+/g);
            return tpl_string.replace(mode_start, function (a) {
                var _a = a.match(mode_end);
                return get_obj_val(data, _a[0]);
            });
        }
    } else {
        y_string(mode_start) || (mode_start = "@");
        y_string(mode_end) || (mode_end = "@");
        if (is_object(data)) {
            var k, v, r_arr = [];
            for (k in data) {
                v = data[k];
                if (data.hasOwnProperty(k)) {
                    r_arr.push(escape(mode_start + k + mode_end));
                }
            }
            if (y_string(tpl_string)) {
                var r_str = r_arr.join("|");
                var reg3 = new RegExp("(" + r_str + ")", "g");
                return tpl_string.replace(reg3, function (a) {
                    var _a = a.replace(new RegExp("(^" + escape(mode_start) + "|" + escape(mode_end) + "$)", "g"), "");
                    return rvFn(data[_a]);
                });
            }
        }
    }
    return false;
}

/**
 * 简单的模板引擎
 * @param {string|Element} tpl
 * @param {string} start 开始标记
 * @param {string} end 结束标记
 * @returns {Function}
 */
function my_template(tpl, start, end) {
    is_element(tpl) && (tpl = tpl.innerHTML);
    var match, rv, fn, str = '';
    var reg = {
        start: y_string(start) ? start : "<@",
        end: y_string(end) ? end : "@>",
        re: {
            start: /(\{.*?\})/g,
            end: /([A-Za-z0-9._\-]+)/g
        },
    };
    var regStr = {
        jsCode: tpl_data_replace(reg, "({@start}.*?{@end})", reg.re.start, reg.re.end),
        getJsCode: tpl_data_replace(reg, "(^\s*{@start}[=]*|{@end}\s*$)", reg.re.start, reg.re.end),
    };
    //构成函数里面的代码
    var fnCode = ['var args = arguments;\nvar r=[];\n'],
        jsCodeReg = new RegExp(regStr.jsCode, "g");
    //辅助添加代码片段
    var add = function (str, opt) {
        opt || (opt = 1);
        var _str = str;
        str = str.replace(/(\n)/g, "☗n☖").replace(/(\r)/g, "☗r☖").replace(/(\t)/g, "☗t☖").replace(/(['"])/g, "\\$1");
        (opt === 1) && fnCode.push("r.push('" + str + "');\n ");
        (opt === 2) && fnCode.push("r.push(" + _str + ");\n ");
    };
    //用于清除条两边标签,获取纯的js代码片段
    var reg_trim_tag = new RegExp(regStr.getJsCode, "g");
    //用于清除js代码片段部分
    var reg_del_js = new RegExp(jsCodeReg.source, "g");
    //用于存储上一次匹配到的位置
    var ma = {index: 0};
    while (match = jsCodeReg.exec(tpl)) {
        str = tpl.slice(ma.index, ma.index = match.index);
        str = str.replace(reg_del_js, "");
        add(str);
        if (String(match[1]).search(reg.start + "=") !== -1) {
            add(match[1].replace(reg_trim_tag, ""), 2);
        } else {
            fnCode.push(String(match[1]).replace(reg_trim_tag, "") + "\n ");
        }
        //console.log(str, match);
    }
    str = tpl.slice(ma.index, tpl.length);
    str = str.replace(new RegExp(jsCodeReg.source, "g"), "");
    add(str);
    fnCode.push("return r.join('').replace(/(☗n☖)/g,'\\n').replace(/(☗r☖)/g,'\\r').replace(/(☗t☖)/g,'\\t');");
    rv = fnCode.join('');
    fn = new Function("data", "arg2", "arg3", rv);
    //console.log(fn);
    return fn;
} 


测试代码:

 <!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="Keywords" content="关键词1,关键词2"><!--关键词-->
    <meta name="Description" content="描述"><!--描述-->
    <meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximun-scale=1.0"><!--宽度为设备宽度,初始缩放为1.0倍,最小缩放为1.0倍,最大缩放为1.0倍-->
    <!-- 设置浏览器不缓存 begin -->
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Cache-control" content="no-cache">
    <meta http-equiv="Cache" content="no-cache">
    <!-- 设置浏览器不缓存 end -->
    <title>js模板</title>
    <script type="text/javascript" src="/js/jq.min.js"></script>
</head>
<body>
<script type="text/javascript" src="/js/jquery.easing.1.3.min.js"></script>
<script type="text/javascript">
    $(function () {
        window.fn2233 = my_template($("#tpl-01").html());
        window.fn4455 = my_template($("#tpl-02").html(), "<@js", "@>");
        window.fn6677 = my_template($("#tpl-03").html(), "<%", "%>");
    });
</script>
<script type="text/plain" id="tpl-01">
    <div>
        <p class="x111">我的模板引擎</p>
        <@ var list = data.list; for(var i=0,len=list.length;i<len;i++){ @>   @>
        <p><@= list[i] @></p>
        <@ } @>
    </div>
</script>
<script type="text/plain" id="tpl-02">
    <div>
        <p class="x111">我的模板引擎-<@js=data.title@></p>
        <@js var list = data.list; for(var i=0,len=list.length;i<len;i++){ @>
        <p><@js= list[i] @></p>
        <@js } @>
    </div>
</script>
<script type="text/plain" id="tpl-03">
    <div>
        <p class="x111">我的模板引擎-<%=data.title%></p>
        <% var list = data.list; for(var i=0,len=list.length;i<len;i++){ %>
        <p><%= list[i] %></p>
        <% } %>
    </div>
    <div>
        <p class="x222">我的模板引擎-<%=data.title%></p>
        <% var list = data.list; for(var i=0,len=list.length;i<len;i++){ %>
        <p><%= list[i] %></p>
        <% } %>
    </div>
    <% console.log(args,data); %>
</script>
</body>
</html> 


如图:







一个简单的js模板引擎就弄好了