【转】组件化的Web王国

自定义标签在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

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

X-Tag和Brick

Mozilla开发了自己的自定义元素
兼容库,叫做 X-Tag。X-Tag是一个为启用Web
Component进行多项兼容的库,并即将提供对Web Component的完整支持。

以下就是使用X-Tag的 my-avatar 自定义组件,与标准文档十分类似:

查看代码演示:

Mozilla同时还创造了一个叫 Brick 的库,其中包括X-Tag,提供“一组用来方便快速构建Web应用程序的UI组件”,使用与Google的Polymer相似的方式。

var _listeners = {},
        _addEventListener,
        _removeEventListener;
    if (window.attachEvent) {

        var _realEventCallbackFns = {};
        _addEventListener = function(element,event,fn) {
            //第三个参数_realFn是为了修正this
            var realFn = function(e) {fn.call(element,e);};
            _realEventCallbackFns[fn] = realFn;
            element.attachEvent("on" + event,realFn);
        };
        _removeEventListener = function(element,event,fn) {
            element.detachEvent("on" + event,_realEventCallbackFns[fn]);
        };
    } else if (window.addEventListener) {
        _addEventListener = function(element,event,fn,capture) {
            element.addEventListener(event, fn,capture);
        };
        _removeEventListener = function (element,event,fn,capture) {
            element.removeEventListener(event,fn,capture);
        };
    } else {
        _addEventListener = function(element,event,fn) {
            element["on" + event] = fn;
        };
        _removeEventListener = function(element,event) {
            delete element["on" + event];
        };
    }

组件间是如何通信的?

在深入示例之前有必要简单地提到组件间通信的问题。如果组件之间是“独立”、“模块化”的,他们又是如何相互通信的呢?

最显而易见的答案就是让组件间相互引用并通过他们之间的API交互。这样做的问题就在于,这种做法会让组件相互依赖。短期内可能还好,一段时间以后,你在修改程序的时候程序会失控,修改一个组件就会对另一个组件产生极大的影响。决定移除一个不能带来预期价值组件可能会让你的应用程序停止工作,因为它背后会有数个组件依赖于它。

此时,解决方案是提供松耦合的,让组件之间很少或者几乎不知道彼此的方案。组件并不直接创建其他组件,在他们需要通信的时候,他们通过“接口/约定”或者通过 “服务”。我们在构建BRJS程序时考虑了很多这些方面的东西,并且使用 ServiceRegistry 访问用于组件间通讯的服务或者是Web
API这样的资源。Angular和Ember采用了服务和依赖注入解决这类问题。

var arr = new Array(),
    i;

可组合

之前也讨论过,基于组件的架构让组件组合成新组件更加容易。这样的设计让组件更加专注,也让其他组件中构建和暴露的功能更好利用。

不论是给程序添加功能,还是用来制作完整的程序,更加复杂的功能也能如法炮制。这就是这种方法的主要好处。

是否有必要把所有的东西转换成组件,事实上取决于你自己。没有任何理由让你的程序由 你自己 的组件组合成你最惊叹的功能 ,乃至 最花哨的功能。而这些组件又反过来构成其他组件。如果你从这个方法中得到了好处,就想方设法地去坚持它。然而要注意的是,不要用同样的方法把事情变得复杂,你并不需要过分关注如何让组件重用。而是要关注呈现程序的功能。

      这样使用变量i已经被重复定义了,所以需要把变量i定义在if之前,即:

可互换

一个功能明确好组件的API能让人轻易地更改其内部的功能实现。要是程序内部的组件是松耦合的,那事实上可以用一个组件轻易地替换另一个组件,只要遵循相同的 API/接口/约定。

假如你使用GoInstant提供的实时功能服务组件,那他们这周关闭服务这样的新闻会影响到你。然而,只要提供了相同的数据同步API,你也可以自行构建使用一个 FirebaseComponent 组件或者 PubNubComponent 组件。

        注意一下啊,由于JS变量作用域没有block,所以请不要使用下面这种:

本文由 埃姆杰 翻译。未经许可,禁止转载! 英文出处:Future Insights。

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

AngularJS

