1. 程式人生 > >jQuery原始碼分析(一)

jQuery原始碼分析(一)

生成jQuery物件

var global = typeof window !== "undefined" ? window : this;
var factory //line 40 第二個引數
( function( global, factory ) {

	"use strict";

	if ( typeof module === "object" && typeof module.exports === "object" ) {

		// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery. // For environments that do not have a `window` with a `document` // (such as Node.js), expose a factory as module.exports. // This accentuates the need for the creation of a real `window`. // e.g. var jQuery = require("jquery")(window); // See ticket #14549 for more info.
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 } )(global,
factory);

如果當前容器沒用window物件,那麼將this作為最父層。factory()函式將會返回jQuery物件。

在CommonJS 或者 CommonJS-like 的環境下 window 物件是沒有問題的;如果一個執行環境,它的“window”物件沒有“document”的屬性,例如Node.js。那麼需要暴露一個factory()方法,並將真實的“window”物件傳入。var jQuery = require(“jquery”)(window);

通過下面這個程式碼片段我們可以驗證,在nodejs中沒有 window物件

router.get('/jquery/window', function(req, res, next) {
    console.log("window:" + typeof window);
    //window:undefined
    res.end();
});

定義公共屬性和工具方法

line:48 - line:144

var arr = [];

var document = window.document;

var getProto = Object.getPrototypeOf;

var slice = arr.slice;

var concat = arr.concat;

var push = arr.push;

var indexOf = arr.indexOf;

var class2type = {};

var toString = class2type.toString;

var hasOwn = class2type.hasOwnProperty;

var fnToString = hasOwn.toString;

var ObjectFunctionString = fnToString.call(Object);

var support = {};

通過這種方式避免後面呼叫過程中一行程式碼過長

isFunction 判斷是否是函式

var isFunction = function isFunction( obj ) {
	// Support: Chrome <=57, Firefox <=52
	// In some browsers, typeof returns "function" for HTML <object> elements
	// (i.e., `typeof document.createElement( "object" ) === "function"`).
	// We don't want to classify *any* DOM node as a function.
	return typeof obj === "function" && typeof obj.nodeType !== "number";
};

在部分瀏覽器中typeof document.createElement( “object” ) === “function”,因此要追加判斷。我自己測試了一下,chrome、firefox、IE8+都不存在這種現象。

isWindow 判斷物件是否是window物件

var isWindow = function isWindow( obj ) {
	// window.window === window;
	return obj != null && obj === obj.window;
};

對於上面例子中var isWindow = function isWindow( obj ) {//…};這種寫法,做點額外補充。所有的函式都有一個name屬性,該屬性儲存的是該函式名稱的字串。沒有名字的函式(匿名函式)依然有name屬性,只是屬性值為空字串。參見下面的例子:

var a = function(){}
console.log(a.name);
//IE: ""
//chrome: "a"
var b = function b(){}
console.log(b.name)
//IE: "b"
//chrome: "b"
var c = function cc(){}
console.log(c.name)
//IE: "cc"
//chrome: "cc"

匿名函式的name屬性為空字串,chrome做了額外的事情。

DOMEval 全域性作用域內的求值操作

var preservedScriptAttributes = {
	type: true,
	src: true,
	noModule: true
};
function DOMEval( code, doc, node ) {
	doc = doc || document;

	var i,
		script = doc.createElement( "script" );

	script.text = code;
	if ( node ) {
		for ( i in preservedScriptAttributes ) {
			if ( node[ i ] ) {
				script[ i ] = node[ i ];
			}
		}
	}
	doc.head.appendChild( script ).parentNode.removeChild( script );
}

javascript的一大特點就是可以在執行時動態的解釋並執行程式碼。
DOMEval()方法在head部分插入了需要執行的js指令碼,該指令碼會立即執行,然後從head部分移除掉了,保持頁面的整潔,這也是為什麼這個方法的名字叫做DOMEval而不是addScript。

這段程式碼一般用於動態執行從伺服器端返回的程式碼。這種情況一般總是會要求程式碼在全域性作用域內執行。

var url,params;
$.get(url,params,function(code){
    //var code="alert(1)";
    var script = document.createElement('script');
    script.text = code
    document.head.appendChild(script).parentNode.removeChild(script)
});

通過這個例子可以看到alert語句被執行了。

toType 判斷物件型別

var class2type = {}; //line:62
function toType( obj ) {
	if ( obj == null ) {
		return obj + "";
	}
	// Support: Android <=2.3 only (functionish RegExp)
	return typeof obj === "object" || typeof obj === "function" ?
		class2type[ toString.call( obj ) ] || "object" :
		typeof obj;
}
//line 478
// Populate the class2type map
// 構成物件的鍵值對
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
function( i, name ) {
	class2type[ "[object " + name + "]" ] = name.toLowerCase();
} );

class2type的值如圖:
class2type

在方法toType中有一個if判斷,用來處理obj等於null的情況,下面的例子現實了null物件的強制轉換結果:

var a = null;
// null
var b = null + "";
// "null"
typeof a
// object
typeof b
// string

同樣的由於undefined==null值為true,所以也會走這個分支。

isArrayLike 判斷物件是不是一個類陣列結構

//line:482
function isArrayLike(obj) {
	// Support: real iOS 8.2 only (not reproducible in simulator)
	// `in` check used to prevent JIT error (gh-2145)
	// hasOwn isn't used here due to false negatives
	// regarding Nodelist length in IE
	var length = !!obj && "length" in obj && obj.length,
		type = toType(obj);

	if (isFunction(obj) || isWindow(obj)) {
		return false;
	}

	return type === "array" || length === 0 ||
		typeof length === "number" && length > 0 && (length - 1) in obj;
}

通過這個函式我們能夠知道,如果我們希望建立一個類陣列的物件,它應該滿足以下幾個條件:

  1. 物件不為null或者undefined
  2. 物件包含length屬性
  3. 物件不是一個函式也不是window物件
  4. 物件型別為array或者length值為0;如果不滿足這兩點那麼該物件一定要滿足length是數值型別,並且length的值大於1,同時(length-1)是該物件的屬性之一
    根據規則4,我們可以構造如下物件:
var obj = {
    0:'name',
    1:'age',
    length:2
    
}
isArrayLike(obj);
// true

實際上String物件就是一個典型的類陣列物件

Object('str')
//String {"str"}
//    0: "s"
//    1: "t"
//    2: "r"
//    length: 3
//    __proto__: String
//    [[PrimitiveValue]]: "str"
isArrayLike(Object('str'));
// true

jquery 利用正則表示式去除空格

var
	// 版本號
	version = "3.3.1",
	// 定義一個jQuery的本地副本
	jQuery = function( selector, context ) {
		// The jQuery object is actually just the init constructor 'enhanced'
		// Need init if jQuery is called (just allow error to be thrown if not included)
		return new jQuery.fn.init( selector, context );
	},
	// Support: Android <=4.0 only
	// Make sure we trim BOM and NBSP
	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;

其中\uFEFF屬於BOM標記,用來表示當前檔案的字元編碼。
BOM(Byte Order Mark),位元組順序標記,出現在文字檔案頭部,Unicode編碼標準中用於標識檔案是採用哪種格式的編碼。
BOM採用UTF-8編碼。幾乎所有的文字編輯軟體都可以顯示並編輯UTF-8編碼的檔案。但是很遺憾,其中很多軟體的表現並不理想。

常見的bug是:

  1. js檔案儲存為非utf-8編碼時(例如GBK)檔案開頭會出現亂碼
  2. html文件在瀏覽器中開啟時,頂部會出現一行空白

其中 \xa0 代表非連續空白符。我們通常所用的空格是\x20,是在標準ASCII可見字元 0x20~0x7e 範圍內。而 \xa0 屬於 latin1 (ISO/IEC_8859-1)中的擴充套件字符集字元,代表空白符nbsp(non-breaking space)。

rtrim 的正則表示式是一種相容寫法,用於去除字串首尾的空格。

靜態方法

line:297-line:472

expando jquery的唯一標示

// Unique for each copy of jQuery on the page
// jquery的唯一標示。資料快取,ajax,事件機制都用到了這個。後面集中分析
expando: "jQuery" + (version + Math.random()).replace(/\D/g, ""),

isReady 文件是否載入完畢

line 3878 列出了jquery DOM載入的相關方法,這裡暫不做討論

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

error 呼叫原生的Error類

error: function (msg) {
	throw new Error(msg);
},

noop

指向一個什麼都不做的函式,我們經常可以看到某個元件的預設回撥被設為$.noop

noop: function () {},

isPlainObject 判斷是否為純粹物件

var getProto = Object.getPrototypeOf;

var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;
var fnToString = hasOwn.toString;
var ObjectFunctionString = fnToString.call( Object );
// "function Object() { [native code] }"
isPlainObject: function (obj) {
	var proto, Ctor;

	// Detect obvious negatives
	// Use toString instead of jQuery.type to catch host objects
	// 使用 toString 而不是jQuery.type來捕獲宿主物件,這是因為type也是呼叫了toString方法,參見jQuery.type()
	//jQuery.type = toType; //line 10291 toType方法前面已經介紹過
	if (!obj || toString.call(obj) !== "[object Object]") {
		return false;
	}

    //獲取物件的原型
	proto = getProto(obj);

	// Objects with no prototype (e.g., `Object.create( null )`) are plain
	// 如果一個物件是通過Object.create( null )來建立的話,那麼它的原型為空,相比於用{}來建立的物件,它的開銷也就更小。
	// 所以如果我們需要一個 json物件僅用來儲存引數,可以使用這個方法
	if (!proto) {
		return true;
	}

	// Objects with prototype are plain iff they were constructed by a global Object function
	// 如果一個物件是是由全域性的Object函式來建立的,那麼它是純粹物件
	Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
	return typeof Ctor === "function" && fnToString.call(Ctor) === ObjectFunctionString;
},

簡單來說,一個物件只有通過{}直接建立、 new Object() 或者通過 Object.create(null) 方式建立那麼它才是一個純粹的物件。

isEmptyObject 是否是空物件

isEmptyObject: function (obj) {

	/* eslint-disable no-unused-vars */
	// See https://github.com/eslint/eslint/issues/6125
	var name;

	for (name in obj) {
		return false;
	}
	return true;
},

只要物件包含至少一個屬性那麼就返回false,即不是空物件。

globalEval 全域性作用域內的求值操作

參見DOMEval

// Evaluates a script in a global context
globalEval: function (code) {
	DOMEval(code);
},

each 遍歷方法

首先我們看一下我們是如何使用$.each介面的:

// 回撥函式中第一個引數是當前元素在陣列中的索引,第二個引數是遍歷到的當前元素
$.each(arrObj,function(i,item){
    if(//condition){
        // 通過return false 可以跳出當前迴圈
        // return false;
    }
    // TODO: 業務邏輯
});
each: function (obj, callback) {
	var length, i = 0;

	if (isArrayLike(obj)) {
		length = obj.length;
		for (; i < length; i++) {
			if (callback.call(obj[i], i, obj[i]) === false) {
				break;
			}
		}
	} else {
		for (i in obj) {
			if (callback.call(obj[i], i, obj[i]) === false) {
				break;
			}
		}
	}
	return obj;
},

通過原始碼,我們可以清晰的看到對於類陣列物件$.each直接遍歷數組裡面的每個元素,而其他物件則是遍歷屬性,如果回撥函式返回false則跳出迴圈。

trim 過濾空格

// Support: Android <=4.0 only
trim: function (text) {
	return text == null ?
		"" :
		(text + "").replace(rtrim, "");
},

關於中間用到的正則表示式,前面已經詳細闡述過了。這裡面有一個小技巧,即通過(text + "")呼叫toString方法。

merge 合併兩個陣列內容到第一個陣列

將第二個陣列物件合併到第一個陣列物件上

// Support: Android <=4.0 only, PhantomJS 1 only
// push.apply(_, arraylike) throws on ancient WebKit
merge: function (first, second) {
    // 將length轉成數值型別
	var len = +second.length,
		j = 0,
		i = first.length;

	for (; j < len; j++) {
		first[i++] = second[j];
	}

	first.length = i;

	return first;
},

makeArray 將類陣列物件轉變為真實陣列

首先看一下介面的使用效果:

var obj = Object('str');
var objArr = $.makeArray(obj);
// (3) ["s", "t", "r"]
obj instanceof Array
// false
objArr instanceof Array
// true

原始碼:

// results is for internal usage only
makeArray: function (arr, results) {
	var ret = results || [];

	if (arr != null) {
		if (isArrayLike(Object(arr))) {
			jQuery.merge(ret,
				typeof arr === "string" ? [arr] : arr
			);
		} else {
			push.call(ret, arr);
		}
	}

	return ret;
},

Object(arr)可以將原始型別轉變為物件型別(裝箱操作)

Object(true)
//Boolean {true}

inArray 返回陣列中指定元素的索引值

$.inArray( value, array [, fromIndex ] )
引數 描述
value 任意型別 用於查詢的值。
array Array型別 指定被查詢的陣列。
fromIndex 可選。Number型別 指定從陣列的指定索引位置開始查詢,預設為 0
inArray: function (elem, arr, i) {
	return arr == null ? -1 : indexOf.call(arr, elem, i);
},

indexOf可以接收兩個引數,第二引數表示指定開始查詢的位置,我們一般很少使用第二個引數

var s = 'aabv'
s.indexOf('a')
// 0
s.indexOf('a',1)
// 1
s.indexOf('a',2)
// -1

grep 過濾原始陣列

$.grep( array, function [, invert ] )
引數 描述
array Array型別將被過濾的陣列。
function Function型別 指定的過濾函式。grep()方法為function提供了兩個引數:其一為當前迭代的陣列元素,其二是當前迭代元素在陣列中的索引。
invert 可選。 Boolean型別 預設值為false,指定是否反轉過濾結果。如果引數invert為true,則結果陣列將包含function返回false的所有元素。
grep: function (elems, callback, invert) {
	var callbackInverse,
		matches = [],
		i = 0,
		length = elems.length,
		// 回撥函式的期望結果,有invert預設為false,所以callbackExpect預設為true
		// 換言之,grep函式保留滿足回撥函式的資料
		callbackExpect = !invert;

	// Go through the array, only saving the items
	// that pass the validator function
	for (; i < length; i++) {
	    // callbackInverse 儲存回撥的非值
		callbackInverse = !callback(elems[i], i);

		if (callbackInverse !== callbackExpect) {
			matches.push(elems[i]);
		}
	}

	return matches;
},

grep函式返回了一個新的陣列物件,因此原陣列的值不會改變。這裡有一個非常巧妙的地方就是設定了callbackExpect變數,這樣就避免了設定兩個陣列分別儲存希望保留的資料和希望排除的資料。

var a = [1,0, null,undefined,'true','false',4];
var b = $.grep(a,function(item,i){
    return item;
});
console.log(b);

b的輸出結果是什麼?

map

$.map( object, callback )
引數 描述
object Array/Object型別 指定的需要處理的陣列或物件。
callback Function型別 指定的處理函式。
// arg is for internal usage only
map: function (elems
            
           

相關推薦

jQuery原始碼分析

生成jQuery物件 var global = typeof window !== "undefined" ? window : this; var factory //line 40 第二個引數 ( function( global, factory ) { "use strict

Flume NG原始碼分析基於靜態properties檔案的配置模組

日誌收集是網際網路公司的一個重要服務,Flume NG是Apache的頂級專案,是分散式日誌收集服務的一個開源實現,具有良好的擴充套件性,與其他很多開源元件可以無縫整合。搜了一圈發現介紹Flume NG的文章有不少,但是深入分析Flume NG原始碼的卻沒有。準備寫一個系列分析一下Flume NG的

GCC原始碼分析——介紹與安裝

原文連結:http://blog.csdn.net/sonicling/article/details/6702031     上半年一直在做有關GCC和LD的專案,到現在還沒做完。最近幾天程式設計的那臺電腦壞了,所以趁此間隙寫一點相關的分析和

Glide原始碼分析從用法來看之with方法

繼續啃原始碼,用過Glide的人,肯定都覺得它好好用,我們一般只需要幾行程式碼,就可以達到我們想要的效果,可以在這個背後是什麼呢?就需要我們來看了。 我一般看原始碼,我喜歡先從用法來看,然後一步一步的再細扣,所以就先從用法來看Glide的整體流程。 用過Glide的人,用下面這段

zigbee 之ZStack-2.5.1a原始碼分析

先看main, 在檔案Zmain.c裡面 main osal_init_system(); osalInitTasks(); ... ... SampleApp_Init( taskID ); // 使用者定義的任務

Docker Client原始碼分析

主要內容: Docker Client在Docker中的定位,以及Docker Client原始碼的初步分析。 本文選取Docker拆分為DockerCE(社群版)和DockerEE(企業版)之後的Docker-CE的第一個穩定版本v17.06.0-ce。 https://github.com/docker

Hibernate使用及原始碼分析

Hibernate使用及原始碼分析(一) 本篇文章主要通過hibernate初級使用分析一下原始碼,只是給初學者一點小小的建議,不喜勿噴,謝謝! hibernate環境搭建 簡單使用 原始碼走讀 一 hibernate環境搭建 這裡直接

SpringCloud原始碼分析--客戶端搭建

一、前言 上一節的註冊中心搭建完成了,本節開始搭建客戶端,其實對於springcloud的Eureka註冊中心而言,他本身就是服務端也是客戶端,我們上節待見服務端註冊中心的時候,已經通過配置來設定其不向自己註冊,和不去檢索服務的功能,保持了其作為服務註冊中心的相對的功能單一性。 二、pom檔案

Vue原始碼分析:入口檔案

Vue原始碼分析(一):入口檔案   首先開啟命令列,從github下載原始碼,下載到自己的工作目錄。 git clone https://github.com/vuejs/vue.git   這裡我下載的是2.5.17版本的,vue 原始碼是由各種模組用 rollup 工具

okhttp原始碼分析——基本流程超詳細

1.okhttp原始碼分析(一)——基本流程(超詳細) 2.okhttp原始碼分析(二)——RetryAndFollowUpInterceptor過濾器 3.okhttp原始碼分析(三)——CacheInterceptor過濾器 4.okhttp原始碼分析(四)——Conn

spring事務管理原始碼分析配置和事務增強代理的生成流程

在本篇文章中,將會介紹如何在spring中進行事務管理,之後對其內部原理進行分析。主要涉及 @EnableTransactionManagement註解為我們做了什麼? 為什麼標註了@Transactional註解的方法就可以具有事務的特性,保持了資料的ACID特性?spring到底是如何具有這樣

Android系統播放器MediaPlayer原始碼分析

前言 對於MediaPlayer播放器的原始碼分析內容相對來說比較多,會從Java->JNI->C/C++慢慢分析,後面會慢慢更新。另外,部落格只作為自己學習記錄的一種方式,對於其他的不過多的評論。 MediaPlayerDemo public class MainA

Android7.1 [Camera] Camera Hal 原始碼分析

原始碼平臺:rk3399   命令列ls看下原始碼的結構 hardware/rockchip/camera/CameraHal: lib目錄 原始碼的檔案看起來有點多,我們看看Android.mk檔案, 這些檔案最終編譯成camera.rk30bo

Cat原始碼分析:Client端

前言 cat的Client端所做的工作就是收集埋點資訊,將埋點資訊處理成messageTree,放到傳送佇列中,在啟動另一個執行緒,非同步消費佇列,進行訊息的傳送。 本文涉及到三個內容: 客戶端初始化:做了哪些準備工作 message的建立過程 客戶端的傳

laravel框架原始碼分析自動載入

一、前言   使用php已有好幾年,laravel的使用也是有好長時間,但是一直對於框架原始碼的理解不深,原因很多,歸根到底還是php基礎不紮實,所以原始碼看起來也比較吃力。最近有時間,所以開啟第5、6遍的框架原始碼探索之旅,前面幾次都是看了一些就放棄,希望這次能夠看完。每一次看原始碼都會有新的收穫,因為框

github上hamsternz/FPGA_DisplayPort 的VHDL原始碼分析

原始碼來源於https://github.com/hamsternz/FPGA_DisplayPort。由於我也是第一次接觸這個介面,所以文中肯定有我理解錯誤的地方,懇請指正。要看懂程式碼首先還是要對協議有一定了解。所以我做的原始碼分析中會和協議結合起來。 激勵檔案test_source_800

vue-element-admin原始碼分析

這兩天看花褲衩大大的手摸手系列,使用vue+element+vuex+axios實現了一個後臺模板(專案地址),在閱讀原始碼的過程中收益匪淺,以下做一些筆記。(由於是學習大大專案的思想,所以略去了很多大大的程式碼)。 這裡只是做一個登陸頁面,然後能提交資料給後臺

lua原始碼分析揭開 table 的神祕面紗

       友情提醒:閱讀本文前請先確保自己對雜湊表有足夠深入的理解,雜湊表的詳解可以參見以下這篇文章:Redis底層詳解(一) 雜湊表和字典。        lua 底層 table (也叫陣列, 物件

subsampling-scale-image-view部分載入bitmap原始碼分析

subsampling-scale-image-view原始碼分析背景介紹使用原始碼分析總結參考 背景 對於安卓開發人員,最頭疼的問題就是記憶體問題了,而記憶體問題又當屬bitmap最頭疼,雖然說現在市面上已經有越來越多成熟的圖片載入框架,像Fresco,Gli

Spark2.3.2原始碼解析: 6. SparkContext原始碼分析 : SparkEnv

    SparkContext 是通往 Spark 叢集的唯一入口,可以用來在 Spark 叢集中建立 RDDs 、 累加器( Accumulators )和廣播變數( Broadcast Variables ) 。 SparkContext 也是整個 Spark 應用程式(