Heng.js前端框架

/*
 * 【Heng.js简介】
 * 框架说明:基于面向对象思想以原生js实现,不依赖任何第三方js库, 未引用外部代码片段!充分考虑不同的应用场景,保证框架的灵活与健壮。
 * 实现功能:实现了与jQuery完全一致的调用方式和细节,设置操作内部循环可设置所有匹配节点,方法可链式调用。
 * 方法分类:静态方法用于框架内部进行数据逻辑和DOM预处理以及其它全局非DOM操作;实例方法调用对应UI组件。
 * 参数配置:根据实际需求配置不同参数灵活调用,无参数时使用默认配置。
 * 选择器支持:id、class、标签、后代选择器、dom节点,实例化对象H("css选择器")。
 * 兼容性:所有方法做了跨浏览器全兼容处理,兼容当前绝大部分浏览器(IE6/7/8/9/10/11,chrome,firefox,Safari等)。
 * 变量安全:闭包环境中,保障框架自身安变量全,避免变量冲突
 * 框架性能:充分考虑优化代码逻辑提升性能。如null释放无用数据空间回收内存; 避免DOM,BOM,ECMAScript间孤岛通信; 基于文档碎片进行DOM操作; 冒泡委托处理节点集合事件...... 
 *
 * 【调用方式】
 * 静态方法: H.Method(value)
 * 实例方法: H("css选择器").Method(value), H("css选择器").Method({key:value})
 * 链式调用: H("css选择器").Method({key:value}).Method()
 * DOM树加载完毕执行: H(fn) 等价于jQuery库中的$(fn)===$(document).ready(fn)
 * 内部循环:实例方法内部对H("css选择器")匹配的节点集合进行内部循环, 可同时操作所有匹配节点
 * 手势触控:针对移动端封装了触控手势事件(Tap轻敲、Pinch捏合、Hold长按、Swipe滑动)。通过e.data报告相应事件状态,如捏合缩放系数、滑动方向、滑动距离等。

 *

 *【结构简叙】
 * 框架主体在闭包环境中保证自身安全,避免变量冲突;通过H或Heng向外引用基础类。
(function(){
    ……框架主体……;
    window.H=window.Heng=H;//向外部引用基础类
})();
H(sel).Method()或Heng.(sel).Method();//外部实例化

 *内部已做了Heng基础类实例化处理new Heng(sel);实际使用时H(sel)即为创建对象,若sel为函数内嵌代码在形成完整DOM树时立即执行。

H(fn)实现了jQuery库中的$(fn)===$(document).ready(fn)形成完整DOM树即可执行。

var H=function (sel){ 
    if(typeof sel=="function"){ H.ready(sel) }
    else{ return new Heng(sel); }
};
H(function(){ H(sel).Method();});//DOM树加载完毕H(fn)等价于jQuery库中的$(fn)===$(document).ready(fn)
 *构造函数中实现“css选择器”获取DOM节点的功能并将节点存储到nodes属性上,遵循“公共数据属性化”的规则。
function Heng(sel) {
    //【原选择器理】:选择器拆分数组,从document开始上级get后代作为下级祖先
    this.sel = sel;
    this.nodes = []; //选择器匹配的节点数组
    if (typeof sel == 'string') {
        if (sel.indexOf(' ') != -1) {
            var nodes = sel.split(' ');
            var childElements = [];
            var node = []; //实时祖先节点数组
            for (var i = 0; i < nodes.length; i++) {
                if (node.length == 0)
                    node.push(document);
                switch (nodes[i].charAt(0)) {
                case '#':
                    childElements = []; //清除上一组值再更新
                    childElements.push(this.getId(nodes[i].substring(1)));
                    node = childElements;
                    break;
                case '.':
                    childElements = [];
                    for (var j = 0; j < node.length; j++) {
                        var temps = this.getClass(nodes[i].substring(1), node[j]);
                        for (var k = 0; k < temps.length; k++) {
                            childElements.push(temps[k]);
                        }
                    }
                    node = childElements;
                    break;
                default:
                    childElements = [];
                    for (var j = 0; j < node.length; j++) {
                        var temps = this.getTagName(nodes[i], node[j]);
                        for (var k = 0; k < temps.length; k++) {
                            childElements.push(temps[k]);
                        }
                    }
                    node = childElements;
                }
            }
            this.nodes = childElements;
        } else { //sel为无空格选择器字符串
            switch (sel.charAt(0)) {
            case '#':
                this.nodes.push(this.getId(sel.substring(1)));
                break;
            case '.':
                this.nodes = this.getClass(sel.substring(1));
                break;
            default:
                this.nodes = this.getTagName(sel);
            }
        }
    } else if (typeof sel == 'object') { //sel为dom节点
        if (sel != undefined) {
            this.nodes[0] = sel;
        }
    }
}
 *静态方法不需实例化,用于框架内部进行数据逻辑和DOM预处理或其它全局非DOM操作;实例方法结合DOM节点完成需求,如果sel匹配组件入口为一个集合,则所有节点都可以实现方法的逻辑与功能。
//静态方法
H.Method() = function (arg) {
    …code…
};

//实例方法
Heng.prototype.Method = function (opts) {
    ……
    var nodes = this.nodes[i]; //sel匹配的的节点数组
    for (var i = 0; i < this.nodes.length; i++) {//内部循环
        var oDiv = this.nodes[i];
        var aUl = oDiv.getElementsByTagName('ul');
        ……
    }
}
 *面向对象编程不可避免会出现this指向混乱的情况,所有类似问题通过闭包在函数外部声明var This=this;修正this指向。
Heng.prototype.slide = function (opts) {
    ……
    var This = this;
    node = function (opts) { //node为dom节点
        //原型上的方法getClass只能通过指向实例的this去引用,此时的this为node节点
        This.getClass(classString, parrent);
        /*this.getClass(classString,parrent)错误*/
     }
  }
 *每一个实例方法都可以通过修改参数对象otps实现不同的需求,未指定的数据会使用默认值。
