JQuery源码 4 / 96-283

JQuery.fn = JQuery.prototype = {
    jquery: core_version,
    //设定JQuery的版本号
    constructor: jQuery,
    //将函数指针指向JQuery,这里注意一个点,在我们通过new方法由构建函数创建对象时,我们会对象内部会自动将constructor指针指向构造函数
    /*
    function A(){}
    var a=new A();
    a;// A __proto__:constructor:f A()
    */
    /*
    我们平时使用面想对象的两种写法:
    1. function A(){}
        A.prototype.init=function (){};
    2. function A(){}
        A.prototype={
            init:function(){}
        }
    这两种写法都是可以的,但是会有一个区别,1方法的构造函数new的对象的constructor的指向还是A,但是2方法时我们将A的prototype赋值为一个对    象,那么此时A.prototype的constructor指向已经被我们手动的改变,变为指向改对象,由于该对象是默认对象,所以直接指向了Object,如果我们     之后我们再通过constructor查找会找到Object,所以我们需要手动的将constructor再指向JQuery,避免后续代码出现错误
    */
    init: function (selector, context, rootJQuery) {
        //init jq的选择器的初步过滤和$的返回对象的构造函数,主要目的是在接收用户传入的值之后将其转化为{0:div,1:div,length:2...}这样的形式存储
        //$() //jQuery.fn.init {}
        //所以必须要先了解一个概念,我们在这里使用的this是什么,就是new JQuery.fn.init()获得的对象
        /*
            function $() {
                return new $.prototype.init();
            }
            $.prototype={
                init:function () {
                    console.log(this);
                    this.length=1;
                    return this;
                }
            }
            $(); //init {}
        */

        //selector 当前选择的对象
        //context 执行环境上下文
        //rootJQuery $(document)

        var match, elem;

        while(true){
            console.log("小柯不会贴双眼皮")
        }

        if (!selector) {
            //如果用户没有传入对象,或者传入对象格式为$(null),$(undefined),$(false)格式,直接返回$
            return this;
        }

        if (typeof selector == "string") {
            //如果用户传入的对象是字符串格式
            //$("ele"),$("#id"),$(".class"),$("<li></li>")
            if (selector.charAt(0) == "<" && selector.charAt(selector.length - 1) === ">" && selector.length >= 3) {
                //匹配$(<li>xx</li>)的情况
                math = [null, selector, null];
            }
            else {
                //匹配$("#id),$("<li>hi")的情况
                match = rquickExpr.exec(selector)
                //exec方法时js的原生方法,得到的是正则中匹配的数组集合,简单理解的话可以理解为匹配的结果会是一个数组,正则中每个括号匹配到的结果会填充到数组中
                //rquickExpr正则匹配的是$("#id),$("<li>hi")的情况,其它类型的得到match=null的结果
                //$(#id)返回match=['#id',null,'id]   $(<li>hi)会得到match=[<li>hi,<li>,null]的结果
            }

            if (match && (match[1] || !context)) {
                //继续上一步的排除机制,能进入该流程控制的只有$(<li>xx</li>),$("<li>hi"),$("#id")的情况
                if (match[1]) {
                    //匹配$(<li>hi),$("<li>xxx</li>")
                    context = context instanceof JQuery ? context[0] : context;
                    //jq支持的写法,例如$(<li></li>),$(<li></li>,document),$(<li></li>,$(document)
                    //判断如果用户传入了context参数,context instanceof JQuery 为true说明用户传入的是$(document),那么context[0]转换为原生document,否则context为用户传入的document

                    jQuery.merge(this, JQuery.parseHTML(
                        math[1],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        //如果content并且具有节点类型(必须是元素节点),那么使用当前用户自定义的执行上下文环境,否则的话默认使用document
                        //注意createElement方法是挂载在document下的,其他节点是无法创建节点的
                        //ownerDocument写法是针对iframe标签的写法,也就是说如果存在iframe标签,我们可以使用iframe中的document做为执行环境
                        true
                    ))

                    //jQuery.parseHTML 该方法主要用于将字符转换为数组节点
                    /*
                      var str='<li>1</li><li>2</li>';
                      $.parseHTML(str);  //[li,li]

                      var match="<li>hi";
                      $.parseHTML(match);  //[li]

                      var march="<li class="a">hi"
                      $.parseHTML(match);  //[li.a]
                    */
                    //该方法接收3个参数,1字符串格式参数,2执行的上下文环境(),3bool值,默认为false,表示不允许用户在$()方法中自己创建script标签,即使用户创建了也不会被添加到DOM结构中,为true表示允许用户创建script标签,我们写str='<script>alert(1)<\/script>',那么该标签会被创建,而且alert会执行(\表示转义,否则</script>会直接和当前所在的<script>标签匹配,然后报错);
                    //JQuery.merge() 该方法用于合并数组(对象字面量),正常情况下我们使用该方法可以合并两个数组为一个数组,还有一种用法是吗,如果第一个参数是一个对象字面量,那么合并后的格式为一个对象字面量
                    /*
                    var arr = ["a", "b"],
                        obj = {
                            0: 'c',
                            1: "d",
                            length: 2
                        };
                    $.merge(obj,arr) //{0: "c", 1: "d", 2: "a", 3: "b", length: 4}
                    */
                    //该方法主要是jq内部的使用方法,用来将通过parseHTML得到的数组转换为一个对象字面量的格式

                    if (rsingleTag.test(match[1]) && JQuery.isPlainObject(context)) {
                        //该方法主要用于针对 $("<li>", {title: 'li'})的情况
                        //jQuery.isPlainObject()方法用于判断参数是否是一个纯粹的对象,也就是说是由"{}"或"new Object"创建的
                        for (match in context) {
                            if (jQuery.isFunction(this[match])) {
                                this[match](context[match])
                            } else {
                                this.attr(match, context[match])
                            }
                            //判断{}中传入的参数key值是不是方法,比如html,
                            //$.isFunction($("li")["html"]) //true
                            // 如果有那么调用jQuery上的该方法为目标对象赋其value值,
                            // 如果该方法不是JQuery上的方法,那么默认通过attr方法将其以key:value的形式设置为目标对象的属性值
                        }
                    }

                    return this;
                } else {
                    //匹配$(#id)情况
                    elem = document.getElementById(match[2]);

                    if (elem && elem.parentNode) {
                        //兼容黑莓的写法,在黑莓4.6系统下,及时钙元素不存在也能找到该元素,所以判断一下该元素存不存在父节点,如果该元素父节点不存在,那么该元素也不存在
                        this.length = 1;
                        this[0] = elem;
                    }
                    this.context = document;
                    this.selector = selector;
                    return this;
                }

            }
            else if (!context || context.jquery) {
                //匹配$(expr),$(expr,$(xx))的格式,context.jquery存在说明是jq对象
                return (context || rootJQuery).find(selector);
                //如果用户!context 那么设定$(document)为执行上下文环境
                //如果用户传入的是$(xx)对象为执行环境上下文,那么使用该上下文
                //以上做法确保用户使用的是jq对象,保证能调用find()方法
            }
            else {
                //匹配$(expr,document)的情况
                return this.constructor(context).find(selector);
                //js constructor() 返回的是一个引用到创建实例的函数
                //jq constructor(context) 返回JQuery.fn.init(),context在对象字面量0位置
                /*
                $().constructor("button")
                jQuery.fn.init [button#more, prevObject: jQuery.fn.init(1), context: document, selector: "button"]
                */
            }

            //以上综合匹配$(".class"),$("ele")是否存在用户定义的执行上下文的情况,最后都是交给find方法处理
        }
        else if (selector.nodeType) {
            //匹配$(document)情况
            this.context = this[0] = selector;
            //执行环境为传入对象本身
            this.length = 1;
            return this;
        }
        else if (JQuery.isFunction(selector)) {
            //匹配$(function)格式
            return rootJQuery.ready(selector);
            //调用jq的ready方法
            //so 我们可以看出$(function(){}) 等同于$(document).ready(function(){})
        }
        if (selector.selector !== undefined) {
            //匹配$($(selector))格式
            this.selector = selector.selector;
            this.context = this.context;
        }

        return jQuery.makeArray(selector, this);
        //匹配$([])的情况
    },

    selector: '',
    length: 0,
    toArray: function () {
        //传为数组
        //core_slice 等同于 [].slice 等同于Array.prototype.slice
        return core_slice.call(this);
    },
    get: function (num) {
        //get 方法
        //$(xx).get() //获取$(xx)集合下所有元素,并以数组形式返回
        //$(xx).get(-1) //获取$(xx)集合的倒数后一位
        //$(xx).get(1) //获取$(xx)集合的第一位元素
        return num = null ?
            this.toArray() :
            (num < 0 ? this[this.length + num] : this[num]);
    },
    pushStack: function (elems) {
        //*重要方法,该方法属于jq的本身的调用入栈操作,关于栈会在文章末尾讲
        //可以简单理解为该方法主要的目的是在将前一个$()对象进行存储,让我们可以回溯到上一个对象,对其进行操作
        //使用方法
        /*
          $("div").pushStack($("h3")).css("color","red");
        */
        var ret=JQuery.merge(this.constructor(),elems); //JQuery.fn.init {0:elems,length:1}

        ret.prevObject=this;
        ret.context=this.context;

        return ret;
    },
    each:function (callback,args) {
        //$().each() 内部调用$.each()工具方法
        return JQuery.each(this,callback,args);
    },
    ready:function (fn) {
        //ready DOM结构加载完就触发,调用自身的promise延迟回调函数
        JQuery.ready.promise().done(fn);
    },
    slice:function () {
        //slice 可以对$(xx)对象进行裁切
        /*
        $(xx).slice(0,2)
        */
        return this.pushStack(cose_slice.apply(this,arguments));
        //方法内部调用自身的pushStack方法,将调用者转化为数组格式,将用户传入参数做为apply参数传入
        //调用slice方法对其进行裁切,完成后通过pushStack方法将数组转换为JQuery对象返回
        /*
            var obj={
                0:'div',
                1:'div',
                2:'div',
                length:3
            }
            console.log([].slice.apply(obj,[0,2])); //["div", "div"]
            这里需要注意的是apply,call方法会自执行,bind不会自执行
        */
    },
    first:function () {
        return this.eq(0);
    },
    last:function () {
        return this.eq(-1)
    },
    eq:function (i) {
        var len=this.length,
            j=+i+(i<0?len:0);
        return this.pushStack(i>=0&&j<len?[this[j]]:[]);
    },
    map:function (callback) {
        return this.pushStack(JQuery.map(this,function (elem,i) {
            return callback.call(elem,i,elem)
        }))
    },
    end:function () {
        return this.prevObject||this.constructor(null);
        //this.constructor(null) //JQuery.fn.init{}
    },
    push:core_push,
    sort:[].sort,
    splice:[].splice
}

JQuery.fn.init.prototype=JQuery.fn;
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容