jQuery源码浅析

如果说php是世界上最好的语言,那么javascript无疑可以称为世界上最飘逸的语言,最近看了下jQuery的源码,实现了一个简陋的jQuery。我觉得要看懂jQuery整体结构,需要搞懂js作用域链,闭包,js prototype继承,关于闭包网络上的定义实在太多了,这里参照了js权威指南里的定义,感觉从js的角度好理解一点。

闭包:js函数对象不仅包含函数的代码逻辑,还引用了当前的作用域链,

函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内

这种特性在计算机科学文献中称为闭包,所有的js函数都是闭包。

javascript运行在它被定义的作用域里,而不是执行的作用域里

关于js作用域参见 : http://www.laruence.com/2009/05/28/863.html

废话少说,上滥代码

<!DOCTYPE html>
<html>
<head>
    <title>jQuery源码浅析</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<script>
/**
 * 
 * 参照jQuery 3.2.1源码
 * 省略了一些规范,如AMD, Commonjs
 * 整个jQuery包含在匿名函数中,函数就是闭包
 */

(function(window, factory){
    
    factory(window);

})(window, function(window){
    /**
     * 为什么要传window对象?
     * 1.在jQuery里可以把window对象当局部变量使用
     * 2.压缩的时候window变量名可以压缩至1个字节
     */
    
    var getProto = Object.getPrototypeOf;
    var class2type = {};
    var toString = class2type.toString;
    var hasOwn = class2type.hasOwnProperty;
    var fnToString = hasOwn.toString;
    var ObjectFunctionString = fnToString.call( Object );
    
    /**
     * 工厂方法,返回jQuery.fn.init的实例,即jQuery对象
     * selector待查找的字符串,源码里还有context参数,此处省略
     * 我们所说的jQuery其实准确的说是jQuery工厂方法,调用jQuery工厂方法返回的才是真正的jQuery对象
     */
    var jQuery = function(selector){
            return new jQuery.fn.init(selector);
        },
        version = "3.2.1";
    
    //jQuery原型对象赋值给jQuery.fn,定义在jQuery.prototype里的方法可以被所有的jQuery对象使用
    jQuery.fn = jQuery.prototype = {
        
        jquery: version,

        constructor: jQuery,
        
        //...省掉了一些方法
    };
    
    
    
    //jQuery.fn的属性init函数,jQuery的选择器使用了Ssize,这里简单的使用一个id选择器 
    jQuery.fn.init = function(selector){
        /* (在Javascript中,This关键字永远都指向函数(方法)的所有者) this指向的是jQuery.fn
        *  这里简单的将DOM对象赋值给this[0],其他属性省略, 我们使用jQuery的时候使用下标0即可将jQuery对象转化为普通的DOM对象
        */
        this[0] = window.document.getElementById(selector);
        return this;
    };
    
    
    /**
    * 这一句很关键
    * 将jQuery.fn赋值给jQuery.fn.init的原型,这样jQuery.fn.init的实例(通常我们所说的jQuery对象就是它)可以使用jQuery.fn的方法
    * 结合之前可以得出 jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype
    * jQuery.fn,jQuery.prototype扩展的方法属性 jQuery对象可以使用
    */
    jQuery.fn.init.prototype = jQuery.fn;
    
    //实现了jQuery的html方法
    jQuery.fn.html = function(value){
        if(typeof value === "string"){
            this[0].innerHTML = value;
            return this;
        }else{
            return this[0].innerHTML;
        }
    };
    
    
    /**
     * 
     * jQuery扩展方法,除了可以扩展jQuery外,还可以扩展你指定的对象
     * jQuery.extend 扩展jQuery,可以理解为扩展类方法
     * jQuery.fn.extend 扩展jQuery.fn,即jQuery实例可以使用,可以理解为扩展实例方法
     * 
     * 具体用法
     * 1.jQuery.extend(obj) 扩展jQeury
     * 2.jQuery.extend(true, obj) 深度扩展jQuery
     * 3.jQuery.extend(obj1, obj2) 扩展obj1
     * 4.jQuery.extend(true obj1, obj2) 深度扩展obj1
     */
    jQuery.extend = jQuery.fn.extend = function() {
        var options, name, src, copy, copyIsArray, clone,
            target = arguments[ 0 ] || {},
            i = 1,
            length = arguments.length,
            deep = false;

        //第一个参数为boolean且为true的时候为深度扩展
        if ( typeof target === "boolean" ) {
            deep = target;
            // Skip the boolean and the target
            target = arguments[ i ] || {};
            i++;
        }
        
        //被扩展的对象不是对象或者函数
        if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
            target = {};
        }

        //参数只有1个对象的情况下,扩展jQuery,多个对象则扩展第一个对象
        if ( i === length ) {
            target = this;
            i--;
        }

        for ( ; i < length; i++ ) {
            //只处理非空值
            if ( ( options = arguments[ i ] ) != null ) {

                // Extend the base object
                for ( name in options ) {
                    src = target[ name ];
                    copy = options[ name ];

                    // Prevent never-ending loop
                    if ( target === copy ) {
                        continue;
                    }

                    //仅在属性为纯粹对象或者 数组的时候深度拷贝才有效
                    if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
                        ( copyIsArray = Array.isArray( copy ) ) ) ) {

                        if ( copyIsArray ) {
                            copyIsArray = false;
                            clone = src && Array.isArray( src ) ? src : [];

                        } else {
                            clone = src && jQuery.isPlainObject( src ) ? src : {};
                        }

                        //递归扩展
                        target[ name ] = jQuery.extend( deep, clone, copy );

                    // Don't bring in undefined values
                    } else if ( copy !== undefined ) {
                        target[ name ] = copy;
                    }
                }
            }
        }
        
        // Return the modified object
        return target;
    };
    
    
    jQuery.extend( {
        
        isFunction: function( obj ) {
            return jQuery.type( obj ) === "function";
        },
        
        isPlainObject: function( obj ) {
            var proto, Ctor;

            // Detect obvious negatives
            // Use toString instead of jQuery.type to catch host objects
            if ( !obj || toString.call( obj ) !== "[object Object]" ) {
                return false;
            }

            proto = getProto( obj );

            // Objects with no prototype (e.g., `Object.create( null )`) are plain
            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;
            return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
        },
        
        type: function( obj ) {
            if ( obj == null ) {
                return obj + "";
            }

            // Support: Android <=2.3 only (functionish RegExp)
            return typeof obj === "object" || typeof obj === "function" ?
            class2type[ toString.call( obj ) ] || "object" :
            typeof obj;
        },
    });

    //简易ready方法实现, 只支持DOMContentLoaded
    jQuery.extend( {
        readyList : [],
        ready: function(fn){
            if(fn && typeof fn === 'function'){
                //向数组末尾添加函数
                jQuery.readyList.push(fn);
            }else{
                //顺序执行ready绑定的方法
                for(var i in jQuery.readyList){
                    fn = jQuery.readyList[i];
                    //fn来自于全局作用域,属于window对象
                    fn.call(window, jQuery);
                }
            }
        }
    });
    
    //只支持DOMContentLoaded
    document.addEventListener( "DOMContentLoaded", completed );
    window.addEventListener( "load", completed );
    function completed() {
        document.removeEventListener( "DOMContentLoaded", completed );
        window.removeEventListener( "load", completed );
        jQuery.ready();
    }
    //只暴露了两个变量到全局作用域
    window.$ = window.jQuery =  jQuery;
});

