// Underscore.js 1.3.3 // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. // Underscore is freely distributable under the MIT license. // Portions of Underscore are inspired or borrowed from Prototype, // Oliver Steele's Functional, and John Resig's Micro-Templating. // For all details and documentation: // http://documentcloud.github.com/underscore (function() { // 建立一個全域物件, 在瀏覽器中表示為window物件, 在Node.js中表示global對象 var root = this; // 儲存"_"(底線變數)被覆寫先前的值 // 如果出現命名衝突或考慮到規範, 可透過_.noConflict()方法恢復"_"Underscore佔用之前的值, 並傳回Underscore物件以便重新命名 var previousUnderscore = root._; // 建立一個空的物件常數, 便於內部共享使用 var breaker = {}; // 將內建物件的原型鏈緩存在局部變數, 方便快速調用 var ArrayProto = Array.prototype, // ObjProto = Object.prototype, // FuncProto = Function.prototype; // 將內建物件原型中常用的方法快取在局部變數, 方便快速調用 var slice = ArrayProto.slice, // unshift = ArrayProto.unshift, // toString = ObjProto.toString, // hasOwnProperty = ObjProto.hasOwnProperty; // 這裡定義了一些JavaScript 1.6提供的新方法 // 如果宿主環境中支援這些方法則優先呼叫, 如果宿主環境中沒有提供, 則會由Underscore實現 var nativeForEach = ArrayProto.forEach, // nativeMap = ArrayProto.map, // nativeReduce = ArrayProto.reduce, // nativeReduceRight = ArrayProto.reduceRight, // nativeFilter = ArrayProto.filter, // nativeEvery = ArrayProto.every, // nativeSome = ArrayProto.some, // nativeIndexOf = ArrayProto.indexOf, // nativeLastIndexOf = ArrayProto.lastIndexOf, // nativeIsArray = Array.isArray, // nativeKeys = Object.keys, // nativeBind = FuncProto.bind; // 建立物件式的呼叫方式, 將傳回一個Underscore包裝器, 包裝器物件的原型中包含Underscore所有方法(類似與將DOM物件包裝為一個jQuery物件) var _ = function(obj) { // 所有Underscore物件在內部均透過wrapper物件進行建構 return new wrapper(obj); }; // 針對不同的宿主環境, 將Undersocre的命名變數存放到不同的物件中 if( typeof exports !== 'undefined') {// Node.js環境 if( typeof module !== 'undefined' && module.exports) { exports = module.exports = _; } exports._ = _; } else {// 瀏覽器環境中Underscore的命名變數被掛在window物件中 root['_'] = _; } // 版本聲明 _.VERSION = '1.3.3'; // 集合相關的方法(資料和物件的通用處理方法) // -------------------- // 迭代處理器, 對集合中每一個元素執行處理器方法 var each = _.each = _.forEach = function(obj, iterator, context) { // 不處理空值 if(obj == null) return; if(nativeForEach && obj.forEach === nativeForEach) { // 如果宿主環境支援, 則優先呼叫JavaScript 1.6提供的forEach方法 obj.forEach(iterator, context); } else if(obj.length === obj.length) { // 對中每一個元素執行處理器方法 for(var i = 0, l = obj.length; i 中每一個元素執行處理器方法 for(var key in obj) { if(_.has(obj, key)) { if(iterator.call(context, obj[key], key, obj) === breaker) return; } } } }; // 迭代處理器, 與each方法的差異在於map會儲存每次迭代的返回值, 並作為一個新的陣列返回 _.map = _.collect = function(obj, iterator, context) { // 用於存放回傳值的數組 var results = []; if(obj == null) return results; // 優先呼叫宿主環境提供的map方法 if(nativeMap && obj.map === nativeMap) return obj.map(iterator, context); // 迭代處理集合中的元素 each(obj, function(value, index, list) { // 將每次迭代處理的回傳值儲存到results數組 results[results.length] = iterator.call(context, value, index, list); }); // 回傳處理結果 if(obj.length === obj.length) results.length = obj.length; return results; }; // 將集合中每個元素放入迭代處理器, 並將本次迭代的返回值作為"memo"傳遞到下一次迭代, 一般用於累計結果或連接數據 _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { // 透過參數數量檢查是否存在初始值 var initial = arguments.length > 2; if(obj == null) obj = []; // 優先呼叫宿主環境提供的reduce方法 if(nativeReduce && obj.reduce === nativeReduce && false) { if(context) iterator = _.bind(iterator, context); return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); } // 迭代處理集合中的元素 each(obj, function(value, index, list) { if(!initial) { // 如果沒有初始值, 則將第一個元素作為初始值; 如果被處理的是物件集合, 則預設值為第一個屬性的值 memo = value; initial = true; } else { // 記錄處理結果, 並將結果傳遞給下次迭代 memo = iterator.call(context, memo, value, index, list); } }); if(!initial) throw new TypeError('Reduce of empty array with no initial value'); return memo; }; // 與reduce作用相似, 將逆向迭代集合中的元素(即從最後一個元素開始直到第一個元素) _.reduceRight = _.foldr = function(obj, iterator, memo, context) { var initial = arguments.length > 2; if(obj == null) obj = []; // 優先呼叫宿主環境提供的reduceRight方法 if(nativeReduceRight && obj.reduceRight === nativeReduceRight) { if(context) iterator = _.bind(iterator, context); return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); } // 逆轉集合中的元素順序 var reversed = _.toArray(obj).reverse(); if(context && !initial) iterator = _.bind(iterator, context); // 透過reduce方法處理數據 return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator); }; // 遍歷集合中的元素, 傳回第一個能夠通過處理器驗證的元素 _.find = _.detect = function(obj, iterator, context) { // result存放第一個能夠通過驗證的元素 var result; // 透過any方法遍歷資料, 並記錄經過驗證的元素 // (如果是在迭代中檢查處理器返回狀態, 這裡使用each方法會更合適) any(obj, function(value, index, list) { // 如果處理器傳回的結果轉換為Boolean類型後值為true, 則目前記錄並傳回目前元素 if(iterator.call(context, value, index, list)) { result = value; return true; } }); return result; }; // 與find方法作用類似, 但filter方法會記錄下集合中所有經過驗證的元素 _.filter = _.select = function(obj, iterator, context) { // 用於儲存通過驗證的元素數組 var results = []; if(obj == null) return results; // 優先呼叫宿主環境提供的filter方法 if(nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); // 迭代集合中的元素, 並將透過處理器驗證的元素放到陣列中並傳回 each(obj, function(value, index, list) { if(iterator.call(context, value, index, list)) results[results.length] = value; }); return results; }; // 與filter方法作用相反, 即傳回沒有經過處理器驗證的元素列表 _.reject = function(obj, iterator, context) { var results = []; if(obj == null) return results; each(obj, function(value, index, list) { if(!iterator.call(context, value, index, list)) results[results.length] = value; }); return results; }; // 如果集合中所有元素都能通過處理器驗證, 則傳回true _.every = _.all = function(obj, iterator, context) { var result = true; if(obj == null) return result; // 優先呼叫宿主環境提供的every方法 if(nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); // 迭代集合中的元素 each(obj, function(value, index, list) { // 這裡理解為 result = (result && iterator.call(context, value, index, list)) // 驗證處理器的結果轉換為Boolean型別後是否為true值 if(!( result = result && iterator.call(context, value, index, list))) return breaker; }); return !!result; }; // 檢查集合中任何一個元素在轉換為Boolean型別時, 是否為true值?或透過處理器處理後, 是否值為true? var any = _.some = _.any = function(obj, iterator, context) { // 如果沒有指定處理器參數, 則預設的處理器函數會傳回元素本身, 並在迭代時透過將元素轉換為Boolean類型來判斷是否為true值 iterator || ( iterator = _.identity); var result = false; if(obj == null) return result; // 優先呼叫宿主環境提供的some方法 if(nativeSome && obj.some === nativeSome) return obj.some(iterator, context); // 迭代集合中的元素 each(obj, function(value, index, list) { if(result || ( result = iterator.call(context, value, index, list))) return breaker; }); return !!result; }; // 檢查集合中是否有值與目標參數完全匹配(同時將符合資料型別) _.include = _.contains = function(obj, target) { var found = false; if(obj == null) return found; // 優先呼叫宿主環境提供的Array.prototype.indexOf方法 if(nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; // 透過any方法迭代集合中的元素, 驗證元素的值和類型與目標是否完全匹配 found = any(obj, function(value) { return value === target; }); return found; }; // 依序呼叫集合中所有元素的同名方法, 從第3個參數開始, 將會被以此傳入到元素的呼叫方法中 // 回傳一個陣列, 儲存了所有方法的處理結果 _.invoke = function(obj, method) { // 呼叫同名方法時傳遞的參數(從第3個參數開始) var args = slice.call(arguments, 2); // 依序呼叫每個元素的方法, 並將結果放入數組中返回 return _.map(obj, function(value) { return (_.isFunction(method) ? method || value : value[method]).apply(value, args); }); }; // 遍歷一個由物件列表組成的陣列, 並傳回每個物件中的指定屬性的值列表 _.pluck = function(obj, key) { // 如果某一個物件中不存在該屬性, 則回傳undefined return _.map(obj, function(value) { return value[key]; }); }; // 傳回集合中的最大值, 如果不存在可比較的值, 則回傳undefined _.max = function(obj, iterator, context) { // 如果集合是陣列, 且沒有使用處理器, 則使用Math.max取得最大值 // 一般會是在一個數組儲存了一系列Number類型的數據 if(!iterator && _.isArray(obj) && obj[0] === obj[0]) return Math.max.apply(Math, obj); // 對於空值, 直接回傳負無窮大 if(!iterator && _.isEmpty(obj)) return -Infinity; // 一個暫時的物件, computed用於在比較過程中儲存最大值(暫時的) var result = { computed : -Infinity }; // 迭代集合中的元素 each(obj, function(value, index, list) { // 如果指定了處理器參數, 則比較的資料為處理器傳回的值, 否則直接使用each遍歷時的預設值 var computed = iterator ? iterator.call(context, value, index, list) : value; // 如果比較值比較上一個值要大, 則將目前值放入result.value computed >= result.computed && ( result = { value : value, computed : computed }); }); // 返回最大值 return result.value; }; // 傳回集合中的最小值, 處理過程與max方法一致 _.min = function(obj, iterator, context) { if(!iterator && _.isArray(obj) && obj[0] === obj[0]) return Math.min.apply(Math, obj); if(!iterator && _.isEmpty(obj)) return Infinity; var result = { computed : Infinity }; each(obj, function(value, index, list) { var computed = iterator ? iterator.call(context, value, index, list) : value; computed 之間 rand = Math.floor(Math.random() * (index 1)); // 將已經隨機得到的元素放到shuffled陣列結尾 shuffled[index] = shuffled[rand]; // 在前面得到的隨機數的位置插入最新值 shuffled[rand] = 值; }); // 傳回一個陣列, 此陣列中儲存了經過隨機混排的集合元素 return shuffled; }; // 對集合中元素, 依照特定的欄位或值進行排列 // 比較Array.prototype.sort方法, sortBy方法支援對物件排序 _.sortBy = function(obj, val, context) { // val應該是物件的屬性, 或一個處理器函數, 如果是一個處理器, 則應該傳回需要進行比較的數據 var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; // 呼叫順序: _.pluck(_.map().sort()); // 呼叫_.map()方法遍歷集合, 並將集合中的元素放到value節點, 將元素中需要進行比較的資料放到criteria屬性中 // 呼叫sort()方法將集合中的元素依照criteria屬性中的資料進行順序排序 // 呼叫pluck取得排序後的物件集合並傳回 return _.pluck(_.map(obj, function(value, index, list) { return { value : value, criteria : iterator.call(context, value, index, list) }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; if(a === void 0) return 1; if(b === void 0) return -1; return a b ? 1 : 0; }), 'value'); }; // 將集合中的元素, 按處理器傳回的key分成多個數組 _.groupBy = function(obj, val) { var result = {}; // val將轉換為進行分組的處理器函數, 如果val不是一個Function類型的資料, 則將被作為篩選元素時的key值 var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; }; // 迭代集合中的元素 each(obj, function(value, index) { // 將處理器的回傳值當作key, 並將相同的key元素放到一個新的陣列 var key = iterator(value, index); (result[key] || (result[key] = [])).push(value); }); // 傳回已分組的數據 return result; }; _.sortedIndex = function(array, obj, iterator) { iterator || ( iterator = _.identity); var low = 0, high = array.length; while(low > 1; iterator(array[mid]) = 0; }); }); }; // 篩選並傳回目前數組中與指定資料不相等的差異數據 // 此函數一般用於刪除數組中指定的資料, 並得到刪除後的新數組 // 此方法的作用與without相等, without方法參數形式上不允許資料被包含在數組中, 而difference方法參數形式上建議是數組(也可以和without使用相同形式的參數) _.difference = function(array) { // 對第2個參數開始的所有參數, 作為一個陣列進行合併(僅合併第一層, 而非深層合併) // rest變數儲存驗證資料, 在本方法中用於與原始資料對比 var rest = _.flatten(slice.call(arguments, 1), true); // 將合併後的陣列資料篩選, 篩選條件是目前陣列中不包含參數指定的驗證資料的內容 // 將符合篩選條件的資料組合為一個新的陣列並傳回 return _.filter(array, function(value) { return !_.include(rest, value); }); }; // 將每個數組的相同位置的資料作為一個新的二維數組返回, 返回的數組長度以傳入參數中最大的數組長度為準, 其它數組的空白位置使用undefined填充 // zip方法應該包含多個參數, 且每個參數應該均為數組 _.zip = function() { // 將參數轉換為數組, 此時args是二維數組 var args = slice.call(arguments); // 計算每一個陣列的長度, 並傳回其中最大長度值 var length = _.max(_.pluck(args, 'length')); // 依照最大長度值建立新的空數組, 此數組用於儲存處理結果 var results = new Array(length); // 迴圈最大長度, 在每次迴圈將呼叫pluck方法取得每個陣列中相同位置的資料(依序從0到最後位置) // 將獲取到的資料儲存在一個新的陣列, 放入results並返回 for(var i = 0; i = 0; i--) { args = [funcs[i].apply(this, args)]; } // 傳回最後一次呼叫函數的回傳值 return args[0]; }; }; // 傳回函數, 此函數為呼叫計數器, 當函數被呼叫times次(或超過times次)後, func函數將被執行 // after方法一般用作非同步的計數器, 例如在多個AJAX請求全部完成後需要執行一個函數, 則可以使用after在每個AJAX請求完成後調用 _.after = function(times, func) { // 若沒有指定或指定無效次數, 則func直接被調用 if(times " ' _.escape = function(string) { return ('' string).replace(/&/g, '&').replace(/, '/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(///g, '/'); }; // 指定一個物件的屬性, 傳回該屬性對應的值, 如果該屬性對應的是一個函數, 則會執行該函數並傳回結果 _.result = function(object, property) { if(object == null) return null; // 取得物件的值 var value = object[property]; // 若值是函數, 則執行並傳回, 否則將直接傳回 return _.isFunction(value) ? value.call(object) : value; }; // 新增一系列自訂方法到Underscore物件中, 用於擴充Underscore插件 _.mixin = function(obj) { // obj是一個集合一系列自訂方法的物件, 此處透過each遍歷物件的方法 each(_.functions(obj), function(name) { // 透過addToWrapper函數將自訂方法加入Underscore建構的物件, 用於支援物件式調用 // 同時將方法加入 _ 本身, 用於支援函數式調用 addToWrapper(name, _[name] = obj[name]); }); }; // 取得一個全域唯一識別, 標識從0開始累加 var idCounter = 0; // prefix表示標識的前綴, 如果沒有指定前綴則直接傳回標識, 一般用於給物件或DOM建立唯一ID _.uniqueId = function(prefix) { var id = idCounter ; return prefix ? prefix id : id; }; // テンプレート メソッドで使用される、テンプレートの区切り記号を定義します _.templateSettings = { //JavaScript 実行可能コードの区切り文字 評価: //g, // 変数の区切り文字を直接出力 補間 : //g, //HTMLを文字列として出力する場合に必要な区切り文字(特殊記号を文字列形式に変換) エスケープ : //g }; var noMatch = /.^/; // エスケープ オブジェクトは、相互に変換する必要がある特殊記号と文字列形式の間の対応を記録し、この 2 つが相互に変換されるときにインデックスとして使用されます。 // まず文字列形式に従って特殊文字を定義します var エスケープ = { '\' : '\', "" : "", 'r' : 'r'、 「ん」:「ん」、 't' : 't', 'u2028' : 'u2028', 'u2029' : 'u2029' }; // すべての特殊文字列を走査し、特殊文字をキーとして使用して文字列形式を記録します for(エスケープ内の変数 p) エスケープ[エスケープ[p]] = p; // バックスラッシュ、一重引用符、キャリッジリターン、ラインフィード、タブ、行区切り文字、段落区切り文字など、テンプレート内で置換する必要がある特殊記号を定義します。 // 文字列内の特殊記号を文字列形式に変換するときに使用されます var エスケープ = /\|'|r|n|t|u2028|u2029/g; // 文字列形式の特殊記号を反転(置換)するときに使用します var unescaper = /\(\|'|r|n|t|u2028|u2029)/g; //文字列内の特殊記号を反転します // テンプレート内で実行する必要がある JavaScript ソース コードは、特殊な記号を使用して反転する必要があります。そうしないと、HTML エンティティまたは文字列の形式で表示されると、構文エラーがスローされます。 var unescape = 関数(コード) { return code.replace(unescaper, function(match,scape) { エスケープを返す[エスケープ]; }); }; // テンプレート文字列にデータを埋めるために使用されるアンダースコア テンプレート解析メソッド // テンプレートの解析プロセス: // 1. テンプレート内の特殊記号を文字列に変換します // 2. エスケープフォームタグを解析し、コンテンツを HTML エンティティに解析します。 // 3. 解析補間フォームタグと出力変数 // 4. 評価フォームタグを解析し、実行可能な JavaScript コードを作成します // 5. データを取得した後にテンプレートに直接入力し、入力された文字列を返す処理関数を生成します // 6. パラメータに従って、埋められた文字列または処理関数のハンドルを返します。 //---------------------- //テンプレート本体では、引数を通じて 2 つのパラメータ、つまり塗りつぶしデータ (obj という名前) と Underscore オブジェクト (_ という名前) を取得できます。 _.template = 関数(テキスト、データ、設定) { // テンプレート設定。設定項目が指定されていない場合は、templateSettings で指定された設定項目が使用されます。 設定 = _.defaults(設定 || {}, _.templateSettings); // テンプレートを実行可能なソース コードに解析し始めます var source = "__p ='" text.replace(escaper, function(match) { //特殊記号を文字列形式に変換します return '\' エスケープ[一致]; }).replace(settings.escape || noMatch, function(match, code) { // エスケープ フォーム タグ <%- %> を解析し、変数に含まれる HTML を _.escape 関数を通じて HTML エンティティに変換します return "' n_.escape(" unescape(code) ") n'"; }).replace(settings.interpolate || noMatch, function(match, code) { // 補間フォームタグ <%= %> を解析し、テンプレートの内容を変数として他の文字列と接続すると、変数として出力されます return "' n(" unescape(code) ") n'"; }).replace(settings.evaluate || noMatch, function(match, code) { // 評価フォーム タグ <% %> を解析します。実行する必要がある JavaScript コードは評価タグに格納されており、現在の文字列の結合はここで終了し、新しい行で JavaScript 構文として実行されます。が文字列の先頭として再度使用されるため、evaluate タグ内の JavaScript コードは正常に実行できます。 return "';n" unescape(code) "n;__p ='"; }) "';n"; if(!設定.変数) ソース = 'with(obj||{}){n' ソース '}n'; ソース = "var __p='';" "var print=function(){__p =Array.prototype.join.call(arguments, '')};n" ソース "return __p;n"; // 関数を作成し、ソース コードを関数の実行本体として使用し、obj と Underscore をパラメータとして関数に渡します var render = new Function(settings.variable || 'obj', '_', ソース); // テンプレートのフィルデータが指定されている場合は、テンプレートの内容を置き換えて、置き換えた結果を返します if(データ) リターンレンダー(データ, _); // フィルデータが指定されていない場合は、受信したデータをテンプレートに置き換える関数を返します // プログラム内で同じテンプレートを複数回埋め込む場合は、最初の呼び出しでは埋め込みデータを指定せず、処理関数の参照を取得した後、直接呼び出すことをお勧めします。 var template = 関数(データ) { return render.call(this, data, _); }; // 作成したソース コード文字列を関数オブジェクトに追加します。通常はデバッグとテストに使用されます。 template.source = 'function(' (settings.variable || 'obj') '){n' ソース '}'; //充填データが指定されていない場合は、処理関数ハンドルを返す テンプレートを返す; }; // Underscore オブジェクトのメソッドチェーン操作をサポートします。wrapper.prototype.chain を参照してください。 _.chain = 関数(obj) { return _(obj).chain(); }; // アンダースコア オブジェクトは関連メソッドをカプセル化します // --------------- //生データをラップするラッパーを作成します // すべての undersocre オブジェクトは、ラッパー関数を通じて内部的に構築およびカプセル化されます。 //アンダースコアとラッパーの内部関係: // - 変数 _ を内部的に定義し、アンダースコア関連のメソッドを _ に追加して、_.bind() などの関数呼び出しをサポートできるようにします。 // - ラッパー クラスを内部で定義し、_ のプロトタイプ オブジェクトがラッパー クラスのプロトタイプを指すようにします。 // -アンダースコア関連のメソッドをラッパー プロトタイプに追加すると、作成された _ オブジェクトにはアンダースコア メソッドが含まれます。 // - Array.prototype 関連のメソッドをラッパー プロトタイプに追加すると、作成された _ オブジェクトには Array.prototype のメソッドが含まれます。 // -new _() は、実際には、wrapper() オブジェクトを作成して返し、元の配列を _wrapped 変数に格納し、元の値を最初のパラメータとして対応するメソッドを呼び出します。 var ラッパー = function(obj) { //元のデータはラップされたオブジェクトの _wrapped 属性に保存されます this._wraped = obj; }; // Underscore のプロトタイプ オブジェクトがラッパーのプロトタイプを指すようにするため、ラッパー プロトタイプのようなメソッドを追加することで、Underscore オブジェクトも同じメソッドを持つようになります。 _.prototype = ラッパー.プロトタイプ; // オブジェクトを返します。現在の Underscore がchain() メソッドを呼び出した場合 (つまり、_chain 属性が true の場合)、ラップされた Underscore オブジェクトが返され、それ以外の場合はオブジェクト自体が返されます。 // 結果関数