见识过不少东西, 也写过一些东西后。开始学习别人的写法, 从最佳实践, 代码组织, 到语言特性, 设计模式。
关于学习别人,
- 分析结构, 编写简易版本(构建骨架模拟操作, 剩下的只需要添加功能完善)
- 了解工作流, 扩展增强它
基础结构
这部分引用的是 jQuery 在 github 上的仓库,使用的模块管理是 requirejs (与 nodejs 中的 require 不是一个规范) 下面的链接是大概位置, 顺序大概是打包后的。如果想搜索相关的代码, 可以利用 github 的仓库内寻找, 能查找文件内容, 很方便
- 立即执行函数 , 构建jQuery同时兼容node环境
- 全局通用变量与函数 , 方便使用
- init函数 , 原型链
- extend , 为jQuery添加新函数
- sizzle 函数
- callbacks 函数
- Deferred promise
- Data 数据缓存
- queue 队列操作: 执行顺序
- attr 元素属性操作
- CSS 操作
- event 操作, on 与 off 函数
- DOM&CSS 操作
- animation tween 函数
- Ajax 操作
- dimension 位置与尺寸
- noconflict 操作
然后简单介绍一下 requirejs 的 amd 规范, 这与前端模块化有关, 采用异步加载, 详细的看(现在没见谁用过= =)
1 2 3 4 5 6 7 8 9 10 11 12
| require(['A','jquery'],function(a,$){ })
define(['jquery'],function($){ return $ })
define(function(){ return 'test' })
|
实现细节
立即执行闭包
对外只返回出一个 jQuery 与 $ 对象, 不污染全局变量。闭包主要作用, 让函数执行完后的变量仍可被引用, 延长生命, 当然这里并不构成闭包, 下面模块与浏览器兼容里的才是(必须要返回函数或者setTimeOut等, 总结一下就是稍后在外部会执行)
1 2 3 4 5 6 7 8 9
| (function(global,factory){ factory(global) })(window,function(window, noGlobal){ 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) : function(w) { if (!w.document) { throw new Error('jQuery requires a window with a document'); } return factory(w); }; }else{ factory(global) } })(window,function(window, noGlobal){ if (!noGlobal) { window.jQuery = window.$ = jQuery; } return jQuery })
|
jQuery 与 jQuery.fn
jQuery.fn 的方法需要进行初始化, 而直接在jQuery上写定的方法则可以直接使用 $.ajax
来执行
1 2 3
| jQuery.fn = jQuery.prototype = { }
|
无 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 = {}
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; })
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] } }
|
jQuery.js1 2 3 4 5 6 7 8
| length = arguments.length, deep = false; console.dir(this) if (typeof target === 'boolean') { deep = target;
|
比较核心(想写)的方法
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.parentNode.insertBefore(e, this.nextSibling); } }) }
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); } }
|
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){ 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 if(value != undefined){ for (; i < len; i++) { fn(elems[i], key, value); } return elems } return fn(elems[0],key) }
|
基本上一个开源项目有不少内容都是后续向下兼容或者功能添加完善上去的, 可以学习一下代码结构和运行流程与原理, 至于模块组织, 现阶段我觉得都差不多, 不知道requirejs 淘汰的原因(也许也没淘汰?只是es6让他不再属于这个时代)
参考文章:
jQuery源码分析系列
前端JQuery系列
jQuery 3.0的domManip浅析