漫谈Web Components技术

第一次听说Web Components相关技术是在2013年从Shadow DOM 101这篇文章得知,只当是个模板技术的浏览器实现。谁想在Google IO 2014竟然将基于Web Components技术的Polymer专门做了个专题进行大篇幅介绍,其中Custom Element加上Imports的组合概念堪称惊艳,有种前端界的MapReduce的感觉。其实Google早在13年就已经花了部分精力对Web Components技术进行了介绍(链接是当时演讲里的ppt,部分协议标准已经产生了变化),只是未像IO 14一样做专题介绍。

比如:

Live demo - 自己实现了一个,得用现代浏览器(推荐webkit内核)打开才行

使用Custom Element创建后可以通过HTML Import加载,然后直接使用

<x-gangnam-style></x-gangnam-style>

1. 规范现状

Web Components技术由W3C规范进行定义,实际由四种技术组成:

  • Custom ELements,可以创建自定义html标签元素,而且支持对现有html元素进行扩展,从而自定义新的API
  • HTML Template, 使用template标签包含html相关代码片段,作用行为基本类似于JS模板引擎里的视图(View)
  • Shadow DOM,,可以创建对外隐藏的web片段,关键是支持独立的样式,不会影响所在文档内的其他样式
  • HTML Imports, 提供对模板或者自定义元素这些资源的加载支持,可以像引用JS或CSS一样引用HTML组件
    截至2014年8月17日,四个技术标准中HTML Template已经从Web Components规范中移除,并加入到HTML5标准中,这意味着该技术协议已经处于完成状态,不会再出现改动。Custom ELements也进入“Last Call”阶段,今后改动可能性也不大了。另外两个仍然存在修改的可能,因此下面的介绍只能当做谈资了解了解罢了。有关Web Components的规范现状与详细定义可以在这个链接中找到。

2. Custom ELements

原本Custom ELements设计是通过新增的element标签来实现自定义html标签元素,但由于种种问题,最终删去了相关规范。目前只能通过document.registerElement(tagName, prototype)方法来定义标签。该方法提供元素原型继承的接口,可以通过Object.create方法创建元素作为prototype传入。在这个gangnam-style示例中就使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
var gangnamProto = Object.create(HTMLElement.prototype);

gangnamProto.createdCallback = function(){
// deal with element view create
}

gangnamProto.attachedCallback = function(){
// deal with event bind
}

document.registerElement("x-gangnam-style", {
prototype: gangnamProto
});

接着就可以直接在html文件中使用x-gangnam-style标签了

3. HTML Template

该技术规范已经加入到HTML5规范中,成为一个新增的HTML5标签。使用非常简单,直接在html文件中添加template标签即可。该标签在浏览器中会被渲染处理,但不会展示,因此会比以前使用script做js的模板容器在效率上会高一些。需要注意的是获得template标签中的内容,需要使用其content属性,该属性返回documentFragment类型。而将content内容插入到目标元素中后会导致内容丢失,因此需要使用cloneNode方法进行拷贝保证其他元素仍然可以使用这个template中的content内容,或是使用document.importNode方法进行拷贝。在这个gangnam-style示例中就使用:

<template id="gangnam-style-tmpl">
<style>
...
</style>

<div id="maia-signature">
<div id="sig" class="zg-sig">
 <div id="psydroid" class="zg-psydroid">
 <img alt="" class="zg-psydroid-head" src="http://www.google.com/zeitgeist/2012/images/psydroid-head.png"> 
 <img alt="" class="zg-psydroid-body" src="http://www.google.com/zeitgeist/2012/images/psydroid-body.png"> 
 <img alt="" class="zg-psydroid-arm-l" src="http://www.google.com/zeitgeist/2012/images/psydroid-arm-l.png">
 <img alt="" class="zg-psydroid-arm-r" src="http://www.google.com/zeitgeist/2012/images/psydroid-arm-r.png">
 <img alt="" class="zg-psydroid-leg-l" src="http://www.google.com/zeitgeist/2012/images/psydroid-leg-l.png">
 <img alt="" class="zg-psydroid-leg-r" src="http://www.google.com/zeitgeist/2012/images/psydroid-leg-r.png">
 </div>
</div>
</div>
</template>
<script>
 ...  
gangnamProto.createdCallback = function(){     
   //deal with element view create     
   var thisDoc = document.currentScript.ownerDocument;  
   var tmpl = thisDoc.querySelector("#gangnam-style-tmpl");               
   //shadow.appendChild(tmpl.content.cloneNode(true)); also works!
   shadow.appendChild(document.importNode(tmpl.content, true)); 
} 
...  
</script>

这样就可以在Custom Element中使用template中已经定义好的html结构与样式。当然为保证接口与样式的封装性,这里还将HTML Template与Shadow DOM结合了起来。

4. Shadow DOM

