手写DOM库
封装DOM
什么叫封装
举例
- 笔记本电脑就是CPU、内存、硬盘、主板、显卡的封装
- 用户只需要接触显示器、键盘、鼠标、触控板等设备即可操作复杂的计算机
接口
- 被封装的东西需要暴露一些功能给外部
- 这些功能就是接口,如USB接口、HDMI接口
- 设备只有支持这些接口、即可与被封装的东西进行通讯
- 比如鼠标、键盘支持USB接口,显示器支持HDMI接口
术语
库
- 把提供给其他人使用的工具代码叫做库
- 比如jQuery、Underscore
API
- 库暴露出来的函数或者属性叫做API(应用编程接口)
框架
- 当库变得很大,并且需要学习才能看懂,那么这个库就叫做框架,比如Vue/React
注意:编程内的术语大部分都很随意,没有固定的解释
使用对象风格来封装
也叫命名空间风格
- window.dom 是提供的全局对象
增
- dom.create(
<div>hi<div>
)用于创建节点 - dom.after(node,node2) 用于新增弟弟节点
- dom.before(node,node2) 用于新增哥哥节点
- dom.append(parent,child) 用于新增子节点
- dom.wrap(
<div><div>
) 用于新增父节点
删
- dom.remove(node) 用于删除节点
- dom.empty(parent) 用于删除后代
改
- dom.attr(node,'title',?) 用于读写属性
- dom.text(node,?) 用于读写文本内容
- dom.html(node,?) 用于读写HTML内容
- dom.style(node,{color:'red'}) 用于修改style
- dom.class.add(node,'blue') 用于添加class
- dom.class.remove(node,'blue') 用于删除class
- dom.on(node,’click’,fn) 用于添加事件监听
- dom.off(node,'click',fn) 用于删除事件监听
查
- dom.find('选择器') 用于获取标签或标签们
- dom.parent(node) 用于获取父元素
- dom.children(node) 用于获取子元素
- dom.siblings(node) 用于获取兄弟姐妹元素
- dom.next(node) 用于获取弟弟
- dom.previous(node) 用于获取哥哥
- dom.each(nodes,fn) 用于遍历所有节点
- dom.index(node) 用于获取排行老几
代码实现
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DOM封装</title>
<style>
.red {
background: red;
}
</style>
</head>
<body>
<div>
<div id="test"><span>test1</span>
<p class="red">段落标签3</p> test2
</div>
<div id="test2">
<p class="red">段落标签3</p>
</div>
</div>
<div id="empty">
<div id="e1"></div>
<div id="e2"></div>
<div id="e3"></div>
</div>
<div id="siblings">
<div id="f1"></div>
<div id="f2"></div>
<div id="f3"></div>
</div>
<div id="travel">
<div id="t1">t1</div>
<div id="t2">t2</div>
<div id="t3">t3</div>
</div>
<script src="dom.js"></script>
<script src="main.js"></script>
</body>
</html>
window.dom = {
create(string) {
const container = document.createElement("template")
container.innerHTML = string.trim();
return container.content.firstChild;
},
after(node, node2) {
console.log(node.nextSibling);
node.parentNode.insertBefore(node2, node.nextSibling);
},
before(node, node2) {
node.parentNode.insertBefore(node2, node);
},
append(parent, node) {
parent.appendChild(node);
},
wrap(node, parent) {
dom.before(node, parent);
dom.append(parent, node);
},
remove(node) {
node.parentNode.removeChild(node);
return node
},
empty(node) {
// node.innerHTML = ''
// const childNodes = node.childNodes
// const { childNodes } = node //上面语法的简写
const array = []
let x = node.firstChild
while (x) {
array.push(dom.remove(node.firstChild))
x = node.firstChild
}
return array
},
attr(node, name, value) { //重载
if (arguments.length === 3) {
return node.setAttribute(name, value)
} else if (arguments.length === 2) {
return node.getAttribute(name)
}
},
text(node, string) { //适配
if (arguments.length === 2) {
if ('innerText' in node) {
node.innerText = string //ie
} else {
node.textContent = string // firefox / chrome
}
} else if (arguments.length === 1) {
if ('innerText' in node) {
return node.innerText
} else {
return node.textContent
}
}
},
html(node, string) {
if (arguments.length === 2) {
node.innerHTML = string
} else if (arguments.length === 1) {
return node.innerHTML
}
},
style(node, name, value) {
if (arguments.length === 3) {
//dom.style(div,'color','red')
node.style[name] = value
} else if (arguments.length === 2) {
if (typeof name === 'string') {
//dom.style(div,'color')
return node.style[name]
} else if (name instanceof Object) {
//dom.style(div,)
const object = name
for (let key in object) {
// ket: border / color
node.style[key] = object[key]
}
}
}
},
class: {
add(node, className) {
node.classList.add(className)
},
remove(node, className) {
node.classList.remove(className)
},
has(node, className) {
return node.classList.contains(className)
}
},
on(node, eventName, fn) {
node.addEventListener(eventName, fn)
},
off(node, eventName, fn) {
node.removeEventListener(eventName, fn);
},
find(selector, scope) {
return (scope || document).querySelectorAll(selector)
},
parent(node) {
return node.parentNode
},
children(node) {
return node.children
},
siblings(node) {
return Array.from(node.parentNode.children).filter(n => n !== node)
},
next(node) {
let x = node.nextSibling
while (x && x.nodeType === 3) {
x = x.nextSibling
}
return x
},
previous(node) {
let x = node.previousSibling
while (x && x.nodeType === 3) {
x = x.previousSibling
}
return x
},
each(nodeList, fn){
for(let i=0;i<nodeList.length;i++){
fn.call(null, nodeList[i])
}
},
index(node){
const list = dom.children(node.parentNode)
let i
for (i =0;list.length;i++){
if (list[i] === node){
break
}
}
return i
}
};
const div = dom.create("<div>newDiv</div>")
console.log(div);
dom.after(test, div);
const div3 = dom.create('<div id="parent"></div>')
dom.wrap(test, div3)
const nodes = dom.empty(window.empty)
console.log(nodes)
dom.attr(test, 'title', 'Hi, I am Rick')
const title = dom.attr(test, 'title')
console.log(`title: ${title}`)
dom.text(test, '你好,这是新的内容')
dom.text(test)
dom.style(test, {border: '1px solid red', color: 'blue'})
console.log(dom.style(test, 'border'))
dom.style(test, 'border', '1px solid black')
dom.class.add(test, 'red')
dom.class.add(test, 'blue')
dom.class.remove(test, 'blue')
console.log(dom.class.has(test, 'blue'))
const fn = () => {
console.log('点击了')
}
dom.on(test, 'click', fn)
dom.off(test, 'click', fn)
const testDiv = dom.find('#test')[0]
console.log(testDiv)
const test2 = dom.find('#test2')[0]
console.log(dom.find('.red', test2)[0])
console.log(dom.parent(test))
const f2 = dom.find('#f2')[0]
console.log(dom.siblings(dom.find('#f2')[0]))
console.log(dom.next(f2))
console.log(dom.previous(f2))
const t = dom.find('#travel')[0]
dom.each(dom.children(t), (n)=> dom.style(n, 'color', 'red'))
console.log(dom.index(f2))
用jQuery 风格重新封装
链式风格
也叫jQuery风格
- window.jQuery() 是提供的全局函数
特殊函数jQuery
- jQuery(选择器)用于获取对应的元素
- 但它却不返回这些元素,相反它返回一个对象,称为jQuery构造出来的对象
- 这个对象可以操作对应的元素
jQuery是构造函数吗
是
- 因为jQuery函数确实构造出了一个对象
不是
- 因为不需要写new jQuery() 就能构造一个对象
- 常规构造函数都要结合new才行
结论
- jQuery是一个不需要加new的构造函数
- jQuery不是常规意义的构造函数
jQuery对象代指jQuery函数构造出来的的对象(也就是api),jQuery对象不是说 “jQuery这个对象”
术语
- Object是个函数,Object对象表示Object构造出来的对象
- Array是个函数,Array对象/数组对象表示Array构造出来的对象
- Function是个函数,Function对象/函数对象表示Function构造出来的对象
链式风格
查
- jQuery(‘#xxx’) 返回值并不是元素,而是一个对象
- jQuery(‘#xxx’).find(‘.red’) 查找 #xxx 里的 .red 元素
- jQuery(‘#xxx’).parent() 获取父元素
- jQuery(‘#xxx’).children() 获取子元素
- jQuery(‘#xxx’).siblings() 获取兄弟元素
- jQuery(‘#xxx’).index() 获取元素排第几(从0开始)
- jQuery(‘#xxx’).next() 获取弟弟
- jQuery(‘#xxx’).prev() 获取哥哥
- jQuery(‘#xxx’).each(fn) 遍历并对每个元素执行fn
命名风格
下面的代码令人误解
- const div = $(‘div#test’)
- 我们会误以为div是一个DOM
- 实际上div是jQuery构造的api对象
改成这样
- const $div = $(‘div#test’)
- 这样命名就可以清楚的知道 $div.appendChild 不存在,因为它不是DOM对象
- $div.find存在,因为它是jQuery对象
后续
使用原型
- 把共用属性(函数)全部放到 $.prototytpe
- $.fn = $.prototype //别名
- 然后让api.__proto__指向 $.fn
设计模式
jQuery用到了那些设计模式
- 不用new的构造函数,这个模式没有专门的名字
- $(支持多种参数),这个模式叫做重载
- 用闭包隐藏细节
- $div.test() 即可读也可写,getter/setter
- $.fn 是 $.prototype 的别名
- 针对不同的浏览器使用了不同的代码,适配器模式
window.$ = window.jQuery = function (selectorOrArray) {//window.$ = window.jQuery 类似添加一个别名,两个=先执行右边的
let elements
if (typeof selectorOrArray === 'string') {
elements = document.querySelectorAll(selectorOrArray)
} else if (selectorOrArray instanceof Array) {
elements = selectorOrArray
}
// api 可以操作elements
return {
each(fn) {
for (let i = 0; i < elements.length; i++) {
fn.call(null, elements[i], i)
}
return this
},
parent() {
const array = []
this.each((node) => {
if (array.indexOf(node.parentNode) === -1) {
array.push(node.parentNode)
}
})
return jQuery(array)
},
children() {
const array = []
this.each((node) => {
array.push(...node.children)
})
return jQuery(array)
},
appendTo(node) {
if (node instanceof Element) {
this.each(el => node.appendChild(el)) //遍历elements,对每个el进行noed.appendChild操作
} else if (node.jQuery === true) {
this.each(el => node.get(0).appendChild(el)) // 遍历element,对每个el进行node.get(0).appendChild(el)操作
}
},
print() {
console.log(elements)
},
// 闭包:函数访问外部变量
addClass(className) {
for (let i = 0; i < elements.length; i++) {
const element = elements[i]
element.classList.add(className)
}
return this
},
find(selector) {
let array = []
for (let i = 0; i < elements.length; i++) {
const elements2 = Array.from(elements[i].querySelectorAll(selector))
array = array.concat(elements2)
}
array.oldApi = this //this 就是 旧api
return jQuery(array)
},
end() {
return this.oldApi //this 就是当前的api // api2
},
oldApi: selectorOrArray.oldApi,
}
}
<!doctype html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>手写jQuery</title>
</head>
<body>
<div class="test">
你好1
<div class="child">child1</div>
<div class="child">child2</div>
<div class="child">child3</div>
</div>
<div class="test">
你好2
<div class="child">child2</div>
<div class="child">child3</div>
</div>
<div class="test">
你好3
<div class="child">child3</div>
</div>
<script src="jQuery.js"></script>
<script src="main.js"></script>
</body>
</html>