本文概述
很少有人不喜欢框架, 但是即使你是其中之一, 也应注意并采用使生活更轻松的功能。
过去, 我反对使用框架。但是, 最近, 我在一些项目中有使用React和Angular的经验。头几次, 我打开代码编辑器并开始用Angular编写代码, 这感觉很奇怪而且很不自然。特别是经过十多年的编码而不使用任何框架。一段时间后, 我决定致力于学习这些技术。很快就发现了一个很大的区别:操作DOM非常容易, 在需要时可以很容易地调整节点的顺序, 并且不需要花很多页面代码来构建UI。
尽管我仍然更喜欢不附加到框架或体系结构上的自由, 但是我不能忽略这样的事实, 即在一个框架中创建DOM元素会更加方便。因此, 我开始研究模仿香草JS体验的方法。我的目标是从React中提取一些想法, 并演示如何在纯JavaScript(通常称为Vanilla JS)中实现相同的原理, 从而使开发人员的生活更加轻松。为此, 我们构建一个简单的应用程序来浏览GitHub项目。
我们正在构建的应用程序。
无论我们使用JavaScript构建前端的哪种方式, 我们都将访问和操作DOM。对于我们的应用程序, 我们将需要构造每个存储库的表示形式(缩略图, 名称和描述), 并将其作为列表元素添加到DOM中。我们将使用GitHub Search API来获取结果。而且, 由于我们在谈论JavaScript, 因此我们来搜索JavaScript存储库。查询API时, 将获得以下JSON响应:
{
"total_count": 398819, "incomplete_results": false, "items": [
{
"id": 28457823, "name": "freeCodeCamp", "full_name": "freeCodeCamp/freeCodeCamp", "owner": {
"login": "freeCodeCamp", "id": 9892522, "avatar_url": "https://avatars0.githubusercontent.com/u/9892522?v=4", "gravatar_id": "", "url": "https://api.github.com/users/freeCodeCamp", "site_admin": false
}, "private": false, "html_url": "https://github.com/freeCodeCamp/freeCodeCamp", "description": "The https://freeCodeCamp.org open source codebase "+
"and curriculum. Learn to code and help nonprofits.", // more omitted information
}, //...
]
}
React的方法
React使将HTML元素写入页面变得非常简单, 这是我用纯JavaScript编写组件时一直希望拥有的功能之一。 React使用JSX, 它与常规HTML非常相似。
但是, 那不是浏览器读取的内容。
在幕后, React将JSX转换为对React.createElement函数的一堆调用。让我们来看一个使用GitHub API中的一项的JSX示例, 并查看其含义。
<div className="repository">
<div>{item.name}</div>
<p>{item.description}</p>
<img src={item.owner.avatar_url} />
</div>;
;
React.createElement(
"div", { className: "repository" }, React.createElement(
"div", null, item.name
), React.createElement(
"p", null, item.description
), React.createElement(
"img", { src: item.owner.avatar_url }
)
);
JSX非常简单。你编写常规的HTML代码, 并通过添加大括号从对象中插入数据。将执行括号内的JavaScript代码, 并将其值插入到结果DOM中。 JSX的优点之一是React可以创建一个虚拟DOM(页面的虚拟表示)来跟踪更改和更新。每当信息更新时, React都不会修改整个HTML, 而是修改页面的DOM。这是React所要解决的主要问题之一。
jQuery方法
开发人员过去经常使用jQuery。我想在此提及它, 因为它仍然很流行, 并且因为它非常接近纯JavaScript中的解决方案。 jQuery通过查询DOM来获取对DOM节点(或DOM节点集合)的引用。它还使用各种功能来包装该引用, 以修改其内容。
尽管jQuery有自己的DOM构造工具, 但我经常在野外看到的只是HTML串联。例如, 我们可以通过调用html()函数将HTML代码插入选定的节点。根据jQuery文档, 如果我们想使用类demo-container更改div节点的内容, 我们可以这样做:
$( "div.demo-container" ).html( "<p>All new content.<em>You bet!</em></p>" );
这种方法使创建DOM元素变得容易。但是, 当我们需要更新节点时, 我们需要查询所需的节点, 或者(通常)在需要更新时退回到重新创建整个代码段。
DOM API方法
浏览器中的JavaScript具有内置的DOM API, 使我们可以直接访问创建, 修改和删除页面中的节点。这在React的方法中得到了体现, 并且通过使用DOM API, 我们比该方法的优势更近了一步。我们仅修改实际需要更改的页面元素。但是, React也会跟踪单独的虚拟DOM。通过比较虚拟和实际DOM之间的差异, React能够确定哪些部分需要修改。
这些额外的步骤有时很有用, 但并非总是如此, 直接操作DOM可能更有效。我们可以使用_document.createElement_函数创建新的DOM节点, 该函数将返回对创建的节点的引用。跟踪这些引用为我们提供了一种简单的方法, 使其仅修改包含需要更新的零件的节点。
使用与JSX示例相同的结构和数据源, 我们可以通过以下方式构造DOM:
var item = document.createElement('div');
item.className = 'repository';
var nameNode = document.createElement('div');
nameNode.innerHTML = item.name
item.appendChild(nameNode);
var description = document.createElement('p');
description.innerHTML = item.description;
item.appendChild(description );
var image = new Image();
Image.src = item.owner.avatar_url;
item.appendChild(image);
document.body.appendChild(item);
如果你唯一想到的是代码执行的效率, 那么这种方法非常好。但是, 效率不仅仅以执行速度来衡量, 还以维护, 易扩展性和可塑性来衡量。这种方法的问题在于它非常冗长, 有时令人费解。即使我们只是构造一个基本结构, 我们也需要编写一堆函数调用。第二个大缺点是创建和跟踪的变量数量庞大。假设你正在使用的组件包含30个DOM元素, 则需要创建和使用30个不同的DOM元素和变量。你可以重用其中的一些, 并且以可维护性和可塑性为代价进行一些处理, 但是它可能会变得非常杂乱, 非常迅速。
另一个明显的缺点是由于需要编写的代码行数众多。随着时间的流逝, 将元素从一个父对象移动到另一个父元素的难度越来越大。我从React真的很欣赏这件事。我可以查看JSX语法, 并在几秒钟内获得包含哪个节点, 位于何处, 并根据需要进行更改。而且, 虽然起初似乎没什么大不了, 但大多数项目都有不断的变化, 这会让你寻找更好的方法。
可用的解决方案
直接使用DOM可以工作并完成工作, 但是这也使得构建页面非常冗长, 尤其是当我们需要添加HTML属性和嵌套节点时。因此, 该想法将是捕获使用JSX之类的技术的一些好处, 并使我们的生活更简单。我们尝试复制的优势如下:
- 用HTML语法编写代码, 以便DOM元素的创建变得易于阅读和修改。
- 由于我们没有使用类似React的虚拟DOM, 因此我们需要一种简单的方法来指示和跟踪我们感兴趣的节点。
这是一个简单的函数, 可以使用HTML代码段完成此操作。
Browser.DOM = function (html, scope) {
// Creates empty node and injects html string using .innerHTML
// in case the variable isn't a string we assume is already a node
var node;
if (html.constructor === String) {
var node = document.createElement('div');
node.innerHTML = html;
} else {
node = html;
}
// Creates of uses and object to which we will create variables
// that will point to the created nodes
var _scope = scope || {};
// Recursive function that will read every node and when a node
// contains the var attribute add a reference in the scope object
function toScope(node, scope) {
var children = node.children;
for (var iChild = 0; iChild < children.length; iChild++) {
if (children[iChild].getAttribute('var')) {
var names = children[iChild].getAttribute('var').split('.');
var obj = scope;
while (names.length > 0)
{
var _property = names.shift();
if (names.length == 0)
{
obj[_property] = children[iChild];
}
else
{
if (!obj.hasOwnProperty(_property)){
obj[_property] = {};
}
obj = obj[_property];
}
}
}
toScope(children[iChild], scope);
}
}
toScope(node, _scope);
if (html.constructor != String) {
return html;
}
// If the node in the highest hierarchy is one return it
if (node.childNodes.length == 1) {
// if a scope to add node variables is not set
// attach the object we created into the highest hierarchy node
// by adding the nodes property.
if (!scope) {
node.childNodes[0].nodes = _scope;
}
return node.childNodes[0];
}
// if the node in highest hierarchy is more than one return a fragment
var fragment = document.createDocumentFragment();
var children = node.childNodes;
// add notes into DocumentFragment
while (children.length > 0) {
if (fragment.append){
fragment.append(children[0]);
}else{
fragment.appendChild(children[0]);
}
}
fragment.nodes = _scope;
return fragment;
}
这个想法很简单但是很强大。我们将要创建的HTML作为字符串发送给函数, 在HTML字符串中, 向要为其创建引用的节点添加var属性。第二个参数是一个对象, 这些引用将存储在该对象中。如果未指定, 我们将在返回的节点或文档片段上创建” nodes”属性(如果最高层级节点不止一个)。一切都用不到60行代码完成。
该功能分为三个步骤:
- 创建一个新的空节点, 并在该新节点中使用innerHTML创建整个DOM结构。
- 遍历节点, 如果var属性存在, 则在范围对象中添加一个属性, 该属性指向具有该属性的节点。
- 返回层次结构中的最高节点, 如果有多个节点, 则返回一个文档片段。
那么, 用于呈现示例的代码现在看起来如何?
var UI = {};
var template = '';
template += '<div class="repository">'
template += ' <div var="name"></div>';
template += ' <p var="text"></p>'
template += ' <img var="image"/>'
template += '</div>';
var item = Browser.DOM(template, UI);
UI.name.innerHTML = data.name;
UI.text.innerHTML = data.description;
UI.image.src = data.owner.avatar_url;
首先, 我们定义对象(UI), 我们将在其中存储对创建的节点的引用。然后, 我们将构成要使用的HTML模板作为字符串, 用” var”属性标记目标节点。之后, 我们使用模板和将存储引用的空对象调用函数Browser.DOM。最后, 我们使用存储的引用将数据放置在节点内。
这种方法还将构建DOM结构和将数据插入单独的步骤分开, 这有助于保持代码的组织性和结构良好。这使我们能够分别创建DOM结构并在数据可用时填充(或更新)数据。
总结
尽管我们中的某些人不喜欢切换到框架并移交控制权的想法, 但重要的是, 我们必须认识到框架带来的好处。它们如此受欢迎是有原因的。
尽管框架可能并不总是适合你的风格或需求, 但是可以采用, 模拟甚至有时与框架分离一些功能和技术。有些事情在翻译中总是会丢失, 但是可以获取和使用很多东西, 而这只花费了框架成本的一小部分。
评论前必须登录!
注册