模拟一个本身的jquery(二) 简单实现$

目标:模拟jquery的$符号选择元素并调用hide与show方法

mquery.js代码javascript

/*¡
 * mquery like jquery
 * require sizzle.js http://sizzlejs.com/
 *
 * Date    2014-3-21
 * author  meng
 */
(function(_window,sizzle){
    if(!sizzle){
        throw new ReferenceError("没有引入Sizzle.js!");
    }
    MQuery.fn=MQuery.prototype;
    function mq(selecter){
        return new MQuery(selecter);
    }
    function MQuery(selecter){
        console.log(typeof selecter);
        if(typeof selecter!=="string" &&!(selecter instanceof Element)){
            throw new TypeError("错误的参数类型,参数必须是字符串,或者Node对象或者NodeList对象");
        }
        if(typeof selecter=="string"){
            this.elements=sizzle(selecter);
        }
        else if(typeof selecter == "object" &&selecter instanceof Element){
                this.elements=[selecter];
        }
        this.length=this.elements.length;
    }
    
    //define hide
    MQuery.fn.hide=function(){
        this.each(function(e){
            e.style.display='none';
        });
    }
    //define show
    MQuery.fn.show=function(){
        this.each(function(e){
            e.style.display='';
        });
    }
    //define each
    MQuery.fn.each=function(callback,index){
        var es=this.elements;
        for (var i = es.length - 1; i >= 0; i--) {
            callback(es[i],i);
        };
    }
    _window.$=mq;
    _window.$.fn=_window.$.prototype=MQuery.prototype;
})(window,Sizzle);
View Code

html代码css

<!DOCTYPE html>
<html>
    <head>
        <title>mquery</title>
        <style>
            .p{
                width: 50px;
                height: 50px;
                border: 1px solid #ddd;
                margin-bottom: 20px;
                padding: 20px;
            }
        </style>
        <script type="text/javascript" src="core/sizzle.js"></script>
        <script type="text/javascript" src="core/mquery.js"></script>
    </head>
    <body>
        <p class="p">ppppp1</p>
        <p class="p">ppppp2</p>
        <button onclick="hide()">hide</button>
        <button onclick="show()">show</button>

        <script type="text/javascript">
            var p = $("p");
            p.hide();
            function show(){
                p.show();
            }
            function hide(){
                p.hide();
            }
            p.hide();
            var p2=$(document.getElementsByTagName("p")[0]);
            console.log(p2);
        </script>
    </body>
</html>
View Code

 

 

运行html,发现实现了本文的目标,一开始运行$("p").hide(),隐藏页面全部的P标签,点击显示按钮调用$("p").show() 显示全部P标签。点击隐藏按钮隐藏P标签。 html

注:mquery.js依赖于sizzle.js,请先去js官网下载最新的sizzle.js。java

 

 代码解析

首先全部代码都包括在(function(_window,sizzle){})(window,Sizzle);里面。由于js是函数做用域,也就是说每一个函数都有本身单独的做用域,若是咱们声明的变量或者函数不是在一个函数内部也就是说在任何函数外声明,那么这些变量以及函数都属于全局做用域,全局做用域在js代码的任何地方都能访问。这样很容易跟其余js的变量起冲突。(若是一个变量声明屡次,js会以最后一次为准)。因此咱们用一个function把全部的变量包住,这样这个js里面的变量只有在这个function内部有效,不会污染全局做用域。node

而后自动调用这个函数,这里把window,跟Sizzle当参数传进来,由于函数内部须要使用这2个对象。这样函数内部就能在本身的做用域访问这2个对象,比从全局做用域访问这2个对象效率要高一些,而且代码清晰明了,知道须要哪些依赖。jquery

if(!sizzle){
throw new ReferenceError("没有引入Sizzle.js!");
}git

检查是否存在sizzle,若是不存在则抛出一个ReferenceError。注:这里sizzle为0、""、null、false、undefined都会抛出异常。github

function mq(selecter){
    return new MQuery(selecter);
}
function MQuery(selecter){
    console.log(typeof selecter);
    if(typeof selecter!=="string" &&!(selecter instanceof Element)){
       throw new TypeError("错误的参数类型,参数必须是字符串,或者Element对象");
    }
    if(typeof selecter=="string"){
       this.elements=sizzle(selecter);
    }
    else if(typeof selecter == "object" &&selecter instanceof Element){
       this.elements=[selecter];
    }
    this.length=this.elements.length;
 }

 

 

声明了2个函数,由于被一个匿名函数包着,因此这2个函数只有在这个函数内部或者这个函数内部的函数能访问。不会影响到全局做用域。ajax

Mquery是一个构造函数,为了区分构造函数与普通函数,构造函数首字母通常大写。这个构造函数必须接收一个字符串参数,若是参数类型不是字符串或者Element类型抛出类型错误异常。json

