最近学习JS的感悟-1

自定义标签在IE6-8的困境

2015/07/20 · HTML5 ·
IE,
自定义标签

原文出处:
司徒正美   

或许未来前端组件化之路都是自定义标签,但这东西早在20年前,JSTL已在搞了。现在Web
Component还只有webkit支持。但一个组件库,还需要一个特殊的标识它们是一块的。不过这个XML已经帮我们搞定了,使用scopeName,如”<xxx:dialog>”。在我继续往下想如何处理如何为这个标签绑定数据,与其他组件通信,管理生命周期,等等大事之前,我还有一个不得不面对的问题,就是如何兼容IE6-8!

比如以下一个页面:

图片 1

在chrome, firefox, IE11, IE11的IE6兼容模式分别如下:

图片 2
图片 3
图片 4
图片 5

我们会发现IE6下实际是多出许多标签,它是把闭标签也变成一个独立的元素节点

图片 6

这个AA:DIV标签被开膛破肚,里面子节点全部暴出来,成为其兄弟节点了。因此想兼容它,就要费点劲。有个两个情况需要考虑,1是用户已经将它写在页面上,情况同上;2是用户是将它放在字符串模版中,这个用正则搞定。不过正则要是碰上复杂的属性名,还是会晕掉。因此我还是打算使用原生的HTML
parser。换言之,字符串,我还是会将它变成节点。这么办呢?!我想了许多办法,后来还是使用VML的命名空间法搞定!

我们将上面的页面改复杂点,再看看效果!

图片 7
图片 8

可以看到其套嵌关系现在完全正确,并且标签名不会大写化,也不会生成多余节点!

好了,我们再判定一下是否为自定义标签,或者准确地说,这个节点是否我们组件库中定义的自定义标签。某些情况下,一个页面可以存在多套组件库,包括avalon的,ploymer的,或者是直接用Web
Component写的。

avalon的组件库将使用命名空间,这样就好区别开。在IE6-9中,判定element.scopeName是否为aa(这是组件库的命名空间,你可以改个更高大上的名字),在其他浏览器判定此元素的localName是否以aa:开头就行了!

JavaScript

function isWidget(el, uiName){ return el.scopeName ? el.scopeName ===
uiName: el.localName.indexOf(uiName+”:”) === 0 }

1
2
3
function isWidget(el, uiName){
  return   el.scopeName ? el.scopeName === uiName: el.localName.indexOf(uiName+":") === 0
}

这个难题解决后,我们就可以开搞基于自定义标签的UI库了!

1 赞 1 收藏
评论

图片 9

           
我这儿写了一个openClassScan参数,解释一下,这个参数是为了解决类似于<div
class = “a
b”></div>这种,因为如果要支持通过API查询如class:a,那么需要每个节点都判定是否contain这个class,比较费时间,而我认为很多时候不需要,所以默认我关闭了。

         
常用的功能当然还是阻止事件冒泡以及阻止默认事件的发生,很遗憾,IE和非IE处理方式还是不一样的,比如阻止冒泡IE采用的是cancelBubble,而其他浏览器采用的是stopPropagation,所以还是需要写:

       
 首先是ready的判定,关于这个可以看我另外一篇日志:http://my.oschina.net/mingtingling/blog/110282

var _getElementsByClassName = null;
        if(document.getElementsByClassName) {
                _getElementsByClassName = function(str) {
                    var fetchedEles = document.getElementsByClassName(str),
                        eles = [];

                    for(var i = 0, len = fetchedEles.length; i < len; i++) {
                        eles.push(fetchedEles[i]);
                    }
                    return eles;
                };
        } else {
            _getElementsByClassName = function(str,openClassScan) {
                var eles = [],
                    allElements = document.getElementsByTagName("*"),
                    i;
                if(openClassScan) {
                    for (i = 0; i< allElements.length; i++ ) {
                        if (tp.dom.containClass(allElements[i],str)) {
                            eles.push(allElements[i]);
                        }
                    }
                } else {
                    for (i = 0; i< allElements.length; i++ ) {
                        if (str === allElements[i].className) {
                            eles.push(allElements[i]);
                        }
                    }
                }
                return eles;
            };
        }
var arr = new Array();
if(xxx) {
   for(var i = 0,len = arr.length ; i < len; i++) {

   }
} else {
   for(var i = 0,len = arr.length ; i < len; i++) {

   }
}

         
 之后写一个辅助函数,判定是否是复杂查询,如果是,那么切开查询字符串,切成数组。

       
 这里我主要讲一下tp.dom.query,也就是查询怎么做的,首先看看常用的查询有:#aa,.aa,input。

