谜一样的jquery之$选择器
jquery是一个强大的js类库,提供了很多便利的操作方法并兼容不同的浏览器,一旦使用便欲罢不能,根本停不下来,今天我们就来解读一下这个神秘的jquery源代码。
前几天思考再三,自己尝试着封装了一下jquery的$选择器,然而并不完善,我只对id,class,和标签选择器进行了封装,发现其实如果实现浅层的封装那么我们很容易就能够实现,但是一旦我们尝试着选择器的层次嵌套就会出来很多大大小小的坑!
下面我们先来看一下我个人封装的jquery的选择器部分。
    window.$ = function (selector) {
        if (typeof selector === 'object') {
            console.log(selector,)
        } else if (typeof selector === 'string') {
            var arr = selector.split(' ')
            for(var i=;i<arr.length;i++){
                oArr = []
                if(arr[i].charAt()=='#'){
                    var a = arr[i].substr()
                    // oEle = oEle.getElementById(a)
                    if(!oEle.length){
                        oEle = oArr= document.getElementById(a)
                    }else{
                        for(var j=;j<oEle.length;j++){
                            // console.log(oEle[j])
                            for(var s = ;s<oEle[j].childNodes.length;s++){
                                // console.log(oEle[j].childNodes[s])
                                if(oEle[j].childNodes[s].nodeType==){
                                    // console.log(oEle[j].childNodes[s].nodeType)
                                    if(oEle[j].childNodes[s].getAttribute('id')==a){
                                        oArr[j] = document.getElementById(a)
                                        // console.log('yes',s)
                                    }
                                    // console.log(oEle[j].childNodes[s].getAttribute('id'))
                                }
                            }
                        }
                        oEle = oArr
                    }
                }else if(arr[i].charAt()=='.'){
                    // console.log(oEle.length)
                    // console.log(oEle)
                    var a = arr[i].substr()
                    if(!oEle.length){
                        oEle = oArr= oEle.getElementsByClassName(a)
                        // console.log(oEle,'class')
                    }else{
                        for(var j=;j<oEle.length;j++){
                            // console.log(oEle)
                            if(oEle[j].getElementsByClassName(a).length){
                                // console.log(oEle)
                                // console.log(1,oEle.length)
                                oArr[j] = oEle[j].getElementsByClassName(a)
                            }
                            // console.log(oEle[j].getElementsByClassName(a))
                        }
                        oEle = oArr
                    }
                    // console.log(oEle)
                }else{
                    // console.log(oEle)
                    // console.log(arr[i])
                    if(!oEle.length){
                        oEle = oArr = oEle.getElementsByTagName(arr[i])
                        // console.log(oEle,'tag')
                    }else{
                        for(var j=;j<oEle.length;j++){
                            // console.log(oEle[j].getElementsByTagName(arr[i]),oEle.length)
                            if(oEle[j].getElementsByTagName(arr[i]).length){
                                oArr[j] = oEle[j].getElementsByTagName(arr[i])
                                // console.log(oEle[j].getElementsByTagName(arr[i]))
                            // console.log(oEle[j].getElementsByTagName(arr[i]),j)
                            }
                        }
                        oEle = oArr
                    }
                    // var oEle = oEle.getElementsByTagName(arr[i])
                    // console.log(oEle)
                }
            }
            // console.log(oEle)
        }
            // console.log(oEle,12)
            console.log(oArr)
            // console.log(oEle.length)
            // console.log(document.getElementById('c'))
            // console.log(oEle.getElementById('c'))
    }
    window.$('.content #c')
