一个工厂

(function(global, factory){"use strict"// operation_1
})(typedef window !== "undefined" ? window : this, function (window, noGlobal) {// operation_2
})=====>  (function(args,...){...})(params,...)            // 一种自执行方法,形式为(function(){})(),也可以写为 !function(){}() 或 ;function(){}();

其中function(window, noGlobal){}有11000多行代码,并且注入到operation_1中执行。

// operation_1
// 这个一个兼容操作,旨在检测文件是否运行在CommonJS中,如NodeJS,如果是的话进入此分支,一般不会进入
if (typeof module === "object" && typeof module.exports === "object") {module.exports = global.document ?factory(global, true) :     // 如果global.document存在,则向外面暴露出 factory 方法function (w) {if (!w.document) {    // 因为不存在w,所以 !w.document为true,所以报错throw new Error("jQuery requires a window with a document");}return factory(w);};
} else {      // 通常直接进入此分支factory(global);      // 没有传入noGlobal,将引发其他操作
}

至此完成了方法的注入调用。但是我们发现传入的实参比形参少一个noGlobal,为此我们全局查找此参数

// noGlobal第三次出现在11124行
if (typeof noGlobal === "undefined") {            // 如果noGlobal未定义(未传入)window.jQuery = window.$ = jQuery;         // 将实例jQuery赋值给窗口的$或jQuery,这也就是$()和jQuery()两个选择器的由来
}

通过使用窗口(全局)的jQuery或$,就可以实现jQuery类或对象的调用,当然调用对象需要用到其构造方法,我们接着往下看

知识点:

  • 自执行方法: 形式有(function(){})()!function(){}();function(){}()
  • “use strict”:开启严格模式,此模式下代码质量要求及书写规范要求更高,但能保证代码的安全,提高编译器效率,加快运行速度
  • typedef module === “object”: 在其他JS模块中的执行检测,详细请跳转至此处

3个变量,6个函数

