目录
- jQuery 如何获取元素
- jQuery 的链式操作是怎样的
- jQuery 如何创建元素
- jQuery 如何移动元素
- jQuery 如何修改元素的属性
- 选择网页元素
- 改变结果集
- 链式操作
- 元素的操作:取值和赋值
- 元素的操作:移动
- 元素的操作:复制、删除和创建
- 工具方法
- 事件操作
- 特殊效果
正文
链式风格(jQuery)
window.jQuery()
是所提供的全局函数
用jQuery风格重新封装DOM
特殊函数jQuery
jQuery(选择器)
用于获取选择器所对应的元素
- 但却不返回这些元素
- 而是返回一个对象,称为
jQuery
构造出来的对象,即 API
- 这个对象可以操作对应的元素
- 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/*
<body>
<div class="red">1</div>
<div class="red">2</div>
<div class="red">3</div>
</body>
*/
window.jQuery = function(选择器){
const nodes = document.querySelector(选择器)
// nodes 是伪数组,要转成数组
const array = Array.from(nodes)
// api 可以操作 array
const api = {
log(){
console.log(array)
}
}
return api // 不 return array 而是 return api
}
// 由于用了querySelector, 也是静态的,不实时变化的
|
jQuery
是构造函数吗?
- 是,因为
jQuery
函数确实构造出了一个对象
- 又不是,因为不需要写
new jQuery()
就能构造一个对象,常规构造函数用 new操作符
结论
jQuery
是一个不需要加new
的构造函数
jQuery
不是常规意义上的构造函数
jQuery
对象 代指jQuery
函数构造出来的对象(*可以认为是被省略掉的api)
- 口头约定,
jQuery
对象并非「jQuery
这个对象」
一些必须搞清的术语
Object
是个函数
Object
对象表示Object
构造出的对象
Array
是个函数
Array
对象/数组对象表示Array
构造出来的对象
Function
是个函数
Function
对象/函数对象表示Function
构造出来的对象
jQuery.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
window.jQuery = function (selector) {
// this.console.log('我是jQuery')
// 核心代码 接受一个选择器;
// 根据选择器得到 一些元素;
// 返回一个对象 这个对象 上有个方法 可以操作 这些元素
const elements = document.querySelectorAll(selector)
// return elements // 不返回此对象
// 而是返回一个 API 可以操作 elements
/*
const api_test = {
//ES6
doSth() {
console.log(elements) // 闭包:函数访问外部 变量
},
// ES5
"doOther": function () {
console.log(elements)
}
}
*/
/*
const api = {
addClass(className) {
// 遍历所有刚才获取的elements 元素,添加 .className 样式
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className)
}
return this // api
}
}
return api // 这里的 this 是 window // window.jQuery
// api 的名字其实没有必要 直接返回对象
*/
return {
addClass(className) {
/* 遍历所有刚才获取的elements 元素,添加 .className 样式 */
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className)
}
return this
}
}
}
|
api 的名字没有必要 直接返回对象本,api 对象在外部的环境中创建
JQ的核心思想
- 提供一个函数 这个函数接收一个CSS选择器
- 通过选择器,获取到一些元素 但并不返回元素
- 而是返回一个对象 这个对象里包含一些方法(函数)
- 这些方法(函数)去操作获取到的元素
- 即用闭包维持 函数外的变量 防止被回收机制删除
- 创建的JQ对象 api 作为返回值 从前面传递到后面 来链式调用
- 调用后 才知道 this 的具体指向
- 直接创建匿名的对象 进行链式操作 声明一个对象紧接再调用它 且只用到一次 省略变量名
- 省略中间过程 留下最简练代码
链式风格各功能接口:增删改查
查
接口
1
2
3
4
5
6
7
8
9
|
jQuery('#xxx') // 返回值并不是元素,而是一个`API`对象
jQuery('#xxx').find('.red') // 查找`#xxx`里的`.red`元素
jQuery('#xxx').parent() // 获取父元素
jQuery('#xxx').children() // 获取子元素
jQuery('#xxx').siblings() // 获取兄弟元素
jQuery('#xxx').index() // 获取排列下标
jQuery('#xxx').next() // 获取下一个元素
jQuery('#xxx').prev() // 获取上一个
jQuery('.red').each(fn) // 遍历并且对每个元素执行`fn`
|
实现find()
jQuery.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
window.jQuery = function (selectorOrArray) {
/* 重载判断
** selectorOrArray不仅接受选择器 还有数组
*/
let elements // 作用域的提升 用const声明的的时候必须直接赋值且不能再改
if (typeof selectorOrArray === 'string') {
// const elements = document.querySelectorAll(selectorOrArray)
elements = document.querySelectorAll(selectorOrArray)
} else if (selectorOrArray instanceof Array) {
// const elements = selectorOrArray // 作用域的提升
elements = selectorOrArray
}
return {
find(selector) {
let arr = []
for (let i = 0; i < elements.length; i++) {
const elements2 = Array.from(elements[i].querySelectorAll(selector))
arr = arr.concat(elements2)
}
// elements = arr
// return this // 返回的是数组 无法进行链式调用
// return newApi // 不能用原来的api对象 需要更新 api 引用
//
// const newApi = jQuery(arr) // 不能用原来的api对象,需要用jQuery构造一个新的api且接受传入的数组
// return newApi
return jQuery(arr) // 简化
},
addClass(className) {
/* 遍历所有刚才获取的elements 元素,添加 .className 样式 */
for (let i = 0; i < elements.length; i++) {
elements[i].classList.add(className)
}
return this
}
}
}
|
当在改变element
的时候,会影响之前所有保留 api 对象的引用
为了不改变api的引用,不能用原来的api对象,需要用jQuery构造一个新的api const newApi = jQuery(arr);return newApi
,避免污染之前的api
- 在方法中,重新封装一个 jQuery 函数
- jQuery函数获取一些元素,把这些元素递给 jQuery
- 让 jQuery 不仅接受选择器,同样可以接受一个数组
- 将数组重新传给 jQuery,封装一个新的 api
- 新的api 结构和之前一样,但保存的 elements 不同
- 得到一个新的 api 对象,这个对象用来操作
arr
- jQuery 传什么(对象) ,就返回一个对象操作什么(对象)
return jQuery(arr)
类似递归的用法
实现回溯操作.end()
return jQuery(arr)
目前只得到要操作的是 arr
- 要传入之前的元素,作为
arr
的属性 arr.OldApi = this // this就是api
.end()
要读取
jQuery.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
return {
oldApi: selectorOrArray.oldApi, // 将oldApi属性加到selectorOrArray上
find(selector) {
let arr = []
for (let i = 0; i < elements.length; i++) {
const elements2 = Array.from(elements[i].querySelectorAll(selector))
arr = arr.concat(elements2)
}
arr.oldApi = this // this是旧的api
return jQuery(arr) // 简化
},
end(){
return this.oldApi // this是新的api2
}
}
|
main.js
1
2
3
4
|
// const api1 = jQuery('.test')
// const api2 = api1.find('.child').addClass('red').addClass('blue')
// const oldApi = api2.end().addClass('yellow')
jQuery('.test').find('.child').addClass('red').addClass('blue').end().addClass('yellow')
|
实现遍历each()
main.js
1
2
3
4
|
const xx = jQuery('.test').find('.child')
x.each((div) => {
console.log(div)
})
|
jQuery.js
1
2
3
4
5
6
|
each(fn) { // 遍历当前所有元素
for (let i = 0; i < elements.length; i++) {
fn.call(null, elements[i], i) // x.each((div)里的 div 就是第一个形参 即 elements[i],先忽略 i
}
return this // this就是这个被省略掉的api
}
|
实现打印当前节点print()
1
2
3
|
print() {
console.log(elements) // elements就是对应的元素们
}
|
实现查父元素parent()
jQuery.js
1
2
3
4
5
6
7
8
9
10
11
12
|
parent() {
const array = []
/* this 就是当前这个被省略掉的api */
this.each((node) => { // 遍历 每次 对于每个元素 获取父节点 把不同的放入数组
/* 不重复同样的节点 */
// if (array.indexOf(node.parentNode) >= 0) // 第0个 或 第一个 有就不push
if (array.indexOf(node.parentNode) === -1) { // 第0个 或 第一个 有就不push
array.push(node.parentNode) // 没有才push
}
})
return jQuery(array)
}
|
实现查子元素children()
jQuery.js
1
2
3
4
5
6
7
8
9
10
|
children() {
const array = []
/* this 就是当前这个被省略掉的api */
this.each((node) => { // 遍历 每次 对于每个元素 获取子节点 把不同的放入数组
// array.push(node.children) // 没有把数组摊平 有不同结构
array.push(...node.children) // 展开操作符... 拆开所有元素 并且合并为一个数组
// 等价于 array.push(node.children[0], node.children[1],...[2]...)
})
return jQuery(array)
}
|
main,js
1
2
|
const y = jQuery('.test')
y.children().print()
|
用each()
和``…`展开操作符简化代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
window.jQuery3 = function (selectorOrArray) {
let elements
if (typeof selectorOrArray === 'string') {
elements = document.querySelectorAll(selectorOrArray)
} else if (selectorOrArray instanceof Array) {
elements = selectorOrArray
}
return {
addClass(className) {
this.each(n => n.classList.add(className))
},
find(selector) {
let array = []
this.each(n => {
array.push(...n.querySelectorAll(selector))
})
arr.oldApi = this
return jQuery(array)
},
parent() {
const array = []
this.each(n => {
if (array.indexOf(n.parentNode) === -1) {
array.push(n.parentNode)
}
})
return jQuery(array)
},
children() {
const array = []
this.each(n => {
array.push(...n.children)
})
return jQuery(array)
},
each(fn) {
for (let i = 0; i < elements.length; i++) {
fn.call(null, elements[i], i)
}
return this
},
print() {
console.log(elements) // elements就是对应的元素们
},
oldApi: selectorOrArray.oldApi, // 将oldApi属性加到selectorOrArray上
end() {
return this.oldApi // this是新的api2
}
}
}
|
太长TL,DR
window.$ = window.jQuery
- 类似
bash alias
,添加别名
命名风格
下面的代码令人误解
const div = $('div#test')
- 不小心写成
div.appendChild(xxx)
- 可能误认为
div
是一个DOM
,但并不是div元素 而是 api 对象
- 实际上
div
是jQuery
构造的API
对象
- DOM 对象只能使用 DOM 的 API,比如 querySelector appendChild
- jQuery 对象只能使用 jQuery 的 API, 比如 each find
避免误解的方法
- DOM 对象使用
el
开头,如 elDiv
来命名
- jQuery 对象使用
$
开头,如 $div
来命名
const $div = $('div#test')
$div.appendChild
不存在,因为它不是DOM
对象
$div.find
存在,因为它是jQuery
对象
约定俗成
实现$('#xxx').siblings()
获取兄弟元素
实现$('#xxx').index()
获取排列下标
实现$('#xxx').next()
获取下一个元素
实现$('#xxx').prev()
获取上一个
查·改
1
2
3
4
5
6
7
8
9
10
|
$('#xxx') // 返回值并不是元素,而是一个`API`对象
$('#xxx').find('.red') // 查找`#xxx`里的`.red`元素
$('#xxx').parent() // 获取父元素
$('#xxx').children() // 获取子元素
$('#xxx').siblings() // 获取兄弟元素
$('#xxx').index() // 获取排列下标
$('#xxx').next() // 获取下一个元素
$('#xxx').prev() // 获取上一个
$('.red').each(fn) // 遍历并且对每个元素执行`fn`
$('#xxx').end() // oldApi: selectorOrArray.oldApi
|
增
1
|
$('<div><span>1</span></div>').appendTo(document.body)
|
- JS的函数重载
- 一个函数可以接受不同类型的参数:代码链接
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/*
<body>
<div class="red">1</div>
<div class="red">2</div>
<div class="red">3</div>
</body>
*/
window.jQuery = function(选择器或标签) {
if (选择器或标签.trim().indexOf('<') === 0) {
// 说明是标签
} else {
// 说明是选择器
const nodes = document.querySelector(选择器)
// nodes 是伪数组,要转成数组
const array = Array.from(nodes)
// api 可以操作 array
const api = {
log() {
console.log(array)
}
}
return api // 不 return array 而是 return api
}
}
|
1
|
$('<div><span>1</span></div>').appendTo(document.body)
|
appendTo()
可以吧新增的元素放到另一个元素里
感觉DOM
是不可见的
不需要知道DOM
的任何细节
只需要使用简洁的API
即可
一个好的封装,能让使用者完全不知道内部细节
这是通过闭包实现的
细节
举例1
1
|
const $div = $('div#test')
|
$div
并不是DOM
对象,而是jQuery
构造的API
对象
- 可否从
$div
得到div
元素?
1
2
3
4
5
6
7
8
9
10
|
$div.get(0) // 获取第0个元素
// div
$div.get(1) // 获取第1个元素
// undefined
$div.get(2) // 获取第2个元素
// undefined
$div.size() // 获取元素的个数
$div.length // 也可以获取元素的个数
$div.get() // 获取所有元素组成的数组
// [div]
|
举例2
1
|
const $div = $('.red') // 假设有3个 `div.red`
|
$div
并不是DOM
对象们,而是jQuery
构造的API
对象们
- 可否从
$div
得到3个div
元素?
1
2
3
4
5
6
7
8
9
10
|
$div.get(0) // 获取第0个元素
// div
$div.get(1) // 获取第1个元素
// undefined
$div.get(2) // 获取第2个元素
// undefined
$div.size() // 获取元素的个数
$div.length // 也可以获取元素的个数
$div.get() // 获取所有元素组成的数组
// [div, div, div]
|
$div.get(0)太麻烦
jQuery
链式风2.0
增+
1
2
3
4
5
6
|
$('body') // 获取 `document.body`
$('body').append($('<div>1</div>')) // 添加子节点
$('body').append('<div>1</div>') // 更方便
$('body').preppend(div || $div) // 添加第一个子节点
$('#test').after(div || $div) // 向后追加节点
$('#test').before(div || $div) // 向前追加节点
|
删-
1
2
|
$div.remove()
$div.empty()
|
改
1
2
3
4
5
6
7
8
9
|
$div.text(?) // 读写文本内容
$div.html(?) // 读写HTML内容
$div.attr('title', ?) // 读写文本内容
$div.css({color: 'red'}) // 用来操作内联的style 读写 `style || $style`更好
$div.addClass('blue') //
$div.removeClass('blue') //
$div.hasClass('blue') //
$div.on('click', fn)
$div.off('click', fn)
|
注意:$div
可能对应到多个div
元素,即默认获取的div或其他元素是(伪)数组,每部操作都要遍历
后续
使用原型
- 把共用属性(函数)全都放到
$.prototype
$.fn = $.prototype
,解决命名太长
- 让
api.__proto__
指向$.fn
公开代码
- 发布到
GitHub
- 添加MD文档
- want star
- get blame
JQ Usage
设计模式
jQuery
用到了那些设计模式
- 不用
new
的构造函数,这个模式没有专门的名字
$(多种参数)
,这个模式叫重载
- 用闭包隐藏细节,这个模式没有专门的名字
$div.text()
即可读,又可写,getter/setter
$.fn
作为$.prototype
的别名,即别名(模式)
jQuery
可针对不同浏览器使用不同代码,这个叫适配器
设计模式就是对通用代码取个名字
设计模式用后总结的,抽象出代码中好的地方,复用
通过jQuery
学封装技巧,帮助理解vue/react
jQuery 都过时了,那我还学它干嘛?
学习路线
- 理解
jQuery
原理
- 使用
jQuery
做一两个项目
- 总结一篇博客
- 学
vue
和react
- 找工作
参考文章
封装DOM.pdf
jQuery中的设计模式.pdf
GitHub Repo源代码链接
jQuery API 中文文档
课程中的代码:https://github.com/FrankFang/dom-2
新的代码:https://github.com/FrankFang/dom-2-prototype/blob/master/src/jquery.js
jQuery设计思想 阮一峰 2011
A guide to the basics of jQuery
JQ官网
相关文章
- 作者: Joel
- 文章链接:
- 版权声明
- 非自由转载-非商用-非衍生-保持署名
- 河
掘
思
知
简