模擬一個本身的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內部處理呢?

相關文章
相關標籤/搜索