jQuery初探

前情提要: 2018 年 7 月 26 日,Github 前端团队的 Mislav Marohnić 在 Twitter 发布了一则消息,表明在 GitHub 网站重构过程中放弃了 jQuery,没有再次使用其他任何框架去代替它,而是使用了原生的 JS,这标志着 jQuery 一个时代的结束

好,那么问题来了,我们还有必要学 jQuery 嘛?
当然有必要(废话,要不怎么会有这篇文章???),可以领会一下 jQuery 的封装思想,以后在封装代码的时候可以学着点

jQuery

先看看阮一峰怎么说, jQuery 设计思想

库实际上是特定种类的 api

封装函数

写代码

<li id="item1">选项1</li>
<li id="item1">选项2</li>
<li id="item1">选项3</li>
<li id="item1">选项4</li>
<li id="item1">选项5</li>

var allchildren = iterm3.parentnode.children
var 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)

然后对以上代码进行封装

// 得到该节点的兄弟姐妹
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
}

再封装一个

var classes = ['a', 'b', 'c']
classes.forEach((value) => item3.classList.add(value))

var classes = {'a': true, 'b': false, 'c': true}
for (let key in classes) {
var value = classes[key]
if (value) {
item3.classList.add(key)
} else {
item3.classList.remove(key)
}
}

封装一下

fucntion addClass(node, classes) {
for (let key in classes) {
var value = classes[key]
if (value) {
node.classList.add(key)
} else {
node.classList.remove(key)
}
}
}

使用就是如下

addClass(item3, {a: true, b:false, c:true})

命名空间 & Node.prototype

代码优化的原则,如果存在类似的代码,就有优化的可能
就比如像如下

fucntion addClass(node, classes) {
for (let key in classes) {
var value = classes[key]
var methodName = value ? 'add' : 'remove'
node.classList[methodName](key)
}
}

然后声明变量, 命名空间就是区分库的名字,以下声明的 ffdom 就是其命名空间

window.ffdom = {}
ffdom.getSiblings = getSiblings
ffdom.addClass = addClass

ffdom.getSiblings(item3)
ffdom.addClass(item3, {a: true, b: false, c: true})

弄个缩写 alias

window.$ = jQuery

最终优化的代码为

window.ffdom = {} /*yui*/
ffdom.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
}
ffdom.addClass = function (node, classes) {
classes.forEach( (value) => node.classList.add(value) )
}

然后使用就是

$.getSiblings(item3)
$.addClass(item3, ['a','b','c'])

那么如何将 ffdom.addClass(item3, ['a', 'b', 'c']) 转化成 item3.addClass(['a', 'b', 'c']) 来使用呢

尝试在 Node 原型链中加入 getSiblings 函数

Node.prototype.getSiblings = function () {
return 1
}

console.log(item3.getSiblings()) // 1
console.dir(item3)

可以在控制台看到在 Node 原型中出现了 getSiblings 函数,并且控制台打印出来的数为 1,通过这样的方式我们可以直接在 Node 原型中加函数,然后所有继承自 Node 的元素都可以调用到这个方法

Node.prototype.getSiblings = function() {
// item3 会传给 this 参数
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) {
// 这里注意下标就是 key, 对应的 'a' 'b' 'c' 就是 value
classes.forEach((value) => this.classList.add(value))
}

console.log(item3.getSiblings())
item3.addClass(['a', 'b', 'c'])

// 用 call , 显式指定 this
console.log(item3.getSiblings.call(item3))
item3.addclass.call(item3, ['a', 'b', 'c'])

this 是个啥 & Node2

首先我们记得是如何调用函数的

f.call(asThis, input1, input2)

其中 asThis 会被当做 this, [input1, input2] 会被当做 arguments
以后还是尽量少使用类似 f(input1, input2)

实际上 this 就是 call 的第一个参数

Node 里面添加函数的时候容易导致一个问题, 就是函数有可能被覆盖掉,那么有什么方法可以避免这个问题呢?
思路是再写一个 “Node2” ,在这个 “Node2” 中去调用原来的 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() // 这里不用传参的原因是在构造时已经传了 item3 这个 node 参数给它了
node2.addClass(['a', 'b', 'c'])

开始认识 jQuery

好了,将 Node2 变成 jQuery ,就是如下

window.jQuery = 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 = jQuery(item3)
node2.getSiblings() // 这里不用传参的原因是在构造时已经传了 item3 这个 node 参数给它了
node2.addClass(['a', 'b', 'c'])

