本文是对jQuery的起源的初步探索。先通过两个函数来扩展原生DOM的操作,然后引入命名空间以及对其重构,接着将该命名空间扩大到Node上,改造一个自己的Node2,引出jQuery。
<li>
标签的所有兄弟元素。<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JS Bin</title> </head> <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> </html>
此时你刚学完原生DOM操作,知道有nextSibling previousSibling parentNode
。你发现貌似没有直接一下子获得全部兄弟元素的API啊,身为一个优秀的90后,你果断手写一个函数实现这个需求啊。
function getSiblings(node){ var allChild = item2.parentNode.children var childObj = {length: 0} for (let i = 0; i < allChild.length; i++){ if (allChild[i] !== node){ childObj[childObj.length] = allChild[i] childObj.length += 1 } } return childObj }
好了,以上的函数就能满足需求了,它接受你传入的某个元素,返回包含这个元素所有兄弟元素的伪数组。
注意: 要用item2.parentNode.children
这样子才不会获得文本节点。所以你想获得item2
的所有兄弟,只需要getSiblings(item2)
获得所有兄弟的演示地址============>demo
<li>
添加一个类领导还没说完,你立马想到了,直接
item2.classList.add('类名')
啊,哈哈,我好聪明啊,不愧是优秀的90后。
给你任意一个元素要直接加上这个类名,别给我的一个一个的加,太二了,如果元素原来有一个不应该存在的类名,给我删了,领导接着说完全部的需求。
这...看来不能item1.classList.add('类名')
item2.classList.add('类名')
item3.classList.add('类名')
这么弱智的干了啊,那我还用函数嘛,你灵机一动。
嗯,不愧是善于思考的90后
function addClass(node, classes){ for (var key in classes){ var value = classes[key] if (value){ node.classList.add(key) } else{ node.classList.remove(key) } } }
上图是为添加元素的时候的item2
的模样,记住它,待会和下图对比。
可以看到,执行方法后,item2
的类名变为b、c,这是因为你是addClass(item2, {a: 0, b: 1, c: true})
这么调用的,意思是类名不应该有a,删除a
,并加上b c
。
以上对象的遍历并取值用到了falsey
值
复习一下,js的6个falsey
值
0
NaN
''
null
undefined
false
除此之外,其他的全是true。
不过你想的太美了,领导看到你的代码中的这个片段,直接抓狂了……
if (value){ node.classList.add(key) } else{ node.classList.remove(key) } }
这段代码给我优化了,明明就是一句话的事。
你回去想了一会,可以这么优化
var methodName = value ? 'add' : 'remove' node.classList[methodName](key)
最后你把如下代码提交。
function addClass(node, classes){ for (var key in classes){ var value = classes[key] var methodName = value ? 'add' : 'remove' node.classList[methodName](key) } }
[]
运算符,classList['add'] === classList.add
给任一元素添加类名==========================>demo
你完成了上面的两个需求后,领导本着锻炼你的原则,又给你提了新的需求。
var shaolinDom = {} //少林开的超市 shaolinDom.addClass = addClass //把addClass这个商品收进来 shaolin.getSibling = getSiblings //把getSiblings这个商品收进来
那我咋用呢,该咋用就咋用呗。
shaolinDom.addClass(item5, {a: true, b: false, c: 0}) //把item5上原本的b c类名删掉,加上 a类名 shaolinDom.getSiblings(item6) //获得item6的所有兄弟元素
var shaolinDom = {} shaolinDom.addClass = function(node, classes){ for (var key in classes){ var value = classes[key] var methodName = value ? 'add':'remove' node.classList[methodName](key) } } shaolinDom.getSiblings = function (node){ var allChild = node.parentNode.children var childObj = {length: 0} for (let i = 0; i< allChild.length; i++){ if (allChild[i] !== node){ childObj[childObj.length] = allChild[i] childObj.length += 1 } } return childObj } shaolinDom.addClass(item5, {a: true, b: false, c: 0}) var allSiblings = shaolinDom.getSiblings(item6) console.log(allSiblings)
引入命名空间=======================>demo
命名空间的优化=====================>demo
shaolinDom
用啊,而且我每次要把item5
传到函数里面,每次好麻烦的啦,你改进一下,让我的元素可以直接调用方法呗,比如item5.getSiblings()
这样多好。这样子操作的话,item5
拥有自主权,就像你买东西,你想去买那个东西你就去买那个东西嘛,而不是东西去选择你啊。item5.getSiblings()
操作,那我这次直接给你干到Node的原型上;Node.prototype.addClass = function(classes){ for (var key in classes){ var value = classes[key] var methodName = value ? 'add':'remove' this.classList[methodName](key) } } Node.prototype.getSiblings = function (){ var allChild = this.parentNode.children var childObj = {length: 0} for (let i = 0; i< allChild.length; i++){ if (allChild[i] !== this){ childObj[childObj.length] = allChild[i] childObj.length += 1 } } return childObj }
this
的知识this
是啥,再给我讲讲咋用呗item5.addClass({a: true, b: false, c: 0}) //既然Node原型都有了这两函数,item5是node类型,直接用呗 console.log(item6.getSiblings())
addClass
函数里面这就一个参数啊,getSiblings
函数怎么没参数啊//上面的代码等同于以下代码 item5.addClass.call(item5, {a: true, b: false, c: 0}) //call()方法的第一个参数就是this console.log(item6.getSiblings.call(item6))
()
去调用函数,自己脑补call()
就好啦,自然也就知道this是谁啦。
进一步升级,绑定Node
的原型链上==================>demo用call()
方便理解this
\================================>demo
没多久,领导的考验又来了
String() Number() Array()
可以直接返回一个对象,我也这么干吧window.Node2 = function(node){ return { getSiblings: function(){ var allChild = node.parentNode.children var childObj = {length: 0} for (let i = 0; i< allChild.length; i++){ if (allChild[i] !== node){ childObj[childObj.length] = allChild[i] childObj.length += 1 } } return childObj }, addClass: function(classes){ for (var key in classes){ var value = classes[key] var methodName = value ? 'add':'remove' node.classList[methodName](key) //闭包的使用 } } } }
var node2 = Node2(item3) //node2就是用Node2()构造函数构造的返回的对象 node2.getSiblings() //对象的点运算符去去操作属性啊 node2.addClass({'a': 0, 'b': true, 'c': true})
jQuery
了自己实现一个构造函数去理解=======================>demo
window.jQuery = function(node){ return { getSiblings: function(){ var allChild = node.parentNode.children var childObj = {length: 0} for (let i = 0; i< allChild.length; i++){ if (allChild[i] !== node){ childObj[childObj.length] = allChild[i] childObj.length += 1 } } return childObj }, addClass: function(classes){ for (var key in classes){ var value = classes[key] var methodName = value ? 'add':'remove' node.classList[methodName](key) //闭包的使用 } } } }
jQuery
?jQuey
就是一个构造函数,它返回一个对象,这个对象有很多key,对应的value又是一些函数。$
这个操作呢window.$ = jQuery
jQuery
实现把某个元素变红,最好验证一下---你的参数是node节点还是一个选择器,提示一下,可以用querySelector()
,querySelector
会返回文档中匹配指定的选择器组的第一个元素window.JQuery = function(nodeOrSelector){ let node //判断一下nodeOrSelector是node还是一个选择器 if(typeof nodeOrSelector === 'string'){ node = document.querySelector(nodeOrSelector) } else{ node = nodeOrSelector } return { getSiblings: function(){ var allChild = node.parentNode.children var childObj = {length: 0} for (let i = 0; i< allChild.length; i++){ if (allChild[i] !== node){ childObj[childObj.length] = allChild[i] childObj.length += 1 } } return childObj }, addClass: function(classes){ for (var key in classes){ var value = classes[key] var methodName = value ? 'add':'remove' node.classList[methodName](key) } } } }
所以
//var node2 = JQuery('#item3')与下列代码作用相同,把item3变红 var node2 = JQuery('ul > li:nth-child(3)')
jQuery的雏形======================>demo
<li>
的内容吧,可以使用querySelectorAll() 返回一个NodeList的伪数组
window.JQuery = function(nodeOrSelector){ let nodes = {} if(typeof nodeOrSelector === 'string'){ let temp = document.querySelectorAll(nodeOrSelector) //NodeList 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){ classes.forEach((value) => { for (let i = 0; i < nodes.length; i++){ nodes[i].classList.add(value) } }) } //等同于get、set方法 nodes.text = function(text){ if(text === undefined){ var 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 }
控制多个<li>
的内容================================>demo
最终,少林在经理的循循善诱下,开始探索jQuery
的道路。虽然jQuery
使用量在下降,但是依然有60\%的web开发人员在用。
总之,jQuery我来啦\~去探索真正强大的jQuery
吧,去理解write less, do more
的含义吧,去体会一句顶一万句
的力量吧。
最后安利一波刘震云的小说一句顶一万句
饥人谷一直致力于培养有灵魂的编程者,打造专业有爱的国内前端技术圈子。如造梦师一般帮助近千名不甘寂寞的追梦人把编程梦变为现实,他们以饥人谷为起点,足迹遍布包括facebook、阿里巴巴、百度、网易、京东、今日头条、大众美团、饿了么、ofo在内的国内外大小企业。 了解培训课程:加微信 xiedaimala03,官网:https://jirengu.com
本文作者:饥人谷方应杭老师