无依赖自定义组件的实践
# 组件

无依赖自定义组件的实践需要依赖三个技术(浏览器原生就支持组件化)
- Custom elements（自定义元素）`customElements.define('my-header', Xheader)`
- Shadow DOM（影子DOM）`attachShadow`
- HTML templates（HTML模板）`<template>和 <slot>`
```js
// HTML模板技术包含两个标签：<template>和 <slot>
// 当需要在页面上重复使用同一个 DOM结构时，可以用 template 标签来包裹它们，然后进行复用
// slot标签让模板更加灵活，使得用户可以自定义模板中的某些内容
const str =
    `
    <style>
        header {
            height: 50px;
            line-height: 50px;
            width: 100%;
            text-align: center;
            color: white;
            background-color: red;
        }
    </style>
    <header><slot name="my-title">头部</slot></header>
    `
class Xheader extends HTMLElement {
    constructor() {
        super();
        const template = document.createElement('template');
        template.innerHTML = str;
        const templateContent = template.content;
        // 创建一颗可见的DOM树，这棵树会附着到某个DOM元素上
        // 这棵树的根节点称之为shadow root，只有通过shadow root 才可以访问内部的shadow dom，并且外部的css样式也不会影响到shadow dom上
        // 相当于创建了一个独立的作用域
        this.attachShadow({
            mode: 'open'// 'open' 表示该shadow dom可以通过js 的函数进行访问
        }).appendChild(
            templateContent.cloneNode(true)// 操作shadow dom
        );
    }
}
// 允许自定义元素及其行为，然后可以在您的用户界面中按照需要使用它们
customElements.define('my-header', Xheader);
```

# 事件绑定

经过测试，发现需要在`appendChild()`之后，才能绑定事件
```js
attachShadow.appendChild(
    templateContent.cloneNode(true) // 操作shadow dom
);
// 编译模板,要在appendChild之后
attachShadow.querySelector(xxx);
let nodes = attachShadow.childNodes;
```