本文参加了由公众号@若川视野 发起的每周源码共读活动, 点击了解详情一起参与。
这是
学习源码整体架构系列第一篇,链接: juejin.cn/post/684490… 。
自执行匿名函数
外部访问不了内部的变量和函数,内部可以访问到外部的变量,但里边定义了自己的变量,也不会访问到外界的变量。匿名函数将代码包裹在里面,防止与其他代码冲突和污染全局环境。
自执行函数表达式(IIEF)
(function(global, factory) {
})(typeof window !== "undefined" ? window : this, function(window, noGloabl){
});
浏览器环境下,最后把$和jQuery函数挂载在window上,所以外界就可以访问到$和jQuery了。
if (!noGlobal) {
window.jQuery = window.$ = jQuery;
}
// 其中`noGlobal`参数只有在这里用到。
支持多种环境下使用 比如 commonjs、amd规范
commonjs 规范支持
Commonjs 实现 主要代表 nodejs
// global是全局变量,factory 是函数
(function(gobal, factory) {
// 使用严格模式
"use strict";
// CommonJS或者CommonJS-like
if (typeof module === "object" && typeof module.exports === 'object') {
// 如果存在global.document 则返回factory(global, true);
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 );
}
// Pass this if window is not defined yet
第一个参数判断window,存在返回window,不存在返回this
})( typeof window !== 'undefined' ? window : this, function(window, Global) {} )
amd 规范 主要代表 requirejs
if (typeof define === "function" && define.amd) {
define('jQuery', [], function() {
return jQuery;
});
}
cmd 规范 主要代表 seajs
无new 构造
实际上也是可以new的,因为jQuery是函数。而且和不用new效果是一样的。new 显示返回对象,所以和直接调用jQuery函数作用是一样的。
源码:
var version = "@VERSION"
// Define a local copy of jQuery
jQuery = function(selector, context) {
// 返回new之后的对象
return new jQuery.fn.init(selector, context)
}
jQuery.fn = jQuery.prototype = {
// jQuery当前版本
jQuery: version,
// 修正构造器为jQuery
contructor: jQuery,
length: 0,
};
init = jQuery.fn.init = function( selector, context, root ) {
...
if(!selector) {
return this;
}
// ...
};
init.prototype = jQuery.fn;
jQuery.fn === jQuery.prototype; // true
init = jQuery.fn.init;
init.prototype = jQuery.fn;
// 相当于
jQuery.fn.init.prototype === jQuery.fn; // true
jQuery.fn.init.prototype === jQuery.prototype; // true
jQuery原型关系图
<sciprt src="https://unpkg.com/jquery@3.4.1/dist/jquery.js">
</script>
console.log(jQuery);
/ 在谷歌浏览器控制台,可以看到jQuery函数下挂载了很多静态属性和方法,在jQuery.fn 上也挂着很多属性和方法。
Vue源码中,也跟jQuery类似,执行的是Vue.prototype._init方法
function Vue (options) {
if (!(this instanceof Vue) ) {
warn('Vue is a constructor and should be called with the `new` keyword');
}
this._init(options);
}
initMixin(Vue);
function initMixin (Vue) {
Vue.prototype._init = function (options) {};
};
核心函数之一:extend
用法:
jQuery.extend( target [, object1] [objectN] ) // Returns: Object
jQuery.extend( [deep ], target, object1 [, objectN] )
示例:
// 1.jQuery.extend(target)
var result1 = $.extend({
job: 前端工程师
})
console.log(result1, result1.job); // $fn 加了一个job属性 前端工程师
// 2.jQuery.extend(target, object1)
var result2 = $.extend(
{ name: 'sam' },
{ job: '前端工程师' }
)
console.log(result2); // {name: 'sam', job: '前端工程师'}
// deep 深拷贝
// 3.jQuery.extend([deep], target, object1 [, objectN])
var result3 = $.extend(true, {
name: 'sam',
other: {
mac: 0,
ubuntu: 1,
windows: 1
}
}, {
job: '前端工程师',
other: {
mac: 1,
linux: 1,
windows: 0
}
})
console.log(result3);
/**
* deep: true
* {
* name: 'sam',
* other: {
* mac: 1,
* ubuntu: 1,
* windows: 0,
* linux: 1
* },
*. job: '前端工程师'
* }
**/
/**
* deep: false
* {
* name: 'sam',
* other: {
* mac: 1,
* linux: 1
* windows: 0,
* },
*. job: '前端工程师'
* }
**/
总结:extend函数既可以实现给jQuery函数实现浅拷贝、也可以使用深拷贝。可以给jQuery上添加静态方法和属性,也可以像jQuery.fn(也就是jQuery.prototype)上添加属性和方法,这个功能归功于this,jQuery.extend被调用时指向JQuery,jQuery.fn.extend被调用时指向jQuery.fn。
浅拷贝实现
// 浅拷贝实现
jQuery.extend = function() {
// options 是扩展对象object1、object2...
var options,
// object对象上的键
name,
// copy object对象上的值,也就是需要拷贝的值
copy,
// 扩展目标对象,可能不是对象,所以或对象
target = arguments[0] || {},
// 定义1为1
i = 0,
// 定义实参个数length
length = arguments.length;
// 只有一个参数
if (i === length) {
target = this;
i--;
}
for (; i< length; i--) {
// 不是undefined,也是不是null
if ((options = arguments[i]) !== null) {
if (name in options) {
copy = options[name]
// 防止死循环,continue 跳出当前此循环
if (name === "__proto__" || target === copy) {
continue;
}
if (copy !== undefined) {
target[name] = copy;
}
}
}
}
// 最后返回目标对象
return target;
}
// 深拷贝则主要在以下代码做判断。可能是数组或对象引用类型的值,做判断
if (copy !== undefined) {
target[name] = copy;
}
深拷贝实现
$.extend = function(){
// options 是扩展的对象object1、object2...
var options,
// object对象上的键
name,
// copy object对象上的值,也就是需要拷贝的值
copy,
// 深拷贝新增的四个变量 deep, src, copyIsArray、clone
deep = false,
// 源目标,需要往上面赋值的
src,
// 需要拷贝的值的类型是函数
copyIsArray,
clone,
// 源目标对象,可能不是对象,所以或空对象
target = arguments[0] || {},
// 定义i为1
i = 1,
// 定义实参个数length
length = arguments.length;
// 处理深拷贝情况
if (typeof target === 'boolean') {
deep = target;
// Skip the boolean and the target
// target 目标对象开始后移
target = arguments[i] || {}
i++;
}
// Handle case when target is a string or something (possible in deep copy)
// target不等于对象,且target不是函数的情况下,强制将其赋值为空对象
if (typeof target !== 'object' && isFunction(target)) {
target = {}
}
// 只有一个参数时
if (i === length) {
target = this;
i--;
}
if (; i < length; i--) {
// 不是undefined,也不是null
if ((target = arguments[i]) !== null) {
for (name in target) {
copy = target[name];
// 防止死循环,continue 跳出当前此循环
if (name === '__proto__' || target === copy) {
continue;
}
// Handle case when target is a string or something (possible in deep copy)
// 这里deep为true,并且需要拷贝的值有值,并且是纯粹的对象
// 或者需要拷贝的值是数组
if (deep && copy && (jQuery.isPainObject(copy) ||
(copyIsArray = Array.isArray(copy)))) {
// 源目标,需要往上面赋值的
src = target[name];
// Ensure proper type for the source value
// 拷贝的值为数组,并且src不是数组,clone对象改为空数组
if (copyIsArray && !Array.isArray(src)) {
clone = [];
// 拷贝的值不是数组,并且src不是纯粹的对象,clone赋值为空对象
} else if (!copyIsArray && jQuery.isPlainObject(src)) {
clone = {};
} else {
// 否则 clone = src
clone= src;
}
// 把下一次循环时,copyIsArray需要重新赋值为false
copyIsArray = false;
// Never move original objects, clone them
// 递归调用自己
targte[name] = jQuery.extend(deep, clone, object);
// Don't bring in undefined values
} else if (copy !== undefined) {
target[name] = copy;
}
}
}
}
// 最后返回目标对象
return target;
}
深拷贝衍生的函数 isFunction
判断函数是否是函数
var isFunction = function isFunction(.obj ) {
// Support: Chrome <=57, Firefox <=52
// In some browsers, typeof returns "function" for HTML <object> elements
// (i.e., `typeof document.createElement( "object" ) === "function"`).
// We don't want to classify *any* DOM node as a function.
return typeof obj === 'function' && typeof obj.nodeType !== 'number';
}
深拷贝衍生的函数isPlainObject
jQuery.isPlainObject(obj)测试对象是否为纯粹的对象(通过"{}"或者"new Object"创建)
jQuery.isPlainObject({}) // true
jQuery.isPlainObject("test") // false
var getProto = Object.getPrototypeOf;
var class2type = {};
var toString = class2type.toString();
var hasOwn = class2type.hasOwnPrototype;
var fnToString = hasOwn.toString();
var ObjectFunctionString = fnToString.call( object );
jQuery.extend({
isPlainObject: function(obj) {
var proto, Ctor;
// Detect obvious negatives
// Use toString instead of jQuery.type to catch host objects
// !obj 值为true 或者不为[object object]
// 直接返回 false
if (!obj || toString.call(obj) !== "[object object]") {
return false;
}
proto = getProto(obj);
// Objects with no prototype (e.g., `Object.create( null )`) are plain
// 原型不存在 比如Object.create(null) 直接返回true
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;
// 构造器是函数,并且fnToString.call(Ctor) === fnToSTring.call( Object );
return typeof Ctor === function && ObjectFunctionString;
}
})
链式调用
JQuery能够链式调用是因为一些函数执行结束后return this。比如jQuery源码中的addClass、removeClass、toggleClass。
jQuery.fn.extend({
addClass: function() {
// ...
return this;
},
removeClass: function() {
// ...
return this;
},
toggleClass: function() {
// ...
return this;
},
})
Jquery.noConflict 很多js库都会有的防冲突函数
// 用法:
<script>
var $ = '我是其他$,jQuery不要覆盖我';
</script>
<script src="./jquery-3.4.1.js"></script>
<script>
$.noConflict()
console.log($); // 我是其他$,jQuery不要覆盖我
</script>
jQuery.noConflict源码
import { jQuery } from "../core.js";
var
// Map over jQuery in case of overwrite
_jQuery = window.jQuery,
// Map over the $ in case of overwrite
_$ = window.$;
jQuery.noConflict = function( deep ) {
// 如果已经存在$ === jQuery
// 把已经存在的_$赋值给window.$
if (window.$ === jQuery) {
window.$ = _$;
}
// 如果是deep为true,并且已经存在window.jQuery === jQuery
// 把已经存在的_jQuery赋值给window.jQuery
if (deep && window.jQuery === jQuery) {
window.jQuery = _jQuery;
}
// 最后返回jQuery
return jQuery;
}
总结
jQuery整体结构:自执行匿名函数、无new构造、支持多种规范、核心函数之extend、链式调用、jQuery noConflict等。
// 重新梳理文中浅析源码结构
// 源码结构
(function(global, factory) {
'use strict';
if (typeof module === 'object' && typeof module.exports === 'object') {
module.export = window.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);
}
})(typeof window !== 'undefined' ? window : this, function(window, noGlobal) {
var version = '3.4.1';
// Define a local copy of jQuery
jQuery = function(selector, context) {
return new jQuery.fn.init(selector, context);
}
jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery,
length: 0,
...
}
jQuery.extend = jQuery.fn.extend = function() {}
jQuery.extend({
isPlainObject: function () {
// ...
}
...
})
init = jQuery.fn.init = function(selector, context, root) {}
init.prototype = jQuery.fn;
if (typeof define === 'function' && define.amd) {
define("jquery", [], function() {
return jQuery;
});
}
jQuery.noConflict = function(deep) {};
if (!noGlobal) {
window.jQuery = window.$ = jQuery;
}
return jQuery;
});
此文章为2024年02月Day1源码共读,每一次脑海里闪过努力的念头,都是未来的你在向你求救。