在上篇文章我们简单实现了一个 jQuery 的基础结构,不过为了顺应潮流,这次咱把它改为模块化的写法,此举得以有效提升项目的可维护性,因此在后续也将以模块化形式进行持续开发。
模块化开发和编译需要用上 ES6 和 rollup,具体原因和使用方法请参照我之前的《冗余代码都走开——前端模块打包利器 Rollup.js 入门》一文。
本期代码均挂在我的github上,有需要的童鞋自行下载(DIY-A-jQuery-master)。
1. 基本配置
为了让 rollup 得以静态解析模块,从而减少可能存在的冗余代码,我们得用上 ES6 的解构赋值语法,因此得配合 babel 辅助开发。
在目录下我们新建一个 babel 配置“.babelrc”:
{ "presets": ["es2015-rollup"] }
以及 rollup 配置“rollup.comfig.js”:
var rollup = require( 'rollup' ); var babel = require('rollup-plugin-babel'); rollup.rollup({ entry: 'src/jquery.js', plugins: [ babel() ] }).then( function ( bundle ) { bundle.write({ format: 'umd', moduleName: 'jQuery', dest: 'rel/jquery.js' }); });
其中入口文件为“src/jquery.js”,并将以 umd 模式输出到 rel 文件夹下。
别忘了确保已安装了三大套:
npm i babel-preset-es2015-rollup rollup rollup-plugin-babel
后续咱们直接执行:
node rollup.config.js
即可实现打包。
2. 模块拆分
从模块功能性入手,我们暂时先简单地把上次的整个 IIFE 代码段拆分为:
src/jquery.js //出口模块 src/core.js //jQuery核心模块 src/global.js //全局变量处理模块 src/init.js //初始化模块
它们的内容分别如下:
jquery.js:
import jQuery from './core'; import global from './global'; import init from './init'; global(jQuery); init(jQuery); export default jQuery;
core.js:
var version = "0.0.1", jQuery = function (selector, context) { return new jQuery.fn.init(selector, context); }; jQuery.fn = jQuery.prototype = { jquery: version, constructor: jQuery, setBackground: function(){ this[0].style.background = 'yellow'; return this }, setColor: function(){ this[0].style.color = 'blue'; return this } }; export default jQuery;
init.js:
var init = function(jQuery){ jQuery.fn.init = function (selector, context, root) { if (!selector) { return this; } else { var elem = document.querySelector(selector); if (elem) { this[0] = elem; this.length = 1; } return this; } }; jQuery.fn.init.prototype = jQuery.fn; }; export default init;
global.js:
var global = function(jQuery){ //走模块化形式的直接绕过 if(typeof exports === 'object' && typeof module !== 'undefined') return; var _jQuery = window.jQuery, _$ = window.$; jQuery.noConflict = function( deep ) { //确保window.$没有再次被改写 if ( window.$ === jQuery ) { window.$ = _$; } //确保window.jQuery没有再次被改写 if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } return jQuery; //返回 jQuery 接口引用 }; window.jQuery = window.$ = jQuery; }; export default global;
留意在 global.js 中我们先加了一层判断,如果使用者走的模块化形式,那是无须考虑全局变量冲突处理的,直接绕过该模块即可。
执行打包后效果如下(rel/jquery.js):
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global.jQuery = factory()); }(this, function () { 'use strict'; /** * Created by vajoy on 2016/8/1. */ var version = "0.0.1"; var jQuery = function jQuery(selector, context) { return new jQuery.fn.init(selector, context); }; jQuery.fn = jQuery.prototype = { jquery: version, constructor: jQuery, setBackground: function setBackground() { this[0].style.background = 'yellow'; return this; }, setColor: function setColor() { this[0].style.color = 'blue'; return this; } }; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; /** * Created by vajoy on 2016/8/2. */ var global$1 = function global(jQuery) { //走模块化形式的直接绕过 if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object' && typeof module !== 'undefined') return; var _jQuery = window.jQuery, _$ = window.$; jQuery.noConflict = function (deep) { //确保window.$没有再次被改写 if (window.$ === jQuery) { window.$ = _$; } //确保window.jQuery没有再次被改写 if (deep && window.jQuery === jQuery) { window.jQuery = _jQuery; } return jQuery; //返回 jQuery 接口引用 }; window.jQuery = window.$ = jQuery; }; /** * Created by vajoy on 2016/8/1. */ var init = function init(jQuery) { jQuery.fn.init = function (selector, context, root) { if (!selector) { return this; } else { var elem = document.querySelector(selector); if (elem) { this[0] = elem; this.length = 1; } return this; } }; jQuery.fn.init.prototype = jQuery.fn; }; global$1(jQuery); init(jQuery); return jQuery; }));
3. extend 完善
如上章所说,我们可以通过 $.extend / $.fn.extend 接口来扩展 JQ 的静态方法/实例方法,也可以简单地实现对象的合并和深/浅拷贝。这是非常重要且实用的功能,在这里我们得完善它。
在 core.js 中我们新增如下代码段:
jQuery.extend = jQuery.fn.extend = function() { var options, target = arguments[ 0 ] || {}, //target为要被合并的目标对象 i = 1, length = arguments.length, deep = false; //默认为浅拷贝 // 若第一个参数为Boolean,表示其为决定是否要深拷贝的参数 if ( typeof target === "boolean" ) { deep = target; // 那么 target 参数就得往后挪一位了 target = arguments[ i ] || {}; i++; } // 若 target 类型不是对象的处理 if ( typeof target !== "object" && typeof target !== "function" ) { target = {}; } // 若 target 后没有其它参数(要被拷贝的对象)了,则直接扩展jQuery自身(把target合并入jQuery) if ( i === length ) { target = this; i--; //减1是为了方便取原target(它反过来变成被拷贝的源对象了) } for ( ; i < length; i++ ) { // 只处理源对象值不为 null/undefined 的情况 if ( ( options = arguments[ i ] ) != null ) { // TODO - 完善Extend } } // 返回修改后的目标对象 return target; };
该段代码可以判断如下写法并做对应处理:
$.extend( targetObj, copyObj1[, copyObj2...] ) $.extend( true, targetObj, copyObj1[, copyObj2...] ) $.extend( copyObj ) $.extend( true, copyObj )
其它情况会被绕过(返回空对象)。
我们继续完善内部的遍历:
var isObject = function(obj){ return Object.prototype.toString.call(obj) === "[object Object]" }; var isArray = function(obj){ return Object.prototype.toString.call(obj) === "[object Array]" }; for ( ; i < length; i++ ) { //遍历被拷贝的源对象 // 只处理源对象值不为 null/undefined 的情况 if ( ( options = arguments[ i ] ) != null ) { var name, clone, copy; // 遍历源对象属性 for ( name in options ) { src = target[ name ]; copy = options[ name ]; // target已有该属性且完全相等,跳出本次循环 if ( target === copy ) { continue; } // 深拷贝,且确保被拷贝属性值为对象/数组 if ( deep && copy && ( isObject( copy ) || ( copyIsArray = isArray( copy ) ) ) ) { //被拷贝属性值为数组 if ( copyIsArray ) { copyIsArray = false; //若被合并属性不是数组,则设为[] clone = src && isArray( src ) ? src : []; } else { //被拷贝属性值为对象 //若被合并属性不是数组,则设为{} clone = src && isObject( src ) ? src : {}; } // 右侧递归直到最内层属性值非对象,再把返回值赋给 target 对应属性 target[ name ] = jQuery.extend( deep, clone, copy ); // 非对象/数组,或者浅拷贝情况(注意排除 undefined 类型) } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // 返回被修改后的目标对象 return target;
这里需要留意的有,我们会通过
jQuery.extend( deep, clone, copy )
来递归生成被合并的 target 属性值,这是为了避免扩展后的 target 属性和被扩展的 copyObj 属性引用了同一个对象,导致互相影响。
通过 extend 递归解剖 copyObj 源对象的属性直到最内层,最内层属性的值(上方代码里的 copy)大致有这么两种情况:
1. copy 为空对象/空数组:
for ( ; i < length; i++ ) { //遍历被拷贝对象 // 只处理源对象值不为 null/undefined 的情况 if ( ( options = arguments[ i ] ) != null ) { //空数组/空对象没有可枚举的元素/属性,这里会忽略 } } // 返回被修改后的目标对象 return target; //直接返回空数组/空对象
2. copy 为非对象(如“vajoy”):
if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = jQuery.isArray( copy ) ) ) ) { //不会执行这里 } else if ( copy !== undefined ) {// 执行这里 target[ name ] = copy; } } } } // 返回如 ['vajoy'] 或者 {'name' : 'vajoy'} return target;
从而确保 target 所扩展的每一层属性都跟 copyObj 的是互不关联的。
4. 建立基础工具模块
在上方的 extend 代码块中其实存在两个不合理的地方:
1. 仅通过 Object.toString.call(obj) === "[object Object]" 作为对象判断条件在我们扩展对象的逻辑中有些片面,适合扩展的对象应当是“纯粹/简单”(plain)的 js Object 对象,但在某些浏览器中,像 document 在 Object.toSting 调用时也会返回和 Object 相同结果;
2. 像 Object.hasOwnProperty 和 Object.prototype.toString.call 等方法在我们后续开发中会经常使用上,如果能把它们写到一个模块中封装起来复用就更好了。
关于 plainObject 的概念可以点这里了解。
基于上述两点,我们新增一个 var.js 来封装这些常用的输出:
export var class2type = {}; //在core.js中会被赋予各类型属性值 export const toString = class2type.toString; //等同于 Object.prototype.toString export const getProto = Object.getPrototypeOf; export const hasOwn = class2type.hasOwnProperty; export const fnToString = hasOwn.toString; //等同于 Object.toString/Function.toString export const ObjectFunctionString = fnToString.call( Object ); //顶层Object构造函数字符串"function Object() { [native code] }",用于判断 plainObj
然后在 core.js 导入所需接口即可:
import { class2type, toString, getProto, hasOwn, fnToString, ObjectFunctionString } from './var.js';
我们进一步修改 extend 接口代码为:
jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[ 0 ] || {}, i = 1, length = arguments.length, deep = false; if ( typeof target === "boolean" ) { deep = target; target = arguments[ i ] || {}; i++; } if ( typeof target !== "object" && !jQuery.isFunction( target ) ) { //修改点1 target = {}; } if ( i === length ) { target = this; i--; } for ( ; i < length; i++ ) { if ( ( options = arguments[ i ] ) != null ) { for ( name in options ) { src = target[ name ]; copy = options[ name ]; if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || //修改点2 ( copyIsArray = jQuery.isArray( copy ) ) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray( src ) ? src : []; //修改点3 } else { clone = src && jQuery.isPlainObject( src ) ? src : {}; } target[ name ] = jQuery.extend( deep, clone, copy ); } else if ( copy !== undefined ) { target[ name ] = copy; } } } } return target; }; //新增修改点1,class2type注入各JS类型键值对,配合 jQuery.type 使用,后面会用上 "Boolean Number String Function Array Date RegExp Object Error Symbol".split(" ").forEach(function(name){ class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); //新增修改点2 jQuery.extend( { isArray: Array.isArray, isPlainObject: function( obj ) { var proto, Ctor; // 明显的非对象判断,直接返回false if ( !obj || toString.call( obj ) !== "[object Object]" ) { return false; } proto = getProto( obj ); //获取 prototype // 通过 Object.create( null ) 形式创建的 {} 是没有prototype的 if ( !proto ) { return true; } // 简单对象的构造函数等于最顶层 Object 构造函数 Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; }, //获取类型(如'function'),后面会用上 type: function( obj ) { if ( obj == null ) { return obj + ""; //'undefined' 或 'null' } return typeof obj === "object" || typeof obj === "function" ? class2type[ toString.call( obj ) ] || "object" : typeof obj; } });
这里我们新增了isArray、isPlainObject 两个 jQuery 静态方法,其中 isPlainObject 比较有趣,为了过滤某些浏览器中的 document 等特殊类型,会对 obj.prototype 及其构造函数进行判断:
1. 通过Object.create( null ) 形式创建的 {} ,或者实例对象都是没有 prototype 的,直接返回 true;
2. 判断其构造函数合法性(存在且等于原生的对象构造器 function Object(){ [native code] })
关于第二点,实际是直接判断两个构造器字符串化后是否相同:
Function.toString.call(constructor) === Function.toString.call(Object)
我们执行打包处理:
node rollup.config.js
在 HTML 页面运行下述代码:
var $div = $('div'); $div.setBackground().setColor(); var arr = [1, 2, 3]; console.log($.type(arr))
效果如下:

留意 $.type 静态方法是我们上方通过 jQuery.extend 扩展进去的:
//新增修改点1,class2type注入各JS类型键值对,配合 jQuery.type 使用 "Boolean Number String Function Array Date RegExp Object Error Symbol".split(" ").forEach(function(name){ class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); jQuery.extend( { type: function( obj ) { if ( obj == null ) { return obj + ""; //'undefined' 或 'null' } return typeof obj === "object" || typeof obj === "function" ? //兼容安卓2.3- 函数表达式类型不正确情况 class2type[ toString.call( obj ) ] || "object" : typeof obj; } });
它返回传入参数的类型(小写)。该方法在我们下一章也会直接在模块中使用到。
本章先这样吧,得感谢这台风天赏赐了一天的假期,才有了时间写文章,共勉~
转自:http://www.cnblogs.com/vajoy/p/5728755.html
更多参考:
jQuery: 插件开发模式详解 $.extend(), $.fn, $.widget()
上一篇: jQuery:从零开始,DIY一个jQuery(1)
本文转自:jQuery:从零开始,DIY一个jQuery(2)
相关推荐
在IT行业中,Web模板是一种非常实用的设计工具,它允许开发者快速构建和定制网站的外观和布局,无需从零开始编写HTML、CSS和JavaScript代码。"WEB模版-DIY制作"这个主题涉及到的是如何自制Web模板,让我们深入探讨...
网页模板在IT行业中扮演着重要的角色,它们为设计师提供了快速构建网页的基础框架,节省了从零开始设计的时间。"DiY-Page 亮彩水晶模板"很可能包含了HTML文件、CSS样式表、JavaScript脚本、图像以及其他多媒体资源,...
使用"DiY-Page 水晶亮蓝风格",你可以省去从零开始设计页面的时间,只需调整内容和细节就能创建一个与主题相符的网站。 在提供的压缩包"diypager10"中,可能包含以下内容: 1. HTML 文件:这是网页的核心,包含了...
这使得开发者无需从零开始编写CSS,从而大大提高了开发效率。 3. **组件**:Bootstrap包含一系列可重用的UI组件,如导航条、下拉菜单、模态框、卡片、轮播图等。这些组件不仅美观,而且易于实现和定制,为创建功能...
qtz40塔式起重机总体及塔身有限元分析法设计().zip
Elasticsearch是一个基于Lucene的搜索服务器
资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。
美国纽约HVAC(暖通空调)数据示例,谷歌地图数据包括:时间戳、名称、类别、地址、描述、开放网站、电话号码、开放时间、更新开放时间、评论计数、评级、主图像、评论、url、纬度、经度、地点id、国家等。 在地理位置服务(LBS)中,谷歌地图数据采集尤其受到关注,因为它提供了关于各种商业实体的详尽信息,这对于消费者和企业都有极大的价值。本篇文章将详细介绍美国纽约地区的HVAC(暖通空调)系统相关数据示例,此示例数据是通过谷歌地图抓取得到的,展示了此技术在商业和消费者领域的应用潜力。 无需外网,无需任何软件抓取谷歌地图数据:wmhuoke.com
2023-04-06-项目笔记-第四百五十五阶段-课前小分享_小分享1.坚持提交gitee 小分享2.作业中提交代码 小分享3.写代码注意代码风格 4.3.1变量的使用 4.4变量的作用域与生命周期 4.4.1局部变量的作用域 4.4.2全局变量的作用域 4.4.2.1全局变量的作用域_1 4.4.2.453局变量的作用域_453- 2025-04-01
1_实验三 扰码、卷积编码及交织.ppt
北京交通大学901软件工程导论必备知识点.pdf
内容概要:本文档总结了 MyBatis 的常见面试题,涵盖了 MyBatis 的基本概念、优缺点、适用场合、SQL 语句编写技巧、分页机制、主键生成、参数传递方式、动态 SQL、缓存机制、关联查询及接口绑定等内容。通过对这些问题的解答,帮助开发者深入理解 MyBatis 的工作原理及其在实际项目中的应用。文档不仅介绍了 MyBatis 的核心功能,还详细解释了其在不同场景下的具体实现方法,如通过 XML 或注解配置 SQL 语句、处理复杂查询、优化性能等。 适合人群:具备一定 Java 开发经验,尤其是对 MyBatis 有初步了解的研发人员,以及希望深入了解 MyBatis 框架原理和最佳实践的开发人员。 使用场景及目标:①理解 MyBatis 的核心概念和工作原理,如 SQL 映射、参数传递、结果映射等;②掌握 MyBatis 在实际项目中的应用技巧,包括 SQL 编写、分页、主键生成、关联查询等;③学习如何通过 XML 和注解配置 SQL 语句,优化 MyBatis 性能,解决实际开发中的问题。 其他说明:文档内容详尽,涵盖面广,适合用于面试准备和技术学习。建议读者在学习过程中结合实际项目进行练习,以更好地掌握 MyBatis 的使用方法和技巧。此外,文档还提供了丰富的示例代码和配置细节,帮助读者加深理解和应用。
《基于YOLOv8的智能电网设备锈蚀评估系统》(包含源码、可视化界面、完整数据集、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计
插头模具 CAD图纸.zip
资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。
《基于YOLOv8的智慧农业水肥一体化控制系统》(包含源码、可视化界面、完整数据集、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计
python爬虫;智能切换策略,反爬检测机制
台区终端电科院送检文档
e235d-main.zip
丁祖昱:疫情对中国房地产市场影响分析及未来展望