AngularJS 可能是现在用于构建程序最流行的前端解决方案了。作为创建者的Google,重新思考HTML,考虑如何重新发明,满足如今Web开发的需要。

Angular中可以使用自定义指令定义组件。之后,你可以使用 HTML
标记声明自定义组件。

查看代码演示: 

这个例子展示了使用Angular指令的简单程度。值scope 定义了从
 my-avatar 元素中取得,并且之后用来构建相应的img标签和渲染成用户头像的属性。

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

Ember

框架与库的争论旷日持久,总的来说框架是强制你按某种方式做事情,所以它是邪恶的。很显然,Angular是个框架,而Ember的作者,Yehuda
Katz和Tom
Dale也很乐意把Ember看作框架。

Ember 有对它称作组件的内建支持。Ember
Components背后的理念是尽可能的向Web
Components看齐,当浏览器支持允许时,就可以很方便地迁移到Web
Components中。

查看代码演示: 

上面的例子中使用了 handlebars 做模板,所以元素的定义不是同一种语法。

 

模块

你可能听说过 “组件是天然模块”的说法。好吧,感谢它,我们又要解释这里的术语!

你可能会觉得“组件”的说法更加适合用来描述UI,而“模块”更适合描述提供服务的功能逻辑。而对于我来说,模块和组件意思相近,都提供组织、聚焦和封装,是与某个功能单位相关的。

       
到寒假的时候,决定自己的毕设不使用现在成熟的JS库,反而自己来写一个不完善的库,这样学习的更多,当然,也比较费时间。

示例组件my-avatar

为了展示我们如何用这些库和框架构建最基本的组件,我们建立了一个带有UI,用于取回和显示用户头像的简单示例。在可能的情况下,该组件会有 my-avatar 标签,会从以下两个属性中取得头像:

  • service 允许设置一个服务。例如 twitter 或者 facebook
  • username 用于取回该用户名相对应的头像

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

Shadow DOM

还记得iframe们吗?我们还在使用它们,是因为他们能确保组件和控件的JavaScript和CSS不会影响页面。 Shadow
DOM 也能提供这样的保护,并且没有iframe带来的负担。正式的说法是:

Shadow
DOM的设计是在shadow根下隐藏DOM子树从而提供封装机制。它提供了建立和保障DOM树之间的功能界限,以及给这些树提供交互的功能,从而在DOM树上提供了更好的功能封装。

var tp = tp || {};

什么是组件?

软件开发是一个语义丰富(术语通常不止一个意思)的领域。很显然,这里的“组件”是一个很泛的称呼,所以有必要指明我们想要表达的,在前端Web应用的语言环境中的意思。

前端Web应用中的组件,是指一些设计为通用性的,用来构建较大型应用程序的软件,这些组件有多种表现形式。它可以是有UI(用户界面)的,也可以是作为
“服务”的纯逻辑代码。

因为有视觉上的表现形式,UI组件更容易理解。UI组件简单的例子包括按钮、输入框和文本域。不论是汉堡包状的菜单按钮(无论你是否喜欢)、标签页、日历、选项菜单或者所见即所得的富文本编辑器则是一些更加高级的例子。

提供服务类型的组件可能会让人难以理解,这种类型的例子包括跨浏览器的AJAX支持,日志记录或者提供某种数据持久化的功能。

基于组件开发,最重要的就是组件可以用来构成其他组件,而富文本编辑器就是个很好的例子。它是由按钮、下拉菜单和一些可视化组件等组成。另一个例子是HTML5上的video元素。它同样包含按钮,也同时含有一个能从视频数据流渲染内容的元素。

           
PS:使用了原生的document.getElementsByClassName的肯定不受这个影响的。

内容提要

使用许多独立组件构建应用程序的想法并不新鲜。Web
Component的出现,是重新回顾基于组件的应用程序开发模式的好时机。我们可以从这个过程中受益,了解如何使用现有技术完成目标,并且在未来做出自己的前端Web应用。