tp.type = tp.type || {};
tp.type.isArray = function(ele) {
    return "[object Array]" === Object.prototype.toString.call(ele);
};
tp.type.isFunction = function(ele) {
    return "[object Function]" === Object.prototype.toString.call(ele);
};
tp.type.isObject = function(ele) {
    return ("function" === typeof ele) || !!(ele && "object" === typeof ele);
};
tp.type.isString = function(ele) {
    return "[object String]" === Object.prototype.toString.call(ele);
};
tp.type.isNumber = function(ele) {
    return "[object Number]" === Object.prototype.toString.call(ele) && isFinite(ele);
};
tp.type.isBoolean = function(ele) {
    return "boolean" === typeof ele;
};
tp.type.isElement = function(ele) {
    return ele && ele.nodeType == 1;
};
tp.type.isUndefined = function(ele) {
    return "undefined" === typeof ele;
};

       
写这个库,首先使用了命名空间,我比较喜欢toper,所以我首先定义了一个变量:

   
 也就是在一个函数内部去判定是否是IE,然后相应的执行相应的函数,但是这样,如果添加的事件监听器很多,每次都if什么的,我个人感觉很不好,所以我后面添加了一个辅助函数:

       
对动画有兴趣的童鞋,可以看看我的最近学习JS的感悟-2,关于动画的http://my.oschina.net/mingtingling/blog/127296

var arr = new Array(),
    i;

       
我看了一下,不同的库的判定方式不一样,我这儿使用的是tangram的判定方式。

tp.event.on = function(element,event,fn) {
        _addEventListener(element,event,fn,false);
         };
tp.use("tp.dom.sizzle",function(sizzle) {});

     
这种方式我在tangram中没有看到,我是看了淘宝的KISSY之后学习到的,也就是所谓的AMD(异步模块定义)。

       
开始写的感觉真是痛苦啊,什么都不懂,所以就去看了看tangram的源码,为什么看tangram呢,其实原因比较搞笑,当时校招的时候我面试百度前端,被刷掉了,当时面试官让我看看它们百度使用的JS库tangram,我就想看看它们那个库到底有什么了不起的。。。

       
 事件这一块儿实际上我做了N多东西,但是由于讲不完,所以暂时不说了。

  

         
 我认为,就这样一个功能比较简单的query就够了,不必要实现类似于jquery里面的如此复杂的查询,如果要使用它,其实也很简单,因为jquery的查询引擎sizzle已经开源,完全可以将它加入到这个库,而现在toper也是这么做的,要调用sizzle就使用:

     
我之前写了一篇日志来实现AMD,当然,效率低下,反正大家看看就行了http://my.oschina.net/mingtingling/blog/113815

         
 除了事件监听器,还需要事件事件的添加,删除等,也就是add,fire,remove等,这里就不说了。

       
 然后就开始下载JS的电子书,可能是自己比较浮躁吧,看书真心看不进去,我还是喜欢边看边写代码这种。写了一段时间,渐渐的觉得最开始的感觉慢慢回来了,当然,也遇到了N多的问题。

tp.event.getTarget = function(event) {
        return event.target || event.srcElement;
    };

       
这种方式也是借鉴了tangram的写法,采用对象字面量的形式。这样所有toper定义的方法都放在了tp这个私有空间内了,比如对DOM的操作就放在tp.dom中。

     
使用use方式,它会自动去下载tp.a.js和tp.b.js,下载完成之后,执行回调函数。

     
 还记得我大二的时候开始接触JS,那个时候从图书馆借了N多的书籍,然后边看边用editplus写,然后遇到问题,各种DEBUG,在做项目的时候,各种兼容性问题,真是痛苦啊。由于项目需要尽快写完,所以就开始接触了jquery,还是从图书馆抱了几本书,然后下载了jquery源码,然后边看书籍边写代码,看了几章之后,觉得貌似不难,然后就从网上下载了jquery的文档,对照着文档,对其调用搞得算是比较清楚了。

           那这样,tp.event.on就变得非常简单了:

var tp = tp || {};

           我把每一个查询如:tp.dom.query(“#aa
input”)分为两种,一种为简单查询(也就是如查询:#aaa),另外一种是复杂查询,每个复杂查询都是由很多简单查询构成的,比如#aaa
input,就可以切成:#aaa和input。

     
 当然,还有浏览器版本的判定,暂时就不贴出来了。这里基本思路就是判定navigator.useAgent返回的字符串中,每个浏览器里面的这个字符串是不一样的,当然,这个过程比较恶心,而且有可能后面某一个浏览器会改变它的userAgent,导致整个判定失效,比如IE,听别人说后面新IE要把userAgent搞成firefox,真心搞不懂,这是要逆天吗?

     
我采用的结构是core+组件的方式,tp.core.js(压缩后为tp.core-min.js),而其他的组件每个组件一个文件,而组件之间可能存在依赖关系,这种依赖关系就通过AMD解决。

     
 由于这个库完全是为毕设做的,所以这里面的很多文件都是为实现毕设的某些功能而写的。

          除了DOM,对变量类型的判定和浏览器的检测也是很重要的。

相关文章