仅100行的JavaScript DOM操作类库
如果你构建过Web引用程序,你可能处理过很多DOM操作。访问和操作DOM元素几乎是每一个Web应用程序的通用需求。我们我们经常从不同的控件收集信息,我们需要设置value值,修改div或span标签的内容。当然有许多库能帮助处理这些行为,其中最流行的当属jQuery,已经成为事实上的标准。有事你并不需要jQuery提供每一样东西,所以在这篇文章中,我们将看看如何创建自己的类库来操作DOM元素。
API
身为开发者的我们每天都要做决定。我相信在测试驱动开发中,我真的非常喜欢的一个事实是它迫使你在开始实际编码之前必须做出设计决定。沿着这些思路,我想我想要的DOM操作类库的API最终看起来可能像这样:
//返回 DOM 元素
dom('.selector').el
//返回元素的值/内容
dom('.selector').val()
//设置元素的值/内容
dom('.selector').val('value')
这应该包括了大多数可能用到的操作。然而如何我们可以一次操作多个对象会显得个更好。如果能生成一个JavaScript对象,那将是伟大之举。
//生成包装DOM元素的对象
dom({
structure: {
propA: '.selector',
propB: '.selector'
},
propC: '.selector'
})
一旦我们将元素存下来,我们能很容易对它们执行val方法。
//检索DOM元素的值
dom({
structure: {
propA: '.selector',
propB: '.selector'
},
propC: '.selector'
}).val()
这将是将数据直接从DOM转换为JavaScript对象的有效方法。
现在我们心理已经清楚我们的API看起来的样子,我们类库代码看起来像下面这样:
var dom = function(el) {
var api = { el: null }
api.val = function(value) {
// ...
}
return api;
}
作用域
很明显,我们打算使用类似getElementById,querySelector或querySelectorAll这样的方法。通常情况下,你可以像下面这样访问DOM:
var header = document.querySelector('.header');
querySeletor是非常有趣的,例如,它不仅仅是document对象的方法,同时也是其他DOM元素的方法。这意味着,我们可以在特定上下文中运行查询。比如:
<header>
<p>Big</p>
</header>
<footer>
<p>Small</p>
</footer> var header = document.querySelector('header');
var footer = document.querySelector('footer');
console.log(header.querySelector('p').textContent); // Big
console.log(footer.querySelector('p').textContent); // Small
我们能在特定的DOM树上操作,并且我们的类库应该支持传递作用域。所以,如果它接受一个父元素选择符是非常棒的。
var dom = function(el, parent) {
var api = { el: null }
api.val = function(value) {
// ...
}
return api;
}
查询DOM元素
按照我们上面所说的,我们将使用querySelector和querySelectorAll查询DOM元素。让我们为这些函数创建两个快捷方式。
var qs = function(selector, parent) {
parent = parent || document;
return parent.querySelector(selector);
};
var qsa = function(selector, parent) {
parent = parent || document;
return parent.querySelectorAll(selector);
};
在那之后我们应该传递el参数。通常情况下将是一个(选择符)字符串,但我们也应该支持:
- DOM元素——类库的val方法会非常方便,所以我们可能需要使用已经引用的元素;
- JavaScript对象——为了创建包含多个DOM元素的JavaScript对象。
下面的switch包括这两种情况:
switch(typeof el) {
case 'string':
parent = parent && typeof parent === 'string' ? qs(parent) : parent;
api.el = qs(el, parent);
break;
case 'object':
if(typeof el.nodeName != 'undefined') {
api.el = el;
} else {
var loop = function(value, obj) {
obj = obj || this;
for(var prop in obj) {
if(typeof obj[prop].el != 'undefined') {
obj[prop] = obj[prop].val(value);
} else if(typeof obj[prop] == 'object') {
obj[prop] = loop(value, obj[prop]);
}
}
delete obj.val;
return obj;
}
var res = { val: loop };
for(var key in el) {
res[key] = dom.apply(this, [el[key], parent]);
}
return res;
}
break;
}
<p>text</p>
<header>
<p>Big</p>
</header>
<footer>
<p>Small</p>
</footer>
访问第一个段落:
dom('p').el
访问header节点里的段落:
dom('p', 'header').el
传递一个DOM元素:
dom(document.querySelector('header')).el
传递一个JavaScript对象:
var els = dom({
footer: 'footer',
paragraphs: {
header: 'header p',
footer: 'footer p'
}
}))
// 最后我们在此得到JavaScript对象。
// 它的属性是实际的结果
// 执行dom函数。例如,获取值
// footer是paragraphs的属性
els.paragraphs.footer.el
获取或设置元素的值
api.val = function(value) {
if(!this.el) return null;
var set = !!value;
var useValueProperty = function(value) {
if(set) { this.el.value = value; return api; }
else { return this.el.value; }
}
switch(this.el.nodeName.toLowerCase()) {
case 'input':
break;
case 'textarea':
break;
case 'select':
break;
default:
}
return set ? api : null;
}
首先我们需要确保api.el属性存在。set是布尔类型变量告诉我们是获取还是设置元素的value属性。有.value属性的元素包括一个辅助方法。switch语句将包含方法的实际逻辑。最后我们返回api本身,为了保持链式操作。当然我们这样做仅当我们使用设置器函数时。
case 'input':
var type = this.el.getAttribute('type');
if(type == 'radio' || type == 'checkbox') {
var els = qsa('[name="' + this.el.getAttribute('name') + '"]', parent);
var values = [];
for(var i=0; i<els.length; i++) {
if(set && els[i].checked && els[i].value !== value) {
els[i].removeAttribute('checked');
} else if(set && els[i].value === value) {
els[i].setAttribute('checked', 'checked');
els[i].checked = 'checked';
} else if(els[i].checked) {
values.push(els[i].value);
}
}
if(!set) { return type == 'radio' ? values[0] : values; }
} else {
return useValueProperty.apply(this, [value]);
}
break;
case 'textarea':
return useValueProperty.apply(this, [value]);
break;
下面看我们如何处理下拉列表(select):
case 'select':
if(set) {
var options = qsa('option', this.el);
for(var i=0; i<options.length; i++) {
if(options[i].getAttribute('value') === value) {
this.el.selectedIndex = i;
} else {
options[i].removeAttribute('selected');
}
}
} else {
return this.el.value;
}
break;
最后是默认操作:
default:
if(set) {
this.el.innerHTML = value;
} else {
if(typeof this.el.textContent != 'undefined') {
return this.el.textContent;
} else if(typeof this.el.innerText != 'undefined') {
return typeof this.el.innerText;
} else {
return this.el.innerHTML;
}
}
break;
上面这些代码我们完成了我们的val方法。这里有一个简单的HTML表单和相应的测试:
<form>
<input type="text" value="sample text" />
<input type="radio" name="options" value="A">
<input type="radio" name="options" checked value="B">
<select>
<option value="10"></option>
<option value="20"></option>
<option value="30" selected></option>
</select>
<footer>version: 0.3</footer>
</form>
如果我们写下面的:
dom({
name: '[type="text"]',
data: {
options: '[type="radio"]',
count: 'select'
},
version: 'footer'
}, 'form').val();
我们会得到:
{
data: {
count: "30",
options: "B"
},
name: "sample text",
version: "version: 0.3"
}
这方法对于把数据冲HTML导成JavaScript对象非常有帮助。这正是我们很多人每天都很常见的任务。
最后结果
最后完成的类库代码仅有100行代码,但它仍然满足我们所需的访问 DOM元素并且获取和设置value值/内容。
var dom = function(el, parent) {
var api = { el: null }
var qs = function(selector, parent) {
parent = parent || document;
return parent.querySelector(selector);
};
var qsa = function(selector, parent) {
parent = parent || document;
return parent.querySelectorAll(selector);
};
switch(typeof el) {
case 'string':
parent = parent && typeof parent === 'string' ? qs(parent) : parent;
api.el = qs(el, parent);
break;
case 'object':
if(typeof el.nodeName != 'undefined') {
api.el = el;
} else {
var loop = function(value, obj) {
obj = obj || this;
for(var prop in obj) {
if(typeof obj[prop].el != 'undefined') {
obj[prop] = obj[prop].val(value);
} else if(typeof obj[prop] == 'object') {
obj[prop] = loop(value, obj[prop]);
}
}
delete obj.val;
return obj;
}
var res = { val: loop };
for(var key in el) {
res[key] = dom.apply(this, [el[key], parent]);
}
return res;
}
break;
}
api.val = function(value) {
if(!this.el) return null;
var set = !!value;
var useValueProperty = function(value) {
if(set) { this.el.value = value; return api; }
else { return this.el.value; }
}
switch(this.el.nodeName.toLowerCase()) {
case 'input':
var type = this.el.getAttribute('type');
if(type == 'radio' || type == 'checkbox') {
var els = qsa('[name="' + this.el.getAttribute('name') + '"]', parent);
var values = [];
for(var i=0; i<els.length; i++) {
if(set && els[i].checked && els[i].value !== value) {
els[i].removeAttribute('checked');
} else if(set && els[i].value === value) {
els[i].setAttribute('checked', 'checked');
els[i].checked = 'checked';
} else if(els[i].checked) {
values.push(els[i].value);
}
}
if(!set) { return type == 'radio' ? values[0] : values; }
} else {
return useValueProperty.apply(this, [value]);
}
break;
case 'textarea':
return useValueProperty.apply(this, [value]);
break;
case 'select':
if(set) {
var options = qsa('option', this.el);
for(var i=0; i<options.length; i++) {
if(options[i].getAttribute('value') === value) {
this.el.selectedIndex = i;
} else {
options[i].removeAttribute('selected');
}
}
} else {
return this.el.value;
}
break;
default:
if(set) {
this.el.innerHTML = value;
} else {
if(typeof this.el.textContent != 'undefined') {
return this.el.textContent;
} else if(typeof this.el.innerText != 'undefined') {
return typeof this.el.innerText;
} else {
return this.el.innerHTML;
}
}
break;
}
return set ? api : null;
}
return api;
}
我创建了一个jsbin的例子,你可以看看类作品。
总结
我上面讨论的类库是AbsurdJS客户端组件的一部分。该模块的完成文档可以在这里找到。这代码的目的并非要取代jQuery或其他可以访问DOM的流行类库。函数的思想是自成一体,一个函数只做一件事并把它做好。这是AbsurdJS背后的主要思想,它也是基于模块化建设的,如router或Ajax模块。
注
原文http://flippinawesome.org/2014/03/10/a-dom-manipulation-class-in-100-lines-of-javascript/
Q群推荐
GitHub家园225932282,GitHub爱好者的天堂,欢迎有兴趣的同学加入
仅100行的JavaScript DOM操作类库的更多相关文章
- javascript DOM 操作基础知识小结
经常用到javascript对dom,喜欢这方便的朋友也很多,要想更好的对dom进行操作,这些基础一定要知道的. DOM添加元素,使用节点属性 <!DOCTYPE html PUBLIC ...
- javascript DOM 操作
在javascript中,经常会需要操作DOM操作,在此记录一下学习到DOM操作的知识. 一.JavaScript DOM 操作 1.1.DOM概念 DOM :Document Object Mode ...
- javascript DOM 操作 attribute 和 property 的区别
javascript DOM 操作 attribute 和 property 的区别 在做 URLRedirector 扩展时,注意到在使用 jquery 操作 checkbox 是否勾选时,用 at ...
- javascript DOM操作之 querySelector,querySelectorAll
javascript DOM操作之 querySelector,querySelectorAll
- javascript DOM操作HTML文档
文档对象模型(DOM)是W3C为解决浏览器混战时代不同浏览器环境之间的差别而制定的模型标准.W3C将文档对象模型定义为:是一个能让程序和脚本动态 访问和更新文档内容.结构和样式的语言平台.提供了标准的 ...
- SVG基础以及使用Javascript DOM操作SVG
SVG 不依赖分辨率 支持事件处理器 最适合带有大型渲染区域的应用程序(比如谷歌地图) 复杂度高会减慢渲染速度(任何过度使用 DOM 的应用都不快) 不适合游戏应用 Canvas 依赖分辨率 不支持事 ...
- Javascript DOM操作实例
最近在学DOM,但是还是没有办法很好的记住API,想找些例子来练习,网上的例子将一个个DOM对象方法挨个举例,并没有集合在一起用,效果不尽人意.所以自己写一份实例,顺便巩固下学到的知识. ...
- JavaScript——DOM操作——Window.document对象
一.找到元素: docunment.getElementById("id"):根据id找,最多找一个: var a =docunment.getElementById(&qu ...
- javascript DOM操作 第19节
<html> <head> <title>DOM对象</title> <script type="text/javascript&quo ...
随机推荐
- C# WebClient、 jsonp实现跨域
WebClient 无传输数据获取 Uri uri = new Uri(allURL); WebClient wc = new WebClient(); wc.Encoding = System.Te ...
- 【技巧总结】Penetration Test Engineer[5]-Operating System Security(SQL Server、MySQL提权)
4.数据库安全基础 4.1.MSSQL 数据库角色权限 sysadmin:执行SQL Server中的任何动作 db_owner:可以执行数据库中技术所有动作的用户 public:数据库的每个合法用户 ...
- Windows执行命令与下载文件总结
1.前言 在渗透或是病毒分析总是会遇到很多千奇百怪的下载文件和执行命令的方法. 2.实现方式 2.1.Powershell win2003.winXP不支持 $client = new-object ...
- 转载-SVN常用命令
SVN(Subversion)是一个自由.开源的项目源代码版本控制工具.目前,绝大多数开源软件和企业代码管理,都使用SVN作为代码版本管理软件. Subversion将文件存放在中心版本库里,这个版本 ...
- 最短路径之迪杰斯特拉(Dijkstra)算法
对于网图来说,最短路径,是指两顶点之间经过的边上权值之和最少的路径,并且我们称路径上的第一个顶点为源点,最后一个顶点为终点.最短路径的算法主要有迪杰斯特拉(Dijkstra)算法和弗洛伊德(Floyd ...
- 26 About the go command go命令行
About the go command go命令行 Motivation Configuration versus convention Go's conventions Getting star ...
- Python基础(1):dir(),help()
Python:3.6.4 开始编写Python程序了...可是,某个模块怎么用呢?模块里的函数怎么用呢?...使用本文介绍的dir().help()两个帮助函数可以 获得绝大部分开发所需要的信息! d ...
- 数据库-mysql管理
MySQL 管理 启动及关闭 MySQL 服务器 首先,我们需要通过以下命令来检查MySQL服务器是否启动: ps -ef | grep mysqld 如果MySql已经启动,以上命令将输出mysql ...
- 经典面试题:js继承方式上
js不是传统的面向对象语言,那么他是怎么实现继承的呢?由于js是基于原型链实现的面向对象,所以js主要通过原型链查找来实现继承,主要有两大类实现方式,分为基于构造函数的继承,以及非构造函数的继承. 由 ...
- Oracle 函数 “判断数据表中不存在的数据,才允许通过”
create or replace function mca_detail_material_val(p_material_code VARCHAR2, --实参 p_material_name VA ...