tp.event.on = function(element,event,fn) {
        if (window.attachEvent) {
            //IE
            //第三个参数_realFn是为了修正this
            var realFn = function(e{fn.call(element,e);};
            _realEventCallbackFns[fn] = realFn;
            element.attachEvent("on" + event,realFn);
        } else if (window.addEventListener) {
            element.addEventListener(event, fn,false);
        } else {
            element["on" + event] = fn;
        }
};

现在就开始构建组件

在 Caplin
Systems 构建基于组件的自有应用程序时,我用到了几条原则和实践。这些原则由 BladeRunnerJS(BRJS) 开源工具集支撑。它被称作”BladeRunnerJS”
是因为我们将程序功能都封装在称作 Blades 的东西中。Blade是可以在某个应用中重用的功能特性,但是不可以在程序间重用。当功能
真的
变得更加通用的时候,我们将相应的定义移到库文件中,供各个程序间使用。特定应用中的组件(blade)和我们程序间的通用组件可以使用,我们只要找到最好满足需求的任何库和框架。

那么,现在什么库和框架能够帮助我们构建组件呢?

在决定构建应用时应使用何种技术时,只需要看看流行的 TodoMVC 网站就可以看到大量可供选择的前端库和框架。你也许会觉得任何一种方案都能用来构建基于组件的应用程序。然而,他们之中的一些方案内置了对组件的支持。其中比较有名的是AngularJS、Ember
和 React。

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;
};

模板

我们中的许多人已经使用像handlebars、mustache或者underscore.js中的模板这样的解决方案(就像我们在上面的Ember示例中用的一样)。Web
Component通过 template元素 提供了模板的原生支持。

原生模板让你可以声明分类为“隐藏DOM”但是解析成HTML的标记片段。他们在页面加载时没有用处,但是可以在运行时实例化。他们可以
被检索到 ,但是在插入活动的DOM树前不会加载任何相关资源。

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

可重用

你看到的示例组件,尤其是Web
Component,更关心可重用的问题。功能明确,实现清晰,API易于理解。自然就能促进组件复用。通过构建可重用组件,我们不仅保持了 DRY(不要重复造轮子)原则,还得到了相应的好处。

这里要提醒: 不要过分尝试构建可重用组件。你更应该关注应用程序上所需要的那些特定部分。如果之后相应需求出现,或者组件的确到了可重用的地步,就花一点额外时间让组件重用。事实上,开发者都喜欢去创造可重用功能块(库、组件、模块、插件等),做得太早将会让你后来痛苦不堪。所以,吸取基于组件开发的其他好处,并且接受不是所有组件都能重用的事实。

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

自定义元素

我们在上面关注的是用Angular、Ember和React构建 my-avatar 的例子。可能的情况下,这样的方式将以页面上或者模板上添加的自定义元素表示。Web
Component包括通过自定义元素获得的原生支持
– 绝对是Web Component标准的基本组成部分。

定义新元素,包括访问元素生命周期的部分事件例如何时创建(createdCallback)、何时添加在DOM树上(attachedCallback)、何时从DOM树上分离(detachedCallback),何时元素属性改变(attributeChangedCallback(attrName, oldVal, newVal))。

自定义元素的一个重要的部分就是有能力从原有元素扩展,因而得到原有元素相应的功能。示例中我们扩展了 <img>元素 。

最终,我们所写的代码中,自定义元素正在并且倾向去做的就是将复杂的东西抽象化,让用户关注于单个组件产生的价值,从而用来构建更加丰富的功能。

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

React

React 虽然是个新人,但是却已经有很多的追随者。它由Facebook开发,并且已经全面用于Instagram的UI和部分Facebook的UI。

使用React构建组件的推荐方式是使用叫做 JSX 的东西来定义它们。这是一种“推荐在React上使用的JavaScript语法转换”。请不要因此分心。他们已经在文档中指出,这个想法就是用来帮助你在JavaScript中写出HTML标记的。

我不是说你并不可以直接在HTML中添加标签,而必须使用JSX创建自己的组件。但是,只要你定义了一个组件,你就可以使用这个组件创造其他组件。

查看代码演示: 

因此,组件使用的声明语法需要相应的HTML元素和对 React.RenderComponent 的调用。

     
 好吧,貌似又超时了,先就这样吧,感觉每次写这种日志都会耗费不少时间。

高内聚