没错其实这个就是至关于mquery的$()函数,jquery的$()参数能接收选择器字符串、html标签字符串、dom对象。

注:咱们这里没有区分字符串是否为html标签,因此不支持传HTML标签字符串。

MQuery构造函数有2个对象elements与length,element对象是一个dom数组。length是这个数组的长度。

注:Sizzle()方法返回的就是一个dom数组。

    
MQuery.fn=MQuery.prototype;
//define hide MQuery.fn.hide=function(){ this.each(function(e){ e.style.display='none'; }); } //define show MQuery.fn.show=function(){ this.each(function(e){ e.style.display=''; }); } //define each MQuery.fn.each=function(callback,index){ var es=this.elements; for (var i = es.length - 1; i >= 0; i--) { callback(es[i],i); }; }

MQuery.fn=MQuery.prototype; 给MQuery声明了一个fn属性,并指向MQuery的原型,这里参考了jquery的作法。之后要给MQuery的原型新增方法能够直接经过fn。

这里给MQuery的原型定义了3个方法分别是hide、show、each。在原型上定义的方法能被每一个实例共享,因此全部MQuery对象都能拥有这3个方法。

jquery的$()获取页面元素常常是一次性获取多个,而后调用某个方法会对全部获取对象起做用。很简单就能实现这种效果,咱们经过sizzle获取的元素已是一个dom数组了并保存在elements数组里面,因此咱们只须要内部遍历element对象,依次调用就好了。

 

function mq(selecter){
    return new MQuery(selecter);
}
_window.$=mq;

可是jquery是经过$()来获取对象的,咱们能够这样模拟jquery的实现

内部声明一个mq函数,函数内部直接实例化MQuery。而后把这个函数暴露出去(经过赋值给window对象并取名为$)。这样就能够像jquery同样经过$()来获取MQuery对象。

_window.$.fn=_window.$.prototype=MQuery.prototype;

这里最后一句是把$的原型指向MQuery的原型,这样之后能够经过$.fn来扩展Mquery的原型,跟jquery的插件写法差很少。

本身的代码就贴到这,下一步是看一下Jquery的实现,而后对比一下差距在哪里。

先从网上下载最新的jquery2.x的源代码,下载地址:https://github.com/jquery/jquery。

jquery的源代码在src文件夹下。

打开jquery.js

define([
    "./core",
    "./selector",
    "./traversing",
    "./callbacks",
    "./deferred",
    "./core/ready",
    "./data",
    "./queue",
    "./queue/delay",
    "./attributes",
    "./event",
    "./event/alias",
    "./manipulation",
    "./manipulation/_evalUrl",
    "./wrap",
    "./css",
    "./css/hiddenVisibleSelectors",
    "./serialize",
    "./ajax",
    "./ajax/xhr",
    "./ajax/script",
    "./ajax/jsonp",
    "./ajax/load",
    "./effects",
    "./effects/animatedSelector",
    "./offset",
    "./dimensions",
    "./deprecated",
    "./exports/amd",
    "./exports/global"
], function( jQuery ) {

return jQuery;

});

jquery源代码使用了require.js。  感兴趣的能够去官网看看。http://www.requirejs.org/。

首先引用了core.js 打开core.js发现了jquery的主要函数Jquery定义。

源代码太长,因此把Jquery的结构贴下来。

