广告招人:阿里巴巴招前端,在这里你能够享受大公司的福利和技术体系,也有小团队的挑战和成长空间。
联系: qingguang.meiqg at alibaba-inc.comjavascript
本文是一篇 Web Components 入门教程css
Web Components 是 W3C 定义的新标准,目前还在草案阶段。html
前端组件化前端
bootstraphtml5
// 初始化 $('#myModal').modal({ keyboard: false }); // 显示 $('#myModal').modal('show'); // 关闭事件 $('#myModal').on('hidden.bs.modal', function (e) { // do something... });
atomjava
// 初始化组件 var dialog = new Dialog( trigger: '#trigger-btn', title: '我是自定义的标题', content: 'hello world', buttons: ['submit', 'cancel'] }); // 显示 dialog.show(); // 关闭事件 dialog.after('hide', function() { // do something... });
统一标准、减小轮子web
简化代码,提升可维护性bootstrap

<hangout-module> <hangout-chat from="Paul, Addy"> <hangout-discussion> <hangout-message from="Paul" profile="profile.png" datetime="2013-07-17T12:02"> <p>Feelin' this Web Components thing.</p> <p>Heard of it?</p> </hangout-message> </hangout-discussion> </hangout-chat> <hangout-chat>...</hangout-chat> </hangout-module>
HTML Imports跨域
HTML Templates浏览器
Custom Elements
Shadow DOM
虽然大部分浏览器还不支持 Web Components ,可是有个叫作 webcomponents.js 的兼容库,可让 Web Components 在不支持它的浏览器上运行起来。只要你在项目中引入这个库,就能够在其余浏览器中将 Web Components 用起来。
经过<link>
标签来引入 HTML 文件,使得咱们能够用不一样的物理文件来组织代码。
<link rel="import" href="http://example.com/component.html" >
注意:受浏览器同源策略限制,跨域资源的 import 须要服务器端开启 CORS。
Access-Control-Allow-Origin: example.com
经过import
引入的 HTML 文件是一个包含了 html, css, javascript 的独立 component。
<template> <style> .coloured { color: red; } </style> <p>My favorite colour is: <strong class="coloured">Red</strong></p> </template> <script> (function() { var element = Object.create(HTMLElement.prototype); var template = document.currentScript.ownerDocument.querySelector('template').content; element.createdCallback = function() { var shadowRoot = this.createShadowRoot(); var clone = document.importNode(template, true); shadowRoot.appendChild(clone); }; document.registerElement('favorite-colour', { prototype: element }); }()); </script>
关于 HTML 模板的做用不用多讲,用过 mustache、handlbars 模板引擎就对 HTML 模板再熟悉不过了。但原来的模板要么是放在 script
元素内,要么是放在 textarea
元素内,HTML 模板元素终于给了模板一个名正言顺的名分: <template>
原来的模板形式:
script 元素
<script type="text/template"> <div> this is your template content. </div> </script>
textarea 元素
<textarea style="display:none;"> <div> this is your template content. </div> </textarea>
如今的模板形式:
template 元素
<template> <div> this is your template content. </div> </template>
主要有四个特性:
惰性:在使用前不会被渲染;
无反作用:在使用前,模板内部的各类脚本不会运行、图像不会加载等;
内容不可见:模板的内容不存在于文档中,使用选择器没法获取;
可被放置于任意位置:即便是 HTML 解析器不容许出现的位置,例如做为 <select>
的子元素。
自定义元素容许开发者定义新的 HTML 元素类型。带来如下特性:
定义新元素
元素继承
扩展原生 DOM 元素的 API
使用 document.registerElement()
建立一个自定义元素:
var Helloworld = document.registerElement('hello-world', { prototype: Object.create(HTMLElement.prototype) }); document.body.appendChild(new Helloworld());
标签名必须包含连字符 ' - '
合法的标签名:<hello-world>
, <my-hello-world>
不合法的标签名:<hello_world>
, <HelloWorld>
若是 <button>
元素不能知足你的需求,能够继承它建立一个新元素,来扩展 <button>
元素:
var MyButton = document.registerElement('my-button', { prototype: Object.create(HTMLButtonElement.prototype) });
var MyButtonProto = Object.create(HTMLButtonElement.prototype); MyButtonProto.sayhello = function() { alert('hello'); }; var MyButton = document.registerElement('my-button', { prototype: MyButtonProto }); var myButton = new MyButton(); document.body.appendChild(myButton); myButton.sayhello(); // alert: "hello"
使用 new
操做符:
var myButton = new MyButton(); myButton.innerHTML = 'click me!'; document.body.appendChild(myButton);
或,直接在页面插入元素:
<my-button>click me!</my-button>
元素能够定义特殊的方法,来注入其生存周期内的关键时间点。生命周期的回调函数名称和时间点对应关系以下:
createdCallback: 建立元素实例时
attachedCallback: 向文档插入实例时
detachedCallback: 从文档移除实例时
attributeChangedCallback(attrName, oldVal, newVal): 添加,移除,或修改一个属性时
var MyButtonProto = Object.create(HTMLButtonElement.prototype); MyButtonProto.createdCallback = function() { this.innerHTML = 'Click Me!'; }; MyButtonProto.attachedCallback = function() { this.addEventListener('click', function(e) { alert('hello world'); }); }; var MyButton = document.registerElement('my-button', { prototype: MyButtonProto }); var myButton = new MyButton(); document.body.appendChild(myButton);
Shadow DOM 是一个 HTML 的新规范,其容许开发者封装本身的 HTML 标签、CSS 样式和 JavaScript 代码。Shadow DOM 使得开发人员能够建立相似 <input type="range">
这样自定义的一级标签。
web 开发经典问题:封装。如何保护组件的样式不被外部 css 样式侵入,如何保护组件的 dom 结构不被页面的其余 javascript 脚本修改。你们都用过 Bootstrap,若是要使用其中的某些组件,例如 modal,一般会把组件的 DOM 结构复制过来。
<div class="modal fade"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button> <h4 class="modal-title">Modal title</h4> </div> <div class="modal-body"> <p>One fine body…</p> </div> <div class="modal-footer"> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-primary">Save changes</button> </div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal -->
这样一坨复制过来的代码,大多数时候并无仔细了解,任什么时候候一个不当心都有可能覆盖了其中的一个 class 样式,这里面可能潜在不少小 bug。Shadow Dom 能够很好的解决组件封装问题。
浏览器渲染 <input type="range">
标签,显示结果以下:
<input type="range">
看起来彷佛很简单,只有一个 input
标签而已。但其实是这样的:
显示 shadow dom 须要开启 Chrome 开发者工具的 'Show user agent shadow DOM'
使用 createShadowRoot
建立影子根节点,其他的操做跟普通 DOM 操做没有太大区别。
<div class="widget">Hello, world!</div> <script> var host = document.querySelector('.widget'); var root = host.createShadowRoot(); var header = document.createElement('h1'); header.textContent = 'Hello, I am Shadow DOM.'; var paragraph = document.createElement('p'); paragraph.textContent = 'This is the content.'; root.appendChild(header); root.appendChild(paragraph); </script>
宿主节点的原有内容 Hello, world!
不会被渲染,取而代之的是 shadow root 里的内容。
<div class="widget">shadow dom</div> <template> <h1>Hello, I am <content></content></h1> </template> <script> var host = document.querySelector('.widget'); var root = host.createShadowRoot(); var template = document.querySelector('template').content; root.appendChild(document.importNode(template, true)); </script>
使用 <content>
标签,咱们建立了一个插入,其将 .widget
中的文本投射出来,使之得以在咱们的影子节点 <h1>
中展现。上面的例子最终渲染成 Hello, I am shadow dom
。
Shadow DOM 和常规 DOM 之间存在一个边界,这个边界能防止常规 DOM 的样式泄露到 Shadow DOM 中来。
<style> p.normal, p.shadow { color: red; font-size: 18px; } </style> <p class="normal">我是一个普通文本</p> <p class="shadow"></p> <script> var host = document.querySelector('.shadow'); var root = host.createShadowRoot(); root.innerHTML = ` <style> p { color: blue; font-size: 24px; } </style> <p>我是一个影子文本</p>`; </script>
经过 :host
选择器能够设置宿主元素的样式。
<style> p { color: red; font-size: 18px; } </style> <p class="normal">我是一个普通文本</p> <p class="shadow"></p> <script> var host = document.querySelector('.shadow'); var root = host.createShadowRoot(); root.innerHTML = ` <style> :host(p.shadow) { color: blue; font-size: 24px; } </style> 我是一个影子文本`; </script>
注意上例中 shadow DOM 内的选择器是 :host(p.shadow)
,而不是跟外部平级的 :host(p)
。 由于:host(p)
的优先级低于外部的 p
选择器,因此不会生效。须要使用 :host(p.shadow)
提高优先级,才能将 .shadow
中的样式覆盖。
有时你可能会想让使用者打破影子边界的壁垒,让他们可以给你的组件添加一些样式,使用 ::shadow 伪类选择器咱们能够赋予用户重写咱们默认定义的自由。
<style> p span, p::shadow span { color: red; font-size: 18px; } </style> <p class="normal"><span>我是一个普通文本</span></p> <p class="shadow"></p> <script> var host = document.querySelector('.shadow'); var root = host.createShadowRoot(); root.innerHTML = ` <style> span { color: blue; font-size: 24px; } </style> <span>我是一个影子文本</span>`; </script>