jquery的原理大概就是我们把一个$暴漏给window,然后我们在$下面可以写很多方法,然后我们在$之后返回了一个jquery的dom对象在调用自己封装的jquery库中的方法来实现各种浏览器的兼容。
下面我们来说一下我封装这个类库的时候碰到的一些坑吧:
首先我们封装类库无非用几种js原生的选择器,比如document.getElementById或者document.getElementsByTagName。
这两个是最常用的方法但是这个也是问题的所在,打个比方,我们id选择器选到的是一个dom对象,但是我们getElementsByTagName取到的却是一个数组。
这个问题怎么解决,因为我们选择器支持选择多个选择器,比如$('#div div'),我们选择id=div的dom元素之后我们在选择dom下面的所有的div,才能实现dom节点循环嵌套,但是换一个角度$('div #div'),我们选择div标签下面的id=div的元素,那么我们如何进行数组和dom元素的转化?
我的决解的方法是自己定义了一个数组,然后每次dom元素取出来都放在数组里面,数组的元素也放在这个数组里面从而实现真正的循环遍历。
下面我们来观看一个jquery是怎么实现$选择器的
( function( global, factory ) {
    "use strict";
    if ( typeof module === "object" && typeof module.exports === "object" ) {
        // For CommonJS and CommonJS-like environments where a proper `window`
        // is present, execute the factory and get jQuery.
        // For environments that do not have a `window` with a `document`
        // (such as Node.js), expose a factory as module.exports.
        // This accentuates the need for the creation of a real `window`.
        // e.g. var jQuery = require("jquery")(window);
        // See ticket #14549 for more info.
        module.exports = global.document ?
            factory( global, true ) :
            function( w ) {
                if ( !w.document ) {
                    throw new Error( "jQuery requires a window with a document" );
                }
                return factory( w );
            };
    } else {
        factory( global );
    }
// Pass this if window is not defined yet
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
if ( !noGlobal ) {
       window.jQuery = window.$ = jQuery;
}
  } )
我们可以看到,jquery用了一个函数立即调用,然后把window传入函数中,这样做的意义是防治我们的类库污染全局变量,可能有的同学感觉这个不是很重要是因为我们写的代码变量比较少,还不设计代码优化,如果类似jquery的类库全部写进window里面,那你的代码也就不用运行了!
之后jquery进行了一些是否window异常,是否有commonJS标准等逻辑判断来增强类库的健壮性,这里我们就不做过多的解释了。
之后我们可以在源代码中看到这样一段代码
jQuery = function( selector, context ) {
        // The jQuery object is actually just the init constructor 'enhanced'
        // Need init if jQuery is called (just allow error to be thrown if not included)
        return new jQuery.fn.init( selector, context );
    },