又是一个软件工程的高频词! 我们将相关的一些功能组织在一起,把一切封装起来,而在组件的例子中,就可能是相关的功能逻辑和静态资源:JavaScript、HTML、CSS以及图像等。这就是我们所说的内聚。

这种做法将让组件更容易维护,并且这么做之后,组件的可靠性也将提高。同时,它也能让组件的功能明确,增大组件重用的可能性。

   
 define的第一个参数是该组件的名字(需要唯一,所以我还是按照命名空间的方式写的),第二个参数是这个组件所依赖的组件,第三个参数是回调函数,也就是当依赖的组件下载完成之后,回调执行,而tp.modules.add就可以将这个模块加载到整个库中,这样的话才能使用tp.use调用。

外部资源(英文)

  • Eric Bidelman – Google I/O 2014 – Polymer and Web Components change
    everything you know about Web
    development
  • Ryan Seddon – Web Directions – Web Components, The Future of Web
    Development
  • Addy Osmani – LXJS – Componentize The Web: Back To The
    Browser!
  • WebComponents.org a place to discuss and evolve web component
    best-practices

       
现在看来,我觉得学习jquery反而使我走了弯路,用jquery是比较方便,也不用考虑兼容性问题了,而且调用非常简单优雅,但是,反而我对原生js感觉越来越陌生了,也导致了后面感觉完全离不开jquery了,想去写一个XXX组件,想了一下,思路有了,然后写的时候遇到各种问题,然后就又回到jquery了。

Platform.js

但是,就像每次提到新特性一样,我们不能确定浏览器是否支持这些特性。

图片 10

截至2014年6月27日,Web Component 的浏览器支持情况

同样,我们也能通过一些神奇的兼容代码,开始使用某些Web
Component所提供的功能。

图片 11

有了兼容库的Web Component支持情况

好消息是两个最先进的浏览器厂商Google和Mozilla正在努力完善兼容库
,帮助我们使用Web Component。

以下示例展示使用platform.js后我们可以如何定义作为img元素扩展的my-avatar元素。最棒的是它能用到原生img元素的所有功能。

查看代码演示: 

点击 HTML5 Rocks Custom Elements
tutorial 以查看创建自定义元素的更多信息。

注:如果你对platform.js感兴趣,也可以看看 bosonic。

原生技术的支持目的就是给我们提供相应的构建基础。所以Web
Component并不是库和框架的末日信号。

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

为什么要构建组件?

既然现在已经明白组件的意思,就看看使用组件的方法构建前端应用的好处。

     
 除了这种判定方式,还可以通过判定是否有某一个函数或某一个变量来判定,这种判定方式我忘记叫什么名字了,反正之前这种叫浏览器嗅探。

Polymer

Polymer 是演示构建基于原生Web
Component功能的最佳示例。它提供了精选的机制用来创建自定义的Polymer元素,并且提供了许多核心的UI组件,让你可以创建自己的应用程序。

图片 12

下面你可以看到 my-avatar 元素的简单创建过程,同时我们也得到了想要的标记。

查看代码演示: 

Google正在大力推动Polymer。请查看 Polymer getting started
guide 查看更多示例。

           我认为:#aa
input这种实际上就是通过document.getElementById查询之后然后查询它的子孙节点中的所有满足tagName为input的元素;而#aaa
>
input这种就是查询它的孩子节点中是否有这种满足条件的元素。现在整个流程比较简单了,对于一个复杂查询,首先进行一个简单查询,然后按照查询的结果集合,进行一次遍历,对每个节点查询它的孩子节点或子孙节点,将所有满足条件的放入到另外一个数组,如果该数组为空,那么直接返回空数组,否则,继续进行下一次查询(依旧查询孩子节点或子孙节点)。

HTML导入

我们长时间以前就可以导入JavaScript和CSS了。 HTML导入功能提供了从其他HTML文档中导入和重用HTML文档的能力。这种简单性同时意味着可以很方便地用一些组件构建另一些组件。

最后,这样的格式很理想,适合可重用组件,并且可以用你最喜欢的包管理解决方案发布(例如: bower、 npm 或者 Component)。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*
*
Website