//模态框
Heng.prototype.dialog = function (opts) {
    //opts={"animate":是否开启动画,"enterDir":进入方向,"maskopa":遮罩透明度,"warncount":警告闪烁次数,"content":弹框内容}
    var def = {
        "animate" : false,
        "enterDir" : "top",
        "maskbg" : "#000000",
        "maskopa" : 0.5,
        "warncount" : 5,
        "content" : "<h1>Hello World</h1>"
    };//def为默认值
    opts = H.extend(def, opts);//数据合并
    ……
}

 * 

 *【方法案例】

H("form").formCheck();一行代码完成页面所有表单验证,实现功能如下:

 *对H("form")匹配到的所有表单内部任意类型的表单元素都会进行校验。

 *每个表单元素在失去焦点时会各自进行独立验证。

 *表单提交时内部所有元素进行统一验证,分为“逐一校验(遇到验证未通过的元素则中断后续检查)”和“一次校验(一次性检查所有元素)”两种模式,通过配置opts["submitCheck"]=true/false切换两种模式。

 *无论验证通过与否,都会有相应验证类型成功或失败的提示信息,可以配置opts参数自定义提示的内容和样式信息。

 *内置常用数据类型的验证,亦可通过opts["customType"]、opts["customReg"]扩展验证类型,极大的提高了表单验证的灵活性。

