一、介紹(官方API): The Memory store provides full read and write capabilities for in memory data. The Memory store is very simple to use, just provide an ar ...
一、介紹(官方API):
The Memory store provides full read and write capabilities for in memory data. The Memory store is very simple to use, just provide an array of objects. The Memory store is also a synchronous store, which also simplifies its usage. All the functions directly return results, so you don’t have to use asynchronous callbacks in your code.
大致翻譯:Memory store 提供了對記憶體可讀可寫的管理單元。它非常簡單,易於使用,內部僅提供了一個對象數組。Memory store是一個同步存儲器,自然功能簡單易用了。所有的方法都是直接返回結果,你不需要去編寫非同步的回調函數。
Example:
require(["dojo/store/Memory"], function(Memory){ var someData = [ {id:1, name:"One"}, {id:2, name:"Two"} ]; store = new Memory({data: someData}); store.get(1) -> Returns the object with an id of 1 store.query({name:"One"}) // 根據特定的查詢條件,返回符合要求的結果集。 store.query(function(object){ return object.id > 1; }) // 通過傳遞一個函數來進行查詢,可以實現更複雜的查詢操作 store.query({name:"One"}, {sort: [{attribute: "id"}]}) // 除了查詢以外,返回的結果集會根據id來排序。 store.put({id:3, name:"Three"}); //保存{id:3, name:"Three"} 對象,並且放在id:3的位置。 store.remove(3); // 根據ID來刪除對象 });
官方API的介紹和切合要點,Memory store的最顯著的特點,就是“簡單”。Memory store的功能就像上面的例子中的一樣,簡單暴力,基本齊全。下麵我們根據dojo里的源碼進一步解析:
二、dojo/store/Memory
構造函數constructor:
constructor: function(options){ // summary: // Creates a memory object store. // options: dojo/store/Memory // This provides any configuration information that will be mixed into the store. // This should generally include the data property to provide the starting set of data. for(var i in options){ this[i] = options[i]; } this.setData(this.data || []); },
嗯,我們通過new欄位來創建memory store到對象時,就會調用到這裡。案例中的options參數為{data:somedata}。實際上是創建了最最簡單的Memory stortt僅僅只用了一個對象數組。所以當然咯,可以有略複雜一點的情況。代碼中this[i]=options[i],可以看出各種配置都體現在Menory對象的屬性里。下麵列一下:
// data: Array // The array of all the objects in the memory store data:null, // idProperty: String // Indicates the property to use as the identity property. The values of this // property should be unique. idProperty: "id", // index: Object // An index of data indices into the data array by id index:null, // queryEngine: Function // Defines the query engine to use for querying the data store queryEngine: SimpleQueryEngine,
data:是必須的,如果不存在,會被賦予一個[]空數組。
index:後續會提到這個屬性,它是一個以id("idProperty") 為索引的表,表的數據是一個改id對應的屬性在data數組裡的序號索引。
idProperty:啊哈,通過這個屬性我們可指定改memory的一個不可重覆的標識符(欄位名),預設是為"id"。
queryEngine:可以指定memory的搜搜引擎,預設使用的是dojo/store/util/SimpleQueryEngine。
setData: function(data){ // summary: // Sets the given data as the source for this store, and indexes it // data: Object[] // An array of objects to use as the source of data. if(data.items){ // just for convenience with the data format IFRS expects this.idProperty = data.identifier || this.idProperty; data = this.data = data.items; }else{ this.data = data; } this.index = {}; for(var i = 0, l = data.length; i < l; i++){ this.index[data[i][this.idProperty]] = i; } }setData
setData可以外部調用,用來更換該Memory的數據源,比如setData([])可以清空。
有一意思的是,從setData的函數體里可以發現,除了官方例子的創建memory的格式,setData還為IFRS開了一個方便之門。我不太瞭解IFRS會使用怎麼樣的數據格式,從代碼中可以看出:1、標識符存放在data.identifier;2、數據存在data.items里。不過當然的,data.items仍被要求是一個有序對象數組。
我們知道構造函數中數據的初始化也是通過setData實現的,在setData的最後面,實際也是this.index的初始化。在迴圈中構建利用this.index[data[i][this.idProperty]] = i; 來創建一個以idproperty為索引的散列表。該散列表保存著對應數據在data里的位置,這樣形式的散列表在訪問數據時可是非常方便。
get: function(id){ // summary: // Retrieves an object by its identity // id: Number // The identity to use to lookup the object // returns: Object // The object in the store that matches the given id. return this.data[this.index[id]]; },get
通過標識符的值,來獲取對應對象。
this.index[id] 取出其對應數據的數組位序,然後通過序號去對象數組中取對象。
getIdentity: function(object){ // summary: // Returns an object's identity // object: Object // The object to get the identity from // returns: Number return object[this.idProperty]; },getIdentity
獲取對象的標識符值。
我們通過它,可以用index[getIdentity(object)]來獲取對象在數組中的位置了。
put: function(object, options){ // summary: // Stores an object // object: Object // The object to store. // options: dojo/store/api/Store.PutDirectives? // Additional metadata for storing the data. Includes an "id" // property if a specific id is to be used. // returns: Number var data = this.data, index = this.index, idProperty = this.idProperty; var id = object[idProperty] = (options && "id" in options) ? options.id : idProperty in object ? object[idProperty] : Math.random(); if(id in index){ // object exists if(options && options.overwrite === false){ throw new Error("Object already exists"); } // replace the entry in data data[index[id]] = object; }else{ // add the new object index[id] = data.push(object) - 1; } return id; },put
保存對象。
註意到保存操作是有可選參數的,從源碼看看都有些什麼。
註意到保存對象id的獲取規則:配置里的“id”欄位>對象里的標識符欄位>一個隨機數字。
如果id已經存在,根據"overwrite"欄位,為真則覆蓋,假則報錯。 如果id不存在,數據存入對象數組最後一位上。 put方法會返回保存對象的標識符值。add: function(object, options){ // summary: // Creates an object, throws an error if the object already exists // object: Object // The object to store. // options: dojo/store/api/Store.PutDirectives? // Additional metadata for storing the data. Includes an "id" // property if a specific id is to be used. // returns: Number (options = options || {}).overwrite = false; // call put with overwrite being false return this.put(object, options); },add
保存一個新的對象,可以指定保存的id,但不可覆蓋。
其實和put是本質是一樣的操作,但更保險,參數overwrite被確定為false。
remove: function(id){ // summary: // Deletes an object by its identity // id: Number // The identity to use to delete the object // returns: Boolean // Returns true if an object was removed, falsy (undefined) if no object matched the id var index = this.index; var data = this.data; if(id in index){ data.splice(index[id], 1); // now we have to reindex this.setData(data); return true; } },remove
通過標識符值,刪除指定對象。
函數體很簡單,刪除後通過setData更新index的內容。操作成功會返回true。
query: function(query, options){ // summary: // Queries the store for objects. // query: Object // The query to use for retrieving objects from the store. // options: dojo/store/api/Store.QueryOptions? // The optional arguments to apply to the resultset. // returns: dojo/store/api/Store.QueryResults // The results of the query, extended with iterative methods. // // example: // Given the following store: // // | var store = new Memory({ // | data: [ // | {id: 1, name: "one", prime: false }, // | {id: 2, name: "two", even: true, prime: true}, // | {id: 3, name: "three", prime: true}, // | {id: 4, name: "four", even: true, prime: false}, // | {id: 5, name: "five", prime: true} // | ] // | }); // // ...find all items where "prime" is true: // // | var results = store.query({ prime: true }); // // ...or find all items where "even" is true: // // | var results = store.query({ even: true }); return QueryResults(this.queryEngine(query, options)(this.data)); },query
強大的query查詢功能。
參數可以是欄位值,來查詢匹配欄位的結果;也可以是一個函數,返回真假,來進行複雜的判斷。
實際上是調用了memory store的搜索引擎,這個搜索引擎預設是/util/SimpleQueryEngine。
三、SimpleQueryEngine
進一步的,我們去看看SimpleQueryEngine是如何實現搜索的。
首先,模塊返回的是一個形參為(query, options)的工具函數。
return function(query, options){ ... }
函數體主要為兩部分,第一個switch,第二個execute方法。
switch部分:
switch(typeof query){ default: throw new Error("Can not query with a " + typeof query); case "object": case "undefined": var queryObject = query; query = function(object){ for(var key in queryObject){ var required = queryObject[key]; if(required && required.test){ // an object can provide a test method, which makes it work with regex if(!required.test(object[key], object)){ return false; } }else if(required != object[key]){ return false; } } return true; }; break; case "string": // named query if(!this[query]){ throw new Error("No filter function " + query + " was found in store"); } query = this[query]; // fall through case "function": // fall through }
有四種格式的查詢條件可以通過,string,function,object,undefined
functiion格式在此直接不處理,往後跑。
string格式為認為是函數體名稱,在當前上下文找該函數體。
當query格式為object和undefined時(其實就是object時):
則生成查詢函數,查詢函數邏輯為,針對object的每一個屬性:
1、如果有test方法,則執行test方法;
(想到什麼了嗎?如果提供的是一個RegExpObject,因持有test方法,則會執行test方法,所以該查詢引擎可以支持正則表達式查詢)
2、如果沒有test方法,則直接進行比較。
總的來說,如果query的格式是一個object,則更像一個複合條件:
舉個例子:
假如待查詢數據為: data=[ {id:1,grade:1,class:1,peopleNum:5}, {id:2,grade:1,class:2,peopleNum:10}, {id:3,grade:1,class:3,peopleNum:13}, {id:4,grade:2,class:1,peopleNum:10}, {id:5,grade:2,class:2,peopleNum:20}, {id:6,grade:2,class:3,peopleNum:7} ]
假如:
query={ test:function(object){ return (object.peopleNum>10} } } //查詢結果為對象peopleNum屬性大於10的結果,即 //[ // {id:3,grade:1,class:3,peopleNum:13}, // {id:5,grade:2,class:2,peopleNum:20} //]
又假如: query={ test:function(object){ rreturn (object.peopleNum>10} }, grade:2, } //查詢結果為對象peopleNum屬性大於10,且grade等於2的結果,即 //[ // {id:5,grade:2,class:2,peopleNum:20} //]
繼續往下看,我們看execute部分的內容:
1 function execute(array){ 2 // execute the whole query, first we filter 3 var results = arrayUtil.filter(array, query); 4 // next we sort 5 var sortSet = options && options.sort; 6 if(sortSet){ 7 results.sort(typeof sortSet == "function" ? sortSet : function(a, b){ 8 for(var sort, i=0; sort = sortSet[i]; i++){ 9 var aValue = a[sort.attribute]; 10 var bValue = b[sort.attribute]; 11 // valueOf enables proper comparison of dates 12 aValue = aValue != null ? aValue.valueOf() : aValue; 13 bValue = bValue != null ? bValue.valueOf() : bValue; 14 if (aValue != bValue){ 15 return !!sort.descending == (aValue == null || aValue > bValue) ? -1 : 1; 16 } 17 } 18 return 0; 19 }); 20 } 21 // now we paginate 22 if(options && (options.start || options.count)){ 23 var total = results.length; 24 results = results.slice(options.start || 0, (options.start || 0) + (options.count || Infinity)); 25 results.total = total; 26 } 27 return results; 28 } 29 execute.matches = query; 30 return execute; 31 };
看第3行,可以知道主要的查詢功能,還是以來arrayUtil.filter來實現的,而這裡更多的代碼是對option欄位的處理。
第5-20行,是對可選參數"sort"做處理:(當此參數存在時)
1,若sort為排序函數,則利用數組的sort方法,進行排序。
2,若sort不為排序函數,參數格式應該為
[{attribute:string1,descending:bool1},{attribute:string2,descending:bool2}......}
含義為:以string1(attribute參數)屬性進行排序,bool1的真假代表著順逆序。
另外,從代碼可以看出:
1、支持通過attribute屬性的valueof()方法進行比較。
2,for迴圈支持了多個屬性值進行比較,當前一個屬性相等時,會取下一個sort參數對象進行比較得出結果(string2,bool2),直到比較出前後
次序或sort參數用完了)
第22-26行,是對可選參數"start"和"count"進行處理,很簡單,根據start和count對result結果進行裁剪返回,值得註意的是,total值的特殊處理,使得裁剪後的result.total是原查詢結果的總數,而result.length才是裁剪後的結果總數。
dojo/store/Memory 和 SimpleQueryEngine 就暫時研究到這一步驟了。謝謝閱讀~