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

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

废话不多说,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原型定义的所有方法了

(0)

相关推荐

  • jQuery学习笔记之总体架构

    先来看代码: 复制代码 代码如下: (function (window, undefined) {            //构建jQuery对象            var document = window.document,                navigator = window.navigator,                location = window.location;            var jQuery = (function () {       

  • 谈一谈jQuery核心架构设计

    jQuery对于大家而言并不陌生,因此关于它是什么以及它的作用,在这里我就不多言了,而本篇文章的目的是想通过对源码简单的分析来讨论 jQuery 的核心架构设计,以及jQuery 是如何利用javascript中的高级特性来构建如此伟大的javascript库. 1 初识jQuery 从核心功能来看,jQuery仅仅做了一件简单而又平凡的事:查询.它的语法如此简洁明了,以致于很多人在不知道javascript是什么的时候就已经会用jQuery了,用一个词形容就是:大道至简. 从设计层面来看,我们

  • jQuery总体架构的理解分析

    jQuery脚本总体结构来说,有如下形式: 复制代码 代码如下: (function( window, undefined ) { // Define a local copy of jQueryvar jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector,

  • jquery的总体架构分析及实现示例详解

    jQuery整体框架甚是复杂,也不易读懂,这几日一直在研究这个笨重而强大的框架.jQuery的总体架构可以分为:入口模块.底层模块和功能模块.这里,我们以jquery-1.7.1为例进行分析. jquery的总体架构 复制代码 代码如下: 16 (function( window, undefined ) {          // 构造 jQuery 对象   22     var jQuery = (function() {   25         var jQuery = functio

  • jQuery源码分析-01总体架构分析

    1. 总体架构 1.1 自调用匿名函数 self-invoking anonymous function 打开jQuery源码,首先你会看到这样的代码结构: 复制代码 代码如下: (function( window, undefined ) { // jquery code })(window); 1. 这是一个自调用匿名函数.什么东东呢?在第一个括号内,创建一个匿名函数:第二个括号,立即执行 2. 为什么要创建这样一个"自调用匿名函数"呢? 通过定义一个匿名函数,创建了一个"

  • 基于jQuery架构javascript基础体系

    jQuery的使用开始成为javascript开发者的主选产品,但如果没有一个整体的规划,简单的拿来主义势必带来后期维护成本的增加,大量的jQuery插件如同一把双刃剑,一方面大大节省了开发时间与周期,但同时也造成WEB页面加载的负担,带来的难护与二级扩展成本将非常庞大,因此,在使用之前就需要一个最基础的javascript架构体系,能明显基础功能组件,插件组件有效实施与扩展. 我们想,jQuery毕竟还只是一个基础的最底层的工具集,封装元素选择器,事件等等,基本上能满足一般的需求,但要从整体的

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

    拜读一个开源框架,最想学到的就是设计的思想和实现的技巧. 废话不多说,jquery这么多年了分析都写烂了,老早以前就拜读过, 不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery又给扫一遍 我也不会照本宣科的翻译源码,结合自己的实际经验一起拜读吧! github上最新是jquery-master,加入了AMD规范了,我就以官方最新2.0.3为准 整体架构 jQuery框架的核心就是从HTML文档中匹配元素并对其执行操作. 例如: 复制代码 代码如下: $().find().

  • jQuery 1.9.1源码分析系列(十五)动画处理之缓动动画核心Tween

    在jQuery内部函数Animation中调用到了createTweens()来创建缓动动画组,创建完成后的结果为: 可以看到上面的缓动动画组有四个原子动画组成.每一个原子动画的信息都包含在里面了. 仔细查看createTweens函数,实际上就是遍历调用了tweeners ["*"]的数组中的函数(实际上就只有一个元素). function createTweens( animation, props ) { jQuery.each( props, function( prop, v

  • jQuery 1.9.1源码分析系列(十三)之位置大小操作

    先给大家展示谢 jQuery.fn.css (propertyName [, value ]| object )(函数用于设置或返回当前jQuery对象所匹配的元素的css样式属性值.如果需要删除指定的css属性,请使用该函数将其值设为空字符串("") 注意:1.如果省略了value参数,则表示获取属性值:如果指定了该参数,则表示设置属性值.2.css()函数的所有"设置"操作针对的是当前jQuery对象所匹配的每一个元素:所有"读取"操作只针对

  • jQuery 1.9.1源码分析系列(十五)之动画处理

    首先需要有队列(queue)的基本知识.见上一章. 相关教程:jQuery下的动画处理总结: http://www.jb51.net/article/42000.htm jQuery 1.9.1源码分析系列(十五)动画处理之缓动动画核心Tween  :http://www.jb51.net/article/75821.htm a.动画入口jQuery.fn.animate函数执行流程详解 ------------------------------------------------------

  • jQuery 1.9.1源码分析系列(十四)之常用jQuery工具

    为了给下一章分析动画处理做准备,先来看一下一些工具.其中队列工具在动画处理中被经常使用. jQuery.fn. queue(([ queueName ] [, newQueue ]) || ([ queueName ,] callback ))(获取或设置当前匹配元素上待执行的函数队列. 如果当前jQuery对象匹配多个元素:获取队列时,只获取第一个匹配元素上的队列:设置队列(替换队列.追加函数)时,则为每个匹配元素都分别进行设置.如果需要移除并执行队列中的第一个函数,请使用dequeue()函

  • jQuery 1.9.1源码分析系列(十)事件系统之绑定事件

    事件绑定的方法有很多种,使用了jquery那么原理那种绑定方式(elem.click = function(){...}))就不太想推荐给大家了.最主要的原因是elem.click=fn这种方式只能绑定一个事件处理,多次绑定的只会保留最后一次绑定的结果. 下面给大家介绍jquery绑定事件的方式有哪些吧. 复制代码 代码如下: jQuery.fn.eventType([[data,] fn]) 比如eventType指的是事件类型,比如click: $("#chua").click(f

  • Jquery 1.9.1源码分析系列(十二)之筛选操作

    废话不多说了直接奔入主题了. jQuery.fn.find( selector ) find接受一个参数表达式selector:选择器(字符串).DOM元素(Element).jQuery对象.分两种情况处理: 第一种,如果传入的参数是非字符串,则先通过jQuery选择器将selector查找出来,然后过滤出包含于当前jQuery对象所匹配的元素的节点. if ( typeof selector !== "string" ) { self = this; return this.pus

  • jQuery 1.9.1源码分析系列(十)事件系统之主动触发事件和模拟冒泡处理

    发现一个小点,先前没有注意的 stopPropagation: function() { var e = this.originalEvent; ... if ( e.stopPropagation ) { e.stopPropagation(); } jQuery重载stopPropagation函数调用的本地事件对象的stopPropagation函数阻止冒泡.也就是说,阻止冒泡的是当前节点,而不是事件源. 说到触发事件,我们第一反应是使用$(...).click()这种方式触发click事

  • Jquery1.9.1源码分析系列(十五)动画处理之外篇

    a.动画兼容Tween.propHooks Tween.propHooks提供特殊情况下设置.获取css特征值的方法,结构如下 Tween.propHooks = { _default: { get: function(){...}, set: function(){...} }, scrollTop: { set: function(){...} } scrollLeft: { set: function(){...} } } Tween.propHooks.scrollTop 和Tween.

  • Android实例HandlerThread源码分析

    HandlerThread 简介: 我们知道Thread线程是一次性消费品,当Thread线程执行完一个耗时的任务之后,线程就会被自动销毁了.如果此时我又有一 个耗时任务需要执行,我们不得不重新创建线程去执行该耗时任务.然而,这样就存在一个性能问题:多次创建和销毁线程是很耗 系统资源的.为了解这种问题,我们可以自己构建一个循环线程Looper Thread,当有耗时任务投放到该循环线程中时,线程执行耗 时任务,执行完之后循环线程处于等待状态,直到下一个新的耗时任务被投放进来.这样一来就避免了多次

随机推荐