jQuery 2.0.3 源码分析之core(一)整体架构


Posted in Javascript onMay 27, 2014

拜读一个开源框架,最想学到的就是设计的思想和实现的技巧。

废话不多说,jquery这么多年了分析都写烂了,老早以前就拜读过,

不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery又给扫一遍

我也不会照本宣科的翻译源码,结合自己的实际经验一起拜读吧!

github上最新是jquery-master,加入了AMD规范了,我就以官方最新2.0.3为准

整体架构

jQuery框架的核心就是从HTML文档中匹配元素并对其执行操作、

例如:

$().find().css()
$().hide().html('....').hide().

从上面的写法上至少可以发现2个问题

1. jQuery对象的构建方式

2 .jQuery方法的调用方式

分析一:jQuery的无new构建

JavaScript是函数式语言,函数可以实现类,类就是面向对象编程中最基本的概念

var aQuery = function(selector, context) {
        //构造函数
}
aQuery.prototype = {
    //原型
    name:function(){},
    age:function(){}
}
var a = new aQuery();
a.name();

这是常规的使用方法,显而易见jQuery不是这样玩的

jQuery没有使用new运行符将jQuery显示的实例化,还是直接调用其函数

按照jQuery的书写方式

$().ready() 
$().noConflict()

要实现这样,那么jQuery就要看成一个类,那么$()应该是返回类的实例才对

所以把代码改一下:

var aQuery = function(selector, context) {
       return new aQuery();
}
aQuery.prototype = {
    name:function(){},
    age:function(){}
}

通过new aQuery(),虽然返回的是一个实例,但是也能看出很明显的问题,死循环了!

那么如何返回一个正确的实例?

在javascript中实例this只跟原型有关系

那么可以把jQuery类当作一个工厂方法来创建实例,把这个方法放到jQuery.prototye原型中

var aQuery = function(selector, context) {
       return  aQuery.prototype.init();
}
aQuery.prototype = {
    init:function(){
        return this;
    }
    name:function(){},
    age:function(){}
}

当执行aQuery() 返回的实例:

 

很明显aQuery()返回的是aQuery类的实例,那么在init中的this其实也是指向的aQuery类的实例

问题来了init的this指向的是aQuery类,如果把init函数也当作一个构造器,那么内部的this要如何处理?

var aQuery = function(selector, context) {
       return  aQuery.prototype.init();
}
aQuery.prototype = {
    init: function() {
        this.age = 18
        return this;
    },
    name: function() {},
    age: 20
}
aQuery().age  //18

这样的情况下就出错了,因为this只是指向aQuery类的,所以需要设计出独立的作用域才行

jQuery框架分隔作用域的处理

jQuery = function( selector, context ) {
        // The jQuery object is actually just the init constructor 'enhanced'
        return new jQuery.fn.init( selector, context, rootjQuery );
    },

很明显通过实例init函数,每次都构建新的init实例对象,来分隔this,避免交互混淆

那么既然都不是同一个对象那么肯定又出现一个新的问题

例如:

var aQuery = function(selector, context) {
       return  new aQuery.prototype.init();
}
aQuery.prototype = {
    init: function() {
        this.age = 18
        return this;
    },
    name: function() {},
    age: 20
}
//Uncaught TypeError: Object [object Object] has no method 'name' 
console.log(aQuery().name())

抛出错误,无法找到这个方法,所以很明显new的init跟jquery类的this分离了

怎么访问jQuery类原型上的属性与方法?

     做到既能隔离作用域还能使用jQuery原型对象的作用域呢,还能在返回实例中访问jQuery的原型对象?

实现的关键点

// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;

通过原型传递解决问题,把jQuery的原型传递给jQuery.prototype.init.prototype

换句话说jQuery的原型对象覆盖了init构造器的原型对象

因为是引用传递所以不需要担心这个循环引用的性能问题

var aQuery = function(selector, context) {
       return  new aQuery.prototype.init();
}
aQuery.prototype = {
    init: function() {
        return this;
    },
    name: function() {
        return this.age
    },
    age: 20
}
aQuery.prototype.init.prototype = aQuery.prototype;
console.log(aQuery().name()) //20

百度借网友的一张图,方便直接理解:

fn解释下,其实这个fn没有什么特殊意思,只是jQuery.prototype的引用

 

分析二:链式调用

DOM链式调用的处理:

1.节约JS代码.

2.所返回的都是同一个对象,可以提高代码的效率

通过简单扩展原型方法并通过return this的形式来实现跨浏览器的链式调用。

利用JS下的简单工厂模式,来将所有对于同一个DOM对象的操作指定同一个实例。

这个原理就超简单了

