JavaScript ES6 import/export 和 class 扩展
JavaScript ES6 import/export and class extends
我构建了一个自定义元素,它是一个汉堡包按钮,现在我正在使用侧边导航。在这侧导航中,我想使用我的汉堡包按钮,所以我尝试导出与我的按钮相对应的 HCHamburger
class,并将其导入我的 SideNav
class。这个想法是在侧边导航打开时为我的按钮位置设置动画。我尝试用 HCHamburger
扩展我的 SideNav
class 但我收到以下错误:Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.
我的 HCHambuger class 看起来像这样:
'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 class 是这样的:
'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 代码,也许这就是我出现问题的原因...我尝试了与 import/export 不同的方法,但没有任何效果。
我想只导出我需要的方法,但它也不起作用。
谢谢
从根本上说,DOM 的 API 和 JavaScript 的继承(目前)只是不匹配。您不能在当前的浏览器上执行 extends HTMLElement
操作。当 custom elements specification settles down and is widely-implemented in its final form, but not right now.
时,您也许可以在某个时候
如果你转译,你会得到你的问题中的错误,因为转译的代码试图按照这些行做一些事情:
function MyElement() {
HTMLElement.call(this);
}
var e = new MyElement();
如果您不转译(需要浏览器支持 ES2015+),您可能会遇到不同的错误:
TypeError: Illegal constructor
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
。有赞成也有反对的声音,"against" 主要指出如果多个脚本试图向它添加相同的属性(或者如果 W3C 或 WHAT-WG 添加新的 properties/methods ),这显然是一个非常现实的可能性。但是如果你让你的 属性 名字不太可能被其他人使用,你可以将这种可能性降到最低:
// 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 和更早版本中不起作用。
我构建了一个自定义元素,它是一个汉堡包按钮,现在我正在使用侧边导航。在这侧导航中,我想使用我的汉堡包按钮,所以我尝试导出与我的按钮相对应的 HCHamburger
class,并将其导入我的 SideNav
class。这个想法是在侧边导航打开时为我的按钮位置设置动画。我尝试用 HCHamburger
扩展我的 SideNav
class 但我收到以下错误:Uncaught TypeError: Failed to construct 'HTMLElement': Please use the 'new' operator, this DOM object constructor cannot be called as a function.
我的 HCHambuger class 看起来像这样:
'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 class 是这样的:
'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 代码,也许这就是我出现问题的原因...我尝试了与 import/export 不同的方法,但没有任何效果。
我想只导出我需要的方法,但它也不起作用。
谢谢
从根本上说,DOM 的 API 和 JavaScript 的继承(目前)只是不匹配。您不能在当前的浏览器上执行 extends HTMLElement
操作。当 custom elements specification settles down and is widely-implemented in its final form, but not right now.
如果你转译,你会得到你的问题中的错误,因为转译的代码试图按照这些行做一些事情:
function MyElement() {
HTMLElement.call(this);
}
var e = new MyElement();
如果您不转译(需要浏览器支持 ES2015+),您可能会遇到不同的错误:
TypeError: Illegal constructor
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
。有赞成也有反对的声音,"against" 主要指出如果多个脚本试图向它添加相同的属性(或者如果 W3C 或 WHAT-WG 添加新的 properties/methods ),这显然是一个非常现实的可能性。但是如果你让你的 属性 名字不太可能被其他人使用,你可以将这种可能性降到最低:
// 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 和更早版本中不起作用。