micro-app 研究-提前准备

403 阅读6分钟

官方教程地址

github.com/micro-zoe/m…

一. webComponent - customElements

创建的自定义元素,因为它不仅提供一个元素容器,还自带了生命周期函数,我们可以在这些钩子函数中进行加载渲染等操作,从而简化步骤。

// /src/element.js

// 自定义元素
class MyElement extends HTMLElement {
  // 声明需要监听的属性名,只有这些属性变化时才会触发attributeChangedCallback
  static get observedAttributes () {
    return ['name', 'url']
  }

  constructor() {
    super();
  }

  connectedCallback() {
    // 元素被插入到DOM时执行,此时去加载子应用的静态资源并渲染
    console.log('micro-app is connected')
  }

  disconnectedCallback () {
    // 元素从DOM中删除时执行,此时进行一些卸载操作
    console.log('micro-app has disconnected')
  }

  attributeChangedCallback (attr, oldVal, newVal) {
    // 元素属性发生变化时执行,可以获取name、url等属性的值
    console.log(`attribute ${attrName}: ${newVal}`)
  }
}

/**
 * 注册元素
 * 注册后,就可以像普通元素一样使用micro-app,当micro-app元素被插入或删除DOM时即可触发相应的生命周期函数。
 */
window.customElements.define('micro-app', MyElement)

二. shadowDom

1. 基本理解

Shadow DOM 允许将隐藏的 DOM 树附加到常规的 DOM 树中——它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的 DOM 元素一样。

image.png

这里,有一些 Shadow DOM 特有的术语需要我们了解:

  • Shadow host:一个常规 DOM 节点,Shadow DOM 会被附加到这个节点上。
  • Shadow tree:Shadow DOM 内部的 DOM 树。
  • Shadow boundary:Shadow DOM 结束的地方,也是常规 DOM 开始的地方。
  • Shadow root: Shadow tree 的根节点。 你可以使用同样的方式来操作 Shadow DOM,就和操作常规 DOM 一样——例如添加子节点、设置属性,以及为节点添加自己的样式(例如通过 element.style 属性),或者为整个 Shadow DOM 添加样式(例如在 <style> 元素内添加样式)。不同的是,Shadow DOM 内部的元素始终不会影响到它外部的元素(除了 :focus-within),这为封装提供了便利。

2. 进行css隔离的原因

Shadow DOM 内部的样式不会影响外部, 外部的样式不会影响shadow Dom的元素的样式, 例如下面案例中同样class 的标签,不会相互影响样式

3. 基本使用

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .haha {
      background-color: pink;
    }
  </style>
</head>
<body>
  <div class="wrapper">
    <div class="inner"></div>
    <div class="haha">哈哈哈哈</div>
  </div>
</body>
<script>
  const inner = document.querySelector('.inner');
  const innerShadow = inner.attachShadow({mode: 'open'});

  const div = document.createElement('div');
  div.innerHTML = "haha";
  div.className = 'haha'

  const style = document.createElement('style');
  style.textContent = `
    .haha {
      color: #ff0000;
    }
  `;
  
  innerShadow.appendChild(style)
  innerShadow.appendChild(div)
</script>
</html>

三.Proxy 代理

1.基本定义

  • Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)。
  • Proxy可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。

2.基本用法

const handler = {
    get: function(obj, prop) {
        return prop in obj ? obj[prop] : 37;
    }
};

const p = new Proxy({}, handler);
p.a = 1;
p.b = undefined;

console.log(p.a, p.b);      // 1, undefined
console.log('c' in p, p.c); // false, 37

3. 一个完整的 traps 列表示例

/*
  var docCookies = ... get the "docCookies" object here:
  https://developer.mozilla.org/zh-CN/docs/DOM/document.cookie#A_little_framework.3A_a_complete_cookies_reader.2Fwriter_with_full_unicode_support
*/

var docCookies = new Proxy(docCookies, {
  "get": function (oTarget, sKey) {
    return oTarget[sKey] || oTarget.getItem(sKey) || undefined;
  },
  "set": function (oTarget, sKey, vValue) {
    if (sKey in oTarget) { return false; }
    return oTarget.setItem(sKey, vValue);
  },
  "deleteProperty": function (oTarget, sKey) {
    if (sKey in oTarget) { return false; }
    return oTarget.removeItem(sKey);
  },
  "enumerate": function (oTarget, sKey) {
    return oTarget.keys();
  },
  "ownKeys": function (oTarget, sKey) {
    return oTarget.keys();
  },
  "has": function (oTarget, sKey) {
    return sKey in oTarget || oTarget.hasItem(sKey);
  },
  "defineProperty": function (oTarget, sKey, oDesc) {
    if (oDesc && "value" in oDesc) { oTarget.setItem(sKey, oDesc.value); }
    return oTarget;
  },
  "getOwnPropertyDescriptor": function (oTarget, sKey) {
    var vValue = oTarget.getItem(sKey);
    return vValue ? {
      "value": vValue,
      "writable": true,
      "enumerable": true,
      "configurable": false
    } : undefined;
  },
});

/* Cookies 测试 */

alert(docCookies.my_cookie1 = "First value");
alert(docCookies.getItem("my_cookie1"));

