JavaScript ES6的import/export和class extends

5

我创建了一个自定义元素,它是汉堡按钮,现在我正在开发一个侧边导航。在这个侧边导航中,我想使用我的汉堡按钮,所以我尝试导出我的HCHamburger类,对应于我的按钮,并将其导入到我的SideNav类中。我的想法是当打开侧边导航时,动画显示我的按钮位置。我尝试通过HCHamburger延伸我的SideNav类,但是我遇到了以下错误:Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.

我的HCHambuger类如下:

'use strict';

export default class HCHamburger extends HTMLElement {

    get menuButton() {
        if (!this._menuButton) {
            this._menuButton = this.querySelector('.hamburger-menu');
        }

        return this._menuButton;
    }

    get bar() {
        if (!this._bar) {
            this._bar = this.querySelector('.bar');
        }

        return this._bar;
    }

    attachedCallback() {
        this.menuButton.addEventListener('click', _ => {
            const sideNavContainerEl = document.querySelector('.js-side-nav-container');
            this.bar.classList.toggle("animate");
            if (sideNavContainerEl.getAttribute('nav-opened') == 'false') {
                this.openMenuButton(sideNavContainerEl);
            } else {
                this.closeMenuButton(sideNavContainerEl);
            }
        });
    }

    sayHello() {
        console.log('TOTO');
    }

    openMenuButton(sideNavContainerEl) {
        this.style.transform = `translateX(${sideNavContainerEl.offsetWidth}px)`;
    }

    closeMenuButton(sideNavContainerEl) {
        this.style.transform = `translateX(0px)`;
    }
}

document.registerElement('hc-hamburger', HCHamburger);

我的 SideNav 类如下:

'use strict';

import Detabinator from './detabinator.js';
import HCHamburger from './hamburger.js';

class SideNav extends HCHamburger {
  constructor () {
    super();
    this.toggleMenuEl = document.querySelector('.js-menu');
    this.showButtonEl = document.querySelector('.js-menu-show');
    this.hideButtonEl = document.querySelector('.js-menu-hide');
    this.sideNavEl = document.querySelector('.js-side-nav');
    this.sideNavContainerEl = document.querySelector('.js-side-nav-container');
    // Control whether the container's children can be focused
    // Set initial state to inert since the drawer is offscreen
    this.detabinator = new Detabinator(this.sideNavContainerEl);
    this.detabinator.inert = true;

    this.toggleSideNav = this.toggleSideNav.bind(this);
    this.showSideNav = this.showSideNav.bind(this);
    this.hideSideNav = this.hideSideNav.bind(this);
    this.blockClicks = this.blockClicks.bind(this);
    this.onTouchStart = this.onTouchStart.bind(this);
    this.onTouchMove = this.onTouchMove.bind(this);
    this.onTouchEnd = this.onTouchEnd.bind(this);
    this.onTransitionEnd = this.onTransitionEnd.bind(this);
    this.update = this.update.bind(this);

    this.startX = 0;
    this.currentX = 0;
    this.touchingSideNav = false;

    this.supportsPassive = undefined;
    this.addEventListeners();
  }

  // apply passive event listening if it's supported
  applyPassive () {
    if (this.supportsPassive !== undefined) {
      return this.supportsPassive ? {passive: true} : false;
    }
    // feature detect
    let isSupported = false;
    try {
      document.addEventListener('test', null, {get passive () {
        isSupported = true;
      }});
    } catch (e) { }
    this.supportsPassive = isSupported;
    return this.applyPassive();
  }

  addEventListeners () {
    this.toggleMenuEl.addEventListener('click', this.toggleSideNav);
    this.sideNavEl.addEventListener('click', this.hideSideNav);
    this.sideNavContainerEl.addEventListener('click', this.blockClicks);

    this.sideNavEl.addEventListener('touchstart', this.onTouchStart, this.applyPassive());
    this.sideNavEl.addEventListener('touchmove', this.onTouchMove, this.applyPassive());
    this.sideNavEl.addEventListener('touchend', this.onTouchEnd);
  }

  onTouchStart (evt) {
    if (!this.sideNavEl.classList.contains('side-nav--visible'))
      return;

    this.startX = evt.touches[0].pageX;
    this.currentX = this.startX;

    this.touchingSideNav = true;
    requestAnimationFrame(this.update);
  }

  onTouchMove (evt) {
    if (!this.touchingSideNav)
      return;

    this.currentX = evt.touches[0].pageX;
    const translateX = Math.min(0, this.currentX - this.startX);

    if (translateX < 0) {
      evt.preventDefault();
    }
  }

  onTouchEnd (evt) {
    if (!this.touchingSideNav)
      return;

    this.touchingSideNav = false;

    const translateX = Math.min(0, this.currentX - this.startX);
    this.sideNavContainerEl.style.transform = '';

    if (translateX < 0) {
      this.hideSideNav();
    }
  }

  update () {
    if (!this.touchingSideNav)
      return;

    requestAnimationFrame(this.update);

    const translateX = Math.min(0, this.currentX - this.startX);
    this.sideNavContainerEl.style.transform = `translateX(${translateX}px)`;
  }

