【前言】最近项目忙的脚不沾地,刚刚结束,准备整理一下以前写的一些学习笔记和技术文章。本文原是很久之前看jq源码时写的片段,隔了很久再看都忘得差不多了。简单整理出来,做个记录。

为一名前端工程师,jQuery是我们熟的不能再熟的工具之一,其强大的功能和近乎完美的兼容封装使其成为前端领域必备的技能。用了很长时间的jq,却一直没有去探索学习过jQuery源码,似乎不算是一名合格的前端er。最近趁着空闲研究了一下,jQuery源码可谓是深邃如海,奥妙无穷,平时工作中偶尔需要封装工具,也不必像它这样面面俱到,但是简单学习其封装思路还是很有意义的。后面还要抽时间仔细学习每一块的源码。这里简单说一说我对jq框架封装的理解,并仿照着封装css的两个方法。

首先,jQuery的本质是一个封装了众多方法的库

这个库的框架是一个闭包

其最外层的框架或者说其骨架如下:

(function(window, undefined) {var jQuery = function () {}window.jQuery = window.$ = jQuery;//暴露全局})(window)复制代码

可以很明显的看出其框架结构,在一个闭包(沙箱)中,所有的函数、方法都放在沙箱内部,有一个名为jQuery的函数,这是核心函数,所有的成员都围绕它运转。

为什么要传入windowundefined,原因很简单,暴露局部变量为全局,自不必说,还有一方面是为了精简代码,减少变量检索时间,window作为形参直接在函数作用域内被检索到,无需每次再向上查找全局,极大地节省了性能提高了效率。

至于undefined,这玩意是为了处理IE8以下的一个小问题,在这些老式浏览器中,undefined是可以作为变量名并被重新赋值的,但是新式浏览器已经不支持这种做法。这里传入undefined就是为了防止undefined被重新赋值。

此时,我们可以在外部直接new一个jQuery的实例对象:

console.log(new jQuery())//空对象,只有一个__proto__属性复制代码

但是显然,这个对象和我们实际使用的jq对象相去甚远,甚至看不到相似之处,别着急,一步步来。

我们的目标是实现类似jq的方式,在外部可拿到jq对象如$('div'),这个jq对象简化一下,大致是如下结构:

['div','div','div',length:3]复制代码

这就意味着,我们必须传入一个元素选择器,在构造函数内接收,通过一些方法,返回出来这么一个对象。

因此,我们先做一个并不严谨的假设,假设jQuery是一个构造函数,通过它来创建对象。

在沙箱内部的jQuery‘构造函数’内,我们传入一个选择器,然后就可以通过构造函数new出对象来:

var jQuery = function (selector) {var ele = document.querySelectorAll(selector);Array.prototype.push.apply(this, ele);
}复制代码

docuemnt.querySelectorAll获取的是一个‘伪数组’或者说集合,得到的dom对象都存放在ele上面,但是我们需要在jq对象上拿到这些对象,所以我们必须手动的将这些对象添加到实例上。借用数组的push方法,不仅能够方便快捷的达到目的,而且数组的length属性可以自动更新。这是一个小技巧。

这里的this指向,毫无疑问就是jQuery的实例对象,因此,我们只需要在jQuery的原型上添加方法,就可以使外部的实例对象访问到这些方法,这已经非常接近jq的思路了。

在此基础上,我们简单给原型上添加两个方法css和html:

jQuery.prototype.css=function(){console.log('hello css')
},
jQuery.prototype.html=function(){ console.log('hello html')
},
...复制代码

通过给原型添加方法,jQuery实例对象可以直接访问使用这些方法,但是这么做似乎太麻烦了些,如果有几十上百个方法,每次都这么添加,代码太过冗余。

因此,使用原型替换的思想,改变原型指向到某个对象上,给这个对象添加方法。可以节省很多代码。

jQuery.fn = jQuery.prototype = {constructor: jQuery,  // 手动添加了丢失的constructor属性 css: function() {console.log("css is ok again");}, html: function() {console.log("html is ok again");}
}复制代码

其中,为了书写方便,我们将jQuery的原型jQuery.prototype赋值给jQuery‘构造函数’的一个属性 jQuery.fn ,后者可以完全代替前者。

至此,我们在外面new 一个jQuery对象就可以拿到一个接近原版的jq对象了,也可以访问原型链上的方法和属性。但是似乎还有哪里不对,new对象这个操作似乎应该在内部完成?没错,我们继续完善它。

在进一步完善我们的小jQuery之前,我们要打个岔,回忆一下工厂函数是怎么回事。

关于工厂函数:

作用:创建实例对象,然后把实例对象给返回出去。

function Person(name, age){this.name = name;this.age = age;
}
//上面是构造函数,我们创建对象的做法:
var xm = new Person("xm", 20)
console.log(xm);//创建了xm对象var xh = new Person("xh", 21);
console.log(xh);//创建了xh对象复制代码

将这个过程封装一下:

 function $(name, age){//$就是工厂函数return new Person(name, age);}// 省去外部的new操作,还能得到实例对象var xm = $("xm", 20);
console.log(xm);var xh = $("xh", 22);
console.log(xh);//得到两个实例对象复制代码

可见,封装好的工厂函数可以通过直接调用,批量创建对象出来。我们回到jQuery的话题来

按照这个思路,我们将jQuery函数也封装一下,使之变成工厂函数:

var jQuery = function (selector) {return new jQuery(selector);
}复制代码

完成了吗?似乎完成了?但是又好像有哪里不对,怎么看着这么眼熟,这不是隔壁的递归函数吗,自己调自己,把自己玩死了。so。。?难道jQuery不能当做工厂函数吗?那么问题来了,他不做谁能做呢?或者,他不是构造函数?听着有点乱,但还真被我们蒙对了!

实际上,在jQuery中,真正的构造函数,并不是jQuery函数!我们先前的假设要改一改了。

真正的构造函数,另有其人,不兜圈子了,直接上结论:jQuery函数的真正作用是“工厂函数”,正牌儿构造函数是jQuery.fn.init

这个jQuery.fn.init是什么鬼?怎么就把正主jQuery赶下位上台了呢?

直接上结论:这个jQuery.fn.init其实是jQuery函数的原型上的一个方法,它是真正的构造函数,通过它创建对象。

那么我们前面的代码要改改了。该挪窝的挪窝,该上位的上位:

var jQuery = function (selector) {return new jQuery.fn.init(selector);
}复制代码

init函数去它该去的地方:

jQuery.fn = jQuery.prototype = {constructor: jQuery, // 手动添加了constructor属性init: function(selector) {var ele = document.querySelectorAll(selector); // this??? ==> init的实例对象 Array.prototype.push.apply(this, ele);}
}复制代码

经过这么一改,this的指向发生了变化,原本存放在jQuery原型上的dom对象们,现在变成了init的儿子,this指向了init。但是我们的方法都是存放在jQuery原型上的,难道还要手动搬回来?算了算了,太麻烦,还好有原型链这个好东西。手绘了一张草图,将就看一下:

init的实例对象想要使用jQuery的方法,丝毫不难,只需要改变原型链指向即可,将自己的原型指向由原本指向init.prototype改为指向jQuery.prototype即可,结果:

代码层面即一句话:

 jQuery.fn.init.prototype = jQuery.fn;复制代码

至此,我们的jQuery架构基本搭建完毕。此时,在沙箱外面,不需要手动new了,直接调用$(),例如$('div'),已经得到了和原版jQuery一样的对象。

剩下的就是添砖加瓦,封装一些方法了,我们以css方法为例,做个简单的封装。

完整代码如下:

(function(window, undefined) {var jQuery = function(selector) {  //jQuery是工厂函数return new jQuery.fn.init(selector); //传入选择器,实例化对象}  //参数selector会传入initjQuery.fn = jQuery.prototype = {//工厂函数的原型   constructor: jQuery,init: function(selector) { //由jQuery一路传来的形参 var ele = document.querySelectorAll(selector); //实现获取对象。         // this ==> init的实例对象    // 把获取到的元素添加到init的实例对象上          Array.prototype.push.apply(this, ele);},css: function(name, value) {// 通过判断参数的个数就能确定css方法要实现什么功能          if (arguments.length === 2) {// 设置单个样式 // 是把获取到的所有元素都设置上这个样式 // this ==>$("p");  伪数组,有length属性,是需要把伪数组中每一项都设置上样式 for (var i = 0; i < this.length; i++) {this[i].style[name] = value; }        }else if(arguments.length === 1){//说明是个对象 设置多个样式 || 获取样式 if (typeof name === "object") { // 设置多个样式 需要给获取到的所有元素都设置上多个样式for(var i = 0; i < this.length; i++){ //this[i] ==> 每一个元素  // 循环的是对象,是设置的样式和样式值for(var k in name){  this[i].style[k] = name[k]; }}}else if(typeof name === "string"){ // 获取样式  注意点: 获取第一个元素对应的值// this ==>$("p") this[0]  ==> 获取到的元素中的第一个元素 // style 操作的是行内样式 //window.getComputedStyle(元素, null); 获取在元素上其效果的样式// 返回值: 是一个对象return window.getComputedStyle(this[0], null)[name]; }}return this;// 目的:实现链式编程}}// 修改init的原型对象 目的是为了让init的实例对象可以访问jq上的方法jQuery.fn.init.prototype = jQuery.fn;window.jQuery = window.$ = jQuery;
})(window)复制代码

css方法的封装略显繁琐,但css这玩意不一直都这样么,在js中处理css,吃力又难受。但也没啥好办法,还好这部分没什么难度。

但是有两点仍然是需要我们注意并且必须做到的,就是关于jQuery的两个主要特点:

隐式迭代和链式编程。

前者使jq所设置的所有样式对所有获取到的对象都起作用,后者则要求在方法的封装结尾,必须返回该对象,以供连续调用实现链式编程。如果封装方法没做到这两点,那么封装出来的也就跟jq没啥关系了,这是需要格外注意的。

【结语】本文是很久以前学习时写的片段整理而成,限于个人水平,对jq封装的思想可能理解的不够深入,可能有许多地方说的不够严谨或者似是而非,还请看官大佬们不吝指出。感谢。

浅析jQuery原理并仿写封装一个自己的库相关推荐

  1. 模拟jQuery,简单仿写API

    jQuery是一个高效.精简并且功能丰富的 JavaScript 工具库.它提供的 API 易于使用且兼容众多浏览器,这让诸如 HTML 文档遍历和操作.事件处理.动画和 Ajax 操作更加简单.最近 ...

  2. VBA,关于filter()函数的局限性和原理,自己写了一个仿造filter同功能自定义函数

    1 filter() 函数的局限性 1.1 filter() 非精确查找,是模糊查询,类似于 like 的功能 filter() 非精确查找,是模糊查询,类似于 like 的功能 1.2 Filter ...

  3. 1个人,3个月业余时间,采用Flutter,居然仿写了一个淘宝电商开源项目

    来源 | https://www.jianshu.com/p/194448388ce9 前言 Flutter现在如火如荼,Google也在大力推广,是到了学习Flutter的时候了,今天推荐一款用Fl ...

  4. 16岁高中生的「卷」,用13000+行代码,从头写了一个C++机器学习库

    人工智能领域现在也流行高中生拯救世界了? >>>> 一个热爱计算机的少年,16 岁就已经可以做出点东西来了,比如开发个粤语编程语言.拿个 Kaggle 冠军.写个游戏.开发个加 ...

  5. Js自行封装一个 Storge第三方库.

    尝试使用 localstorge 自己封装一个 库 // localStorage function /***** @param {*} key 键* @param {*} nullValue 空值的 ...

  6. [js高手之路] 跟GhostWu一起封装一个字符串工具库-扩展字符串位置方法(4)

    本文,我们接着之前的框架继续扩展,这次扩展了一共有5个与字符串位置相关的方法 between( left, right ) 返回两个字符串之间的内容, 如果第二个参数没有传递,返回的是找到的第一个参数 ...

  7. Dmc雷赛板卡仿写(二):库文件导入报错PVOID未声明的标识符

    导入库成功但是.h文件报错 例如: 解决办法:在导入头文件LTDMC.h之前,导入windows头文件 #include <windows.h> #include <LTDMC.h& ...

  8. [js高手之路] 跟GhostWu一起封装一个字符串工具库-扩展trim,trimLeft,trimRight方法(2)

    我们接着上一篇的继续,在上一篇我们完成了工具库的架构,本文扩展字符串去空格的方法, 一共有3个 1,trimLeft: 去除字符串左边的空格 2,trimRight: 去除字符串右边的空格 3,tri ...

  9. [Electron]仿写一个课堂随机点名小项目

    自从前几个月下了抖音,无聊闲暇时就打会打开抖音,因为打开它有种莫名其妙打开了全世界的感觉... 无意中看到这个小视频:随机点名 于是仿写了一个课堂点名小项目,算是对Electron的一个简单的认识,有 ...

最新文章

  1. linux文件需求管理,CaliberRM 需求管理系统
  2. rewrite.php wordpress 缓存 固定连接,【转】Wordpress中文标签无法正常连接 - 完美解决方案...
  3. Android 自定义软键盘实现
  4. Rsync(远程同步):Linux中Rsync命令的10个实际示例
  5. centos 6.5 安装谷歌浏览器Chrome
  6. 二十、SAP中定义内表
  7. BZOJ4133 : Answer的排队
  8. php 抽象 接口类 区别,PHP 抽象類和接口區別
  9. android listview 只加载显示的图片大小,Android ListView只加载当前屏幕内的图片(解决list滑动时加载卡顿)...
  10. android Cursor用法
  11. 全15期BIM等级考试解析(内附考试秘籍)
  12. matlab模拟信号受噪声干扰分析,如何用matlab编写噪声调频干扰信号
  13. Android关闭输入法
  14. python是哪一类型编程语言_什么是编程语言呢?编程语言有哪些种类呢?
  15. Rmxprt Maxwell 生成2D和3D全模型方法
  16. 上班第一天,大家都在干什么呢?‘Java研发工程师上班‘
  17. 2021华东师范大学计算机专硕考研经验贴
  18. API网关—Ocelot之负载均衡
  19. java中面积构造方法_JAVA图形面积与周长(抽象类)
  20. 《Python编程从入门到实践 第2版》 最强入门Python书籍

热门文章

  1. MethodTrace 生成的trace文件为空
  2. Android 布局开发之百分比布局、弹性布局
  3. 关于java数据库章节connection连接不成功的时候!!!
  4. React Native之箭头函数和延展操作符(...)
  5. Java继承_Hachi君浅聊Java三大特性之 封装 继承 多态
  6. vue父子组件传值,sync语法糖
  7. codeforces524E
  8. Docker安装MariaDB
  9. 树莓派私有云(OwnCloud)搭建(三) OwnCloud安装
  10. Android Service与Activity的交互