这里的jquery其实就是$,jquery类库最后运行了这样一句话
window.jQuery = window.$ = jQuery;
所以我们的jquery其实就是$ ,我们在$方法传入两个参数,第一个参数为你输入的选择器,第二个参数为上下文,之后我们return了一个新的对象jquery中的fn,这样的实现是为了得到我们传入的那个dom节点,因为我们所有的方法都写在了window中的$里面,我们通过return的形式来获取dom之后在调用自身的方法,这个形式类似fetch中的promise对象一样,之后我们可以.then执行一样,两者都有着异曲同工之妙。
好了我们接着往下看:
   init = jQuery.fn.init = function( selector, context, root ) {
        var match, elem;
        // HANDLE: $(""), $(null), $(undefined), $(false)
        if ( !selector ) {
            return this;
        }
        // Method init() accepts an alternate rootjQuery
        // so migrate can support jQuery.sub (gh-2101)
        root = root || rootjQuery;
        // Handle HTML strings
        if ( typeof selector === "string" ) {
            if ( selector[  ] === "<" &&
                selector[ selector.length -  ] === ">" &&
                selector.length >=  ) {
                // Assume that strings that start and end with <> are HTML and skip the regex check
                match = [ null, selector, null ];
            } else {
                match = rquickExpr.exec( selector );
            }
            // Match html or make sure no context is specified for #id
            if ( match && ( match[  ] || !context ) ) {
                // HANDLE: $(html) -> $(array)
                if ( match[  ] ) {
                    context = context instanceof jQuery ? context[  ] : context;
                    // Option to run scripts is true for back-compat
                    // Intentionally let the error be thrown if parseHTML is not present
                    jQuery.merge( this, jQuery.parseHTML(
                        match[  ],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );
                    // HANDLE: $(html, props)
                    if ( rsingleTag.test( match[  ] ) && jQuery.isPlainObject( context ) ) {
                        for ( match in context ) {
                            // Properties of context are called as methods if possible
                            if ( jQuery.isFunction( this[ match ] ) ) {
                                this[ match ]( context[ match ] );
                            // ...and otherwise set as attributes
                            } else {
                                this.attr( match, context[ match ] );
                            }
                        }
                    }
                    return this;
                // HANDLE: $(#id)
                } else {
                    elem = document.getElementById( match[  ] );
                    if ( elem ) {
                        // Inject the element directly into the jQuery object
                        this[  ] = elem;
                        this.length = ;
                    }
                    return this;
                }
            // HANDLE: $(expr, $(...))
            } else if ( !context || context.jquery ) {
                return ( context || root ).find( selector );
            // HANDLE: $(expr, context)
            // (which is just equivalent to: $(context).find(expr)
            } else {
                return this.constructor( context ).find( selector );
            }
        // HANDLE: $(DOMElement)
        } else if ( selector.nodeType ) {
            this[  ] = selector;
            this.length = ;
            return this;
        // HANDLE: $(function)
        // Shortcut for document ready
        } else if ( jQuery.isFunction( selector ) ) {
            return root.ready !== undefined ?
                root.ready( selector ) :
                // Execute immediately if ready is not present
                selector( jQuery );
        }
        return jQuery.makeArray( selector, this );
    };
这里面我们传入了选择器,我们先判断了是不是存在这个selecter,如果不存在的话我们就return this;
之后我们判断传入的这个字符串的情况的和不是字符串的逻辑,如果传入的不是字符串,那我们就看这个元素的nodeType值是不是不为0,如果不为0就说明元素存在,可能是text,可能是dom
对象等等,之后我们就做一些逻辑return this,如果selecter为方法的话我们就判断跟节点的ready是不是undefined,如果不是的话我们执行root.ready(),否则就执行selecter方法。
注:这里的所有this都是jquery的对象,我们是判断了对象是不是包的数组,如果是我们做这样的操作:
this[ ] = selector;
this.length = ;
return this;
我们取出第一项,之后把它变成jquery对象我们就可以继续调用jquery的方法了,不然我们得到了一个数组我们是不能继续调用方法的,这里将会报错!
之后如果我们传入的selecter为字符串,那么我们就进行逻辑判断
if ( selector[ ] === "<" &&
selector[ selector.length - ] === ">" &&
selector.length >= ) { // Assume that strings that start and end with <> are HTML and skip the regex check
match = [ null, selector, null ]; } else {
match = rquickExpr.exec( selector );
}
这里面是判断选择器是否是一个标签,如果不是标签的话我们就执行
match = rquickExpr.exec( selector );
rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,
我们匹配出的match如果是标签在前id在后我们的match不为空,这样我们就继续运行下面的代码,这里也是jquery健壮性的体现!
if ( match && ( match[ ] || !context ) )
之后如果成功说明上面代码都成立,我门就继续往下执行
            if ( match[  ] ) {
                    context = context instanceof jQuery ? context[  ] : context;
                    // Option to run scripts is true for back-compat
                    // Intentionally let the error be thrown if parseHTML is not present
                    jQuery.merge( this, jQuery.parseHTML(
                        match[  ],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );
                    // HANDLE: $(html, props)
                    if ( rsingleTag.test( match[  ] ) && jQuery.isPlainObject( context ) ) {
                        for ( match in context ) {
                            // Properties of context are called as methods if possible
                            if ( jQuery.isFunction( this[ match ] ) ) {
                                this[ match ]( context[ match ] );
                            // ...and otherwise set as attributes
                            } else {
                                this.attr( match, context[ match ] );
                            }
                        }
                    }
                    return this;
                // HANDLE: $(#id)
                } 
之后我们做了去标签之后id的逻辑,这里面的merge是合并的方法,parseHTML是判断是否有上下文的逻辑(这个上下文基本上我用jquery从来没用过- -)。之后我们一样都把这个jquery对象return。
基本上的逻辑就是这样,我们定义jquery的时候声明了许多变量许多方法,然后我们吧一个$暴漏在外部,我们的$内部new了一个jquery对象,然后调用的方法return this来实现jquery的链式操作,之后我们对传入进来的选择器做了各种逻辑的判断增加了健壮性。
不知道大家有没有注意到,我们的jquery对象想要变成dom对象我们需要get(0) 或者[0]。其实我们的jquery对象就是一个包着dom对象的一个数组对象,所以我们才能这样子来取出来我们我原生dom对象!其实jquery实现的思想很简单,复杂的是我们对各个浏览器兼容做的逻辑,我们对各个函数传入值情况作了逻辑,其实想实现jquery很简单,但是想实现真正的健壮性很好的jquery非常费劲了,这些都需要我们去经验积累罢了!
谜一样的jquery之$选择器的更多相关文章
- jQuery 的选择器常用的元素查找方法
		jQuery 的选择器常用的元素查找方法 基本选择器: $("#myELement") 选择id值等于myElement的元素,id值不能重复在文档中只能有一个id值是myE ... 
- HTML 学习笔记 JQuery(选择器)
		学习前端也有一段时间了,今天终于进入到JQuery阶段了,对于新手来讲,JQuery的选择器类型之多 功能之强大实在不是一天两天能够记得完的.现在,就采用边学边记录的方式.以后要是忘了的话,也有一个地 ... 
- jQuery的选择器中的通配符总结
		1.选择器 (1)通配符: $("input[id^='code']");//id属性以code开始的所有input标签 $("input[id$='code']&quo ... 
- JQuery 层次选择器
		<!DOCTYPE HTML> <html> <head> <title> 使用jQuery层次选择器 </title> <scrip ... 
- jQuery过滤选择器
		//基本过滤器$('li:first').css('background','#ccc');//第一个元素$('li:last').css('background','red');//最后一个元素$( ... 
- jquery相对选择器,又叫context选择器,上下文选择器;find()与children()区别
		jquery相对选择器有两个参数,jQuery函数的第二个参数可以指定DOM元素的搜索范围(即以第二个参数指定的内容为容器查找指定元素). 第二个参数的不同的类型,对应的用法如下表所示. 类型 用法 ... 
- jQuery之选择器
		jQuery元素选择器和属性选择器允许您通过标签名.属性名或内容对 HTML 元素进行选择和操作,而在 HTML DOM中,选择器可以对DOM元素组或单个DOM 节点进行操作.通俗点说,选择器的作用就 ... 
- 关于jquery ID选择器的一点看法
		最近看到一道前端面试题: 请优化selector写法:$(".foo div#bar:eq(0)") 我给出的答案会是: 1. $("#bar") 2. $( ... 
- jQuery的选择器中的通配符[id^='code']  【转】
		JQuery 1.选择器 (1)通配符: $("input[id^='code']");//id属性以code开始的所有input标签 $("input[id$='cod ... 
随机推荐
- 模块的封装之C语言类的继承和派生
			[交流][微知识]模块的封装(二):C语言的继承和派生 在模块的封装(一):C语言的封装中,我们介绍了如何使用C语言的结构体来实现一个类的封装,并通过掩码结构体的方式实 现了类成员的保护.这一部分,我 ... 
- [SpringBoot] - 配置文件的多种形式及优先级
			学习两个注解: @PropertySource @ImportResource ↓ @ConfigurationProperties 与 @Bean 结合为属性赋值 与 ... 
- CentOS下安装Python-pip
			1.安装epel-release软件包:自动配置yum的软件仓库,弥补centos内容更新有时比较滞后或是一些扩展的源没有. yum -y install epel-release 2.安装pytho ... 
- Leetcode 15
			//用类似双指针的方法,确定第一个i的位置后,j和k向左向右移动使nums[j]+nums[k] = -nums[i];注意特判 class Solution { public: vector< ... 
- module.exports和exports
			require 用来加载代码,而 exports 和 module.exports 则用来导出代码.但很多新手可能会迷惑于 exports 和 module.exports 的区别,为了更好的理解 e ... 
- javascript垃圾收集
			javascript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存.而在C和C++之类的语言中,开发人员的一项基本任务就是手工跟踪内存的使用情况 ,这是造成许多问题的一个根 ... 
- java并发编程:线程安全管理类--原子操作类--AtomicIntegerFieldUpdater<T>
			1.类 AtomicIntegerFieldUpdater<T> public abstract class AtomicIntegerFieldUpdater<T> exte ... 
- java并发编程:线程安全管理类--原子操作类--AtomicLong
			可以用原子方式更新的 long 值.有关原子变量属性的描述,请参阅 java.util.concurrent.atomic 包规范.AtomicLong 可用在应用程序中(如以原子方式增加的序列号), ... 
- js获取当前日期加上30天之后的日期
			var date1 = new Date(); var date2 = new Date(date1); date2.setDate(date1.getDate() + 30); console.lo ... 
- poj 3744 概率dp 快速幂 注意排序 难度:2
			/* Scout YYF I Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 5304 Accepted: 1455 De ... 
