先造两个小轮子
我们先造两个函数, 用来简化我们DOM操作
获取所有兄弟节点
function getSiblings(node) {
let target = node.parentNode.children
let siblings = {length:0}
for(let i=0; i<target.length; i++) {
if(target[i] !== node) {
[].push.call(siblings,target[i])
}
}
return siblings
}
批量增加删除class
function addClass(node, classes) {
for(key in classes) {
if(classes[key]) {
node.classList.add(key)
}else {
node.classList.remove(key)
}
}
}
在我们写代码的时候, 如果出现类似的代码, 就有优化的可能
上面第二个例子, 我们发现if里面的两句很像, 我们考虑下能不能优化下呢
if(classes[key]) {
node.classList.add(key)
}else {
node.classList.remove(key)
我们可以优化为
let methodName = classes[key] ? 'add' : 'remove'
node.classList[methodName](key)
优化后的代码
function addClass(node, classes) {
for(let key in classes) {
let methodName = classes[key] ? 'add' : 'remove'
node.classList[methodName](key)
}
}
命名空间
接下来把两个函数包装下, 让别人知道他俩是一个家族的, 都是用来操作DOM的, 这里为了简单化, 就不用原型链或者类的方法来写了
window.ssdom = {}
ssdom.getSiblings = getSiblings
ssdom.addClass = addClass
上面这种给函数加个前缀的方法就是命名空间
这样我们就可以继续优化我们的代码
window.ssdom = {}
ssdom.getSiblings = function(node) {
let target = node.parentNode.children
let siblings = {length:0}
for(let i=0; i<target.length; i++) {
if(target[i] !== node) {
[].push.call(siblings,target[i])
}
}
return siblings
}
ssdom.addClass = function(node, classes) {
for(let key in classes) {
let methodName = classes[key] ? 'add' : 'remove'
node.classList[methodName](key)
}
}
这种设计模式的优点
- 命名更直观, 更容易理解
- 避免变量覆盖
直接使用元素对象调用函数
但是这样还是有一丢丢麻烦, 我们可不可以直接用节点调用函数呢?
我们来试试吧, 这里我们想到了两种方法
方法一:
我们给Node的原型创建一个getSiblings
和addClass
方法, 那么所有node对象都可以继承这两个方法了
Node.prototype.getSiblings = function() {
let target = this.parentNode.children
let siblings = {length:0}
for(let i=0; i<target.length; i++) {
if(target[i] !== this) {
[].push.call(siblings,target[i])
}
}
return siblings
}
Node.prototype.addClass = function(classes) {
for(let key in classes) {
let methodName = classes[key] ? 'add' : 'remove'
this.classList[methodName](key)
}
}
这样就可以直接用节点调用了
假设我们有下面html
<ul>
<li id="item1">选项1</li>
<li id="item2">选项2</li>
<li id="item3">选项3</li>
<li id="item4" class="c">选项4</li>
<li id="item5">选项5</li>
</ul>
我们可以直接用node节点调用
item3.getSiblings()
item4.addClass({a:true, c:false, b:true})
但是这也有个问题, 如果别人已经在原型定义过同名函数, 那么我们就会覆盖别人的方法,为了避免我们覆盖别人的方法, 我们用另外一种方法试试
方法二:
我们自己造一个函数, 把它叫做jQuery, 然后把node传给这个函数, 这个函数返回一个对象, 对象里面包含上面两个方法, 这样就不会出现覆盖原先的方法的情况了
function jQuery(node) {
return {
getSiblings: function() {
let target = node.parentNode.children
let siblings = {length:0}
for(let i=0; i<target.length; i++) {
if(target[i] !== node) {
[].push.call(siblings, target[i])
}
}
return siblings
},
addClass: function(classes) {
for(let key in classes) {
let methodName = classes[key] ? 'add' : 'remove'
node.classList[methodName](key)
}
}
}
}
window.$ = jQuery
//调用
let $node2 = $(item3)
$node2.getSiblings()
这个方法就解决了第一种方法会覆盖别人同名方法的问题
上面的代码, 我们只能传一个节点, 我们再稍微增加一点儿功能, 我们让它也可以接收一个字符串
function jQuery(nodeOrSelector) {
/*************增加接收string类型选择器功能******************/
let node
if(typeof nodeOrSelector === 'string') {
node = document.querySelector(nodeOrSelector)
}else {
node = nodeOrSelector
}
/*******************原来的代码********************/
return {
getSiblings: function() {
let target = node.parentNode.children
let siblings = {length:0}
for(let i=0; i<target.length; i++) {
if(target[i] !== node) {
[].push.call(siblings, target[i])
}
}
return siblings
},
addClass: function(classes) {
for(let key in classes) {
let methodName = classes[key] ? 'add' : 'remove'
node.classList[methodName](key)
}
}
}
}
window.$ = jQuery
//调用
let $node2 = $('#item3')
$node2.getSiblings()
增加一次选择多个元素功能
再来继续完善功能, 我们来让他可以一次可以选择多个node, 并增加一些有用的API
function jQuery(nodeOrSelector) {
let nodes = {}
if(typeof nodeOrSelector === 'string') {
/****将querySelectorAll获取的nodeList转换成纯伪数组***/
let tmp = document.querySelectorAll(nodeOrSelector)
for(let i=0; i<tmp.length; i++){
nodes[i] = tmp[i]
}
nodes.length = tmp.length
}else if(nodeOrSelector instanceof Node) {
nodes = {
0: nodeOrSelector,
length: 1
} //为了保证得到的数据一致性, 虽然这里只获得一个node, 我们依然把他变成伪数组以保证跟获取多个节点时是相同的数据类型
}
/***********批量增加class*****************/
nodes.addClass = function(classes) {
for(let key in classes) {
for(let i=0; i<nodes.length; i++) {
let methodName = classes[key] ? 'add' : 'remove'
nodes[i].classList[methodName](key)
}
}
}
/************获取或设置所有获取节点的文本内容********/
nodes.text = function(text) {
if(text === undefined) {
let texts = []
for(let i=0; i<nodes.length; i++) {
texts.push(nodes[i].textContent)
}
return texts
}else {
for(let i=0; i<nodes.length; i++) {
nodes[i].textContent = text
}
}
}
return nodes
}
window.$ = jQuery
let $node2 = $('ul > li')
console.log($node2.text())
这里我们的jQuery就造完啦, 当然这个是超级山寨版的, 跟原版的根本比不了...
轮子造完啦
通过上面的例子, 我想告诉大家两点
- 造轮子其实并没有大家想象的那么难, 但是造好用的轮子那就需要很深的功底了
- jquery并没有那么高深莫测, 它其实就是将原生DOM方法进行了增强, 为了让我们更加方便的操作DOM
今天就讲到这里吧