aQuery().init().name()
分解
a = aQuery();
a.init()
a.name()

把代码分解一下,很明显实现链式的基本条件就是实例this的存在,并且是同一个

aQuery.prototype = {
    init: function() {
        return this;
    },
    name: function() {
        return this
    }
}

所以我们在需要链式的方法访问this就可以了,因为返回当前实例的this,从而又可以访问自己的原型了

aQuery.init().name()

优点:节省代码量,提高代码的效率,代码看起来更优雅

最糟糕的是所有对象的方法返回的都是对象本身,也就是说没有返回值,这不一定在任何环境下都适合。

Javascript是无阻塞语言,所以他不是没阻塞,而是不能阻塞,所以他需要通过事件来驱动,异步来完成一些本需要阻塞进程的操作,这样处理只是同步链式,异步链式jquery从1.5开始就引入了Promise,jQuery.Deferred后期在讨论。

分析三:插件接口

jQuery的主体框架就是这样,但是根据一般设计者的习惯,如果要为jQuery或者jQuery prototype添加属性方法,同样如果要提供给开发者对方法的扩展,从封装的角度讲是不是应该提供一个接口才对,字面就能看懂是对函数扩展,而不是看上去直接修改prototype.友好的用户接口,

jQuery支持自己扩展属性,这个对外提供了一个接口,jQuery.fn.extend()来对对象增加方法

从jQuery的源码中可以看到,jQuery.extend和jQuery.fn.extend其实是同指向同一方法的不同引用

jQuery.extend = jQuery.fn.extend = function() {
jQuery.extend 对jQuery本身的属性和方法进行了扩展
jQuery.fn.extend 对jQuery.fn的属性和方法进行了扩展

通过extend()函数可以方便快速的扩展功能,不会破坏jQuery的原型结构

jQuery.extend = jQuery.fn.extend = function(){...}; 这个是连等,也就是2个指向同一个函数,怎么会实现不同的功能呢?这就是this 力量了!

针对fn与jQuery其实是2个不同的对象,在之前有讲述:

    jQuery.extend 调用的时候,this是指向jQuery对象的(jQuery是函数,也是对象!),所以这里扩展在jQuery上。
    而jQuery.fn.extend 调用的时候,this指向fn对象,jQuery.fn 和jQuery.prototype指向同一对象,扩展fn就是扩展jQuery.prototype原型对象。
    这里增加的是原型方法,也就是对象方法了。所以jQuery的api中提供了以上2中扩展函数。

extend的实现

jQuery.extend = jQuery.fn.extend = function() {
    var src, copyIsArray, copy, name, options, clone,
        target = arguments[0] || {},    // 常见用法 jQuery.extend( obj1, obj2 ),此时,target为arguments[0]
        i = 1,
        length = arguments.length,
        deep = false;
    // Handle a deep copy situation
    if ( typeof target === "boolean" ) {    // 如果第一个参数为true,即 jQuery.extend( true, obj1, obj2 ); 的情况
        deep = target;  // 此时target是true
        target = arguments[1] || {};    // target改为 obj1
        // skip the boolean and the target
        i = 2;
    }
    // Handle case when target is a string or something (possible in deep copy)
    if ( typeof target !== "object" && !jQuery.isFunction(target) ) {  // 处理奇怪的情况,比如 jQuery.extend( 'hello' , {nick: 'casper})~~
        target = {};
    }
    // extend jQuery itself if only one argument is passed
    if ( length === i ) {   // 处理这种情况 jQuery.extend(obj),或 jQuery.fn.extend( obj )
        target = this;  // jQuery.extend时,this指的是jQuery;jQuery.fn.extend时,this指的是jQuery.fn
        --i;
    }
    for ( ; i < length; i++ ) {
        // Only deal with non-null/undefined values
        if ( (options = arguments[ i ]) != null ) { // 比如 jQuery.extend( obj1, obj2, obj3, ojb4 ),options则为 obj2、obj3...
            // Extend the base object
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];
                // Prevent never-ending loop
                if ( target === copy ) {    // 防止自引用,不赘述
                    continue;
                }
                // Recurse if we're merging plain objects or arrays
                // 如果是深拷贝,且被拷贝的属性值本身是个对象
                if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                    if ( copyIsArray ) {    // 被拷贝的属性值是个数组
                        copyIsArray = false;
                        clone = src && jQuery.isArray(src) ? src : [];
                    } else {    被拷贝的属性值是个plainObject,比如{ nick: 'casper' }
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }
                    // Never move original objects, clone them
                    target[ name ] = jQuery.extend( deep, clone, copy );  // 递归~
                // Don't bring in undefined values
                } else if ( copy !== undefined ) {  // 浅拷贝,且属性值不为undefined
                    target[ name ] = copy;
                }
            }
        }
    }
    // Return the modified object
    return target;