define([
    "./var/arr",
    "./var/slice",
    "./var/concat",
    "./var/push",
    "./var/indexOf",
    "./var/class2type",
    "./var/toString",
    "./var/hasOwn",
    "./var/support"
], function( arr, slice, concat, push, indexOf, class2type, toString, hasOwn, support ) {

var
    document = window.document,
    version = "@VERSION",
    //首先定义了Jquery函数。
    jQuery = function( selector, context ) {
        return new jQuery.fn.init( selector, context );
    },

//定义Jquery的原型。
jQuery.fn = jQuery.prototype = {

    // The current version of jQuery being used
    jquery: version,
    //由于重写了JQuery的原型因此把constructor重新指向jQuery
    constructor: jQuery,
    // Start with an empty selector
    selector: "",
    // The default length of a jQuery object is 0
    length: 0,

    //方法定义
    toArray: function() {},
    get: function( num ) {},
    pushStack: function( elems ) {},
    each: function( callback, args ) {},

    map: function( callback ) {},

    slice: function() {},

    first: function() {},

    last: function() {},

    eq: function( i ) {},

    end: function() {},
    // For internal use only.
    // Behaves like an Array's method, not like a jQuery method.
    push: push,
    sort: arr.sort,
    splice: arr.splice
};

jQuery.extend = jQuery.fn.extend = function() {};

jQuery.extend({
    // Unique for each copy of jQuery on the page
    expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),

    // Assume jQuery is ready without the ready module
    isReady: true,

    error: function( msg ) {},

    noop: function() {},
    isFunction: function( obj ) {},
    isArray: Array.isArray,
    isWindow: function( obj ) {},
    isNumeric: function( obj ) {},
    isPlainObject: function( obj ) {},
    isEmptyObject: function( obj ) {},
    type: function( obj ) {},
    globalEval: function( code ) {},
    camelCase: function( string ) {},
    nodeName: function( elem, name ) {},
    each: function( obj, callback, args ) {},
    trim: function( text ) {},
    makeArray: function( arr, results ) {},
    inArray: function( elem, arr, i ) {},
    merge: function( first, second ) {},
    grep: function( elems, callback, invert ) {},
    map: function( elems, callback, arg ) {},
    guid: 1,
    proxy: function( fn, context ) {},
    now: Date.now,
    support: support
});
// Populate the class2type map
jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
});
function isArraylike( obj ) {}
return jQuery;
});
View Code
  1. 这里jquery定义原型方式与mquery不同。jquery是jquery.fn=jquery.prototype={};这样的好处是代码看起来更美观原型方法都定义在一个大括号里,可是这样至关于重写了jquery的原型,这样原型的constructor会指向Object而不是jquery。因此jquery又从新指定了constructor: jQuery。
  2. jquery定义了extend函数这里暂时无论。
  3. 关于jquery函数的定义jQuery = function( selector, context ) {return new jQuery.fn.init( selector, context );}这里比较有意思。jquery的内部new了一个jquery的原型上的一个构造函数。(固然不能直接new jquery不然会无限循环,也不能直接返回this,这样须要前台调用的时候new jquery(),这就是为何mquery定义了一个mq的函数,mq内部再new mquery()的缘由。)要想知道为何只有去看一下这个init函数了。
// Initialize a jQuery object
define([
    "../core",
    "./var/rsingleTag",
    "../traversing/findFilter"
], function( jQuery, rsingleTag ) {

// A central reference to the root jQuery(document)
var rootjQuery,

    // A simple way to check for HTML strings
    // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
    // Strict HTML recognition (#11290: must start with <)
    rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,

    init = jQuery.fn.init = function( selector, context ) {
        var match, elem;

        // HANDLE: $(""), $(null), $(undefined), $(false)
        if ( !selector ) {
            return this;
        }

        // Handle HTML strings
        if ( typeof selector === "string" ) {
            if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) {
                // 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[1] || !context) ) {

                // HANDLE: $(html) -> $(array)
                if ( match[1] ) {
                    context = context instanceof jQuery ? context[0] : context;

                    // scripts is true for back-compat
                    // Intentionally let the error be thrown if parseHTML is not present
                    jQuery.merge( this, jQuery.parseHTML(
                        match[1],
                        context && context.nodeType ? context.ownerDocument || context : document,
                        true
                    ) );

                    // HANDLE: $(html, props)
                    if ( rsingleTag.test( match[1] ) && 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[2] );

                    // Check parentNode to catch when Blackberry 4.6 returns
                    // nodes that are no longer in the document #6963
                    if ( elem && elem.parentNode ) {
                        // Inject the element directly into the jQuery object
                        this.length = 1;
                        this[0] = elem;
                    }

                    this.context = document;
                    this.selector = selector;
                    return this;
                }

            // HANDLE: $(expr, $(...))
            } else if ( !context || context.jquery ) {
                return ( context || rootjQuery ).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.context = this[0] = selector;
            this.length = 1;
            return this;

        // HANDLE: $(function)
        // Shortcut for document ready
        } else if ( jQuery.isFunction( selector ) ) {
            return typeof rootjQuery.ready !== "undefined" ?
                rootjQuery.ready( selector ) :
                // Execute immediately if ready is not present
                selector( jQuery );
        }

        if ( selector.selector !== undefined ) {
            this.selector = selector.selector;
            this.context = selector.context;
        }

        return jQuery.makeArray( selector, this );
    };

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

// Initialize central reference
rootjQuery = jQuery( document );

return init;

});
View Code

init函数实如今core文件夹下面的init.js。 至于上面那个答案参见第116行init.prototype = jQuery.fn; 这里把init函数的原型又指向了jquery的原型- - , 因此new init()是能够共享jquery原型的全部方法的。

这个init函数对应了Mquery的构造函数。 不过jquery能接受2个参数,第一个是选择器字符串,第二个是上下文,若是传了第二我的参数那么将会在这个上下文内查找想要的元素,效率会有所提高。这里没搞懂的是sizzle是按照css的语法来查找元素的,css是可以指定上下文的,例如:传入"#header .logo",应该已经指定上下文为#header了吧。应该不须要第二我的参数了吧 - -搞不懂。只有等功力够了研究一下sizzle了。

jquery的init函数内部很是复杂由于jquery的$()能支持css选择器、dom元素、html字符串、函数。而且对$("#id")有特殊判断,直接调用了document.getElementById,这里也没想明白为何不在sizzle内部处理呢?

相关文章
相关标签/搜索