你的位置:首页 > Java教程

[Java教程]dojo/store/Memory 和 SimpleQueryEngine 解读 (非常简单的存储器管理工具 和 非常简单的查询引擎)


一、介绍(官方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 dates12           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 paginate22     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 就暂时研究到这一步骤了。谢谢阅读~