本节要点:
千万不要学jQuery Mobile,因为它跟jQuery没啥关系,已经过时了。
- 本节首先实现一个类似jQuery的API,来帮助理解什么是jQuery。
- 看jQuery的API文档。
- 建议先阅读阮一峰的jQuery设计思想。
此处引用部分内容:
- jQuery的基本设计思想和主要用法,就是"选择某个网页元素,然后对其进行某种操作"。这是它区别于其他Javascript库的根本特点。
- jQuery设计思想之二,就是提供各种强大的过滤器,对结果集进行筛选,缩小选择结果。
- jQuery设计思想之三,就是最终选中网页元素以后,可以对它进行一系列操作,并且所有操作可以连接在一起,以链条的形式写出来。
- jQuery设计思想之四,就是使用同一个函数,来完成取值(getter)和赋值(setter),即"取值器"与"赋值器"合一。到底是取值还是赋值,由函数的参数决定。
- jQuery设计思想之五,就是提供两组方法,来操作元素在网页中的位置移动。一组方法是直接移动该元素,另一组方法是移动其他元素,使得目标元素达到我们想要的位置。
- jQuery设计思想之六:除了对选中的元素进行操作以外,还提供一些与元素无关的工具方法(utility)。不必选中元素,就可以直接使用这些方法。
- jQuery设计思想之七,就是把事件直接绑定在网页元素之上。
一、写一个简化版的jQuery
封装两个函数
html:
<!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>
</ul>
</body>
</html>
JS:
/* getSiblings: 获得node 的 兄弟姐妹们 */
function getSiblings(node){ /* API */
var allChildren = node.parentNode.children
var 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: 给node添加class */
function addClass(node, classes){
for(let key in classes){
var value = classes[key]
if(value){
node.classList.add(key)
}else{
node.classList.remove(key)
}
}
}
/* 获得item3的兄弟姐妹们 */
console.log(getSiblings(item3))
/* 给item2添加class="a c" /
addClass(item2, {'a':true,'b':false,'c':true})
效果如下
控制台:
{0: li#item1, 1: li#item2, 2: li#item4, 3: li#item5, length: 4}
0: li#item1
1: li#item2
2: li#item4
3: li#item5
length: 4
__proto__: Object
html:
<li id="item2" class="a c">选项2</li>
代码优化准则一:如果出现类似的代码,就有优化的可能
JS:
/* addClass: 给node添加class */
function addClass(node, classes){
for(let key in classes){
var value = classes[key]
/* 优化: obj.fn === obj[fn]*/
var methodName = value ? 'add' : 'remove'
node.classList[methodName]
}
}
命名空间
曾经这种方法很流行,现在已经过时了。
命名空间就是把一些函数放在同一个空间里,好处是可以区分不同命名空间的同名API,使它们不会冲突。
比如document就是一个命名空间,document里有很多API,这些API统一用document.xxx形式调用,如果是yyy.xxx则调用的是别的命名空间的API。
更多信息看命名空间。
JS:
window.omdom = {} /* omdom只是一个名字,可以随意起 */
ondom.getSiblings = function (node){ /* API */
var allChildren = node.parentNode.children
var 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,只保留了添加class功能*/
omdom.addClass = function (node, classes){
classes.forEach((value) => node.classList.add(value) )
}
omdom.getSiblings(item1)
omdom.addClass(item2, ['a','b','c'])
改进一:node.xxx()
上面命名空间的调用方法仍然有些麻烦,我们希望能够是node.getSiblings() , node.addClass()这种形式。有两种方法:
- 扩展Node接口
我们可以直接在Node的原型链即Node.prototype里添加我们自己构造的两个函数,代码如下:
Node.prototype.getSiblings = function (){
var allChildren = this.parentNode.children
var 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
}
Node.prototype.addClass = function (classes){
classes.forEach((value) => this.classList.add(value) )
}
/* 以下两种方法表示,帮助理解this */
/* 隐式this */
item2.getSiblings()
item3.addClass(['a','b','c'])
/* 显式this */
item2.getSiblings.call(item2)
item3.addClass.call(item3, ['a','b','c'])
但是使用这种方法,如果多人一起给Node.prototype添加接口呢?互相又不知道对方加了什么,很可能就互相覆盖了,所以我们要用另一种方法!
- 自己写一个新的Node
window.Node2 = function(node) {
return {
getSiblings: function() {
var allChildren = node.parentNode.children
var 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) {
classes.forEach((value) => node.classList.add(value) )
}
}
}
var node2 = Node2(item3)
node2.getSiblings()
node2.addClass(['a', 'b', 'c'])
第二种方法就是 无侵入 !
那上面的这些操作跟jQuery有什么关系呢?
现在把上述代码的Node2改成jQuery:只是个函数名字的变化
window.jQuery = function(node){
...
}
var node2 = jQuery(item3)
node2.getSiblings()
node2.addClass(['a', 'b', 'c'])
由此,我们就可以理解jQuery到底在干什么:
jQuery就是一个升级的DOM,它接受一个旧的节点,返回一个新的对象,这个对象有新的API如getSiblings、addClass。这些新的API的实现是在jQuery内部,我们写代码只需要去调用即可。
更简单的说,jQuery就是一个构造函数,接受一个参数,这个参数可能是节点,然后返回一个方法对象去操作这个节点。
实际上的jQuery比上述的更加强大,比如我们不只是接受一个节点,还可以接受一个字符串:
CSS:
.red{
color: red;
}
JS:
window.jQuery = function(nodeOrSelector){
let node
if(typeof nodeOrSelector === 'string'){
node = document.querySelctor(nodeOrSelector)
}lese{
node = nodeOrSelector
}
...
}
var node2 = jQuery('#item3')
// var node2 = jQuery('ul > li:nth-child(3)')
node2.getSiblings()
node2.addClass(['red'])
效果:选项3 变为 红色 。
改进二: 接受多个node
JS:
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 = { // 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)
}
} )
}
return nodes
}
var node2 = jQuery('ul > li')
node2.addClass(['red'])
接受的nodeOrSelector经过if - else if 判断,获得一个伪数组,然后对这个伪数组进行操作。
改进三:添加几个jQuery的API
比如获取节点的文本内容或者设置节点的文本内容。
这里先插一句。jQuery不喜欢写get、set,所以jQuery把以上两个功能合并在一起,如果没有传递参数就是获得节点的文本内容,如果传递参数就是设置节点的文本内容。代码如下:
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){
classes.forEach((value) => {
for(let i=0; i<nodes.length; i++){
nodes[i].classList.add(value)
}
} )
}
// 以下为新添加内容 以下为新添加内容 以下为新添加内容 以下为新添加内容 以下为新添加内容
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
}
var node2 = jQuery('ul > li')
node2.addClass(['red'])
console.log(node2.text())
node2.text('hi')
解释说明:
- 你可以给我一个选择器,也可以是一个节点——nodeOrSelector,然后我判断nodeOrSelector是一个节点还是多个节点并将结果以伪数组的形式放在nodes里。然后你可以调用我写的API。
这里的“我”,就是本文写的简化版jQuery。 - 调用API有两种方法,文中一直用的是闭包的方法(闭包:函数+函数外定义的变量就叫闭包)。实际上,node2就是nodes,如果你能理解这一点,就可以直接使用
node[0].classList.add('red')来直接给第一个li添加red类。node[0]就是nodes的第一项。
jQuery API
这里给大家一个网站——www.jquery123.com/,里面有jQuery的所有API。
主要还是看得懂我给的网站内容就行,那上面有例子。
总结
- jQuery 在兼容性方面做得很好,1.7 版本兼容到 IE 6
- jQuery 还有动画、AJAX 等模块,不止 DOM 操作
- jQuery 的功能更丰富
- jQuery 使用了 prototype,因为还涉及到了 new ,我目前还不会,以后再补充吧。
补充:
window.$ = jQuery,一般用$符号代替jQuery,不过也可能存在$被占用的情况,这种情况下我们只能老老实实的使用jQuery了。- 建议一个小习惯:如果是jQuery构造出来的对象,就在对象名前加上一个
$,来标记这个对象是jQuery构造的,这样在使用的过程中不会造成混淆。例如:var $nodes = $('ul > li') - 再补一遍 什么是jQuery?
jQuery就是一个升级的DOM,它接受一个旧的节点,返回一个新的对象,这个对象有新的API如getSiblings、addClass。这些新的API的实现是在jQuery内部,我们写代码只需要去调用即可。
更简单的说,jQuery就是一个构造函数,接受一个参数,这个参数可能是节点,然后返回一个方法对象去操作这个节点。