这个技术规范堪称Web Components的灵魂所在,而且早已经用于HTML5标签技术中,只是不曾暴露接口给JS调用罢了。例如HTML5中的video标签就包含了Shadow DOM。现在可以在任何DOM节点上创建Shadow DOM,使用这个技术的好处在于Shadow DOM可以完全封装其中的dom结构与style样式,使得相关信息对用户透明。特别是包含在其中的样式,完全不会影响到其他元素,从而使得形成独立UI组件成为可能。Shadow DOM创建起来非常简单,调用createShadowRoot即可:

 gangnamProto.createdCallback = function(){

var shadow = this.createShadowRoot();
shadow.appendChild(document.importNode(tmpl.content, true));
}

同时Shadow DOM还提供<content>标签,该标签可以使用rel属性让浏览器选择性的填充其中的内容。(不知为何该标签在Chrome 36里失效 实际没有失效,只是dev tool展示比较奇怪,让本人误会了)
为方便CSS样式进行Shadow DOM的匹配选择,该技术还提供了多个CSS相关选择符,包括::shadow,/deep/,::host,::host-context,::content等。
目前该技术仍然在修订中,所以还存在不少变数。

5. HTML Import

在前面将Web Component封装在一个HTML文件中后,如何引用该组件并重复运用就成为了问题,这时就需要使用HTML Import技术。该技术使用起来很方便,一句话即可:

<link rel=”import” href=”./x-gangnam-style.html”>

同时可以使用rel=import获得该link节点:

var link = document.querySelector(‘link[rel=import]’);

目前该技术也仍然在修订中,而且很多细节都在变更。

6. 总结

这些技术目前还没有浏览器完全支持,而且规范还在修改中,因此完全不存在工业界使用的可能性。但是这些技术其实早就在浏览器中不断使用,只是没有暴露相关接口而已,规范需要决定的只是怎么暴露,如何让开发者方便使用而已,因此这些Web Component技术的普及可谓是必然。当然,Web组件的最终形态肯定不是直接使用这些规范,多看看微软GUI组件的发展轨迹就可以知道基于新的Web Component规范的Web组件未来还有很长的路要走。

出自jquery作者简单js模板中的技巧

短短的一段代码,看懂之后能真正认识javascript语言中的各种技巧 来自JavaScript Micro-Templating

// Simple JavaScript Templating
// John Resig - http://ejohn.org/ - MIT Licensed
(function(){
  var cache = {};
  // tmpl解析函数,参数str通常为模板tag的id,参数data为传入的数据
  this.tmpl = function tmpl(str, data){ 
   //首先判断传入str第一个字符是否为非单词字符,\W等价于[^A-Za-z0-9_],注意判断条件取反
    var fn = !/\W/.test(str) ? 
      //为真,说明开头字符为单词字符,即str表示id,则首先查找缓存中的模板函数
      cache[str] = cache[str] || 
        //未缓存则并取出模板中的内容运行tmpl解析函数,由于未传入data参数,故返回模板函数指针进行缓存
        tmpl(document.getElementById(str).innerHTML) : 

      // 为假,说明开头字符为非单词字符,即str表示模板内容,则创建相应的模板函数fn对象
      new Function("obj", //fn接受一个参数obj,实质即data
         //函数内容,p为需要显示的内容以数组形式保存,内部函数print即调用p的push方法将所有参数压入(该函数用法不明)
        "var p=[],print=function(){p.push.apply(p,arguments);};" + 

        //使用with关键字,将参数obj即data带入到执行环境中,这样模板中的变量即可使用
        "with(obj){p.push('" +
        // 在创建fn函数对象时即将str模板内容转换为完全的js代码,
        // 例子:<i class='a'><%=user.id%></i>
        str
        // 先清除各种换行符以方便后面作为替代符号进行使用
          .replace(/[\r\t\n]/g, " ")
        // 将模板开头标志替换为缩进符,
        // 例子:<i class='a'>\t=user.id%></i>
          .split("<%").join("\t")           
        // 模板中非脚本部分(即html标签)中的单引号替换为换行符,其中$1即正则中小括号能匹配的内容
        // 例子:<i class=\ra\r>\t=user.id%></i>
          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
        // 将模板中直接显示的变量提取出来,用单引号与逗号进行分隔,由于字符串使用单引号,故而上一步需要记录
        // 例子:<i class=\ra\r>',user.id,'</i>
          .replace(/\t=(.*?)%>/g, "',$1,'")
        // 将代替模板开头标志的缩进符替换为push调用结束,注意例子中的\t在上一步已被删去
          .split("\t").join("');")
        // 将模板结束标志替换为p.push调用开始,意为模板标志之间的html标签即以字符串的形式压入p数组
        // 注意例子中的%>也在显示变量那步被删除
          .split("%>").join("p.push('")
        // 还原模板中原有字符串部分的单引号
        // 例子:<i class=\\'t\\'>',user.id,'</i>
          .split("\r").join("\\'")
        // fn函数返回最终拼接的字符串,p.push('<i class=\\'t\\'>',user.id,'</i>');,注意push方法是可以压入多个元素的
      + "');}return p.join('');");

    //如果传入data,则返回运行模板函数后的最终结果,否则返回模板函数fn指针
    return data ? fn( data ) : fn; 
  };
})();
本站总访问量