jquery 学习思考

见识过不少东西, 也写过一些东西后。开始学习别人的写法, 从最佳实践, 代码组织, 到语言特性, 设计模式。

关于学习别人,

  1. 分析结构, 编写简易版本(构建骨架模拟操作, 剩下的只需要添加功能完善)
  2. 了解工作流, 扩展增强它

基础结构

这部分引用的是 jQuery 在 github 上的仓库,使用的模块管理是 requirejs (与 nodejs 中的 require 不是一个规范) 下面的链接是大概位置, 顺序大概是打包后的。如果想搜索相关的代码, 可以利用 github 的仓库内寻找, 能查找文件内容, 很方便

  1. 立即执行函数 , 构建jQuery同时兼容node环境
  2. 全局通用变量与函数 , 方便使用
  3. init函数 , 原型链
  4. extend , 为jQuery添加新函数
  5. sizzle 函数
  6. callbacks 函数
  7. Deferred promise
  8. Data 数据缓存
  9. queue 队列操作: 执行顺序
  10. attr 元素属性操作
  11. CSS 操作
  12. event 操作, on 与 off 函数
  13. DOM&CSS 操作
  14. animation tween 函数
  15. Ajax 操作
  16. dimension 位置与尺寸
  17. noconflict 操作

然后简单介绍一下 requirejs 的 amd 规范, 这与前端模块化有关, 采用异步加载, 详细的看(现在没见谁用过= =)

1
2
3
4
5
6
7
8
9
10
11
12
//主模块 main.js
require(['A','jquery'],function(a,$){
//code here
})
//其他文件 定义依赖 jQuery 的模块
define(['jquery'],function($){
return $
})
//test.js 文件中 定义模块 test
define(function(){
return 'test'
})

实现细节

立即执行闭包

对外只返回出一个 jQuery 与 $ 对象, 不污染全局变量。闭包主要作用, 让函数执行完后的变量仍可被引用, 延长生命, 当然这里并不构成闭包, 下面模块与浏览器兼容里的才是(必须要返回函数或者setTimeOut等, 总结一下就是稍后在外部会执行)

1
2
3
4
5
6
7
8
9
(function(global,factory){
//some code
factory(global)
})(window,function(window, noGlobal){
//也是 some code
if (!noGlobal) {
window.jQuery = window.$ = jQuery;
}
})

import 与浏览器兼容

根据传入的 global 与 module 判断环境, 再选择执行模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(function(global,factory){
if (typeof module === 'object'){
module.exports = global.document
? factory(global, true) //就不会有 window.xxx 的绑定了, 而只是返回 jQuery
: function(w) {
if (!w.document) {
throw new Error('jQuery requires a window with a document');
}
return factory(w);
};
}else{
factory(global) //没有 module 的话直接运行
}
})(window,function(window, noGlobal){
//还是 some code
if (!noGlobal) {
window.jQuery = window.$ = jQuery;
}
return jQuery
})

jQuery 与 jQuery.fn

jQuery.fn 的方法需要进行初始化, 而直接在jQuery上写定的方法则可以直接使用 $.ajax 来执行

jQuery上的方法

jQuery.fn上的方法

1
2
3
jQuery.fn = jQuery.prototype = {
//some code
}

jQuery原型链

无 new 返回对象

没有使用 new 却构造出了新的对象, 通过原型链让 init 的对象能使用jQuery原型方法, 另外 jQuery 可以通过不同的 selector 创建对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
jQuery = function(selector, context) {
return new jQuery.fn.init(selector, context);
}
jQuery.fn = jQuery.prototype = {}
// some code
init = (jQuery.fn.init = function(selector,context,root){
context = context?context:window.document
var elem = context.querySelectorAll(slector)
if(elem.length !== 0){
elem.forEach((i,ind)=>{this[ind]=i})
}
this.length = elem.length
return this;//
})
//让这样创造的对象能使用jQuery原型方法的关键
init.prototype = jQuery.fn;

jQuery 的 extend 方法

为jQuery添加方法, 分为jQuery.extend与jQuery.fn.extend,一个给本身一个给原型链, 原理是调用的时候 this 是不同的

1
2
3
4
5
jQuery.extend = jQuery.fn.extend = function() {
for (name in arguments[0]) {
this[name] = arguments[name]
}//然后完善的话, 1.多个参数重载2.深复制3.防止bug如有环
}
jQuery.js
1
2
3
4
5
6
7
8
//some code
length = arguments.length,
deep = false;
console.dir(this) //打印一下就知道了
// Handle a deep copy situation
if (typeof target === 'boolean') {
deep = target;
//some code

一个是jQuery本身, 一个是它的原型对象

比较核心(想写)的方法

dom 操作

这里用其他几个函数包装了一个私有函数, 通过传入的回调函数不同实现不同的功能, 姑且就叫回调包装吧, 函数 domManip 的主要功能就是添加 DOM 元素, 由添加的位置不同而提供了四个公开函数 append、prepend、before、after, 同时还可以执行脚本, 对字符串还会解析为节点, 如果是 script:src 会远程下载后执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
jQuery.fn.after = function(){
return domManip(this, aruguments, function(e){
if(this.parentNode){
//此处的 this 从后文可以看出是一个dom元素
this.parentNode.insertBefore(e, this.nextSibling);
}
})
}
//args 假定只有一种类型
function domManip(collection, args, callback){
//这是个私有函数
var args = concat.apply([], args),
length = collection.length
if(length > 1){
collection.forEach(i=>{
domManip(i, args, callback)
})// 为了链式调用的返回
return collection
}else{
callback.call(collection[0], args);
}//后续1.参数判断2.脚本执行
}

attr 操作

很多有关属性的操作包括css()方法都是依靠这么一个私有函数 access 来实现的, 从注释上可以看出, 这是一个对集合元素进行get 或 set 操作的多功能方法, 当然主要功能还是传入的回调函数 fn 来实现

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
jQuery.fn.css = function(name, value){
return access(
this, function(elem, name, value) {
//后续写在插件部分里, 不然这里太长了
return value !== undefined ?
jQuery.style(elem, name, value):
jQuery.css(elem, name);
}, name, value);
}
jQuery.fn.attr = function(name, value){
//jQuery.attr 只是为了近似jQuery的写法(照抄过来的)
return access(this, jQuery.attr, name, value);
}
jQuery.attr = function(elem, name, value){
if(value === undefined){
return elem.getAttribute(name)
}
elem.setAttribute(name, value + '');
return value;
}
function access(elems, fn, key, value){
var len = elems.length
// set
if(value != undefined){
for (; i < len; i++) {
fn(elems[i], key, value);
}
return elems
}
// get
return fn(elems[0],key) // jQuery 只会返回第一个的属性值
} //后续1.参数重载(判断参数类型得到不同的执行过程)

基本上一个开源项目有不少内容都是后续向下兼容或者功能添加完善上去的, 可以学习一下代码结构和运行流程与原理, 至于模块组织, 现阶段我觉得都差不多, 不知道requirejs 淘汰的原因(也许也没淘汰?只是es6让他不再属于这个时代)


参考文章:

jQuery源码分析系列

前端JQuery系列

jQuery 3.0的domManip浅析

文章作者:
文章链接: https://luckyray-fan.github.io/2019/10/06/jquery-study/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 luckyray