目标:模拟jquery的$符号選擇元素并調用hide與show方法
mquery.js代碼
/*¡
* 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代碼
<!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标簽。
注:mquery.js依賴于sizzle.js,請先去js官網下載下傳最新的sizzle.js。
代碼解析
首先所有代碼都包括在(function(_window,sizzle){})(window,Sizzle);裡面。因為js是函數作用域,也就是說每個函數都有自己單獨的作用域,如果我們聲明的變量或者函數不是在一個函數内部也就是說在任何函數外聲明,那麼這些變量以及函數都屬于全局作用域,全局作用域在js代碼的任何地方都能通路。這樣很容易跟其他js的變量起沖突。(如果一個變量聲明多次,js會以最後一次為準)。是以我們用一個function把所有的變量包住,這樣這個js裡面的變量隻有在這個function内部有效,不會污染全局作用域。
然後自動調用這個函數,這裡把window,跟Sizzle當參數傳進來,因為函數内部需要使用這2個對象。這樣函數内部就能在自己的作用域通路這2個對象,比從全局作用域通路這2個對象效率要高一些,并且代碼清晰明了,知道需要哪些依賴。
if(!sizzle){
throw new ReferenceError("沒有引入Sizzle.js!");
}
檢查是否存在sizzle,如果不存在則抛出一個ReferenceError。注:這裡sizzle為0、""、null、false、undefined都會抛出異常。
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個函數隻有在這個函數内部或者這個函數内部的函數能通路。不會影響到全局作用域。
Mquery是一個構造函數,為了區分構造函數與普通函數,構造函數首字母一般大寫。這個構造函數必須接收一個字元串參數,如果參數類型不是字元串或者Element類型抛出類型錯誤異常。
沒錯其實這個就是相當于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
- 這裡jquery定義原型方式與mquery不一樣。jquery是jquery.fn=jquery.prototype={};這樣的好處是代碼看起來更美觀原型方法都定義在一個大括号裡,但是這樣相當于重寫了jquery的原型,這樣原型的constructor會指向Object而不是jquery。是以jquery又重新指定了constructor: jQuery。
- jquery定義了extend函數這裡暫時不管。
- 關于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*(<[wW]+>)[^>]*|#([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内部處理呢?