朋友们,美好的一天!
使用“导入/导出”语法的ES6模块是功能强大的工具,并且是流行框架的组件的有力竞争者。
让我通过在画布上绘制各种形状来演示这一点。
受到MDN JavaScript指南这一部分的启发。
这是在我们的小型应用程序中将实现的功能:
- 自动创建指定尺寸的画布并将其呈现在页面上
- 在画布上绘制给定大小和颜色的正方形,圆形和三角形的能力
- 将代码分为包含应用程序逻辑部分的模块
在创建应用程序的过程中,我们将特别注意默认和命名的导出/导入以及静态和动态导入。大多数应用程序将使用类语法编写。
希望您至少对使用类和画布有基本的了解。
项目代码在这里。
可以在此处查看该应用程序的演示。
让我们从支持开始。
总体还不错。平均约93%。
项目结构如下(您可以一次创建所有文件,也可以根据需要创建它们):
modules
helpers
convert.js
shapes
circle.js
square.js
triangle.js
canvas.js
index.html
main.js
main.css
标记如下所示:
<div>
<section>
<h3>Circle</h3>
<label>
X:
<input type="number" value="75" data-prop="x" />
</label>
<label>
Y:
<input type="number" value="75" data-prop="y" />
</label>
<label>
Radius:
<input type="number" value="50" data-prop="radius" />
</label>
<label>
Color:
<input type="color" value="#ff0000" data-prop="color" />
</label>
<button data-btn="circle" class="draw_btn">Draw</button>
</section>
<section>
<h3>Square</h3>
<label>
X:
<input type="number" value="275" data-prop="x" />
</label>
<label>
Y:
<input type="number" value="175" data-prop="y" />
</label>
<label>
Length:
<input type="number" value="100" data-prop="length" />
</label>
<label>
Color:
<input type="color" value="#00ff00" data-prop="color" />
</label>
<button data-btn="square" class="draw_btn">Draw</button>
</section>
<section>
<h3>Triangle</h3>
<label>
X:
<input type="number" value="150" data-prop="x" />
</label>
<label>
Y:
<input type="number" value="100" data-prop="y" />
</label>
<label>
Length:
<input type="number" value="125" data-prop="length" />
</label>
<label>
Color:
<input type="color" value="#0000ff" data-prop="color" />
</label>
<button data-btn="triangle" class="draw_btn">Draw</button>
</section>
</div>
<button>Clear Canvas</button>
<script src="main.js" type="module"></script>
您在这里要注意什么?
对于每个形状,将创建一个单独的部分,其中包含用于输入必要数据的字段和一个按钮,用于开始在画布上绘制形状的过程。在圆的截面示例中,此类数据为:起始坐标,半径和颜色。我们将输入字段设置为初始值以启用对应用程序运行状况的快速测试。 ``data-prop''属性旨在获取用于在脚本中输入的字段的值。 “ data-btn”属性用于确定按下了哪个按钮。最后一个按钮用于清除画布。
注意脚本的连接方式。需要值为“模块”的“类型”属性。在这种情况下,“ defer”属性不是必需的,因为默认情况下,模块的加载是延迟的(即在页面完全加载之后)。另请注意,我们仅在页面中包含“ main.js”文件。其他文件在“ main.js”内部用作模块。
模块的主要功能之一是每个模块都有自己的作用域(上下文),包括“ main.js”。一方面,这是很好的,因为它避免了对全局名称空间的污染,并因此防止了相同名称的变量和函数之间的冲突。另一方面,例如,如果不同的模块需要访问相同的DOM元素,则必须使用全局变量创建一个单独的脚本并将其连接到主模块之前的页面,或者显式创建全局变量(window.variable = value),或者在每个模块中创建相同的变量,或在模块之间交换变量(实际上,我们会这样做)。
还有第四种方法:直接通过ID访问DOM元素。您知道这种可能性吗?例如,如果我们在标记中有一个标识符为“ main”的元素,则可以简单地将其称为main(main.innerHTML =“ <p> Some Awesome Content <p />”),而无需先定义(搜索)该元素使用“ document.getElementById()”或类似方法。但是,这种方法是非标准的,因此不建议使用,因为虽然我个人觉得这个机会非常方便,但是还不知道将来是否会支持。
静态模块的另一个功能是只能导入一次。重新导入将被忽略。
最后,模块的第三个功能是导入后不能更改模块代码。换句话说,在模块中声明的变量和函数只能在导入了该模块的该模块中进行更改,而这不能完成。这有点类似于Module设计模式,它是通过包含私有变量和函数的对象或带有私有字段和方法的类实现的。
继续。让我们添加一些最小的样式:
body {
max-width: 768px;
margin: 0 auto;
color: #222;
text-align: center;
}
canvas {
display: block;
margin: 1rem auto;
border: 1px dashed #222;
border-radius: 4px;
}
div {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
section {
padding: 1rem;
}
label {
display: block;
}
input {
margin: 0.25rem 0;
}
input:not([type="color"]) {
width: 50px;
}
button {
margin: 0.25rem auto;
cursor: pointer;
}
ul {
list-style: none;
}
li {
margin: 0.5rem auto;
width: 320px;
border-bottom: 1px dotted #222;
}
p {
margin: 0.25rem 0;
}
这里没什么特别的。您可以根据自己的喜好添加美丽。
让我们继续到模块。
“ canvas.js”文件包含用于创建和渲染画布的类代码,以及在创建特定形状时显示的消息的列表(这些消息表示有关(常规)形状的面积和周长的信息):
// export default
// , IIFE ( , )
// , .. class ClassName..., export default ClassName
export default class Canvas {
// : ,
constructor(parent, width, height) {
this.parent = parent;
this.width = width;
this.height = height;
//
this.ctx = null;
//
this.listEl = null;
// ,
this.clearCanvas = this.clearCanvas.bind(this);
}
//
createCanvas() {
//
// ,
//
if (this.ctx !== null) {
console.log("Canvas already created!");
return;
} else {
// "canvas"
const canvasEl = document.createElement("canvas");
//
//
canvasEl.setAttribute("width", this.width);
canvasEl.setAttribute("height", this.height);
//
this.parent.append(canvasEl);
//
this.ctx = canvasEl.getContext("2d");
}
//
return this;
}
//
//
createReportList() {
if (this.listEl !== null) {
console.log("Report list already created!");
return;
} else {
const listEl = document.createElement("ul");
this.parent.append(listEl);
this.listEl = listEl;
}
return this;
}
//
clearCanvas() {
this.ctx.clearRect(0, 0, this.width, this.height);
this.listEl.innerHTML = "";
}
}
convert.js文件包含用于将度数转换为弧度的函数:
//
export const convert = (degrees) => (degrees * Math.PI) / 180;
Shapes目录中的每个文件都是特定形状的模块。通常,除了绘图方法以及用于计算形状的面积和周长的公式外,这些模块的代码是相同的。考虑一个模块,其中包含用于绘制圆的代码(circle.js):
//
//
// - "Module"
import { convert } from "../helpers/convert.js";
//
//
export class Circle {
// ""
//
// , "" ctx listEl
constructor({ ctx, listEl, radius, x, y, color }) {
this.ctx = ctx;
this.listEl = listEl;
this.radius = radius;
this.x = x;
this.y = y;
this.color = color;
//
this.name = "Circle";
//
this.listItemEl = document.createElement("li");
}
//
draw() {
//
this.ctx.fillStyle = this.color;
//
this.ctx.beginPath();
// arc 6 :
// "x", "y", , ,
// ( "0, 2 * Math.PI")
// , :
this.ctx.arc(this.x, this.y, this.radius, convert(0), convert(360));
//
this.ctx.fill();
}
//
report() {
//
this.listItemEl.innerHTML = `<p>${this.name} area is ${Math.round(Math.PI * (this.radius * this.radius))}px squared.</p>`;
//
this.listItemEl.innerHTML += `<p>${this.name} circumference is ${Math.round(2 * Math.PI * this.radius)}px.</p>`;
this.listEl.append(this.listItemEl);
}
}
最后,在“ main.js”文件中,对“ Canvas”类模块进行静态默认导入,创建该类的实例并处理按钮按下,其中包括动态导入相应的图形类模块并调用其方法:
//
//
import Canvas from "./modules/canvas.js";
// ,
// :
// ,
const { ctx, listEl, clearCanvas } = new Canvas(document.body, 400, 300).createCanvas().createReportList();
// ""
// ,
// "async"
document.addEventListener("click", async (e) => {
//
if (e.target.tagName !== "BUTTON") return;
//
if (e.target.className === "draw_btn") {
//
// ,
//
const { btn: btnName } = e.target.dataset;
//
// -
const shapeName = `${btnName[0].toUpperCase()}${btnName.slice(1)}`;
//
const shapeParams = {};
//
const inputsEl = e.target.parentElement.querySelectorAll("input");
//
inputsEl.forEach((input) => {
//
//
const { prop } = input.dataset;
//
// , ,
const value = !isNaN(input.value) ? input.valueAsNumber : input.value;
//
shapeParams[prop] = value;
});
//
shapeParams.ctx = ctx;
shapeParams.listEl = listEl;
console.log(shapeParams);
//
// "Module"
const ShapeModule = await import(`./modules/shapes/${btnName}.js`);
// -
//
// "Module" ( )
const shape = new ShapeModule[shapeName](shapeParams);
//
shape.draw();
//
shape.report();
} else {
//
// "Canvas"
clearCanvas();
}
});
您可以在此处使用代码。
如您所见,ES6模块提供了与将代码划分为相对独立的块相关的非常有趣的可能性,这些块包含可以立即或按需加载的应用程序逻辑部分。与模板文字一起使用时,它们是流行框架的组件的很好替代。我的意思是,首先,在客户端渲染页面。此外,这种方法允许您仅重新渲染已进行更改的DOM元素,从而允许您在没有虚拟DOM的情况下执行操作。但是,在以下文章之一中,会对此进行更多介绍。
希望您发现自己感兴趣的东西。感谢您的关注。