zoukankan      html  css  js  c++  java
  • mass Framework attr模块 v3

    为了获取最佳的性能与兼容性,attr模块在v3中分割为两大块。attr模块大胆HTML5的classList,Array.isArray等新式API, attr_fix则是专门为IE6789准备的。attr模块存在大量的钩子,为摆平浏览器做了许多部件,但我们没有必须把所有补丁都集成到一个JS文件,其中过半是为IE侍侯的,因此分割出去对标准浏览器的加载量非常有利。

    下面是attr.js,它相当于jQuery的attributes.js(github中的划分)。

    //==================================================
    // 属性操作模块 v3
    //==================================================
    define("attr",!!top.getComputedStyle ? ["$node"] : ["$attr_fix"], function( $ ){
        var rreturn = /\r/g,
        rtabindex = /^(a|area|button|input|object|select|textarea)$/i,
        rnospaces = /\S+/g,
        support = $.support
        function getValType( el ){
            var ret = el.tagName.toLowerCase();
            return ret == "input" && /checkbox|radio/.test(el.type) ? el.type : ret;
        }
    
        $.implement({
            /**
             *  为所有匹配的元素节点添加className,添加多个className要用空白隔开
             *  如$("body").addClass("aaa");$("body").addClass("aaa bbb");
             *  <a href="http://www.cnblogs.com/rubylouvre/archive/2011/01/27/1946397.html">相关链接</a>
             */
            addClass: function( item ){
                if ( typeof item == "string") {
                    for ( var i = 0, el; el = this[i++]; ) {
                        if ( el.nodeType === 1 ) {
                            item.replace(rnospaces, function(clazz){
                                el.classList.add(clazz);
                            })
                        }
                    }
                }
                return this;
            },
            //如果不传入类名,则清空所有类名,允许同时删除多个类名
            removeClass: function( item ) {
                var removeSome  = item && typeof item === "string",removeAll = item === void 0
                for ( var i = 0, node; node = this[ i++ ]; ) {
                    if ( node.nodeType === 1 ) {
                        if(removeSome && node.className){
                            item.replace(rnospaces, function(clazz){
                                node.classList.remove(clazz);
                            })
                        }else if(removeAll){
                            node.className = "";
                        }
                    }
                }
                return this;
            },
           
            //如果第二个参数为true,要求所有匹配元素都拥有此类名才返回true
            hasClass: function( item, every ) {
                var method = every === true ? "every" : "some",
                rclass = new RegExp('(\\s|^)'+item+'(\\s|$)');//判定多个元素,正则比indexOf快点
                return $.slice(this)[ method ](function( el ){//先转换为数组
                    return  (el.className || "").match(rclass);
                });
            },
            //如果存在(不存在)就删除(添加)指定的类名。对所有匹配元素进行操作。
            toggleClass: function( value, stateVal ){
                var type = typeof value , classNames = type === "string" && value.match( rnospaces ) || [], className, i,
                isBool = typeof stateVal === "boolean";
                return this.each(function( el ) {
                    i = 0;
                    if(el.nodeType === 1){
                        var self = $( el ),
                        state = stateVal;
                        if(type == "string" ){
                            while ( (className = classNames[ i++ ]) ) {
                                state = isBool ? state : !self.hasClass( className );
                                self[ state ? "addClass" : "removeClass" ]( className );
                            }
                        } else if ( type === "undefined" || type === "boolean" ) {
                            if ( el.className ) {
                                $._data( el, "__className__", el.className );
                            }
                            el.className = el.className || value === false ? "" : $._data( el, "__className__") || "";
                        }
                    }
                });
            },
            //如果匹配元素存在类名old则将其置换为类名neo
            replaceClass: function( old, neo ){
                for ( var i = 0, node; node = this[ i++ ]; ) {
                    if ( node.nodeType === 1 && node.className ) {
                        var arr = node.className.match( rnospaces ), arr2 = [];
                        for ( var j = 0; j < arr.length; j++ ) {
                            arr2.push( arr[j] == old ? neo : arr[j]);
                        }
                        node.className = arr2.join(" ");
                    }
                }
                return this;
            },
            val : function( item ) {
                var el = this[0], getter = valHooks[ "option:get" ];
                if ( !arguments.length ) {//读操作
                    if ( el && el.nodeType == 1 ) {
                        var ret =  (valHooks[ getValType(el)+":get" ] ||
                            $.propHooks[ "@default:get" ])( el, "value", getter );
                        return  typeof ret === "string" ? ret.replace( rreturn, "" ) : ret == null ? "" : ret;
                    }
                    return void 0;
                }
                //我们确保传参为字符串数组或字符串,null/undefined强制转换为"", number变为字符串
                if( Array.isArray( item ) ){
                    item = item.map(function (item) {
                        return item == null ? "" : item + "";
                    });
                }else if( isFinite(item) ){
                    item += "";
                }else{
                    item = item || "";
                }
                return this.each(function( el ) {//写操作
                    if ( el.nodeType == 1 ) {
                        (valHooks[ getValType(el)+":set" ] ||
                            $.propHooks[ "@default:set" ])( el, "value", item , getter );
                    }
                });
            }
        });
      
        var cacheProp = {}
        function defaultProp(node, prop){
            var name = node.tagName+":"+prop;
            if(name in cacheProp){
                return cacheProp[name]
            }
            return cacheProp[name] = document.createElement(node.tagName)[prop]
        }
        $.mix({
            fixDefault: $.noop,
            propMap:{//属性名映射
                "accept-charset": "acceptCharset",
                "char": "ch",
                "charoff": "chOff",
                "class": "className",
                "for": "htmlFor",
                "http-equiv": "httpEquiv"
            },
            prop: function(node, name, value){
                if($["@bind"] in node){
                    if(node.nodeType === 1 && !$.isXML( node )){
                        name = $.propMap[ name.toLowerCase() ] || name;
                    }
                    var access = value === void 0 ? "get" : "set"
                    return ($.propHooks[name+":"+access] ||
                        $.propHooks["@default:"+access] )(node, name, value)
                }
            },
            attr: function(node, name, value){
                if($["@bind"] in node){
                    if ( typeof node.getAttribute === "undefined" ) {
                        return $.prop( node, name, value );
                    }
                    //这里只剩下元素节点
                    var noxml = !$.isXML( node ), type = "@w3c", isBool
                    if( noxml ){
                        name = name.toLowerCase();
                        var prop = $.propMap[ name ] || name
                        if( !support.attrInnateName ){
                            type = "@ie"
                        }
                        isBool = typeof node[ prop ] == "boolean" && typeof defaultProp(node,prop) == "boolean"//判定是否为布尔属性
                    }
                    //移除操作
                    if(noxml){
                        if (value === null || value === false && isBool ){
                            return $.removeAttr(node, name )
                        }
                    }else if( value === null ) {
                        return node.removeAttribute(name)
                    }
                    //读写操作
                    var access = value === void 0 ? "get" : "set"
                    if( isBool ){
                        type = "@bool";
                        name = prop;
                    }
                    return ( noxml  && $.attrHooks[ name+":"+access ] ||
                        $.attrHooks[ type +":"+access] )(node, name, value)
                }
            },
            //只能用于HTML,元素节点的内建不能删除(chrome真的能删除,会引发灾难性后果),使用默认值覆盖
            removeProp: function( node, name ) {
                if(node.nodeType === 1){
                    if(!support.attrInnateName){
                        name = $.propMap[ name.toLowerCase() ] ||  name;
                    }
                    node[name] = defaultProp(node, name)
                }else{
                    node[name] = void 0;
                }
            },
            //只能用于HTML
            removeAttr: function( node, name ) {
                if(name && node.nodeType === 1){
                    name = name.toLowerCase();
                    if(!support.attrInnateName){
                        name = $.propMap[ name ] ||  name;
                    }
                    //小心contentEditable,会把用户编辑的内容清空
                    if(typeof node[ name ] != "boolean"){
                        node.setAttribute( name, "")
                    }
                    node.removeAttribute( name );
                    // 确保bool属性的值为bool
                    if ( node[ name ] === true ) {
                        node[ name ] = false;
                        $.fixDefault(node, name, false)
                    }
                }
            },
            propHooks:{
                "@default:get": function( node, name ){
                    return node[ name ]
                },
                "@default:set": function(node, name, value){
                    node[ name ] = value;
                },
                "tabIndex:get": function( node ) {
                    //http://www.cnblogs.com/rubylouvre/archive/2009/12/07/1618182.html
                    var ret = node.tabIndex;
                    if( ret === 0 ){//在标准浏览器下,不显式设置时,表单元素与链接默认为0,普通元素为-1
                        ret = rtabindex.test(node.nodeName) ? 0 : -1
                    }
                    return ret;
                }
            },
            attrHooks: {
                "@w3c:get": function( node, name ){
                    var ret =  node.getAttribute( name ) ;
                    return ret == null ? void 0 : ret;
                },
                "@w3c:set": function( node, name, value ){
                    node.setAttribute( name, "" + value )
                },
                "@bool:get": function(node, name){
                    //布尔属性在IE6-8的标签大部字母大写,没有赋值,并且无法通过其他手段获得用户的原始设值
                    return node[ name ] ? name.toLowerCase() : void 0 
                },
                "@bool:set": function(node, name){
                    //布尔属性在IE6-8的标签大部字母大写,没有赋值,并且无法通过其他手段获得用户的原始设值
                    node.setAttribute( name, name.toLowerCase() )
                    node[ name ]  = true;
                    $.fixDefault(node, name, true)
                }
    
            }
        });
        "Attr,Prop".replace($.rword, function( method ){
            $.fn[ method.toLowerCase() ] = function( name, value ) {
                return $.access( this, name, value, $[ method.toLowerCase() ] );
            }
            $.fn[ "remove" + method] = function(name){
                return this.each(function() {
                    $["remove" + method]( this, name );
                });
            }
        });
        //========================propHooks 的相关修正==========================
        var prop = "accessKey,allowTransparency,bgColor,cellPadding,cellSpacing,codeBase,codeType,colSpan,contentEditable,"+
        "dateTime,defaultChecked,defaultSelected,defaultValue,frameBorder,isMap,longDesc,maxLength,marginWidth,marginHeight,"+
        "noHref,noResize,noShade,readOnly,rowSpan,tabIndex,useMap,vSpace,valueType,vAlign";
        prop.replace($.rword, function(name){
            $.propMap[name.toLowerCase()] = name;
        });
    
        //safari IE9 IE8 我们必须访问上一级元素时,才能获取这个值
        if ( !support.optSelected ) {
            $.propHooks[ "selected:get" ] = function( node ) {
                for( var p = node;typeof p.selectedIndex != "number";p = p.parentNode){}
                return node.selected;
            }
        }
        //========================valHooks 的相关修正==========================
        var valHooks = {
            "option:get":  function( node ) {
                var val = node.attributes.value;
                //黑莓手机4.7下val会返回undefined,但我们依然可用node.value取值
                return !val || val.specified ? node.value : node.text;
            },
            "select:get": function( node ,value, getter) {
                var option,  options = node.options,
                index = node.selectedIndex,
                one = node.type === "select-one" || index < 0,
                values = one ? null : [],
                max = one ? index + 1 : options.length,
                i = index < 0 ? max :  one ? index : 0;
                for ( ; i < max; i++ ) {
                    option = options[ i ];
                    //旧式IE在reset后不会改变selected,需要改用i === index判定
                    //我们过滤所有disabled的option元素,但在safari5下,如果设置select为disable,那么其所有孩子都disable
                    //因此当一个元素为disable,需要检测其是否显式设置了disable及其父节点的disable情况
                    if ( ( option.selected || i === index ) &&
                        (support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
                        (!option.parentNode.disabled || !$.type( option.parentNode, "OPTGROUP" )) ) {
                        value = getter( option );
                        if ( one ) {
                            return value;
                        }
                        //收集所有selected值组成数组返回
                        values.push( value );
                    }
                }
                return values;
            },
            "select:set": function( node, name, values, getter ) {
                $.slice(node.options).forEach(function( el ){
                    el.selected = !!~values.indexOf( getter(el) );
                });
                if ( !values.length ) {
                    node.selectedIndex = -1;
                }
            }
        }
        //checkbox的value默认为on,唯有chrome 返回空字符串
        if ( !support.checkOn ) {
            "radio,checkbox".replace( $.rword, function( name ) {
                valHooks[ name + ":get" ] = function( node ) {
                    return node.getAttribute("value") === null ? "on" : node.value;
                }
            });
        }
        //处理单选框,复选框在设值后checked的值
        "radio,checkbox".replace( $.rword, function( name ) {
            valHooks[ name + ":set" ] = function( node, name, value) {
                if ( Array.isArray( value ) ) {
                    return node.checked = !!~value.indexOf(node.value ) ;
                }
            }
        });
        if(typeof $.fixIEAttr == "function"){
            $.fixIEAttr(valHooks, $.attrHooks);
        }
        return $;
    });
    

    attr_fix模块,里面只有一个补丁函数。

    define("attr_fix", !!top.getComputedStyle, ["$node"], function($){
        $.fixIEAttr = function(valHooks, attrHooks){
            var rnospaces = /\S+/g,  
            rattrs = /\s+([\w-]+)(?:=("[^"]*"|'[^']*'|[^\s>]+))?/g,
            rquote = /^['"]/,
            defaults = {
                checked: "defaultChecked",
                selected: "defaultSelected"
            }
            $.fixDefault = function(node, name, value){
                var _default =  defaults[name];
                if(_default){
                    node[ _default ] = value;
                }
            }
            if(!("classList" in $.html)){
                $.fn.addClass = function( item ){
                    if ( typeof item == "string") {
                        for ( var i = 0, el; el = this[i++]; ) {
                            if ( el.nodeType === 1 ) {
                                if ( !el.className ) {
                                    el.className = item;
                                } else {
                                    var a = (el.className+" "+item).match( rnospaces );
                                    a.sort();
                                    for (var j = a.length - 1; j > 0; --j)
                                        if (a[j] == a[j - 1])
                                            a.splice(j, 1);
                                    el.className = a.join(" ");
                                }
                            }
                        }
                    }
                    return this;
                }  
                $.fn.removeClass =  function( item ) {
                    if ( (item && typeof item === "string") || item === void 0 ) {
                        var classNames = ( item || "" ).match( rnospaces ), cl = classNames.length;
                        for ( var i = 0, node; node = this[ i++ ]; ) {
                            if ( node.nodeType === 1 && node.className ) {
                                if ( item ) {//rnospaces = /\S+/
                                    var set = " " + node.className.match( rnospaces ).join(" ") + " ";
                                    for ( var c = 0; c < cl; c++ ) {
                                        set = set.replace(" " + classNames[c] + " ", " ");
                                    }
                                    node.className = set.slice( 1, set.length - 1 );
                                } else {
                                    node.className = "";
                                }
                            }
                        }
                    }
                    return this;
                }
            }
    
            attrHooks[ "@ie:get"] = function( node, name ){
                var str = node.outerHTML.replace(node.innerHTML, ""), obj = {}, k, v;
                while (k = rattrs.exec(str)) { //属性值只有双引号与无引号的情况
                    v = k[2]
                    obj[ k[1].toLowerCase() ] = v ? rquote.test( v ) ? v.slice(1, -1) : v : ""
                }
                return obj[ name ];
            }
            attrHooks["@ie:set"] = function( node, name, value ){  
                var attr = node.getAttributeNode( name );
                if ( !attr ) {//不存在就创建一个同名的特性节点
                    attr = node.ownerDocument.createAttribute( name );
                    node.setAttributeNode( attr );
                }
                attr.value = value + "" ;
            }
            
            var support = $.support
            if ( !support.attrInnateValue ) {
                // http://gabriel.nagmay.com/2008/11/javascript-href-bug-in-ie/
                //在IE6-8如果一个A标签,它里面包含@字符,并且没任何元素节点,那么它里面的文本会变成链接值
                $.propHooks[ "href:set" ] =  attrHooks[ "href:set" ] = function( node, name, value ) {
                    var b
                    if(node.tagName == "A" && node.innerText.indexOf("@") > 0
                        && !node.children.length){
                        b = node.ownerDocument.createElement('b');
                        b.style.display = 'none';
                        node.appendChild(b);
                    }
                    node.setAttribute(name, value+"");
                    if (b) {
                        node.removeChild(b);
                    }
                }
            }
            //========================attrHooks 的相关修正==========================
            if ( !support.attrInnateHref ) {
                //http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
                //IE的getAttribute支持第二个参数,可以为 0,1,2,4
                //0 是默认;1 区分属性的大小写;2取出源代码中的原字符串值(注,IE67对动态创建的节点没效),4用于取得完整路径
                //IE 在取 href 的时候默认拿出来的是绝对路径,加参数2得到我们所需要的相对路径。
                "href,src,width,height,colSpan,rowSpan".replace( $.rword, function( method ) {
                    attrHooks[ method.toLowerCase() + ":get" ] =  function( node,name ) {
                        var ret = node.getAttribute( name, 2 );
                        return ret == null ? void 0 : ret;
                    }
                });
                "width,height".replace( $.rword, function( attr ){
                    attrHooks[attr+":set"] = function(node, name, value){
                        node.setAttribute( attr, value === "" ? "auto" : value+"");
                    }
                });
                $.propHooks["href:get"] = function( node, name ) {
                    return node.getAttribute( name, 4 );
                };
            }
            if(!document.createElement("form").enctype){//如果不支持enctype, 我们需要用encoding来映射
                $.propMap.enctype = "encoding";
            }
            if ( !support.attrInnateStyle ) {
                //IE67是没有style特性(特性的值的类型为文本),只有el.style(CSSStyleDeclaration)(bug)
                attrHooks[ "style:get" ] = function( node ) {
                    return node.style.cssText.toLowerCase() || undefined ;
                }
                attrHooks[ "style:set" ] = function( node, name, value ) {
                    node.style.cssText = value + "";
                }
            }
            //========================valHooks 的相关修正==========================
            if(!support.attrInnateName){//IE6-7 button.value错误指向innerText
                valHooks["button:get"] =  attrHooks["@ie:get"]
                valHooks["button:set"] =  attrHooks["@ie:set"]
            }
            delete $.fixIEAttr;
        }
        return $;
    })
    
    
    

    到目前为止,lang, css, event, attr模块都分割完成。它比jQuery分割为jquery1.9与jquery-compat-1.9更细腻,带来的性能提升更好。

    顺便庆祝一下,此篇是mass Framework专题博文的第100篇博文!

    机器瞎学/数据掩埋/模式混淆/人工智障/深度遗忘/神经掉线/计算机幻觉/专注单身二十五年
  • 相关阅读:
    java内存泄露
    hbase java api
    配置CRT远程登录
    kafka分区消费模型
    JAVA内存区域
    JVM分代和垃圾回收
    spring中bean的作用域
    分布式RPC
    session共享
    ZooKeeper实现分布式session
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2830798.html
Copyright © 2011-2022 走看看