总结:

    通过new jQuery.fn.init() 构建一个新的对象,拥有init构造器的prototype原型对象的方法
    通过改变prorotype指针的指向,让这个新的对象也指向了jQuery类的原型prototype
    所以这样构建出来的对象就继续了jQuery.fn原型定义的所有方法了

Javascript 相关文章推荐
Dom 结点创建 基础知识
Oct 01 Javascript
jWiard 基于JQuery的强大的向导控件介绍
Oct 28 Javascript
jquery移动点击的项目到列表最顶端的方法
Jun 24 Javascript
Bootstrap 最常用的JS插件系列总结(图片轮播、标签切换等)
Jul 14 Javascript
js放到head中失效的原因与解决方法
Mar 07 Javascript
深入理解vue.js中的v-if和v-show
Jun 22 Javascript
vue中当图片地址无效的时候,显示默认图片的方法
Sep 18 Javascript
使用electron将vue-cli项目打包成exe的方法
Sep 29 Javascript
小程序点击图片实现自动播放视频
May 29 Javascript
解决Vue在Tomcat8下部署页面不加载的问题
Nov 12 Javascript
taro 实现购物车逻辑的实例代码
Jun 05 Javascript
微信小程序视频弹幕发送功能的实现
Dec 28 Javascript
利用javascript实现全部删或清空所选的操作
May 27 #Javascript
For循环中分号隔开的3部分的执行顺序探讨
May 27 #Javascript
浅析javascript中function 的 length 属性
May 27 #Javascript
JavaScript模块随意拖动示例代码
May 27 #Javascript
javascript中不提供sleep功能如何实现这个功能
May 27 #Javascript
js实现网页自动刷新可制作节日倒计时效果
May 27 #Javascript
纯js实现遮罩层效果原理分析
May 27 #Javascript
You might like
德生PL660的电路分析和打磨
2021/03/02 无线电
提升PHP执行速度全攻略(下)
2006/10/09 PHP
php define的第二个参数使用方法
2013/11/04 PHP
php绘制一个矩形的方法
2015/01/24 PHP
详解WordPress中的头像缓存和代理中的缓存更新方法
2016/03/01 PHP
img标签中onerror用法
2009/08/13 Javascript
最短的javascript:地址栏载入脚本代码
2011/10/13 Javascript
javascript事件冒泡详解和捕获、阻止方法
2014/04/12 Javascript
纯JavaScript实现的兼容各浏览器的添加和移除事件封装
2015/03/28 Javascript
JQuery插件ajaxfileupload.js异步上传文件实例
2015/05/19 Javascript
js中跨域方法原理详解
2015/07/19 Javascript
javascript实现将文件保存到本地方法汇总
2015/07/26 Javascript
jQuery满屏焦点图左右滚动特效代码分享
2015/09/07 Javascript
jQuery使用Selectator插件实现多选下拉列表过滤框(附源码下载)
2016/04/08 Javascript
JavaScript生成验证码并实现验证功能
2016/09/24 Javascript
Angularjs 创建可复用组件实例代码
2016/10/09 Javascript
Seajs是什么及sea.js 由来,特点以及优势
2016/10/13 Javascript
获取今天,昨天,本周,上周,本月,上月时间(实例分享)
2017/01/04 Javascript
微信小程序删除处理详解
2017/08/16 Javascript
npm ci命令的基本使用方法
2020/09/20 Javascript
python实现去除下载电影和电视剧文件名中的多余字符的方法
2014/09/23 Python
Python中selenium实现文件上传所有方法整理总结
2017/04/01 Python
Tensorflow 同时载入多个模型的实例讲解
2018/07/27 Python
对numpy下的轴交换transpose和swapaxes的示例解读
2019/06/26 Python
浅析Python 引号、注释、字符串
2019/07/25 Python
Python上下文管理器全实例详解
2019/11/12 Python
Python continue语句实例用法
2020/02/06 Python
python的列表List求均值和中位数实例
2020/03/03 Python
Python pip安装第三方库实现过程解析
2020/07/09 Python
Linux安装Python3如何和系统自带的Python2并存
2020/07/23 Python
Monki官网:斯堪的纳维亚的独立时尚品牌
2020/11/09 全球购物
小学教研工作制度
2014/01/15 职场文书
社区维稳工作方案
2014/06/06 职场文书
自习课吵闹检讨书范文
2014/09/26 职场文书
Python 数据可视化之Matplotlib详解
2021/11/02 Python
GO语言异常处理分析 err接口及defer延迟
2022/04/14 Golang