顯示廣告
隱藏 ✕
看板 KnucklesNote
作者 Knuckles (站長 那克斯)
標題 [JS] 克服JS的奇怪部分 ch8 檢驗知名的框架與資源庫
時間 2016-12-10 Sat. 00:09:56


Udemy課程: JavaScript全攻略:克服JS的奇怪部分
https://www.udemy.com/javascriptjs/learn/v4/overview
上完第八章的心得筆記

章節8 檢驗知名的框架與資源庫

70. 深入瞭解原始碼:jQuery(一)

jQuery 是一個JS資源庫,用來簡化JS語法、解決跨瀏覽器問題

jQuery 主要是用來操作網頁的 DOM (Document Object Model)

例如這個網頁 http://knuckles.disp.cc/test/jsWeirdPart/ch8/index.html
HTML的部份為
<html>
	
<head>
	
</head>
	
<body>
	
	
<div id="main" class="container">
	
	
	
<h1>People</h1>
	
	
	
<ul class="people">
	
	
	
	
<li>John Doe</li>
	
	
	
	
<li>Jane Doe</li>
	
	
	
	
<li>Jim Doe</li>
	
	
	
</ul>
	
	
</div>
	
	
<script src="jquery-1.11.2.js"></script>
	
	
<script src="app.js"></script>
	
</body>
</html>
我們先載入了1.11.2版的 jQuery
然後在 app.js 中,用 jQuery 取得網頁中的三個 <li> 元件(element)
var q = $("ul.people li");
console.log(q);
在開發人員工具的顯示結果為
[圖]


jQuery 使用 $ 符號做為全域變數,若「$」被其他資源庫用掉了,也可以改用「jQuery」
例如 $("ul.people li") 也可以改為 jQuery("ul.people li")

意思是執行函數 $(),傳入一組類似css選擇器(selector)的字串 "ul.people li"
代表要尋找 class="people" 的 <ul> 元件,裡面的所有 <li> 元件

使用 console.log 將輸出結果顯示出來,為一個 jQuery.fn.init 的陣列物件
這個 init 陣列物件存了三個 <li> 元件
點一下 __proto__ 看一下他的原型為一個 jQuery 物件
可以看到裡面有一大堆的 jQuery 函數可以用

接著我們要看 jQuery 的原始碼,看他的運作機制
打開 jquery-1.11.2.js