  blockClicks (evt) {
    evt.stopPropagation();
  }

  onTransitionEnd (evt) {
    this.sideNavEl.classList.remove('side-nav--animatable');
    this.sideNavEl.removeEventListener('transitionend', this.onTransitionEnd);
  }

  showSideNav () {
    this.sideNavEl.classList.add('side-nav--animatable');
    this.sideNavEl.classList.add('side-nav--visible');
    this.detabinator.inert = false;
    this.sideNavEl.addEventListener('transitionend', this.onTransitionEnd);
  }

  hideSideNav () {
    this.sideNavEl.classList.add('side-nav--animatable');
    this.sideNavEl.classList.remove('side-nav--visible');
    this.detabinator.inert = true;
    this.sideNavEl.addEventListener('transitionend', this.onTransitionEnd);
  }

  toggleSideNav () {
    if (this.sideNavContainerEl.getAttribute('nav-opened') == 'true') {
      this.hideSideNav();
      this.sideNavContainerEl.setAttribute('nav-opened', 'false');
    } else {
      this.showSideNav();
      this.sideNavContainerEl.setAttribute('nav-opened', 'true');
    }
  }
}

new SideNav();

我正在使用webpack构建我的JS代码,可能这就是我的问题所在...我尝试了不同的导入/导出方法,但没有任何效果。我曾尝试仅导出所需的方法,但也无法解决问题。谢谢。

ES5对定制元素的考虑:https://github.com/w3c/webcomponents/issues/423 “基于构造函数的新定制元素API与ES5不兼容”。 - Ivan Rave
1个回答

4
基本上,DOM的API与JavaScript的继承(目前)存在不匹配。在当前浏览器上无法执行extends HTMLElement操作。当自定义元素规范稳定下来并以其最终形式广泛实现时,可能会有所改变,但现在不行
如果进行转换,您将得到问题中提到的错误,因为转换后的代码尝试执行以下操作:

function MyElement() {
  HTMLElement.call(this);
}
var e = new MyElement();

如果您不进行转译(需要浏览器支持ES2015+),则可能会出现不同的错误:
TypeError:非法构造函数

class MyElement extends HTMLElement {
}
let e = new MyElement();

你有几个选项,不需要从HTMLElement继承:包装和原型增强。

包装

你有一个函数可以“包装”元素。它可以为单个元素或一组元素创建包装器,例如jQuery;这是一个非常简单的示例集合:

// Constructor function creating the wrapper; this one is set-based
// like jQuery, but unlike jQuery requires that you call it via `new`
// (just to keep the example simple).
function Nifty(selectorOrElementOrArray) {
  if (!selectorOrElementOrArray) {
    this.elements = [];
  } else {
    if (typeof selectorOrElementOrArray === "string") {
      this.elements = Array.prototype.slice.call(
        document.querySelectorAll(selectorOrElementOrArray)
      );
    } else if (Array.isArray(selectorOrElementOrArray)) {
      this.elements = selectorOrElementOrArray.slice();
    } else {
      this.elements = [selectorOrElementOrArray];
    }
  }
}
Nifty.prototype.addClass = function addClass(cls) {
  this.elements.forEach(function(element) {
    element.classList.add(cls);
  });
};

// Usage
new Nifty(".foo").addClass("test");
new Nifty(".bar").addClass("test2");
.test {
  color: green;
}
.test2 {
  background-color: yellow;
}
<div id="test">
  <span class="bar">bar1</span>
  <span class="foo">foo1</span>
  <span class="bar">bar2</span>
  <span class="foo">foo2</span>
  <span class="bar">bar3</span>
</div>

原型增强

你可以增强HTMLElement.prototype。有些人赞成这样做,而另一些人则反对,主要是因为如果多个脚本试图向其中添加相同的属性(或者W3C或WHAT-WG向其中添加新的属性/方法),就可能会发生冲突,这显然是一个非常真实的可能性。但是,如果你的属性名称不太可能被其他人使用,你就可以将这种可能性降到最低:

// Add xyzSelect and xyzAddClass to the HTMLElement prototype
Object.defineProperties(HTMLElement.prototype, {
  "xyzSelect": {
    value: function xyzSelect(selector) {
      return Array.prototype.slice.call(this.querySelectorAll(selector));
    }
  },
  "xyzAddClass": {
    value: function xyzAddClass(cls) {
      return this.classList.add(cls);
    }
  }
});

// Usage
var test = document.getElementById("test");
test.xyzSelect(".foo").forEach(function(e) { e.xyzAddClass("test"); });
test.xyzSelect(".bar").forEach(function(e) { e.xyzAddClass("test2"); });
.test {
  color: green;
}
.test2 {
  background-color: yellow;
}
<div id="test">
  <span class="bar">bar1</span>
  <span class="foo">foo1</span>
  <span class="bar">bar2</span>
  <span class="foo">foo2</span>
  <span class="bar">bar3</span>
</div>

原型扩展在现代浏览器和IE8中可用。它在IE7及更早版本中不可用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接