解决 Custom Elements 在 Safari 中的问题

Custom Elements 是 Web Components 中非常重要的一部分,它可以让我们自定义 HTML 元素,并提供自己的行为和样式。但是在 Safari 中,对于一些较为复杂的自定义元素,可能会出现一些问题,比如无法正确实例化、属性无法正常赋值等等。本文将介绍这些问题的原因,并提供解决方案。

问题描述

下面是一个很简单的自定义元素 hello-world,它有一个 name 属性和一个 sayHello 方法。

--------- --------------------------
  -------
    ---------- -
      ----------------- --------
      ------- --- ----- -----
      -------- -----
      ------- ---- --
    -
  --------
  ---- ------------------
    ------ ----- ---------------------
  ------
-----------

--------
  ----- ---------- ------- ----------- -
    ------------- -
      --------
      ----- -------- - ------------------------------------------------
      ----- ----- - ------------------------------------- ------
      ------------------------ ----------------------------
      -------------------------------------------------- - --------------------------
    -
  
    ---------- -
      ------------------- --------------------------------
    -
  -
  
  ------------------------------------------- ------------
---------

在 Chrome 和 Firefox 中,我们可以正确地实例化这个元素,设置属性并调用方法:

----- ------- - --------------------------------------
---------------------------- ---------
-----------------------------------
------------------- -- -- ------- -------

但是在 Safari 中,我们会遇到两个问题:

  1. 在实例化元素时,会抛出一个 Class constructor HelloWorld cannot be invoked without 'new' 的错误。
  2. 在设置属性后,this.getAttribute('name') 返回的始终是 null

这些问题的原因可以归结为 Safari 中 Reflect.construct()CustomElementRegistry.define() 方法的实现存在缺陷。

问题分析

实例化错误

在 Chrome 和 Firefox 中,当我们通过 document.createElement() 创建一个自定义元素时,会得到这个元素的一个实例对象,该对象隐式继承了自定义元素的原型链。而在 Safari 中,由于 Reflect.construct() 方法的实现存在问题,它不会正确地继承原型链,而是把自定义元素的原型和构造函数分别绑定到实例的 __proto__constructor 属性上。这样的结果导致我们无法通过 instanceof 操作符判断一个对象是否是自定义元素的实例。

解决方案是手动绑定原型链。我们可以在自定义元素的构造函数中,设置当前实例的原型为自定义元素的原型:

----- ---------- ------- ----------- -
  ------------- -
    --------
    ----- -------- - ------------------------------------------------
    ----- ----- - ------------------------------------- ------
    ------------------------ ----------------------------
    -------------------------------------------------- - --------------------------
  
    -- -------
    -------------------------------------------------- ----------------------
  -

  -- ---
-

属性赋值错误

在 Chrome 和 Firefox 中,当我们通过 element.setAttribute() 设置元素的属性时,引擎会自动调用自定义元素的 attributeChangedCallback 回调函数,并将 nameoldValue 作为参数传入。而在 Safari 中,CustomElementRegistry.define() 方法的实现存在问题,它在注册自定义元素时没有正确地绑定 attributeChangedCallback,导致在属性赋值时这个回调函数无法触发。

解决方案是手动注册该回调函数。我们可以通过 observeAttributes() 方法手动触发 attributeChangedCallback,并将注册的属性名和回调函数传入:

----- ---------- ------- ----------- -
  ------ --- -------------------- -
    ------ ---------
  -

  ------------- -
    --------
    ----- -------- - ------------------------------------------------
    ----- ----- - ------------------------------------- ------
    ------------------------ ----------------------------
    -------------------------------------------------- - --------------------------
  
    -------------------------------------------------- ----------------------

    -- ----------
    -------------------------------- ------------------------------------------
  -

  ------------------------------ --------- --------- -
    -- ----- --- ------- -
      -------------------------------------------------- - ---------
    -
  -

  ------------------------ --------- -
    ----- ------ - ------------ ----- ------------------ ----- ---------------- -------
    ----- -------- - --- -------------------------- -- -
      -------------------------- -- -
        -- -------------------------------------- --- --- -
          -------------------------------- ------------------ ------------------------------------------------------
        -
      ---
    ---
    ---------------------- --------
  -
-

总结

在 Safari 中,Custom Elements 的实现存在一些缺陷,导致了一些问题。但是我们可以通过手动绑定原型链和手动注册属性变化回调等方式来解决这些问题。通过本文的介绍,我们可以更加深入地理解 Custom Elements 的实现原理,并掌握一些实用的技巧和方法。

完整代码示例:https://codepen.io/anon/pen/eLbZap

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/66432ebdd3423812e4120c38