從本篇開始會陪大家一起從零開始走一遍 jQuery 的奇妙旅途,在整個系列的實踐中,我們會把 jQuery 的主要功能子產品都了解和實作一遍。
這會是一段很長的曆程,但也會很有意思 —— 作為前端領域的經典之作,jQuery 裡有着太多奇思妙想,如果能夠深入了解它,對于我們穩固js基礎、提升前端大法技能來說大有裨益。
另外,本系列的相關代碼均可以從 我的github 上擷取到(DIY-A-jQuery-master)。
1. 免 new 實作
我們在使用很多插件的時候,都需要使用 new XXX() 的寫法來執行個體化一個引用:
var list = new Slip(document.getElementById('slip'), {
//options
});
jQuery 同樣作為一個面向對象的工具庫,在我們建立一個執行個體時卻無需使用 new 文法,節省了一些代碼量:
var $div = $('div');
//不需要如下寫法:
//var $div = new $('div');
這種便捷的形式依賴了工廠模式,其實作非常簡單,把 new 封裝在庫内即可,讓每次調用 jQuery() 時自行在内部進行一次執行個體化:
(function() {
var _jQuery = window.jQuery,
_$ = window.$;
var version = "0.0.1",
jQuery = function(selector) {
console.log(document.querySelector(selector))
};
jQuery.prototype = {
jquery: version,
constructor: jQuery
};
window.$ = window.jQuery = function(selector) {
return new jQuery(selector); //notice here~
};
})();
留意這裡我們走的 IIFE 形式,讓 jQuery 代碼庫形成自己的作用域,避免污染外部變量。
于是乎以上就是咱寫的第一個 JQ 雛形,簡單跑一下:
<div></div>
<script>
var $div = $('div'); //<div></div>
console.log($div.jquery); //0.0.1
</script>
别忘了後續我們還希望能通過 $.extend / $.fn.extend 來擴充 JQ 的靜态方法和原型方法,我們把出口方法抽出來增加這個 extend 的API:
function Factory(selector){ //抽出構造函數
return new jQuery(selector);
}
Factory.fn = jQuery.prototype;
Factory.extend = Factory.fn.extend = function(){
console.log(this)
};
window.$ = window.jQuery = init;
這樣我們也能直接通過 $.fn.jquery 來擷取目前 JQ 版本号了。
如果希望可以通過 $.prototype 直接通路 jQuery 的原型對象,再修改下這句代碼即可:
Factory.prototype = Factory.fn = jQuery.prototype;
2. 寫法優化
事實上我們不太喜歡再寫多一個備援的 Factory 構造函數來作為 window.jQuery 的引用,也不喜歡(在子產品内部)使用 Factory.extend() 來擴充 JQ,它聽起來和 JQ 沒有半毛錢關系。
如果可以,直接把 jQuery 方法作為接口輸出,且在子產品内部能以 jQuery.extend() 的形式來調用擴充接口,這樣的形式更佳。
也就是說我們希望代碼應該是這樣寫的:
jQuery.extend = jQuery.fn.extend = function(){
console.log(this)
};
window.jQuery = window.$ = jQuery;
“直接把 jQuery 方法作為接口輸出”意味着我們要把工廠模式挪入 jQuery 方法中,顯然我們不能這樣改:
var version = "0.0.1",
jQuery = function (selector) {
return new jQuery(selector);
};
這樣死循環了,調用棧會直接爆掉~
于是我們可以抽出一個 init 方法來做初始化處理(比如簡單地注入檢索到的元素到JQ對象中),把 jQuery 方法中的内容更改為 return new init(selector) 就行了。
保證兩個前提:
1. this 指向 jQuery 上下文
2. 其原型指向 jQuery 的原型
第一點很好了解,友善我們直接在 init 方法中通過對 this 的操作來處理 JQ 執行個體上下文,如:
//注入元素到 JQ 執行個體對象中
this[0] = elem;
this.length = 1;
針對這點,我們不妨把 init 作為 jQuery.prototype 的屬性方法來實作:
var version = "0.0.1",
jQuery = function(selector) {
return new jQuery.fn.init() //修改點1
};
//友善我們使用 jQuery.fn 來引用 jQuery 原型對象
jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery
};
//修改點2 —— init 作為原型方法,確定 this 指向正确
jQuery.fn.init = function( selector ) {
if ( !selector ) {
return;
} else {
var elem = document.querySelector( selector );
if ( elem ) {
this[0] = elem;
this.length = 1;
}
}
};
jQuery.extend = jQuery.fn.extend = function(){
console.log(this)
};
window.$ = window.jQuery = jQuery;
然而這時候存在一個問題 —— JQ執行個體對象無法通路原型屬性/方法:
var $div = $('div');
console.log($div.jquery); //undefined
原因很簡單——我們還未實作上述提及的第二個前提——“init 原型指向 jQuery 的原型”
在 js 中,執行個體的内部原型(__proto__)總是指向其構造函數的原型(prototype),而經過我們這番修改,JQ執行個體的構造函數已經變成了 jQuery.fn.init ,而其原型并非指向 jQuery 的原型,這導緻 JQ 執行個體無法順其原型鍊爬取到 jQuery.prototype。
要實作這個條件,隻需要做小小改動——把 jQuery.fn.init 的原型指向 jQuery 的原型(jQuery.prototype / jQuery.fn)即可:
var init = jQuery.fn.init = function( selector ) {
if ( !selector ) {
return;
} else {
var elem = document.querySelector( selector );
if ( elem ) {
this[0] = elem;
this.length = 1;
}
}
};
init.prototype = jQuery.fn; //修改點
這裡貼下完整代碼:
var version = "0.0.1",
jQuery = function(selector) {
return new jQuery.fn.init(selector)
};
jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery
};
var init = jQuery.fn.init = function( selector, context, root ) {
if ( !selector ) {
return;
} else {
var elem = document.querySelector( selector );
if ( elem ) {
this[0] = elem;
this.length = 1;
}
}
};
init.prototype = jQuery.fn;
jQuery.extend = jQuery.fn.extend = function(){
console.log(this)
};
window.$ = window.jQuery = jQuery;
3. 鍊式寫法實作
JQ 裡一個很大的亮點是,它支援鍊式寫法,調用起來非常友善:
$('div').removeClass('hide').css('width', '100px')
其實作其實非常簡單 —— 確定每個調用的方法尾部均傳回自身即可,這裡我們新增兩個執行個體方法做示例:
jQuery.fn = jQuery.prototype = {
jquery: version,
constructor: jQuery,
setBackground: function(){
this[0].style.background = 'yellow';
return this //傳回自身引用
},
setColor: function(){
this[0].style.color = 'blue';
return this //傳回自身引用
}
};
var init = jQuery.fn.init = function( selector ) {
if ( !selector ) {
return this;
} else {
var elem = document.querySelector( selector );
if ( elem ) {
this[0] = elem;
this.length = 1;
}
return this;
}
};
鍊式調用:
<div>hello world</div>
<script>
var $div = $('div');
$div.setBackground().setColor();
</script>
效果如下,杠杠的:
4. 沖突處理
存在某些情況,使用者可能并不想拿 window.$ 甚至 window.jQuery 來引用 JQ 接口,或者已經有其它庫使用了 window.$ 這個變量,如果我們粗暴地改變其引用肯定是不合理的。
so 我們來實作 JQ 中沖突處理的靜态接口 jQuery.noConflict,這意味着在代碼段開始時,就得先儲存下目前 window.$ 和 window.jQuery 兩個變量:
(function(){
var _jQuery = window.jQuery,
_$ = window.$;
//var version = "0.0.1"......
})()
然後是實作 noConflict 方法,退耕還林,把儲存的變量吐回去即可:
(function(){
var _jQuery = window.jQuery,
_$ = window.$;
//var version = "0.0.1"......
jQuery.noConflict = function( deep ) {
//確定window.$沒有再次被改寫
if ( window.$ === jQuery ) {
window.$ = _$;
}
//確定window.jQuery沒有再次被改寫
if ( deep && window.jQuery === jQuery ) {
window.jQuery = _jQuery;
}
return jQuery; //傳回 jQuery 接口引用
};
window.jQuery = window.$ = jQuery;
})();
deep 參數類型為 Boolean,若為真,表示要求連window.jQuery 變量都需要吐回去。
留意在尾部我們傳回了 jQuery 的接口引用,這意味着我們可以以
var $$$ = jQuery.noConflict()
的形式來把它賦予新的變量。
接着在外部運作如下代碼:
<head>
<meta charset="UTF-8">
<title>DIY A JQ</title>
<script>
$ = 'old $';
jQuery = 'old JQ'
</script>
<script src="jQuery.js"></script>
</head>
<body>
<div>hello world</div>
<script>
var $div = $('div');
$div.setBackground().setColor();
var $$$ = $.noConflict(true);
console.log($);
console.log(jQuery);
console.log($$$);
</script>
輸出如下:
第一篇就寫到這裡,相關的代碼可以從 我的github 上下載下傳(DIY-A-jQuery-master)到。
下次我們會試着實作子產品化的寫法,并與時俱進,改用 ES6解構指派文法 + Rollup 來進行打包以減少可能存在的備援代碼段。
共勉~
轉自:http://www.cnblogs.com/vajoy/p/5510743.html
更多參考:
jQuery: 操作select option方法集合
jQuery: 插件開發模式詳解 $.extend(), $.fn, $.widget()
AngularJS jQuery 共存法則
下一篇:jQuery:從零開始,DIY一個jQuery(2)
本文轉自: jQuery:從零開始,DIY一個jQuery(1)