一開始是一個立即執行函數 function( global, factory ){ ... }
(function( global, factory ) {

	
if ( typeof module === "object" && typeof module.exports === "object" ) {
	
	
// For CommonJS and CommonJS-like environments ...
	
	
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 ) {
//...
裡面是在做一些執行環境的檢查,然後執行傳入的函數 factory

接著傳入兩個輸入值,第一個 global 為全域物件,傳入
typeof window !== "undefined" ? window : this
意思是若 window 不是 undefined 的話傳 window,不然的話用 this 傳目前的全域物件

第二個輸入值 factory 傳入一個匿名函數 function( window, noGlobal ){ ... }
裡面建立了一大堆的函數,這邊的內容才是 jQuery 的主程式

factory 會在立即執行函數裡被執行,
所以會得到新的執行環境

一開始先建立一些變數,接著建立最重要的 jQuery 函數
var
	
version = "1.11.2",
	
jQuery = function( selector, context ) {
	
	
return new jQuery.fn.init( selector, context );
	
}
jQuery 為一個函數,第一個輸入值 selector 就是用來選取DOM的字串
函數內容只有用 new 運算子加上 jQuery.fn.init 後輸出

所以我們在 app.js 中使用 $() 輸入 selecter 字串後
輸出為 jQuery.fn.init
我們不需要使用 new 運算子加上 $(),因為 $() 只是一般函數
他的內容會用 new 執行函數建構子 jQuery.fn.init 後,再輸出建立的物件
這樣就不用每次使用 $() 時還要再加 new 了

接著往下尋找函數建構子 jQuery.fn.init

先找到
jQuery.fn = jQuery.prototype = { 
	
jquery: version,
	
constructor: jQuery,
	
//...
}
jQuery 雖然不是函數建構子,但也會有屬性 prototype
將屬性 prototype 取個別名 fn
這樣就不用每次都要打 prototype

這邊將 jQuery.fn 用物件實體語法產生一個自訂的物件來取代了

再來是實作了ch5提過的 extend 函數
同時加在了 jQuery 下與 jQuery.fn 下
jQuery.extend = jQuery.fn.extend = function() {
	
//...
}
函數 extend() 的功能是將第二個輸入值之後,每個輸入物件的所有成員
全部複製到第一個輸入值的物件中

如果只有一個輸入物件的話,就把這個輸入物件的成員加到jQuery這個函數物件上

接下來就是執行 extend 輸入一個物件實體,將一堆成員加進jQuery
jQuery.extend({
	
//...
});


71. 深入瞭解原始碼:jQuery(二)


繼續往下看到這個 Sizzle
var Sizzle =
/*!
 * Sizzle CSS Selector Engine v2.2.0-pre
 * http://sizzlejs.com/
 *
 * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 *
 * Date: 2014-12-16
 */
(function( window ) {

	
//...

return Sizzle;

})( window );

jQuery.find = Sizzle;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.pseudos;
jQuery.unique = Sizzle.uniqueSort;
jQuery.text = Sizzle.getText;
jQuery.isXMLDoc = Sizzle.isXML;
jQuery.contains = Sizzle.contains;
這裡又使用了一個立即執行函數建立了新的執行環境
這一大段是將另一個資源庫 Sizzle CSS Selector Engine 整個加了進來
然後將產生的 Sizzle 物件再加進 jQuery 的成員
用來將 selector 字串轉為 HTML 上的元件


再繼續往下翻到
// Initialize a jQuery object

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

	
// Use the correct document accordingly with window argument (sandbox)
	
document = window.document,

	
// 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 ) {

	
	
//...

	
	
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 );
終於找到了函數建構子 jQuery.fn.init

比較特別的是這個函數建構子在最後使用了
return jQuery.makeArray( selector, this );

函數建構子若不加 return,JS會自動輸出建立的物件 this
這邊加了 return,是為了將 this 用 makeArray 做些加工後再輸出


接下來的這行有點難懂
init.prototype = jQuery.fn;

其實就是把前面在 jQuery 的原型 jQuery.fn 上建立的一堆成員
覆寫到函數建構子 init 的原型上

因為我們不想每次使用 jQuery 函數時還要再加 new
所以我們是在 jQuery 函數中使用 new 加上建構子 jQuery.fn.init
產生的物件原型再指到 jQuery 函數的原型上
這樣就可以用一般函數來建立物件,並且讓物件有原型可以使用


72. 深入瞭解原始碼:jQuery(三)

前面的例子只有用 jQuery 取得網頁上的元件
接下來我們使用 jQuery 提供的函數來改變元件的 class 屬性
$("ul.people").addClass("newclass").removeClass("people");
使用 addClass() 將 <ul> 元件的 class 屬性加上 "newclass"
接著用 removeClass() 將 class 屬性的 people 移除

使用開發者工具看,class的確由 "people" 變成了 "newclass"
[圖]


不過為什麼函數 removeClass() 可以直接用 . 接在函數 addClass() 後呢
因為這是 jQuery 的一個模式叫做方法鏈結(method chaining)

方法鏈結: 使用 obj.method1().method2() 時,
兩個函數的 this 都是指向一開始的物件 obj

來看看 jQuery 怎麼做的
在 jquery-1.11.2.js 中搜尋 addClass
jQuery.fn.extend({
	
addClass: function( value ) {

	
	
//...

	
	
return this;
	
},

	
removeClass: function( value ) {

	
	
//...

	
	
return this;
	
},
	
//...
找到這一段,使用 extend 在 jQuery 函數的原型加上的 addClass
最後是 return this;

所以當我們執行一個 jQuery 物件的成員函數 addClass 時
這物件本身沒有這個成員函數,所以會在原型鍵上找到 addClass
而 addClass 最後又回傳 this 代表一開始的物件本身
所以後面又可以再接另一個 jQuery 的成員函數


最後來看一下 jQuery 檔案的最後一段
var
	
// Map over jQuery in case of overwrite
	
_jQuery = window.jQuery,

	
// Map over the $ in case of overwrite
	
_$ = window.$;

jQuery.noConflict = function( deep ) {
	
if ( window.$ === jQuery ) {
	
	
window.$ = _$;
	
}

	
if ( deep && window.jQuery === jQuery ) {
	
	
window.jQuery = _jQuery;
	
}

	
return jQuery;
};

// Expose jQuery and $ identifiers, even in
// AMD (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
// and CommonJS for browser emulators (#13566)
if ( typeof noGlobal === strundefined ) {
	
window.jQuery = window.$ = jQuery;
}

return jQuery;

}));
最後是要將 jQuery 與 $ 設定為全域變數
但為了避免蓋掉本來的全域變數
所以先把全域上的 window.jQuery 和 window.$ 備份在 _jQuery 和 _$

建立一個函數 noConflict
若要恢復成原本的 $ 和 jQuery 時可執行 $.noConflict()
然後用一個新的變數名稱來儲存回傳的 jQuery

最後就是將 jQuery 設定成全域變數
	
window.jQuery = window.$ = jQuery;





--
※ 作者: Knuckles 時間: 2016-12-10 00:09:56
※ 編輯: Knuckles 時間: 2016-12-13 16:33:42
※ 看板: KnucklesNote 文章推薦值: 0 目前人氣: 0 累積人氣: 730 
分享網址: 複製 已複製
r)回覆 e)編輯 d)刪除 M)收藏 ^x)轉錄 同主題: =)首篇 [)上篇 ])下篇