本文记录手写实现jQuery的两个API-getSiblings及addClass的思路及过程
1. 使用原生JS实现getSiblings和addClass的功能
<body>
<ul>
<li id="item1">选项1</li>
<li id="item2">选项2</li>
<li id="item3">选项3</li>
<li id="item4">选项4</li>
<li id="item5">选项5</li>
<li id="item6">选项6</li>
</ul>
</body>
实现getSiblings功能
//获取item3的兄弟元素
let allChildren = item3.parentNode.children
let array = {length: 0}
for(let i = 0; i < allChildren.length; i++){
if(allChildren[i] !== item3){
array[array.length] = allChildren[i]
array.length += 1
}
}
console.log(array)
// {0: li#item1, 1: li#item2, 2: li#item4, 3: li#item5, 4: li#item6, length: 5}
// 0:li#item1
// 1:li#item2
// 2:li#item4
// 3:li#item5
// 4:li#item6
// length:5
// __proto__:Object
实现addClass功能
let classes = {'a': true, 'b': false, 'c': true} // 用一个哈希表示class是否存在
for(let key in classes){
let value = classes[key]
// 给item3添加或移除class
if(value){
item3.classList.add(key)
}else{
item3.classList.remove(key)
}
}
2. 封装原生代码
封装getSiblings函数
function getSiblings(node){
let allChildren = node.parentNode.children
let array = {length: 0}
for(let i = 0; i < allChildren.length; i++){
if(allChildren[i] !== node){
array[array.length] = allChildren[i]
array.length += 1
}
}
return array
}
getSiblings(item3)
封装addClass函数
function addClass(node, classes){
for(let key in classes){
let value = classes[key]
let methodName = value ? 'add' : 'remove'
node.classList[methodName](key)
}
}
addClass(item3, {'a': true, 'b': false, 'c': true})
3. 命名空间
function getSiblings(node){
let allChildren = node.parentNode.children
let array = {length: 0}
for(let i = 0; i < allChildren.length; i++){
if(allChildren[i] !== node){
array[array.length] = allChildren[i]
array.length += 1
}
}
return array
}
function addClass(node, classes){
for(let key in classes){
let value = classes[key]
let methodName = value ? 'add' : 'remove'
node.classList[methodName](key)
}
}
window.newDOM = {}
newDOM.getSiblings = getSiblings
newDOM.addClass = addClass
newDOM.getSiblings(item3)
newDOM.addClass(item3, {'a': true, 'b': false, 'c': true})
4. 改写
方法1:修改Node原型
Node.prototype.getSiblings = function(){
let allChildren = this.parentNode.children
let array = {length: 0}
for(let i = 0; i < allChildren.length; i++){
if(allChildren[i] !== this){
array[array.length] = allChildren[i]
array.length += 1
}
}
return array
}
item3.getSiblings()
为Node.prototype添加getSiblings方法,在函数内使用this获取指定的item,addClass方法同上
Node.prototype.addClass = function(classes){
for(let key in classes){
let value = classes[key]
let methodName = value ? 'add' : 'remove'
this.classList[methodName](key)
}
}
item3.addClass({'a': true, 'b': false})
5. 避免覆盖相同名称的已有方法
如上文所述,我们已经对getSiblings方法和addClass方法进行了封装并且修改了Node的原型,但是为了避免Node原型中已经存在相同名称的方法,我们重写一个Node2原型
window.Node2 = function(node){
return {
getSiblings: function(){
let allChildren = node.parentNode.children
let array = {length: 0}
for(let i = 0; i < allChildren.length; i++){
if(allChildren[i] !== node){
array[array.length] = allChildren[i]
array.length += 1
}
}
return array
},
addClass: function(classes){
for(let key in classes){
let value = classes[key]
let methodName = value ? 'add' : 'remove'
node.classList[methodName](key)
}
}
}
}
var node2 = Node2(item3)
node2.getSiblings()
node2.addClass({'a': true, 'b': false, 'c': true})
需要注意的是,我们在改写的过程中在函数内使用了this来获取item3,但在重写出的Node2中我们声明了一个变量使用Node2构造函数,并把item3作为参数传入,因此函数内只需使用参数node。
6. 添加选择器功能
上文中的Node2在使用的过程中只能接受用户传入一个节点,因此需要对Node2进行改进
window.jQuery = function(nodeOrSelector){
let node
if(typeof nodeOrSelector === 'string'){
node = document.querySelector(nodeOrSelector)
}else{
node = nodeOrSelector
}
return {
getSiblings: function(){
let allChildren = node.parentNode.children
let array = {length: 0}
for(let i = 0; i < allChildren.length; i++){
if(allChildren[i] !== node){
array[array.length] = allChildren[i]
array.length += 1
}
}
return array
},
addClass: function(classes){
for(let key in classes){
let value = classes[key]
let methodName = value ? 'add' : 'remove'
node.classList[methodName](key)
}
}
}
}
var nodeItem = jQuery('#item3')
nodeItem.getSiblings()
nodeItem.addClass({'a': true, 'b': false, 'c': true})
改进过程中首先函数的名称进行了修改,并不影响功能的实现,然后给函数添加判断条件,使得用户传入的节点及选择器都可以使用,在这个过程中node和getSiblings函数、addClass函数构成了闭包。
7. 多个选择器
上文中的选择器只能选择一个节点,如果用户想要一次选择多个节点,则需要继续进行改进
window.jQuery = function(nodeOrSelector){
let nodes = {}
if(typeof nodeOrSelector === 'string'){
let temp = document.querySelectorAll(nodeOrSelector)
for(let i = 0; i < temp.length; i++){
nodes[i] = temp[i]
}
nodes.length = temp.length
}else if(nodeOrSelector instanceof Node){
nodes = {0: nodeOrSelector, length: 1}
}
nodes.addClass = function(classes){
for(let key in classes){
let value = classes[key]
let methodName = value ? 'add' : 'remove'
for(let i = 0; i < nodes.length; i++){
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
}
var nodeItem = jQuery('ul > li')
nodeItem.addClass({'a': true, 'b': false, 'c': true})
console.log(nodeItem.text())
nodeItem.text('hi')
本次修改允许用户通过 ul > li
选择多个节点,返回的nodes为一个伪数组,同时添加一个新的text()方法,在text()方法的参数为空时,获取节点的文本;在text()方法的参数不为空时,设置所有选中节点的text
8. 总结
本文实现的jQuery实际上就是一个构造函数,接受一个参数,参数可以是一个节点也可以是一个选择器,然后返回一个方法对象去操作节点或选择器选中的节点。