Heng.prototype.formCheck = function (opts) {
    //opts={"customType":"无formCheck的class值","customReg":"必须的自定义类型的正则文本","customTip":"必须的自定义类型的错误提示"}
    //自定义校验类型用"formCheck-"+opts["customType"]作为class值
    var def = {
        "user" : "*请输入3-16位字母数字",
        "password" : "*请输入5-17位以字母开头的字符数字组合",
        "email" : "*请输入正确邮箱地址",
        "Mobilephone" : "*请输入正确手机号",
        "radioBox" : "请选择",
        "ch" : "请输入中文字符",
        "wrongStyle" : "font-size:12px; color:#F00;",
        "passContent" : "成功",
        "passStyle" : "font-size:12px; color:#0C0;",
        "submitCheck" : false //提交时逐条验证还是一次验证显示所有错误信息。默认一次校验
    };
    opts = H.extend(def, opts);
    //dataType绑定到具体表单元素class值
    var dataType = ["formCheck-user", "formCheck-password", "formCheck-email", "formCheck-mobilePhone", "formCheck-ch", "formCheck-radioBox"];
    var Reg = {
        "user" : "^[a-zA-Z0-9_]{3,16}$",
        "password" : "^[a-zA-Z]+\w{5,17}$",
        "email" : "^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$",
        "mobilePhone" : "^(13+\d{9})|(159+\d{8})|(153+\d{8})$",
        "ch" : "[\u4e00-\u9fa5]"
    };
    if (opts["customType"]) {
        Reg[opts["customType"]] = opts["customReg"];
        opts[opts["customType"]] = opts["customTip"];
        //需要放在formCheck-radioBox之前
        dataType.splice((dataType.length - 2), 1, ("formCheck-" + opts["customType"]));
    }
    var This = this;
    for (var i = 0; i < this.nodes.length; i++) {
        var form = this.nodes[i];
        form.nodes = []; //dataType匹配的表单元素集合的超集,保存在每个form下
        for (var j = 0; j < dataType.length; j++) { //表单元素超集_二维数组;
            var resultArr = this.getClass(dataType[j], form);
            if (resultArr.length != 0) {
                resultArr.dataClass = dataType[j]; //将对应class值绑定在子数组上
                form.nodes.push(resultArr);
            }
        }
        for (var k = 0; k < form.nodes.length; k++) { //绑定blur事件
            (function () {
                if (form.nodes[k].dataClass != "formCheck-radioBox") {
                    var regoptsKEY = form.nodes[k].dataClass.slice(form.nodes[k].dataClass.indexOf("-") + 1);
                    var regTest = new RegExp(Reg[regoptsKEY]);
                    for (var l = 0; l < form.nodes[k].length; l++) {
                        form.nodes[k][l].onblur = function () {
                            var wrongSpan = This.getClass("formCheck-wrong", this.parentNode)[0];
                            if (!regTest.test(this.value)) {
                                wrongSpan.innerHTML = opts[regoptsKEY];
                                wrongSpan.style.cssText = def["wrongStyle"];
                            } else {
                                wrongSpan.innerHTML = def["passContent"];
                                wrongSpan.style.cssText = def["passStyle"];
                            }
                        }
                    }
                } else {
                    if (form.nodes[k].dataClass == "formCheck-radioBox") {
                        for (var m = 0; m < form.nodes[k].length; m++) {
                            var RBA = form.nodes[k][m];
                            (function (RBA) {
                                //每组radio或checkBox的input集合,form.nodes[k][m].radioBoxArr三维数组
                                form.nodes[k][m].radioBoxArr = form.nodes[k][m].parentNode.getElementsByTagName("input");
                                for (var n = 0; n < form.nodes[k][m].radioBoxArr.length; n++) {
                                    if (form.nodes[k][m].radioBoxArr[n].checked) {
                                        var statePre = true;
                                        break;
                                    } else {
                                        var statePre = false;
                                    }
                                    form.nodes[k][m].state = statePre;
                                    form.nodes[k][m].radioBoxArr[n].onclick = function () {
                                        for (var n = 0; n < RBA.radioBoxArr.length; n++) {
                                            if (RBA.radioBoxArr[n].checked) {
                                                var statePre = true;
                                                break;
                                            } else {
                                                var statePre = false;
                                            }
                                        }
                                        RBA.state = statePre;
                                        var wrongSpan = This.getClass("formCheck-wrong", this.parentNode)[0];
                                        if (RBA.state) {
                                            wrongSpan.innerHTML = opts["passContent"];
                                            wrongSpan.style.cssText = opts["passStyle"];
                                        } else {
                                            wrongSpan.innerHTML = opts["radioBox"];
                                            wrongSpan.style.cssText = opts["wrongStyle"];
                                        }
                                    }
                                }
                            })(RBA)
                        }
                    }
                }
            })()
        }
        (function (form) {
            form.onsubmit = function (e) {
                var e = e || window.event;
                for (var k = 0; k < form.nodes.length; k++) {
                    var regoptsKEY = form.nodes[k].dataClass.slice(form.nodes[k].dataClass.indexOf("-") + 1);
                    var regTest = new RegExp(Reg[regoptsKEY]);
                    for (var l = 0; l < form.nodes[k].length; l++) {
                        if (def["submitCheck"]) {
                            if (form.nodes[k].dataClass != "formCheck-radioBox") {
                                var wrongSpan = This.getClass("formCheck-wrong", form.nodes[k][l].parentNode)[0];
                                if (!regTest.test(form.nodes[k][l].value)) {
                                    wrongSpan.innerHTML = opts[regoptsKEY];
                                    wrongSpan.style.cssText = def["wrongStyle"];
                                    return false; //停止执行中断循环,阻止默认
                                } else {
                                    wrongSpan.innerHTML = def["passContent"];
                                    wrongSpan.style.cssText = def["passStyle"];
                                }
                            } else if (form.nodes[k].dataClass == "formCheck-radioBox") {
                                var wrongSpan = This.getClass("formCheck-wrong", form.nodes[k][l].parentNode)[0];
                                if (form.nodes[k][l].state) {
                                    wrongSpan.innerHTML = opts["passContent"];
                                    wrongSpan.style.cssText = opts["passStyle"];
                                } else {
                                    wrongSpan.innerHTML = opts["radioBox"];
                                    wrongSpan.style.cssText = opts["wrongStyle"];
                                    return false;
                                }
                            }
                        } else {
                            if (form.nodes[k].dataClass != "formCheck-radioBox") {
                                var wrongSpan = This.getClass("formCheck-wrong", form.nodes[k][l].parentNode)[0];
                                if (!regTest.test(form.nodes[k][l].value)) {
                                    wrongSpan.innerHTML = opts[regoptsKEY];
                                    wrongSpan.style.cssText = opts["wrongStyle"];
                                    e.preventDefault();
                                    e.returnValue = false;
                                } else {
                                    wrongSpan.innerHTML = opts["passContent"];
                                    wrongSpan.style.cssText = opts["passStyle"];
                                }
                            } else if (form.nodes[k].dataClass == "formCheck-radioBox") {
                                var wrongSpan = This.getClass("formCheck-wrong", form.nodes[k][l].parentNode)[0];
                                if (form.nodes[k][l].state) {
                                    wrongSpan.innerHTML = opts["passContent"];
                                    wrongSpan.style.cssText = opts["passStyle"];
                                } else {
                                    wrongSpan.innerHTML = opts["radioBox"];
                                    wrongSpan.style.cssText = opts["wrongStyle"];
                                    e.preventDefault();
                                    e.returnValue = false;
                                }
                            }
                        }
                    }
                }
            }
        })(form)
    }
}