docCookies.setItem("my_cookie1", "Changed value");
alert(docCookies.my_cookie1);

四.正则表达式

先看micro-app里面的正则

/^[\s\S]+{/ 
/^((html[\s>~,]+body)|(html|body|:root))/ 

1.定义

是用来描述字符串内容格式,使用它通常用于匹配一个字符串的内容是否符合格式要求。

2.字符串的正则方法

  • split:根据匹配字符串切割父字符串
  • match:使用正则表达式与字符串相比较,返回一个包含匹配结果的数组
  • search:使用正则表达式或指定字符串进行搜索,返回第一个出现的匹配项的下角标
  • replace:使用正则表达式和字符串比较,然后用新的子串来替换被匹配的子串

3.正则表达式方法

  • exec:在目标字符串中执行一次正则匹配操作
    在全局模式下,当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把正则表达式对象的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。这就是说,您可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。
  • test:测试当前正则是否能匹配目标字符串

4.[] 字符集

简单类:就是任意多个字符进行集合书写,多个字符连续书写。
比如:[abc]
范围类:有时匹配的东西过多,而且类型又相同,全部输入太麻烦,我们可以在中间加了个横线。
比如:[0-9] [A-Z] [a-z]
组合类:允许用中括号匹配不同类型的单个字符,或者是一类及多类,或者一些单一的符号。
比如:[0-9a-z]

5.修饰符 g i

g 修饰符用于执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。
i 修饰符用于执行对大小写不敏感的匹配。

6.边界符 ^ $

^ 开头 表示字符串必须以^后面的内容作为开头。
结尾表示 结尾 表示前面匹配的结果必须位于字符串最后

7.预定义类 \d \D \s \S \w \W

\d [0-9] \d可以匹配一个任意的数字字符
\D [^0-9] \D 匹配任意一个不是数字的字符
\s 可以匹配任意的空白。
\S 可以匹配任意的不是空白的字符。
\w 可以匹配任意的字母、数字或下划线。
\W 可以匹配任意的字母、数字、下划线以外的内容。

量词

如果需要将正则的某个符号连续匹配多次,可以使用量词对符号进行修饰。量词的写法:{},内部书写对应的数字,表示量词是多少。

软硬出现次数
{n}硬性量词对应零次或者n次
{n,m}软性量词至少出现n次但不超过m次(中间不能有空格)
{n,}软性量词至少出现n次
?软性量词出现零次或一次
*软性量词出现零次或多次(任意次)
+软性量词出现一次或多次(至少一次)

中文匹配

固定字符集写法:[\u4e00-\u9fa5]
可以匹配一个任意的中文汉字。

分组

正则中使用()表示分组,内部的内容会作为一个整体进行操作。
后期如果小括号后面有量词,表示小括号内部整体重复匹配多少次。
/(byebye){2}/.test(“byebye”) true

正则表达式不包含某些字符

^: 以·····开头的字符串

$:以·····结尾的字符串

如:

    只包含数字字母: [0-9a-zA-Z]

    以数字开头: ^[0-9]

    以字母结尾:[a-zA-Z]$

而不包含数字字母:

    [^0-9a-zA-A] :那个开始符是写在中括号里面的!!

五.观察者模式

观察者模式定义了观察者和被观察者的一对多的依赖关系,它们之间存在直接的联系;当被观察者发布通知时,所有依赖于它的观察者都将得到通知。


╭─────────────╮  Fire Event  ╭──────────────╮
│             │─────────────>│              │
│   Subject   │              │   Observer   │
│             │<─────────────│              │
╰─────────────╯  Subscribe   ╰──────────────╯

六.发布订阅模式

发布订阅模式中,发布者发布消息时不会将消息直接发送给订阅者,发布者和订阅者之间不存在直接的联系;在发布者和订阅者之间存在第三方平台,称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,可以处理所有发布者发布的消息并将它们分发给对应的订阅者,实现了发布者与订阅者之间的解耦。

╭─────────────╮                 ╭───────────────╮   Fire Event   ╭──────────────╮
│             │  Publish Event  │               │───────────────>│              │
│  Publisher  │────────────────>│ Event Channel │                │  Subscriber  │
│             │                 │               │<───────────────│              │
╰─────────────╯                 ╰───────────────╯    Subscribe   ╰──────────────╯

七.观察者模式 和 发布订阅模式的差异

  • 观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
  • 发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。
  • 观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。
  • 观察者 模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。

  尽管它们之间有区别,但有些人可能会说发布-订阅模式是观察者模式的变异,因为它们概念上是相似的。

八.setAttribute

增加一个指定名称和治的新属性,或者把一个现有属性设定为指定的值 elementNode.setAttribute(name,value)

注意:

把指定的属性设置为指定的值。如果不存在具有指定名称的属性,该方法将创建一个新属性。

九.eval('this')和(0,eval)('this')的区别是

1.在ecma规范中,eval存在直接调用和间接调用两种方式,而直接调用时上下文为当前执行环境,间接调用时上下文为全局环境

2.直接调用eval时,为直接调用,而使用表达式计算得到的eval是间接调用

这样就很明了了,eval('this')和(0,eval)('this')的区别是,一个是在当前执行环境下,一个是在全局执行环境下,后面的调用方式才可百分百确定指向的是全局宿主对象