所以 jQuery 的初步印象就是这样,给一个旧的对象,然后返回一个新的对象以及这个新对象的 api,但实际上这个新的 api 还是调用了旧的 api
所以 JQuery 实际上是一个构造函数,这个构造函数接受一个参数,这个参数可能是一个节点,这个构造函数被调用后返回一个对象,通过操作这个对象里的方法去操作节点

但是 JQuery 会更加聪明,假如传的是一个字符串(一个css选择器),它在代码里会做类型检查

window.jQuery = function(nodeOrSelector) {
let node
if (typeof nodeOrSelector === 'string') {
node = document.querySelector(nodeOrSelector)
} else {
node = nodeOrSelector
}
//... 下面的代码是一致的
}

上面也同时使用到了闭包,因为 addClass 这个函数,里面使用到了 node变量 , 而这个 node 是在该函数的外面进行声明的,我们对这个变量 node 和 这个匿名函数统称为 闭包,闭包的好处在于,它维持住了 node 这个变量,只要有 addClass 被调用的场合,它就不会消失,并且外面的代码也不能直接访问到 node 这个变量,因为作用域在 function(nodeOrSelector) 里面

操作多节点

那么如何操作多个节点呢?

window.jQuery = function(nodeOrSelector) {
let nodes = {} // 先声明一个多节点的伪数组
if (typeof nodeOrSelector === 'string') { // 假如传进来的参数是一个 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) { // 假如传进来的是一个 Node 值
nodes = {0: nodeOrSelector, length: 1} // 也返回一个伪数组,保持返回值的统一
}
nodes.getSiblings = function() {
}
// 为得到的每个节点增加 class
nodes.addClass = function(classes) {
if (typeof classes === 'string') {
let temp = classes;
classes = Array(temp);
}
// 注意这里 nodes 已经变成伪数组了,所以需要遍历
classes.forEach((value) => {
for (let i=0; i < nodes.length; i++) {
nodes[i].classList.add(value)
}
})
}
// 得到每个节点的 text
nodes.getText = function() {
var texts = []
for (let i=0; i < nodes.length; i++) {
texts.push(nodes[i].textContent)
}
return texts
}
// 设置每个节点的 text
nodes.setText = function(text) {
for (let i=0; i < nodes.length; i++) {
nodes[i].textContent = text
}
}
nodes.text = function(text) {
if (text === undefined) {
nodes.getText()
} else {
nodes.setText(text)
}
}
return nodes
}

// 使用
var node2 = jQuery('ul > li')
node2.addClass(['blue'])
node2.text()
node2.text('h1');

jQuery 使用举例

中文文档参见 cndevdocs

html 代码

<ul>
<li>选项1</li>
<li>选项2</li>
<li>选项3</li>
<li>选项4</li>
<li>选项5</li>
</ul>
<button id="x">X</button>

css 代码

.red {
color: red;
}

js 代码

var nodes = jQuery('ul > li')
x.onclick = function() {
nodes.toggleClass('red') // toggle 开关 切换
}

或者还可以链式操作

html 代码

<ul>
<li class="red">选项1</li>
<li class="red">选项2</li>
<li class="red">选项3</li>
<li class="red">选项4</li>
<li class="red">选项5</li>
</ul>
<button id="x">X</button>

css 代码

.red {
color: red;
}
.green {
color: green;
}

js 代码

var nodes = jQuery('ul > li')
x.onclick = function() {
nodes.removeClass('red').addClass('green')
}

还有个 addClass

html 代码

<ul>
<li>选项1</li>
<li>选项2</li>
<li>选项3</li>
<li>选项4</li>
<li>选项5</li>
</ul>
<button id="x">X</button>

css 代码

.red {
color: red;
}
.blue {
color: blue;
}
.green {
color: green;
}
.yellow {
color: yellow;
}
.black {
color: black;
}

js 代码

var nodes = jQuery('ul > li')
var classes = ['red', 'blue', 'green', 'yellow', 'black']
x.onclick = function() {
nodes.addClass(function(index, currentClass) {
return classes[index]
})
}

总结

  • jQuery 兼容性方面做的很好, 1.7 版本兼容到 IE6,但是现在 3 版本已经不做 IE 的兼容了
  • jQuery 还有动画 AJAX 等模块,不止 DOM 操作
  • jQuery 功能更丰富
  • jQuery 使用了 prototype 以及 new

小技巧:
如果用的是 jQuery,声明变量最好这样写

var $nodes = $('ul>li')
$nodes.classList

这样可以提醒自己用的是 jQuery 的 api
以上为大概对 jQuery 的初步理解