H("div").on ("Tap",fn/{"Tap":fn,"Hold":fn});自定义事件处理程序实现触控手势操作:

 *触控手势不属于系统事件,需一套在数据层管理事件及函数添加、销毁、遍历执行的机制,模拟系统原生add/removeEventListener方法的功能。

 *触控手势事件的触发依赖系统touchstart/touchmove/touchend,触点ID号identifier跟踪同一手指。通过e.data报告手势状态。

 *Tap:屏幕停留时间小于250ms,因不可避免手指抖动、力度不均影响触点状态,手指离屏时坐标偏移需小于阀值(水平:10px,垂直:5px)。

 *Pinch:手指移动中水平或垂直方向坐标相对进屏时偏移大于10px时可触发,两根手指水平垂直偏移量4个值中的最大者作为缩放系数e.data.k。

 *Hold:手指在屏幕停留时间大于500ms,如果提前离开或者手指偏移量大于阀值(水平:10px,垂直:5px)则停止定时器不触发。

 *swipe:手指需在1000ms内连续(中途不反向)移动至少30px,对比进出屏触点坐标水平垂直偏移量,取较大者来确定e.data报告滑动方向和距离。

/*自定义事件处理程序*/
H.addEvent = function (type, handler) {
    this.handlers = {}; //{type1:[fn],type2:[fn]}
    if (typeof this.handlers[type] == "undefined") {
        this.handlers[type] = [];
    }
    this.handlers[type].push(handler);
};
H.fireEvent = function (type, data) {//调用对应类型函数触发事件
    if (this.handlers[type]instanceof Array) {
        var arrayEvent = this.handlers[type];
        for (var i = 0, len = arrayEvent.length; i < len; i++) {
            if (typeof arrayEvent[i] === "function") {
                arrayEvent[i](data);
            }
        }
    }
};
H.removeEvent = function (type, handler) {
    if (this.handlers[type]instanceof Array) {
        var arrayEvent = this.handlers[type];
        for (var i = 0, len = arrayEvent.length; i < len; i++) {
            if (arrayEvent[i] === handler) {
                arrayEvent.splice(i, 1);
                break;
            }
        }
    }
}
/*触控手势实现和事件绑定*/
Heng.prototype.on = function (handle, fn) { //调用方式(type,fn) 或 ({type1:fn1,type2:fn2})
    for (var i = 0; i < this.nodes.length; i++) {
        var node = this.nodes[i];
        var iStouch = false;
        var hasTap = false,
        hasPinch = false,
        hasHold = false,
        hasSwipeLeft = false,
        hasSwipeRight = false;
        left = true,
        right = true,
        up = true,
        down = true;
        var inTime,
        outTime,
        touchID,
        touchID1,
        inX,
        inY,
        inX1,
        inY1,
        outX,
        outY,
        moveX,
        moveY,
        moveX1,
        moveY1,
        disX,
        disY,
        disX1,
        disY1,
        t;
        if (arguments.length == 1) {
            for (var k in handle) {
                if ((k === "Tap") || (k === "Pinch") || (k === "Hold") || (k === "Swipe")) {
                    iStouch = true; //触控和鼠标事件不和共用
                    this.iStouch = iStouch;
                    checkType(k);
                }
            }
        } else {
            iStouch = (handle === "Tap") || (handle === "Pinch") || (handle === "Hold") || (handle === "Swipe");
            this.iStouch = iStouch;
            checkType(handle);
        }
        if (iStouch) {
            H.bind(node, "touchstart", tsFn);
            H.bind(node, "touchend", teFn);
            if (!hasTap) {
                H.bind(node, "touchmove", tmFn);
            }
            if (arguments.length == 1) {
                for (var j in handle) {
                    H.addEvent(j, handle[j]);
                }
            } else {
                H.addEvent(handle, fn);
            }
        } else {
            if (arguments.length == 1) {
                for (var j in handle) {
                    H.bind(node, j, handle[j]);
                }
            } else {
                H.bind(node, handle, fn)
            }
        }
    }
    function checkType(x) {
        switch (x) {
        case "Tap":
            hasTap = true;
            break;
        case "Pinch":
            hasPinch = true;
            break;
        case "Hold":
            hasHold = true;
            break;
        case "swipeLeft":
            hasSwipe = true;
            break;
        }
    }
    function tsFn(e) {
        touchID = e.changedTouches[0].identifier;
        inTime = new Date().getTime();
        inX = e.changedTouches[0].clientX;
        inY = e.changedTouches[0].clientY;
        if (e.changedTouches[1]) {
            touchID1 = e.changedTouches[1].identifier;
            inX1 = e.changedTouches[1].clientX;
            inY1 = e.changedTouches[1].clientY;
        }
        if (hasHold) {
            if (e.targetTouches.length === 1 && e.changedTouches[0].identifier === touchID) {
                t = window.setTimeout(function () {
                        H.fireEvent("Hold", e);
                    }, 500)
            }
        }
    }
    function tmFn(e) {
        if (hasHold) {
            if ([Math.abs(moveY - inY) >= 5] && [Math.abs(moveX - inX) >= 10]) {
                window.clearTimeout(t);
            }
        } else if (hasPinch && e.targetTouches.length === 2 && e.changedTouches[1].identifier === touchID1 && e.changedTouches[0].identifier === touchID) {
            e.preventDefault();
            disX = Math.abs(inX1 - inX);
            disY = Math.abs(inY1 - inY);
            disX1 = Math.abs(moveX1 - moveX);
            disY1 = Math.abs(moveY1 - moveY);
            if ((Math.abs(disX - disX1) >= 10) || (Math.abs(disY - disY1) >= 10)) {
                e.data.k = (Math.abs(disX - disX1) > Math.abs(disY - disY1)) ? (disX1 / disX) : (disY1 / disY); //缩放因子
                H.fireEvent("Pinch", e);
            }
        } else if (hasSwipe && e.targetTouches.length === 2) {
            if (e.changedTouches[0].clientX >= moveX) {
                left = true
            } //对比相邻两次的移动判断是否中途反向
            else {
                right = false
            }
            if (e.changedTouches[0].clientY >= moveY) {
                up = true
            } else {
                down = false
            }
            e.preventDefault();
        }
        moveX = e.changedTouches[0].clientX;
        moveY = e.changedTouches[0].clientY;
        if (e.changedTouches[1]) {
            moveX1 = e.changedTouches[1].clientX;
            moveY1 = e.changedTouches[1].clientY;
        }
    }
    function teFn(e) {
        outTime = new Date().getTime();
        outX = e.changedTouches[0].clientX;
        outY = e.changedTouches[0].clientY;
        if (hasTap && e.targetTouches.length === 1 && e.changedTouches[0].identifier === touchID) {
            if ([(outTime - inTime) <= 250] && [Math.abs(outY - inY) <= 5] && [Math.abs(outX - inX) <= 10]) {
                H.fireEvent("Tap", e);
            }
        } else if (hasHold && (outTime - inTime) <= 500) {
            window.clearTimeout(t);
        } else if (hasSwipe && e.targetTouches.length === 1 && e.changedTouches[0].identifier === touchID) {
            if (Math.abs(outX - inX) >= Math.abs(outY - inY)) {
                e.data.dis = Math.abs(outX - inX);
                if (outX >= inX) {
                    e.data.direction = "right"
                } else {
                    e.data.direction = "left"
                }
            } else {
                e.data.dis = Math.abs(outY - inY);
                if (outY >= inY) {
                    e.data.direction = "down"
                } else {
                    e.data.direction = "up"
                }
            }
            if ((outTime - inTime) <= 1000 && (Math.abs(outY - inY) >= 30 || Math.abs(outX - inX) >= 30)) {
                if ((left && right) || (up && down)) { //保证中途连续不反向
                    H.fireEvent("swipe", e);
                }
            }
        }
    }
    return this;
};