$.ready(function(){
    console.log('----设置id为test的元素文档内容,并获取文档内容----' + $('test').html('jQuery').html());
});

$.ready(function(){
    console.log(1);
});

$.ready(function(){
    console.log(2);
});

$.ready(function(){
    console.log(3);
});

var obj1 = {
    ball : {
        nba : 'nba'
    },
};

var obj2 = {
    ball : {
        cba : 'cba'
    }
};

var obj3 = {
    ball : {
        nba : 'nba'
    }
};

//扩展obj1
$.extend(obj1, obj2);
/**
 * {ball : {'cba' : 'cba'}}
 */

//深度扩展obj3
$.extend(true, obj3, obj2);
/**
 * {ball : {'nba' : 'nba'}, {'cba' : 'cba'}}
 */
</script>
<body>
    <div id="test">my jQuery</div>
</body>
</html>

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏行者常至

007.golang 切片slice

11330
来自专栏Golang语言社区

厚土Go学习笔记 | 20. slice 继续

slice是一个序列的值,可以根据序列号来访问。可以使用 len(s) 返回 slice s 的长度。 写一个例子,利用 for 循环,打印出 slice 的每...

361140
来自专栏技术墨客

React学习(4)——深入说明JSX与props

我们可以将JSX理解为React.createElement(component, props, ...children)方法的语法糖。JSX的代码:

7420
来自专栏Pythonista

Golang之fmt格式“占位符”

15730
来自专栏柠檬先生

jquery 筛选元素(1)

.eq()   减少匹配元素的集合为指定的索引的那一个元素。   .eq(index)     index一个整数,指示元素的位置,以0为基数。  ...

21570
来自专栏阮一峰的网络日志

CSS 的空格处理

上面是一行 HTML 代码,文字的前部、内部和后部各有两个空格。为了便于识别,这里使用半圆形符号◡表示空格。

17120
来自专栏柠檬先生

jquery 操作DOM元素(1)

.clone()   创建一个匹配的元素集合的深度拷贝。   .clone([withDataAndEvents])     ...

22050
来自专栏Java帮帮-微信公众号-技术文章全总结

JS之文档对象模型DOM

<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=gbk"> ...

30760
来自专栏技术墨客

React 深入说明JSX语法与Props特性

我们可以将JSX理解为React.createElement(component, props, ...children)方法的语法糖。JSX的代码:

11030
来自专栏Golang语言社区

Golang语言社区--标准库分析之strconv包

大家好,我是Golang语言社区主编彬哥,这篇是给大家转载的关于Go语言的strconv 包相关的知识。

92370

扫码关注云+社区

领取腾讯云代金券