/**
* 内部变量
*/
var arr = [];
var getProto = Object.getPrototypeOf;
var slice = arr.slice;
...            // 有些变量看起来没有必要定义,但是为了避免魔鬼变量(数字、字符串),代码中具有特殊用途的变量必须定义/**
* 内部方法
*/
// 数组扁平化,将数组深度遍历并组成新的一维数组
var flat = arr.flat ? function(array){return arr.flat.call(array);
} : function (array) {       // 如果不能直接扁平化,则利用concat.apply()方法实现// Array.prototype.concat.apply([],array) 递归扫描array数组,并扁平化追加到参数1内,因此需要输入一个空数组return arr.concat.apply([], array);
};// 判断是否是方法
var isFunction = function isFunction(obj) { return typeof obj === "function" && typeof obj.nodeType !== "number";             // typeof obj.nodeType !== "number",任何DOM元素的nodetype都是数字,常见的有1 9 11
};// 简单的理解为,创造一个类似node的Script节点(内容为code)并挂载到doc上
function DOMEval(code, node, doc) {doc = doc || document;var i, val, script = doc.createElement("script");script.text = code;if (node) {for (i in preservedScriptAttributes) { // 对于xx中的每个属性// 有些浏览器不支持脚本的“nonce”属性。另一方面,仅仅使用“getAttribute”是不够的,因为每当“nonce”属性连接到上下文时,// 它就会重置为空字符串。在`节点.getAttribute`添加支票是为了`jQuery.globalEval`这样它就可以通过一个对象伪造一个包含nonce的节点。val = node[i] || node.getAttribute && node.getAttribute(i); // 如果node存在这个属性,则设置到script中if (val) {script.setAttribute(i, val);}}}doc.head.appendChild(script).parentNode.removeChild(script); // script挂载到头节点上
}// 获取对象的类型
function toType(obj) {if (obj == null) {return obj + "";   // 强转成字符串,null + "" == "null"}return typeof obj === "object" || typeof obj === "function" ?            // 本篇代码常使用三元表达式取代if/else,并嵌套逻辑表达式,阅读起来较吃力class2type[toString.call(obj)] || "object" :                  // 利用Object.protoType的原型方法获取对象的类型,而不是typedef,此举更精确typeof obj;
}

重头戏来了,下面的是jQuery对象的构造方法

// 在160行左右
jQuery = function (selector, context) {// The jQuery object is actually just the init constructor 'enhanced'// Need init if jQuery is called (just allow error to be thrown if not included)return new jQuery.fn.init(selector, context);            // 返回的是新建的jQuery对象,这就是实现jQuery链式调用的支柱之一
};// 在3300行左右
init = jQuery.fn.init = function (selector, context, root) {var match, elem;// HANDLE: $(""), $(null), $(undefined), $(false)if (!selector) {return this;}// Method init() accepts an alternate rootjQuery// so migrate can support jQuery.sub (gh-2101)root = root || rootjQuery;     // 如果没传入// Handle HTML stringsif (typeof selector === "string") {if (selector[0] === "<" &&selector[selector.length - 1] === ">" &&selector.length >= 3) {// Assume that strings that start and end with <> are HTML and skip the regex check// 假设字符串是HTML文本形式,则跳过正则表达式验证match = [null, selector, null];} else {match = rquickExpr.exec(selector);      // 如果selector为 "#xx"模式,则结果为["#xx",undefined,"xx"]}// Match html or make sure no context is specified for #id// 匹配html 或 确保没有为#id指定上下文if (match && (match[1] || !context)) {// HANDLE: $(html) -> $(array)       // 将html解析成类数组并merge返回if (match[1]) {context = context instanceof jQuery ? context[0] : context;// Option to run scripts is true for back-compat// Intentionally let the error be thrown if parseHTML is not present// 将选择器HTML标签解析成DOM元素,并与jQuery对象合并jQuery.merge(this, jQuery.parseHTML(match[1],context && context.nodeType ? context.ownerDocument || context : document,true));// HANDLE: $(html, props)// 如果是一个选择器字符串是一个单独的标签并且上下文是POJO对象(也就是DOM元素)// 因为在上面合并了DOM元素和jQuery对象,所以DOM的上下文属性也要继承到jQuery对象中(如有错误,烦请大佬评论指正)if (rsingleTag.test(match[1]) && jQuery.isPlainObject(context)) {for (match in context) {// Properties of context are called as methods if possibleif (isFunction(this[match])) {          // 如果上下文的属性是DOM对象的可执行方法时,this[match](context[match]);    // 将props对象的fncName属性的值作为参数传递给DOM集合对象中的fncName函数并执行之// ...and otherwise set as attributes} else {this.attr(match, context[match]);  // 否则将上下文的属性添加到jQuery对象中}}}return this;            // 直接返回jQuery对象// HANDLE: $(#id)} else {// 如果是匹配ID,直接用原生jselem = document.getElementById(match[2]);if (elem) {// Inject the element directly into the jQuery objectthis[0] = elem;this.length = 1;}return this;}// HANDLE: $(expr, $(...))// 如果不存在上下文,或者传入的是jQuery对象,如$("p")、$("p",$(".test"))} else if (!context || context.jquery) {// 返回 root.find(selector),定位到find()定义处,你会发现find()中调用了jQuery.find()方法,又jQuery.find = Sizzle,而Sizzle是本篇代码的重要部分 -- CSS选择器return (context || root).find(selector);           // HANDLE: $(expr, context) (which is just equivalent to: $(context).find(expr)// 如果存在真实的上下文环境,如 $("p",".test")} else {return this.constructor(context).find(selector);}// HANDLE: $(DOMElement)} else if (selector.nodeType) {         // 如果传入的是节点,直接将节点返回this[0] = selector;this.length = 1;return this;// HANDLE: $(function)// Shortcut for document ready} else if (isFunction(selector)) {             // 如果传入的是方法return root.ready !== undefined ?root.ready(selector) :// Execute immediately if ready is not presentselector(jQuery);               // 传入jQuery调用selector方法}return jQuery.makeArray(selector, this);            // 返回一个类数组
};

至此,我们了解了jQuery对象的创建过程,创建对象又包含着选择器的执行过程:

  1. 传入的选择器分为无效选择器、字符串、节点、方法四类,其中重点是字符串
  2. 传入字符串包括HTML字符串和普通字符串,利用正则表达式可以准确分别:
    1. 如果是HTML字符串,直接解析成DOM元素并组合到jQuery对象中返回
    2. 如果是ID选择器,直接调用原生JS方法得到结果并返回即可
    3. 如果上下文没指定或上下文为jQuery对象时,在根或jQuery对象中根据选择器查找下级节点(find()本质也就是Sizzle())
    4. 如果上下文为真实的DOM元素时,则根据DOM创建jQuery对象,操作同上

如果对上述执行流程不了解,请自行选择测试案例并打断点进行调试(如果错误,敬请评论指正)。

按照业务流程,下面我们应该根据创建对象时返回的find(),跳过300行代码来到Sizzle篇。但为了以后不出现遗漏,还是一行行往下看吧。
上面主要讲到了jQuery对象,那么jQuery对象的成员变量和成员函数是怎么声明定义的呢? jQuery类的静态成员又是怎么定义的呢? 我们跳回到jQuery()构造函数处接着往下看。

知识点:

  • 魔鬼变量: 变量的使用要谨慎,要具备优良的可读性和
  • 数组扁平化:两种方法实现
  • 原型方法获取数据类型
  • 三元表达式实现if/else
  • 创建jQuery对象时传入的选择器类型的不同及执行时的差别

N个变量,2N个函数

下面我们将看来两个重要函数的定义:jQuery.fn和jQuery.extend,首先我们来看jQuery.fn

jQuery.fn = jQuery.prototype = {jquery: version,constructor: jQuery,length: 0,...get: function (num) {if (num == null) {return slice.call(this);}return num < 0 ? this[num + this.length] : this[num];},// Take an array of elements and push it onto the stack   // 将元素加入栈中pushStack: function (elems) {            // 此方法是jQuery链式调用的核心// Build a new jQuery matched element setvar ret = jQuery.merge(this.constructor(), elems);    // 将新来的参数合并到constructor中,并保留指向// Add the old object onto the stack (as a reference)ret.prevObject = this;// Return the newly-formed element setreturn ret;            // 返回合并后的新对象,此对象中保存了前一个对象的信息},slice: function () {// 同上,在做切片之后,能够根据返回的ret找到栈的上一个对象return this.pushStack(slice.apply(this, arguments));},end: function () {   // 获取对象在栈中的上一个对象,没有的话返回构造器return this.prevObject || this.constructor();}
}

链式调用:
链式调用即是对一个对象进行一连串链式操作,在jQuery中体现为$(ele).show().find(child).hide(),而此操作的核心就是上面的pushStack(),对象的大部分成员函数其最下层会调用pushStack()并返回一个新的对象。篇幅原因,请自行打断点进行deBugger;

原型对象 prototype:
在了解jQuery.fn之前,大家需要对jQuery.prototype有个了解。
在 JavaScript 中,每个函数对象都有一个默认的属性 prototype,称为函数对象的原型成员,这个属性指向一个对象,称为函数的原型对象。而这个原型对象上定义的成员将用来共享给所有通过这个函数创建的对象使用(类似于实例化)。通常调用new出来的对象的prototype属性就会得到这个原型对象,再通过修改此对象的成员,来达到对所有实例对象增删成员的目的。
Object 函数对象是 JavaScript 中定义的顶级函数对象,在 JavaScript 中所有的对象都直接或者间接地使用 Object 对象的原型。 当访问对象的成员时,首先检查成员本身,再检查其原型,再检查其原型的原型(可达Object),直到找到访问的成员。

jQuery.fn:
再来看jQuery文件中定义 jQuery.fn = jQuery.prototype = {...},我们就知道这是改变jQuery对象的原型对象,并且能够影响到所有创建的jQuery对象,也就是说在这里定义的所有成员,都能通过jQuery对象访问到。如果我们需要自定义jQuery对象的某种属性或操作,直接在这里操作即可。

如果说对象的成员能通过原型对象赋予得到,但静态类的成员怎么扩展呢? 并且拓展对象成员每次都要通过修改原型对象? 下面我们来看 jQuery.extend = jQuery.fn.extend = function(){}

jQuery.extend = jQuery.fn.extend = function () {var options, name, src, copy, copyIsArray, clone,target = arguments[0] || {},             // 如果传入参数,arguments[0]指传入函数的第一个参数i = 1,length = arguments.length,deep = false;// Handle a deep copy situation// 处理深拷贝场景,在这里不讨论if (typeof target === "boolean") {deep = target;// Skip the boolean and the targettarget = arguments[i] || {};i++;}// Handle case when target is a string or something (possible in deep copy)// 如果传入的不是对象也不是方法,或许是深拷贝if (typeof target !== "object" && !isFunction(target)) {target = {};}// 如果只传入一个对象,那么就将target切换为jQuery对象,同时坐标定位到第一个元素if (i === length) {target = this;            // 一般情况下的挂载载体i--;}for (; i < length; i++) {// Only deal with non-null/undefined valuesif ((options = arguments[i]) != null) {// Extend the base object  for (name in options) {         // 循环挂载此对象中的信息copy = options[name];// Prevent Object.prototype pollution   Prevent never-ending loop// 放置原型对象污染,防止死循环if (name === "__proto__" || target === copy) {continue;}// Recurse if we're merging plain objects or arrays           // 如果是合并纯粹的对象(POJO)或数组则递归执行, 一般挂载单个对象不会进入此分支if (deep && copy && (jQuery.isPlainObject(copy) ||(copyIsArray = Array.isArray(copy)))) {src = target[name];// Ensure proper type for the source valueif (copyIsArray && !Array.isArray(src)) {clone = [];} else if (!copyIsArray && !jQuery.isPlainObject(src)) {clone = {};} else {clone = src;}copyIsArray = false;// Never move original objects, clone them// 不移动原始对象,而是克隆他们。  这涉及到js中对象引用的问题(类似c++的引用),如果修改了某引用,其原始对象也会发生改变target[name] = jQuery.extend(deep, clone, copy); // 递归入口,// Don't bring in undefined values} else if (copy !== undefined) {target[name] = copy;                    // 一般直接在此完成挂载(由于jQuery类及对象都是类数组模式,因此使用数组下标进行赋值即可完成拓展)}}}}// Return the modified objectreturn target;
};

上述代码清晰地展示了成员是怎么挂载到类和对象上的,在前面提到jQuery创建时返回形式为类数组,而挂载的本质还是对类数组的插入更新。
extend方法分为jQuery.extend()和jQuery.fn.extend(),前者是直接挂载在类上,后者是挂载在对象的原型对象上。两者要区分开。并且由于作用域的不同,内部函数和挂载成员不要混淆。

知识点:

  1. 链式调用的由来:
  2. 挂载的本质:
  3. 区分成员引用: js 中的函数其实是对象,函数名是对 Function 对象的引用。
  4. 类成员与对象成员的增删改

挂载实例和内部函数

下面的代码是一些类静态成员与文件内部函数的定义,这里挑一些难以理解的介绍一下:

// 静态成员挂载:jQuery.extend
expando: "jQuery" + (version + Math.random()).replace(/\D/g, ""),            // 一个用来标记的实时变量// 判断是否是纯对象
isPlainObject: function (obj) {var proto, Ctor;// Detect obvious negatives// Use toString instead of jQuery.type to catch host objects//  使用toString.call(obj)方法获取对象的类型 ======完整形式:Object.prototype.toString.call(obj)  if (!obj || toString.call(obj) !== "[object Object]") {return false;}      proto = getProto(obj);// Objects with no prototype (e.g., `Object.create( null )`) are plain     // 没有原型的对象是实体类if (!proto) {return true;}// Objects with prototype are plain iff they were constructed by a global Object function       // 被全局对象函数创造的对象也是实体类Ctor = hasOwn.call(proto, "constructor") && proto.constructor;return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;               // 判断Ctor是否为Object,详情请转至fnToString定义处
},// 注入函数调用函数
each: function (obj, callback) {            // 细心的同学肯定在前面看到过each函数,不过是挂载在类原型对象中的,其内部还是调用的此方法var length, i = 0;if (isArrayLike(obj)) {length = obj.length;for (; i < length; i++) {if (callback.call(obj[i], i, obj[i]) === false) {                  // 将obj的每个成员传入callback并执行,最后返回新的obj(就是对原对象进行处理得到新对象)break;}}} else {for (i in obj) {if (callback.call(obj[i], i, obj[i]) === false) {                  // 大家有没有发现诸多 xx.call() 形式的函数?break;}}}return obj;
},// 处理结果映射函数
map: function (elems, callback, arg) {var length, value,i = 0,ret = [];// Go through the array, translating each of the items to their new valuesif (isArrayLike(elems)) {length = elems.length;for (; i < length; i++) {value = callback(elems[i], i, arg);          // 对函数进行某种处理if (value != null) {ret.push(value);                        // 将处理后的值进行保存(可能是上述操作不会改变原数组的值,因此需要保存处理结果)}                  }// Go through every key on the object,} else {for (i in elems) {value = callback(elems[i], i, arg);if (value != null) {ret.push(value);}}}// Flatten any nested arrays// 嵌套数组扁平化return flat(ret);
},// 内部成员函数:
if (typeof Symbol === "function") {        // ES6 新的数据类型 Symbol// 添加了一个fn迭代器,等价于arr数组的默认迭代方法,此后fn迭代器会迭代arr数组jQuery.fn[Symbol.iterator] = arr[Symbol.iterator];
}// Populate the class2type map
// 普通的对象赋值,但是意义在于将toString.call()能获得的所有类型都列了出来,避免了魔鬼变量的出现
jQuery.each("Boolean Number String Function Array Date RegExp Object Error Symbol".split(" "),function (_i, name) {class2type["[object " + name + "]"] = name.toLowerCase();}
);// 类数组判断
function isArrayLike(obj) {var length = !!obj && "length" in obj && obj.length, // 连续与,只有obj存在则检验len属性存在,只有length存在才检验长度,避免出现报错type = toType(obj);if (isFunction(obj) || isWindow(obj)) {             // 函数或window,直接返回falsereturn false;}return type === "array" || // 是数组length === 0 || //   或者长度为0 typeof length === "number" && length > 0 && (length - 1) in obj; // 或者长度是数字且len-1在obj中,因为稀疏数组的遍历会存在跳过空值
}

xxx.call():
xxx.call()的原始形式是obj1.(function).call(obj2,args, ...),其本质就是将obj1的function放到obj2上使用。obj2在传入函数参数时不能省略,当其省略时被认为是全局对象。
通过xxx.call()可以实现继承,此外还有obj1.(function).apply(obj2, [], args, ...),其作用类似于call(),详情请转至此处。

知识点:

  1. POJO对象(纯粹实体对象)的判断:
  2. 函数作为参数注入执行(解耦合):
  3. 常用的操作: 如数组创建、数组内元素定位、类数组合并、xx.call()形式调用、抛出异常函数
  4. ES6中新数据类型Symbol:
  5. 类数组

结语

至此本章也就结束了,在此大致讲述了jQuery对象的创建过程,以及类和对象成员的挂载,期间穿插着介绍了一些内部成员函数。通过这些学习,你已经对一些jQuery基本函数有了一定的了解,了解到了一些其他JS知识,也可以自定义一些组件,下一章我们将跳回jQuery对象创建函数,并沿着find()函数往下探索,欢迎来到jQuery的核心世界之一 ---- Sizzle插件。

jQuery源码分析系列(一)初识jQuery相关推荐

  1. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  2. [转]jQuery源码分析系列

    文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaro ...

  3. jQuery源码分析系列 : 整体架构

    query这么多年了分析都写烂了,老早以前就拜读过, 不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery又给扫一遍 我也不会照本宣科的翻译源码,结合自己的实际经验一起拜读吧! ...

  4. jQuery源码分析系列:.domManip() .buildFragment() .clean()

      .domManip(args,table,callback):是jQuery DOM操作的核心函数,可以扩展出如下方法: append/appendTo: prepend/prependTo: b ...

  5. JQuery源码分析 - 闭包机制在jQuery中的使用及冲突解决

    jQuery中的闭包机制 本系列中我们将基于jquery3.5.1版本对jQuery源码进行分析,分析以源码加注释的方式展示. 本节中将分析jQuery源码中的 14 ~ 40行:自执行函数定义.环境 ...

  6. jQuery源码分析系列:属性操作

    属性操作 1.6.1相对1.5.x最大的改进,莫过于对属性.attr()的重写了.在1.6.1中,将.attr()一分为二: .attr()..prop(),这是一个令人困惑的变更,也是一个破坏性的升 ...

  7. jQuery源码分析系列(37) : Ajax 总结

    综合前面的分析,我们总结如下3大块: jQuery1.5以后,AJAX模块提供了三个新的方法用于管理.扩展AJAX请求 前置过滤器 jQuery. ajaxPrefilter 请求分发器 jQuery ...

  8. jQuery源码分析系列:事件模块概述

    jQuery的事件模块是较复杂的,前面仅仅提到了对事件对象的包装.即统一了一些兼容性的问题.这篇会综述下jQuery的整个事件模块.后面会详细分析jQuery.event.add/jQuery.eve ...

  9. jQuery源码分析系列目录

    jQuery是对JavaScript的最佳实践的产物,这么好的东西阅读后一定会有感悟,还是要拿出来和大家分享滴,从今天开始阅读jQuery并将笔记进行记录,每天更新 1. 简便使用jQuery-源码阅 ...

最新文章

  1. ASP.NET 中HttpRuntime.Cache缓存数据
  2. 前端模块化--这是我看过讲得比较好的东东
  3. 动态数组怎么定义_Excel VBA 数组基础知识,初学者不可不学的关键知识
  4. python appium 并行多设备_学会使用python启动多个appium server,然后获取多台设备的driver...
  5. 对CPU的IO操作的理解
  6. 浮点数和整数的区别python_Python中整数和浮点数
  7. HarmonyOS之深入解析图像的位图操作和属性解码
  8. 进程间通信————无名管道
  9. Map 四种获取 key 和 value 值的方法,以及对 map 中的元素排序
  10. 机器学习速成课程 | 练习 | Google Development——编程练习:特征集
  11. 为什么公司要对员工的薪资保密?
  12. 一个oracle并发性问题的分析和解决
  13. 《淘宝技术这十年》读书总结
  14. 一部《再忆王家沱》讲述百年重庆历史,堪称中国版《百年孤独》
  15. C# 设计模式:创建型
  16. 如何注册微信个人公众号,教程来啦!怎样注册微信个人公众订阅号
  17. Creo 9.0安装教程
  18. 浅谈nodejs与php设计构思层面上的差异
  19. C++学习笔记和面试备考(二, 转)
  20. Nexus 私服资源的上传下载

热门文章

  1. 在Mac上怎么使用Charles进行抓包
  2. 动态调整div大小 html,如何动态的根据用户屏幕的分辨率改变div的大小?
  3. 商业智能BI的前景如何?看完这篇文章你就明白了
  4. 大学python挂科补考_大学挂科后补考不过怎么样一种体验?
  5. 2021-08-06
  6. Python爬取视频之爱情电影及解密TS文件和两种合并ts的方法
  7. bert获得词向量_NLP中的词向量对比:word2vec/glove/fastText/elmo/GPT/bert
  8. 数位DP(期末机测题)
  9. 不用网页另存PDF,浏览器在线简单查找下载PDF文件分享
  10. 喜欢这样的游戏---流畅的俯视坦克射击游戏