H("ul").tree ()无极树形渲染,数据源分析:

 *扁平json:[ {"id":节点描述,"text":节点文本},{节点},{节点}]。一维数据无法展示嵌套关系,只需一次循环即可遍历到所有数据,但是必须以id或其它属性描述不同数据间的结构和嵌套关系才能还原html视图。

 *标准json:[ {"text":节点文本,"children":[{子节点},{子节点}]},{节点} ],不固定的无限层级嵌套子节点。数据嵌套关系直观表达了即将生成的html结构,传统循环无法解决数据层级不固定的遍历需求,必须通过函数递归将当前数据作为下一级子数据遍历的入口,才能层层深入查找多维数据。

function render(jsonData, parentNode) {
    for (var j = 0; j < jsonData.length; j++) {
        var oli = document.createElement("li");
        oli.innerHTML = jsonData[j]["text"];
        parentNode.appendChild(oli);
        if (jsonData[j]["children"]) {
            var oUl = document.createElement("ul");
            oli.appendChild(oUl);
            render(jsonData[j]["children"], oUl);//将当前数据作为下一级子数据遍历的入口
        }
    }
}

 * Author: wang-heng

 * Date: 2015-07-16

 * Copyright @ wh412057730原创

 */

点击这里查看框架源代码

你可能感兴趣的:(面向对象,oop,前端框架,原型,原生js)