angularJS 实现ngEnter

Ng中有一些内置的指令,来响应我们的操作事件,比如ng-click,ng-change ng-select等等,但是我发现众多指令中缺少一个 当键盘按下的指令,比如 按回车的指令,比如我在一个搜索框中输入关键字之后按回车键,然后自动进行关键字来搜索,虽然ng中没有自带这个指令,但是我们很容易的定义一个这样的指令。下面就是一个ng-enter指令来完成这样的操作,使用的时候与ng-click这等方法类似,传递一个函数即可。当绑定该指令的元素处于焦点状态的时候,按下回车键就会触发 ng-enter的函数。

app.directive('ngEnter', function () {
    return function (scope, element, attrs) {
        element.bind("keydown keypress", function (event) {
            if(event.which === 13) {
                scope.$apply(function (){
                    scope.$eval(attrs.ngEnter);
                });

                event.preventDefault();
            }
        });
    };
});

关于 keydown,keypress可以参阅http://www.cnblogs.com/silence516/archive/2013/01/25/2876611.html

使用方式 <input ng-enter='myfunction()'/>

dojo统一的数据访问接口——dojo Oject store API

关于dojo中统一数据访问接口

数据的操作以及展现是web端很重要的一块内容,这里就涉及到了不同数据的读取等操作,由于数据可能可能来源于不同的格式,比如常见的有JSON,CSV,XML 甚至不同的厂家会有独有的自定义格式,如果我们要写一个数据展现的控件的话比如chart,grid,为了让数据的提供者和数据的使用者更好地分工,这个时候我们理应屏蔽掉不同数据源之间的区别。这里就需要给访问不同的数据提供相同的访问方式,也就是让数据提供统一的访问接口,数据读取和展现都依赖同一套接口就OK了。在服务端访问不同的数据库,有OLEDB,ODBC、JDBC等协议,类似,web端也有类似的接口。就那web本地存储来说,有 web StoreRage API ,Web SQL API、 index db API 这些都是浏览器的API,然后不同的浏览器厂商根据这些协议各自在自己浏览器上具体实现,这样才能保证我们的同一份JS代码,在不同的浏览上实现相同的功能。这也是面向接口编程给我们带来的好处。订好了协议大家都按统一的实现,这样大家好 才是真的好。

数据的提供者不用再关心数据如何被展示,以及数据的使用者不用关心数据来自于哪里。通过接口(协议)来实现职责的分离,即使同一个人来完成数据的获取以及展示,也要将数据的获取与展示做到分离,因为这种松散的耦合 也方便系统的维护。

说起dojo 数据访问API我们之前会使用dojo data 里面的的东东 也就是旗下的 如下四类接口

  • Dojo.data.api.read 提供读取数据的功能,同时也支持对数据集的搜索,排序,和过滤。
  • Dojo.data.api.write 提供创建,删除,更新数据项功能
  • Dojo.data.api.identify 提供基于唯一的标识符来定位和查询数据项的功能。
  • Dojo.data.api.notification 提供当 datastore的数据项改变等事件发生时通知侦听器的功能。最基本的事件包括数据的创建,修改和删除等。这也是Dojo.data的一项很重要的功能,通过此接口可以将数据展现层与数据中间层更好的分离开来。

以上只是dojo/data接口的定义,对于不同的数据格式格式读取也有具体的实现,如果上面是接口的话 下面的就是类的具体实现了。

  • dojo.data.ItemFileReadStore 用于JSON数据的只读的DataStore。
  • dojo.data.ItemFileWriteStore 用于JSON数据的可读写的DataStore
  • dojox.data.CsvStore 用于CVS数据的只读的DataStore
  • dojox.data.OpmlStore用于OPML(Outline Processor Markup Language)数据的只读的DataStore
  • dojox.data.HtmlTableStore 用于HTML table数据的只读的DataStore
  • dojox.data.XmlStore 用于XML数据的可读写的DataStore
  • dojox.data.FlickrStore 用于读取web服务flickr.com提供的数据
  • dojox.data.QueryReadStore 用于读取由服务器端提供的JSON数据

具体内容可以参见
http://www.ibm.com/developerworks/cn/web/0905_hukuang_dojodata/不错的文章好好读读

基本以上能满足所有对数据操作的需求。。如果你已经读完了上面的这篇文章,或者对dojo data API有所了解,你可能会发现这些对数据的操作非常滴麻烦,虽然能达到了接口的统一,但是繁琐的交互接口也不利于Widget之间的通信,关键是开发者用着不爽,好消息是,这些接口的确已经被淘汰了。。于是引入今天的主题- Dojo Object Store API

那么首先区分一下两个名词,旧的数据访问接口是 dojo data store API ,就是上面介绍的那些东西,在dojo目录的 dojo/data 里面,建议以后不要用这里面的东西来存取东西,也就是说 不要再用 dojo/data/ItemFileReadStore dojo/data/ItemFileWriteStore这两个东西了。。。新的数据统一访问接口是 dojo Object Store 。。具体的接口以及实现在源码(文档)目录的 dojo/store里面,请注意区分

##关于dojo Object store API。

翻译过来就是dojo 对象存储接口,从dojo 1.6中就有了,它的设计设计灵感来自于HTML5 object store API即HTML5中的 IndexDB API,用来取代之前较麻烦的dojo data api.旨在提供一套简单、容易实现、利于数据交互、容易扩展的数据存储API,简化和减轻数据的交互,让模块间的开发更松散的耦合性,让用户的界面展现、数据的存储、交互、达到更好的分离。来更好滴在web端实现MV*架构。

dojo对象存储API背后的几个理念

  • 数据与UI关注点分离,让我们将数据与展现关注点进行分离,并且方便数据与部件之间独立的实现。
  • 保持简单 可以仅仅地创建一个具有query和get方法的对象来创建一个简单的数据存储对象,如果要提供创建一个新的对象功能,可以增加一个add方法,如果要增加更新对象的功能 只需要增加一个put方法
  • 基于纯的javascript对象 dojo store api 使用纯的JS对象即{},使用get返回一个对象或者使用query来返回一个对象的数据,这些对象可以像普通的JS对象一样被操做,比如可以通过点直接访问查询结果对象的属性,或者可以用 for in 来循环便利对象的属性。如果要保存/更新对象可以将一个更新过的纯JS对象传递给put方法即可。
  • 基于Promise 更好地来处理异步的存储。通过返回promise 来做到异步和同步存取的统一
  • 独立简单的功能实现 不需要任何方法来设置存储对象的功能,如果你想知道是否可以添加一个新的对象到存储对象中,只需要检查他是否具有add方法,如果想知道它是否支持查询,只需要检查是否有query方法;如果只是想实现一个只读的存储对象,只需要实现读的方法(get, query, getIdentity)
  • 功能的层次性增强 通过分层的功能,就可以基于一个轻量级的,简单的存储,通过插件对原始的store进行包装来增加store的功能。比如Dojo中提供了(dojo/store/Cache)包装数据存储对象实现数据缓存,用来提高性能。dojo/store/Observable包装数据存储对象用来实现对对象变化的检测,而这并不是必须的。这种插件机制 让我们的原始的存储对象更简单轻量。

关于dojo Object API中的方法以及属性。

下面的这些方法都是可选的。实现的时候可以根据需要来提供不同的方法。任何一个方法除了特别指明意外都可以返回一个promise 来更好滴完成异步的操作。(这里与W3C中 IndexDB api 有些出入)可以到dojo/store/api/store里面来查看这些接口具体的方法和属性

  • get(id) 查询方法根据唯一标识返回一个对象。
  • query(query, options) 查询方法 根据查询条件来查询对象。返回一个数据或者promise(可以从then方法里获得查询结果) ,查询结果具有forEach() , map(), filter(), reduce() 方法来让我们,同时返回结果还包括一个total属性来标标识返回结果列表中包含的对象的的个数(当然也可以自己调用length手动获得),第一个参数为查询条件可以为字符串,对象,函数类型,第二个参数是为可选部分,包含三个属性start查询的起始处。count返回结果的最大个数 sort 排序信息,sort为数组类型可以根据多个字段进行排序
  • put(object, options)保存更新对象。也可以用来增加新对象。第一个参数就是要更新或者增加的对象,
  • add(object, options) 增加新对象
  • remove(id) 根据唯一标识来移除对象
  • getIdentity(object) 获得对象的唯一标识 (这个必须是同步的方法 直接返回结果)
  • queryEngine(query, options) 构造查询条件
  • transaction() 开启一个事物 与 数据库的事物理念类似。具有两个方法commit()提交所有的改变 和abort 撤销所有的更新即回滚。
  • getChildren(object, options) 获取对象的子元素,
  • getMetadata(object) 获取对象的元数据信息

另外还包括两个属性

  • idProperty 指定对象的唯一标识 一旦指定了,构造对象的时候要确保数据的这个字段是唯一的否则会报错
  • data 原始数据的数组

以上只是API中的方法,dojo也有对应的类对此进行了实现后面会做具体的介绍。此外为了直观些可以看一下直接画的图
http://naotu.baidu.com/viewshare.html?shareId=arsu34c0w8ow

关于返回的对象。

dojo/store通过query或者get返回的对象(或者数组)应该是普通的哈希对象,拥有标准JS对象访问或者修改他们属性的能力。这种能保证了访问/修改对象属性的方便性,比如可以直接通过点字段名的形式访问(如A.Name)如果这些对象拥有一些方法,那么这些方法应该是对象的原型上,即通过hasOwnProperty(methodName) 返回false。这样能够在使用for in 的时候只枚举对象的属性。

检测结果集的变化

这个属于对dojo store API的增强。如果一个store 被dojo/store/Observable进行了包装,我们可以监听查询结果集(通过query得到的结果)的变化(比如增加 更新 都会发出相应的通知)从而让我们监听这些事件来对UI进行更新,这样也就达到了数据与UI的分离,当我们数据发生变化的时候自动来完成UI的更新,实现了开发上的分层。我们只需要在写UI的时候,来监听这些变化即可,由数据的变动来自动触发UI的变动。从而实现数据驱动UI,主要通过查询结果集的observer方法来完成

  • observe(listener, includeObjectUpdates) 第一个参数是一个监听函数函数签名listener(object, removedFrom, insertedInto);第一个参数为变动的对象可能是增加的新对象,修改或者是删除的对象。removedFrom 为删除对象的索引 如果这个值为-1 说明没有对象被删除,可能是发生了增加或者是修改行为。insertedInto 返回被增加对象在结果集中的索引.

  • close 如果查询结果集调用colse 方法 ,停止监听这些变化。

关于 dojo/store/Memory

说了这么多以上仅仅是纯理论的东西,都是对API层面的介绍。dojo中也有对应的类对其进行了实现主要有dojo/store/Memory dojo/store/JsonRest.以dojo/store/DataStore

  • dojo/store/Memory 一个简单的内存存储, 是dojo/store API的实现类, 这个是创建 dojo store 非常有用的一个类,特别是小型的数据集,可以用内存的对象数组作为数据源。构造好之后就可以对其进行操作了。

    数据源为PM2.5信息的数据 如下格式的数据

     var mydata=[
        {
            "aqi": 30, //空气质量指数(Air Quality Index
            "aqimsg": "优",
            "collectTime": 1418798880775,
            "lvmsg": "lv-good",
            "name": "房山良乡",
            "path": "beijing/fangshanliangxiang",
            "source": "aqicn.org",
            "updateTime": "星期二21点",
            "values": {}
            },
            {
            "aqi": 25,
            "aqimsg": "优",
            "collectTime": 1418798880775,
            "lvmsg": "lv-good",
            "name": "昌平定陵",
            "path": "beijing/changpingdingling",
            "source": "aqicn.org",
            "updateTime": "星期二21点",
            "values": {}
        }。。。
        。。。。。由于篇幅这里不全部列出 知道这个格式就行了
    ];
    

引入dojo/store/Memory 构造Memory对象

require(["dojo/store/Memory"], function(Memory){
    mystore = new Memory({data: myData,idProperty:'name'});
    //注意这里的idProperty,JSON对象的唯一标识 如果不指定默认为"id"字段

});

查询

  • get根据对象唯一标识来查询一个,返回一个JS对象,查询‘密云镇’的数据

    var obj1= mystore.get('密云镇');//get 跟据唯一标示(idProperty)来查询对象,这里查询 name为 丰台花园的信息
    
        {
            "aqi": 23,
            "aqimsg": "优",
            "collectTime": 1418798880775,
            "lvmsg": "lv-good",
            "name": "密云镇",
            "path": "beijing/miyunzhen",
            "source": "aqicn.org",
            "updateTime": "星期二21点",
            "values": {}
        }
    
  • query 根据查询条件来检索对象 返回符合条件的对象数组,查询结果如果参数为空返回所有的数据

    var    alldataarray=mystore.query()
    
  • 查询条件可以为一个对象

     var    array2=mystore.query({name:'密云镇'}) //
    
    //好吧 再复杂点 查询空气质量指数为 38 并且评级为‘优’的检测点
     var    array3=mystore.query({
          "aqi": 38,
          "aqimsg": "优"
         });
    
     //查询对象还可以为 函数 与 Array.filter类似 实现复杂的查询 或者模糊查询
     //查询 api 小于38 
     var    array4=mystore.query(function(obj){
             return obj.aqi<38;
       }
     );
    
  • 还可以把查询函数参数放到store 对象上作为store的一个方法,在调用query的时候写入方法的名字

     mystore.myquerymethod=function(obj){
         return obj.aqi<38;
     };
       var array5=mystore.query('myquerymethod');
    //console.log(array5);
    

这样有什么好处呢??个人认为这种情况能很方便把查询条件缓存下来,在其他的地方下次查询方便使用,并且可以给查询方法取有意义的名字 比如增加个查询方法 查询评级为优的检测站点

mystore.getgoodstation=unction(obj){
     return obj.aqimsg==='优';
       };
mystore.query('getgoodstation');
  • 更复杂的查询

查询条件:空气质量指数在38-50之间的检测点,然后根据aqi从大到小排序,从第三个开始返回,返回的最大个数3

 var result6 =mystore.query(function(obj){
   return obj.aqi>38&&obj.aqi<50
 },{
     start:3,
     count:3,          
   sort:[{
     attribute:'aqi',
     descending: true //为ture 表示从大到小,为false 表示从小到大
   }]
 }); 

result6.forEach(function(a){
  console.log(a.name)
})

说明:在query的结果集中 有个total属性,这个是满足条件的个数。即满足query的 第一个参数的对象的个数,在query第二个参数中,如果不包含start和count属性的话,则total等于最终结果集的个数即length==total

因为返回的数组具有forEach,fitler,map等方法,方便对结果进一步处理

array6.forEach(function(a){
  console.log(a.name)
})

关于查询结果集的接口定义 可以查阅 dojo/store/api/Store.QueryResults ,如果是异步查询的话 query 会返回一个promise ,即他会拥有一个then方法,从callback里面可以获取查询结果。`比如

query().then(function(data){
    console.log(data);
});

在实际的实现中dojo/store/Memory 是对内存中数据的操作是同步的,而dojo/store/JsonRest则是异步的。

修改对象的属性很简单

var array7 =mystore.query(function(obj){
        return obj.name=="密云镇"
});  
 array7.forEach(function(a){
   console.log(a.aqi)
   a.aqi=100 //修改赋值 没有调用put方法
 });

var array8 =mystore.query(function(obj){
      return obj.name=="密云镇"
})       
 array8.forEach(function(b){
  console.log(b.aqi)//100 说明已经修改了
 })

可是说好的修改用put呢??? 好吧,JS是引用传递的,查询到的对象是JS中的普通对象。。。对象属性直接赋值就能实现修改了,

var objmiyun= mystore.get('密云镇');
objmiyun.aqi=10;      
var objmiyun1= mystore.get('密云镇');
console.log(objmiyun.aqi)//10
console.log(objmiyun===objmiyun1);//为true 说明两次get返回的是同一个对象的引用

那么通过query 查询到的又是如此呢

 mystore.getmiyun=function(obj){
        return obj.name==='密云镇';
 };

var arraym3= mystore.query("getmiyun");
var arraym4= mystore.query("getmiyun");
console.log(arraym3[0]===arraym4[0])//true
console.log(arraym3[0]===mystore.data[1])//true

综上不管是通过get还是query来查询,得到的对象都是原始数据的引用。所以修改很easy了

注意:原则上说我们调用一下put方法来标识更新对象。store.put(obj) 这样如果通过dojo/store/Observable 包装的话 监听函数才能监听的到.

删除

调用 store 的remove方法就行了 参数是 对象的唯一标识

store.remove(id);

其实感觉删除的方法设计不好,删除的参数只能是唯一标识,却不能是根据查询条件来删除。。。也无法达到批量删除的目的。。感觉如果参数为Obj的查询参数类型,以及唯一标识数组类型 就更好了。。

另外更新数据源可以使用 store.setData方法。

dojo/store/JsonRest

dojo/store/JsonRest(与dojox里面的JsonRest不同) 也是dojo Object storeAPI的实现,与Memory不同的是,Memory的数据源存在内存中,而JSONRest的数据源是远程的JSON,JsonRest 实现了对标准Rest接口访问的封装,JSONRest非常适合大量数据的展示(数据存在远端,分页异步获取等来降低性能的损耗),上面已经提到JSONrest 所有的操作都是以promise形式返回的。JsonRest object store 与服务端交互的方式与dojox.data.JsonRestStore类似,但是dojo/store/JsonRest是基于新的dojo Object store APi 简单方便使用的理念,进行重新设计,并做出了很大的简化。只需要提供一个url 既可以构造一个dojo/store/JsonRest

JsonRest 主要干了一件事 就是在不同的方法里面发送不同的类型的http请求,最终完成了服务端数据的CURD(对应的请求类型为get,put,post,delete。使用JSONrest的难点就是服务端必须是标准的Rest风格的。因此要求服务的编写者对Rest有一定的了解,同时要求返回的格式必须为JSON,那么首先要了解前端发出的请求格式。这里假设构造好了一个JSONRest

require(['dojo/store/JsonRest', "dojo/domReady!"], function(JsonRest) {     
        jsonreststore = new JsonRest({target:"/Data/"});

        jsonreststore.get("some-id").then(function(someObject){

        });
});

假设服务的基地址为 /Data/

  • get("someid")/Data/someid 发送get请求,返回promise
  • query(query,options);//query 为 string或者object,options 为 object 可选

  • query("someid") 如果query的参数为类型 发送请求同上 即向/Data/someid 发送get请求,返回promise

  • query({name:'hello',aqi:20}),如果query的参数为Object类型,则会使用 dojo/io-query::objectToQuery()将Object转为查询字符串的形式添加到URL上,即/Data/?name=hello&aqi=20 发送get请求。如果包含两个参数,第二个参数也会被序列化为查询字符串添加到url上,

    query({name:’hello’,aqi:20}, {

       count: 10,
      sort: [
        { attribute: "aqi" }
      ]
    });
    

发送的get请求的URL为/Data/?name=hello&count=10&aqi=20&sort(+aqi) sort信息中+表示升序 -表示降序

put(object, options);

  • put({"aqi":30,"aqimsg": "优"})//向/Data/发送一个Post请求,参数放在在body里面,表示增加元素
  • put({"aqi":30,"aqimsg": "优"},{id:11});//两个参数 第二个指定id,向/Data/11 发送put请求,表示更新元素{“aqi”:30,”aqimsg”: “优”}在请求body里面. put方法的第二参数如果包含overwrite并设置为true,请求头中会包含 If- Match: * 如果设置为false请求头中包含If- None-Match: *,如果第二个参数中包含incrementa并设置为true,发送Post请求表示增加。
  • add(obj,options) 同上,特别之处是 options.overwrite 始终被设置为false,即请求头中始终包含·If- None-Match: *通过这种方式来告诉服务器是创建还是更新操作 `关于 if-node-Match 可以参考http://wuhua.iteye.com/blog/385451

remove('id') //向 Data/id发送delete请求。

另外如果要为请求增加header信息,可以在第二个参数里面设置headers字段即可,比如要添加分页信息,如果要为所有的请求统一添加请求header,可以在构造JsonRest对象的构造函数里面设置如

var store = new JsonRest({
  target: "/FooObject/",
  headers: { "X-Custom-Header": "Foo" } // 所有的请求都会包含X-Custom-Header: Foo
});

store.query({ foo: "value1" }, {
  headers: { "X-Custom-Header": "Bar" } // 仅在本次请求中包含 include X-Custom-Header: Bar instead
});

JSONREST与分页查询

上面的查询中query(query,options);options可以包含一个headers字段,里面可以包含分页信息。在请求header中添加Range字段即可(range是http1.1中标准header的一部分,专门用来表示分页的,如果浏览器不支持http1.1可以使用自定义的header X-Range
比如

query({
    aqi: 40
    }, {
    headers: {
        Range: "items=0-24"
    }
});

鉴于http的标准,要求服务端返回的头中包含分页信息 即Content-Range 头例如 Content-Range: items 0-24/66 0-24表示实际返回的索引0-24的对象,66表示符合条件的对象的总个数。为啥要这种格式呢,因为这是http协议上就这么说的。

对于使用者来说,JsonRest的难点还是后端的实现,后端可以解析http请求的内容(method,querystring,请求头,请求body) 来对CURD做实际的处理。

将异步和同步进行统一处理

上面的MemoryStoreJsonRest store 都是实现了dojo Object Store API 具有的方法也基本一致,但是一个重要区别就是MemoryStore的操作是同步的结果直接返回,而JsonRest操作是异步的,返回的是promise,可以在then方法的回调函数里面获得操作结果,我们可以使用dojo/when 来封装以屏蔽这种差距。不用再关心是同步还是异步的,统一在then方法里面进行处理,比如这里有个store 你不知道他是Memorystore,还是JsonRest store。你关注的是查询里面的数据,可以如下处理

require(['dojo/when'], function (when) {
  when(store.get(id), function(object){

  });
  }};

通过这种方式 来达到了操作上的统一,当然你也可以使用其他的方式进行门面封装 ,暴露出来统一的操作方法,既然官方推荐了,我们就暂时用 dojo/when吧。

包装器wrappers。

在dojo里面如果要增强已有的类的话,可以使用包装器进行层次性的增强,类似插件的性质,这种理念能够让我们做到按需的选择,根据不同的功能来选择不同的wrapper类进行包装,以避免不必要的资源浪费。对于dojo/store来说 这里有两个重要的包装器dojo/store/Cachedojo/store/Observable

dojo/store/Cache

一个Cache对象主要是用来缓存JsonRest的,将远程端的JsonRest的缓存到本地,避免发重复的查询请求,这样就需要构造一个MemoryStore来存储缓存下来的数据,JsonRest来与远程的数据交互。使用的例子如下

require(['dojo/store/Memory', 'dojo/store/JsonRest', 'dojo/store/Cache'],
    function (Memory, JsonRest, Cache) {
  memoryStore = new Memory({});
  restStore = new JsonRest({target:"/Data/"});
  store = new Cache(restStore, memoryStore);
  ...

查询所有的数据

var results = store.query();

查询结果会被缓存到MemoryStore里面,这里我们可以查询数据,来避免不必要的请求

object = store.get("some-id");

如果对store进行更新 删除 增加操作,对应的memoryStore 也会自动发生更新,可以直接访问memoryStore 来查看缓存的数据。

“dojo/store/Observable”

dojo/store/Observable包装后的dojo store(只要是实现了dojo/store API 的类都可以用Observable来包装)功能会得到增强–在支持原来的API的基础上 又让查询结果集的变化可以被监听的。通过store.query得到的结果数组(结果集)有一个observe方法(如果是异步的返回的promise也会拥有这个方法),通过这个方法可以监听我们对查询结果集的操作变动情况,注意observe方法存在查询结果的方法中,而不是store中,事实store上会拥有一个notify方法,用于发出广播(这是一个典型的观察者模式),因为我们对数据的操作都是在store上进行的。因此监听查询结果的变动情况。好吧可能还需要解释一下,操作对象(即更新删除增加对象)是在store上进行的。 而observe是在查询结果集中进行的,也就是说如果store中变动的对象符合调用observe的查询结果集当时的查询条件,那么这些变动将会发通知到查询结果集里面,然后查询结果集的observe会监听到。这种通知变化的机制与旧版本的dojo/data 是不一样的。通过这种模式 可以实现数据的变化与UI更新的分离,对于UI组件实现者来说只需要监听查询结果集的变动即可(显示查询结果集中的东西),对于UI组件的使用者来说完全不必关系如何更新UI,只需要对store进行 CURD即可。

我们的关注的是我们要查询的数据,我们的操作是对store的操作,我们队store操作的时候,store会调用notify方法,如果变化的内容符合查询结果集的查询条件,则结果集的observe会被监听的到。还是上代码吧.

构建 ObservableStore

require(["dojo/store/Memory", 'dojo/store/Observable',"dojo/domReady!"], function(    
                Memory,Observable) {

                memorystore = new Memory({
                    data: pmData,
                    idProperty: 'name'
                 });
                observablestore = new Observable(store); 
    });

这里还是以空气质量PM2.5的数据为例吧,查询 aqi 值大于80的站点。值越高空气质量越差

var worsedata=observablestore.query(function(obj){
        return obj.aqi>80;
    })
 worsedata.forEach(function(d){
     console.log(d.name);
 })

经过Observable 来包装Memory Store(也可以包装JsonRest),调用query之后的结果集(resultset中)拥有一个observe方法,接口的定义格式如下

resultSet.observe(listener, includeObjectUpdates);

第一个参数为监听器,一个毁掉函数,接受三个参数 listener(object, removedFrom, insertedInto);

  • object 被操作(修改,删除,增加)的对象
  • removedFrom 表示object在操作之前在查询结果集中的旧的索引位置,如果为-1 表示改对象之前不在结果集中(不符合查询条件),现在符合条件的对象增加了(可能是store中创建了新符合查询条件的对象,也可能是原来不符合查询条件的对象进行修改之后满足了条件,总之我们的关注点是查询结果集 比如 以上面的查询条件来说有个aqi大于80的对象被增加到了store中 或者 store中一个aqi原来为75的修改成了 aqi为85( 满足了 aqi大于80这个条件了)。
  • insertedInto 表示object在结果集新的索引。如果为-1,表示object不在当前结果集中了,被从查询结果集中移除了。可能是被删除,也可能对象进行了修改,比如 有个aqi大于80的对象从store中被删除了, 或者 store中一个aqi原来为85的修改成了 aqi为75( 不在满足 aqi大于80这个条件了)。

个人感觉 把removedFrominsertedInto 分别改为 oldIndex和newIndex更适合。说白了就是发生变化的对象在变化前后在结果集中的索引位置。

另外observe 还有个参数includeObjectUpdates 表示是否监听查询结果集中对象的更新跟新,但是这种更新之后对象依然符合查询条件,比如上面的 aqi大于80中有个 aqi为 82的对象 修改为aqi 为85,这种情况下,没有影响到查询结果集的个数,如果includeObjectUpdates 设置为false或者不设置的话,这种变化是无法被监听到的,如果设置为true的话 就可以了。调整元素的顺序也是如此。如果想监听查询结果集的变化的话 建议设置为true吧。

对于listener来说 我们可以根据removedFrom和insertedInto 来判断进行了什么操作

  • removedFrom===-1, 新增了符合查询条件的元素,此时insertedInto不可能为-1 insertedInto为新增元素的索引位置,需要UI中新增一项
  • insertedInto===-1 符合查询条件的元素被移除,此时removedFrom不可能为-1,removedFrom代表查询结果集中元素被移除前在结果集中的索引。UI需要UI中移除相应的项
  • removedFrom===insertedInto 且大于-1 ,结果集中的元素发生了更新 需要UI对应的项进行更新

记住我们关注点是查询结果集即符合查询条件的对象,对store的操作只有让查询结果集发生变化(能让查询结果集增加或者修改)listener才会触发。一个元素从查询结果集中被移除 不一定从store上移除,相反如果结果集中增加了元素也不一定是store上新增的对象。而我们关注点是查询结果集,虽然我们的操作是在store上进行的。这种变动会直接反应到查询结果上面。因此Observalstore中的查询结果集 会随着store中的数据的变化做相应的更新。

继续看代码吧 接着上面的例子

 worsedata.observe(function(object, removedFrom, insertedInto){
    console.log(object);
    if(removedFrom===-1){
        console.log("worsedata中的对象增加,新增对象的索引为",insertedInto)
    }
    else if(insertedInto===-1){
        console.log("worsedata中的对象被移除,删除对象的索引为",removedFrom)
    }
 else if(removedFrom===insertedInto){
     //需要将observe的第二个参数设置为 true
    console.log("worsedata中的对象的属性发生更新,更新对象的索引为",removedFrom)
 }
worsedata.forEach(function(a){
     console.log("name:",a.name,'aqi:',a.aqi);
    });
 },true);

newobj={
        "aqi": 83,
        "aqimsg": "优",
        "collectTime": 1418798880775,
        "lvmsg": "lv-good",
        "name": "天安门",
        "path": "beijing/fengtaiyungang",
        "source": "aqicn.org",
        "updateTime": "星期二21点",
        "values": {}
    }
 observablestore.put(newobj); 

新增加的这个对象满足worsedata的条件(即aqi大于80)所以上面的函数会监听的到变动,worsedata值也会自动做相应的增加。如果 此前aqi为70的话,则上面的监听函数不会被触发,因为虽然对store增加了元素,但是这不会对我们的之前的查询结果造成影响,关于修改的例子这里就不再举例子了。Observe的这种设计是非常合理

另外 查询结果集还有个 cancel方法来取消监听(dojo store API 上说的是close ,这里有点出入)

dojo/store/DataStore

为了兼容之前的dojo/data APIdojo/data store的转为Dojo Object store ,dojo官方推出了dojo/store/DataStore,同时在dojo/data里面也增加了一个dojo/data/ObjectStore,将dojo Object store 转为dojo data store,如果是开始新项目的话,其实强烈建议不要再用dojo/data里面的东西了。

总结

dojo Object store 提供了新的数据交互接口,可以说是一套简单易用的数据的交互协议,而dojo/store/Memorydojo/store/JsonRest 是两种不同场景的实现。通过dojo/store/Cache 以及dojo/store/Observable来增强store的功能,同时为了兼容旧的数据接口也提供了相应的类来进行接口的适配。目前来说dojo/store这些功能还不够强大 。另外dojo 2.0将会使用 dstore作为dojo/store 设计思路与dojo store API类似,但是也有出入。 这个功能强大了好多。最新版本的dgrid已经支持dstore(以前只支持实现了dojo Object store API的 store)。

好吧 dojo/data包里面的已经是老掉牙的东西,dojo Object store 目前的 dojo/store包里的东西也正在被换血。。dojo store的未来 是 dstore 既然如此,可是本博文还有什么用呢,这不是在祸害人么。。。我想估计也没多少人能看到这里。。哈哈,能保持耐心看到这里的就直接看dstore 的源码吧。。

The dstore package is a data infrastructure framework, providing the tools for modelling and interacting with data collections and objects. dstore is designed to work with a variety of data storage mediums, and provide a consistent interface for accessing data across different user interface components.

不翻译了

dstore 正在完善中,等dojo2.0的时候就会转正了。应该还是在dojo/store这个大模块里面

写了这么多不知道在写什么,希望对你有所帮助 也可以留言 求轻黑

dojocdn

谷歌为dojo提供了CDN服务,可惜我朝无法正常访问,于是自己在国内空间 -@七牛云存储 上部署了一份。。感谢

dojo的最新版本 1.10.2

http://dojocdn.qiniudn.com/dojo-1.10.2/dojo/dojo.js

未压缩版本

http://dojocdn.qiniudn.com/dojo-1.10.2/dojo/dojo.js.uncompressed.js

本目录与dojo源码的目录一致,同时包含了dojo dijit dojox dgrid put-selector xstyle dstore 目录结构如下

http://dojocdn.qiniudn.com/dojo-1.10.2
                                        |dojo
                                        |dijit
                                        |dojox
                                        |dgrid
                                        |put-selector
                                        |xstyle
                                        |dstore

可以通过dgrid/*,put-selector/*,xstyle!xxx.html,dstore/*引用对应的插件,无需额外的配置

如果要找dojo中的主题

claro.css

http://dojocdn.qiniudn.com/dojo-1.10.2/dijit/themes/claro/claro.css

tundar主题

http://dojocdn.qiniudn.com/dojo-1.10.2/dijit/themes/tundra/tundra.css

soria主题

http://dojocdn.qiniudn.com/dojo-1.10.2/dijit/themes/soria/soria.css

可以通过dgrid/*,put-selector/*,xstyle!xxx.html,dstore/*引用对应的插件,无需额外的配置

以创建grid为例的Demo

Javascript中的类、对象、以及继承

关于对象

虽然JS不是正宗的面向对象语言,但是“对象”这个词却与JS密不可分,好吧,用专业术语来讲JS是 基于对象的语言,或者是基于原型的语言。那么我们先从对象说起。经过艰难险阻,我们只是想要一个功能强大的对象而已。

首先说一下空对象,在JS中可以使用以下两种方式定义“空”对象-其实null才是空对象

var person={};

或者

var person=new Object(); // 或者不带括号 person=new Object   

以上两种方式本质是一致的。平时使用的时候推荐 var person={} 其实内部也是通过调用new Object实现的,类似还有数组、正则对象的定义也是如此。

var myarray=new Array();
var myarray1=[];

我们可以把使用{}、[]定义对象或者数组的方式人为是JS引擎给我们提供了快捷方式。毕竟这两个都是最常用的。
对象定义好之后我们可以给他动态地添加属性或者方法比如

   obj.name='张三';
   obj.eat=function(){
   console.log("吃饭");
}

能够动态地给对象增删属性也是动态语言的好处。

通过 var person={},我们有了一个干净的对象,如此的干净,纯洁,然而是真的如此吗.然而他天生拥有了一些方法 比如 toStringvalueOfisPrototypeOf,hasOwnPropertyconstructor等方法。这些对象来自于哪里呢,是的 来自于他的原型。看来纯洁只是表面而已。

关于原型,原型链。

每个对象都会有个原型(原型对象),自动拥有他的原型的方法,如果想获得一个person对象的原型对象,可以通过Object.getPrototypeOf(person)来获得,还可以通过person.__proto__。原型是对象的隐藏属性,当我们要访问一个对象的属性时候,会首先从对象自身查找,如果对象自身没有这个属性会到他的原型对象__proto__中去找,如果原型对象中还有没有的话的,则会继续到该对象的原型对象的原型对象上去找(原型对象本身也是个对象,所以它自身也会有__proto__属性。。。这样就形成了原型链。。null是原型的链的终点,如果最终没有找到的话则返回undfined。规定undefined没有任何可以访问的属性。如果要访问null的属性则会报错了,比如

var person={age:12};
console.log(person.age); //12
console.log(person.Adress);//undfined 访问到原型链的终点也没有找到这个属性返回 
console.log(person.Adress.school);//抛出异常TypeError: Cannot read property 'school' of undefined 

上面那个错误提示,是我们在调试中经常遇到的比较经典的错误了,遇到这个问题,我们首先第一反应school的上一级的对象没有得到正确的赋值。的确,person没有这个属性的。

原型链的本质:__proto__.__proto__.__proto__.....直到原型链的终点null。

关于函数对象。

在JS中每个函数都是一个对象,可以像其他普通对象一样,添加属性和方法等,即函数对象。每个函数都继承了Object.prototype的方法.就像上面提到的toStringvalueOfisPrototypeOf,hasOwnProperty,constructor。既然说函数也是对象那么他是有谁构造出来的呢,答案是Function。每个function都是Function的一个实例。或者说每个function都有这同一个构造函数即Function

构造器

 new Function ([arg1[, arg2[, ...argN]],] functionBody)

function add(x,y){
     return x+y;
 }
 Peron.constructor===Function //true

其本质与下面一致

var add1=new Function('x','y','return x+y');

实际上我们很少使用new Function 来构造一个函数实例,使用Function构造器生成的Function对象是在函数创建时被解析的。这比你使用函数声明(function)并在你的代码中调用低效,因为使用函数语句声明的function是跟其他语句一起解析的。

关于构造函数,new

在学习其他的面向对象语言的时候,我们会定义一个class,然后new一个对象,我们都知道Javascript中的函数同时也可以作为构造函数,也可以通过new生成一个基于这个构造函数的实例,比如ObjectArrayDateRegExp,Error等这些都是ES标准中内置的构造函数。刚才前面的var person=new Object其实是调用的构造函数,例如为了模拟一个类,会这样写

function Person(name){
    this.name = name;
}

通过使用new Person 就可以创建一个Person的实例 即对象

var bob = new Person('Bob')
// {name: 'Bob'}

为了确定 bob的确是一个 Person的对象,返回一个对象{name: 'Bob'}我们可以调用,当作为构造函数使用的时候这里的this 指向是当前对象实例。

bob instanceof Person
//true

(JS中有个潜规则 一般当做构造函数的用的函数首字母会大写)当然我们也可以不加new 直接当做一个普通的函数来调用它

Person('Bob')
// undefined

直接调用没有返回值,而在调用的时候 this.name=name这里的this指向的是全局作用域,在浏览器环境下这里的this 为window,于是通过调用Person(‘Bob’) 仅仅是创建了一个全局的变量name

name
// 'Bob'
this==window
window.name
// Bob

如果我们的全局变量中已经有了一个为name的变量,那么原来的name值将会被覆盖。于是对全局的变量造成了污染,为我们的程序造成隐患

如果有时候我们要生成对象 却忘记写了new 关键字,又想避免对全局的变量造成污染,我们可以使用这个技巧。

function Person(name){
    if (!(this instanceof Person))
        return new Person(name)
    this.name = name
}
  • 首先检查 this 是不是一个Person的实例 事实上如果是通过new的话 this 则会是 Person的实例

如果this不是Person实例的话 则通过new Person 来调用 构造函数 并且返回这个实例,

事实表明如果new的构造函数中return一个值类型的值,那么Javascript将会忽略它,并且创建一个对象实例作为返回,如果我们返回是其他类型的对象呢。

function Cat(name){
    this.name = name
}
function Person(name){
    return new Cat(name)
}
var bob = new Person('Bob')
bob instanceof Person
// false
bob instanceof Cat
// true

卧槽,这里我new 的是 Person,却得到了一个猫的实例,贵圈好乱。。如果我们new 的函数返回的事引用类型,我们只能得到该引用类型的对象。。

比如也可以返回一个数组

function Person(name){
    return [name]
}
new Person('Bob')
// ['Bob']

如果返回的是Javascript中的原生类型的值(number、string,boolean,null,undefined data) ,这些将会被忽略。构造函数将会正常地返回一个this的实例

function Person(name){
    this.name = name
    return 5
}
new Person('Bob')
// {name: 'Bob'}

###关于Method。

刚才说了 函数可以作为构造函数 通过new 来生成一个新的对象,同时函数还有另外一个功能就是作为的方法。

如果一个函数属于某一个对象 我们称这个函数是这个对象的方法,如下给构造函数添加方法

function Person(name){
    this.name = name
    this.sayHi = function(){
        console.log('Hi, I am ' + this.name); 
    }
}
Person.run=function(){
   console.log('run');
}
var bob = new Person('Bob')
bob.sayHi()
// 'Hi, I am Bob'
bob.run();//报错。。。为什么报错了呢。。。因为。。run是Person这个函数对象的属性,其实例是不具有的
Person.run();

关于prototype 继承

通过子类继承父类,子类可以拥有父类中的属性和方法,在JAVA中我们可以通过如下的方式实现继承

public class Mammal{
    public void breathe(){

    }
}
public class Cat extends Mammal{

}

但是目前(ESCRIPT 5)来讲 JS中还没有class 和extends字段,其实JS是基于原型的语言,JS可以通过原型来实现继承

function Mammal(){
}
Mammal.prototype.breathe = function(){
    console.log("呼吸")
}
function Cat(){
}
Cat.prototype = new Mammal()
Cat.prototype.constructor = Cat
// 现在Cat 拥有了 breath的能力!

刚才说过了每个函数都是一个对象具有了Object实例对象的基本属性,那么实例怎么得到这个这些属性的呢,是的,实例是通过原型获得的,那么这个原型到底在哪呢,答案是:prototype属性。每个函数对象有个prototype属性。prototype是一个对象,作为实例的原型。即实例的原型__proto__为构造函数的prototype属性。

function Person(name){
    this.name = name
    this.sayHi = function(){
        console.log('Hi, I am ' + this.name); 
    }
}
var p=new Person();
    p.__proto__===Person.prototype

每个function的prototype对象都有一个constructor属性,默认这个constructor属性指向这个构造函数自身.
于是就有

Person.prototype.constructor===Person//true

因为我们说过了一个对象可以获得其原型上的属性(如果对象自身不存在这个同名属性的话),因此我们很容易获得这个对象的constructor属性。于是。

Person.prototype.constructor===Person//true
p.constructor===Person //true

三者是指向同一东西即构造函数Person,这里注意区分函数和实例

最终的对象拥有一系列的属性和方法,有些属性是来自自身(构造函数中通过this.xxx定义的属性属于对象自身的属性),有些事来自原型,每个对象都有一个特别的属性__proto__ 指向这个对象构造函数的prototype,每个原型又有一个__proto__属性,一直向上追溯到Object 直到原型链的终点为null,这样就形成了一个原型链,准确来说一个对象的属性一部分来自对象本身,其余的则来自于原型链

  • 对象可以定义自身的方法 来覆盖原型中的方法

默认情况下,构造函数的prototype为Object实例即{}

function Mammal(){
}
Mammal.prototype
// Mammal {}
var mammal = new Mammal()
mammal.__proto__ === Mammal.prototype
//true

每个function的prototype对象都有一个constructor属性,默认这个constructor属性指向这个构造函数自身,但是。。。

function Cat(){
}
Cat.prototype = new Mammal()//修改了Cat的prototype为Mamma实例

var cat=new Cat();

cat.constructor===Cat.prototype.constructor//实例的constructor来自__proto__即Cat的prototype.constructor。
//而此时prottoype为Mammal实例。则会继续沿着Mammal的实例寻找
//constructor。Mammal的实例的原型__proto__为Mammal.prototype //于是找到了Mammal.prototype.constructor 即函数Mammal
cat.constructor===Mammal//true;

实际上我们需要手动调整cat的构造函数 让cat 知道自己是个Cat。

function Cat(){
}
Cat.prototype = new Mammal()//修改了Cat的prototype为Mamma实例
Cat.prototype.constructor=Cat;//修改了constructor的指向。

上面修改了Cat.prototype.constructor属性,那么Mammal的原型上的constructor会不会变化呢。不会的,Mammal实例自身没有constructor属性,而是在Mammal的原型上的,如果设置新的constructor的话,不会修改对Mammal实例原型__proto__ 的属性修改,而是为Mammal实例自身增加一个constructor属性即Cat,于是在cat找constructor的时候
碰到这个新新增的constructor就终止查找了。

综合上述的查找cat实例的原型链为cat.__proto__->Cat.prototype->(new Mammal()).__proto__->Mammal.prototype->Object.prototype->null

访问属性的时候如果对象自身没有会沿着原型链查找,设置属性的时候如果对象自身存在则更更新,如果对象自身不存在则会增加该属性,对原型链不会造成影响。除非对原型的属性直接修改。

      function Person(name){
              this.name=name;
    }
var  zhangsan=new Person("张三");
var  lisi=new Person("李四");
     zhangsan.__proto__.run=function(){
         console.log(this.name+'run');
     }
     zhangsan.eat=function(){
         console.log(this.name+'eat something')
     }

    zhangsan.eat();//张三 eat something 
    zhangsan.run();//张三run 
    lisi.run();//李四run 
    lisi.eat();//报错.....

给构造函数添加方法

给一个构造函数添加方法有两种形式

function Person(name){
    this.name = name
    this.sayHello = function(){
        return 'Hello, my name is ' + this.name
    }
}

或者把方法挂到原型上

function Person(name){
    this.name = name
}
Person.prototype.sayHello = function(){
    return 'Hello,my name is ' + this.name
}

对于第一种方式 每次new一个Person,都会创建一个新的sayHello函数,对于第二个版本,sayHello函数只会创建一次,所有Person实例会共享同一个原型Person.prototype,使用这种方式相对节省内存。那么为什么不把name在挂在原型上,刚才已经说明原因了,name是可以设置的属性,设置之后会增加到对象的实例上,无论如何都不会节省内存。如果放到原型上反而会多浪费一些内存。推荐定义构造函数的时候我们把方法放在原型上,把函数直接放在构造函数内部。

关于this,apply,call

正如我们所知道的,函数如果附加到对象上就变成了方法,并且函数里面的this指向这个对象,真的如此么?还是看之前的例子

function Person(name){
    this.name = name
}
Person.prototype.sayHello = function(){
    return 'Hello,my name is ' + this.name
}
var zhangsan=new Person("张三");
var lisi =new Person("李四");
zhangsan.sayHello();//"Hello,my name is 张三"
lisi.sayHello();//"Hello,my name is 李四"

这里的sayHello并没有直接在zhangsan,lisi这两个对象上,实际 上是在他们的原型上Person.prototype,那么sayHello调用的时候sayHello 是怎么知道是张三

事实上 函数中this 在被调用之前没有绑定到任何对象,只有调用的时候才确定的。

当你调用zhangsan.sayHello(),sayHello里面的this与jack绑定,当你调用lisi.sayHello()的时候,sayHello里面的this指向李四,绑定是个动态的并使对函数本身改变什么,他们调用的都是同以歌函数,只是里面的this是动态确定的。函数被调用的时候this会自动地被绑定为调用他的对象。this被称为函数的当前上下文,如果一个函数不在一个对象上则属于全局对象,如果浏览器环境下则为window

事实上我们可以可以明确地将一个函数的上下文与一个对象绑定。继续上面的例子,比如在全局环境下定义一个singing函数

function singging(){
    console.log(this.name+'singging');
}

如果是在全局环境下直接调用

singing();//输出singing  因为全局环境下没有name,即为空。

可以使用apply 将函数在执行的时候与一个对象绑定

singging.apply(zhangsan)//张三singging

刚才说过了函数也是一个对象,每个函数都有是Funtion的实例,每个函数都共享一个原型Function.prototype。即

singging.__proto__===Function.prototype

而call 就是位于这个原型对象上的属性之一,所以任何一个函数都可以调用apply 将这个函数绑定到你选择的对象上,即使这个函数不在这个对象上。事实上我们可以有不同的方式来使用apply,继续沿用概念刚才的例子,先补上之前的代码

function Person(name){
    this.name = name
}
Person.prototype.sayHello = function(){
    console.log('Hello,my name is ' + this.name)
}
var zhangsan=new Person("张三");
var dog={
        name:'旺财'
    };
zhangsan.sayHello.apply(dog)
//输出 Hello,my name is 旺财 

唉。。。连旺财都会讲hello了,贵圈好乱。本来是属于张三里面方法,却被旺财盗用了。因为里面的this纸指向了旺财即dog,this.name就是dog.name。。。没有谁永远属于谁,虽然sayHello是zhangsan的方法,却禁不住旺财的诱惑。。。。。。这就是传说中的方法借用。
说到方法的借用想到一个比较经典的例子,如何把一个arguments转为数组类型??如何呢,我不说了。。什么,不知道arguments。。。。

###继续回到apply。

apply中第一个参数是是要绑定的对象。如果要传递一些参数,可以放在第二个参数里面,是一个数组类型。 作为函数的arguments来传递。

function Person(name){
    this.name = name
}
Person.prototype.sayHello = function(){
    console.log('Hello,my name is ' + this.name)
}
var zhangsan=new Person("张三");
var lisi=new Person("李四");

function sayHelloTo(other){
    console.log("Hello"+other.name+" , My name is "+ this.name);
}
sayHelloTo.apply(zhangsan,[lisi])//Hello李四 , My name is 张三 。

说到apply,不得不提他的同胞兄弟call,他俩干的活是一样的,第一个参数是要绑定的对象,唯一的区别就是给绑定的函数传递的参数 apply是第二个参数以数组(其实并不是真正的数组 arguments)的形式传递,call是直接放在第一个参数的后面。比如

sayHelloTo.call(zhangsan,lisi);//Hello李四 , My name is 张三

至于apply 在特定的情况是是十分有用的,比如给函数传递的参数是一个数组的话。比如要获取一个数字组成的数组的最大值。可以

Math.max(4, 1, 8, 9, 2)

但是这并不是通用的方法 结合apply可以很容易的解决

var myarray=[1,2,3,4,5,6,7,8]
Math.max.apply(Math, myarray)//8

那么apply可以作为构造函数,这样我只需要给函数传递一个数组作为参数就可以了,就不用把每个参数列出来传递了,比如

new Person.apply(Person, args)

//很遗憾。为什么这样不可以呢,有没有其他的解决方案呢http://stackoverflow.com/questions/813383/how-can-i-construct-an-object-using-an-array-of-values-for-parameters-rather-th#answer-813401

###自定义new 方法

JavaScript 中的 new 操作符有三个基本任务。

  • 首先, 它创建新的空对象。
  • 接下来,它将设置新对象的 __proto__指向函数的prototype,以匹配所调用函数的原型属性。
  • 最后,操作符调用函数,将新对象作为“this”引用传递。

比如要定义一个数组对象。

var o = {};
o.__proto__ = Array.prototype;
Array.apply(o);

o.push(3)

明白了原理我们就可以自己尝试写个new方法

给每个函数添加一个new方法,这个方法要位于Function.prototype上

Function.prototype.new = function(){
    var args = arguments//args为传递过来的参数
    var constructor = this; //this为调用new的对象,即最初的真正的构造函数
    function Delegate(){
        constructor.apply(this, args)//newDelegate的时候this指向Delegate实例,
        //调用真正的构造函数
    }            
    Delegate.prototype = constructor.prototype;//真正的原型对象赋值给Delegate
    return new Delegate //返回一个Delegate实例 即我们需要的对象
}

以上过程中只是修改构造函数执行这个步骤,我们刚才定义的Person类也会有一个这样new的方法

var wangwu= Person.new('王五');

这样就可以像调用普通的函数一样来执行一个构造函数了,同时也可以使用apply传递数组参数来创建实例

Person.new.apply(Person, ['王五'])

###实现基于原型继承封装

定义个extend的函数来扩展已有的函数对象,使其拥有两一个函数对象的功能实现继承

var utils={};
utils.extend = (function() {
    //用户维护原型链的代理
    var F = function() {};
    // 基于Parent 来扩展Child
    return function(Child, Parent) {
        F.prototype = Parent.prototype;
        Child.prototype = new F();
        Child.prototype.super__ = Parent.prototype;//存储父类的原型为了方便获得父类的构造函数
 // 子类的构造函数中需要this.super__.constructor.call(this,args来获得父类中构造函数中定义个属性
        Child.prototype.constructor = Child;
    };
}());

使用方式:

//定义父类
function superfn(name,age){
    this.name=name;
    this.age=age;
    console.log("superfn被调用");
}         
superfn.prototype.getName=function(){
    console.log(this.name+this.age);
};         
function subfn(name,age) {
    this.color = 'red';
    this.super__.constructor.call(this,name,age);//调用父类的构造函数,传递this 很关键,接受父类的属性
};
utils.extend(subfn,superfn);
subfn.prototype.getcolor=function(){
    console.log(this.color);
}
var sub=new subfn("xxx",7);
sub.getName();

总结:

  • 每个对象都有个原型对象属性,原型也是对象,也有原型对象属性,这样每个对象都有一个原型链在Chrome 或者FF 浏览器上可以通过obj.__proto__ 获得这个这个对象的直接原型,(原型链的最低端),最顶端是null。ES标准钟通过Object.getPrototypeOf(obj)来获得对象的原型。
  • 每个对象都有对应的构造函数,比如{} 的构造函数为Object,function a(){}的构造函数为Function,对象的直接原型就是对应构造函数的原型对象属性。即{}.__proto__
    ===Object.prototype; a.__proto__=Function.prototype;
  • Object也是个函数对象,作为对象他也有原型Object.__proto__==Function.prototype,而Function.prototype的__proto__又指向了Object.prototype
    默认情况下函数的prototype.constructor指向这个函数自身,这就意味着,Object.__proto__.__proto__.constructor === Object为true.
  • 函数有protottype和__proto__属性。__proto__是继承的原型。prototype为其构造的实例的原原型。对象(非函数对象)有__proto__,没有prototype属性

最后:一句话。JS是如此的自由,自由的甚至得到不想要的结果。让人着迷,心醉。。。我们要做的就是要学会如何控制她,为我所用,JS大法好。

高性能动画

现代浏览器在完成以下四种属性的动画时,消耗成本较低: position(位置), scale(比例缩放), rotation(旋转) 和 opacity(透明度)。如果你对其他的属性设置动画,你就需要对你的冒险负责。而且你的动画将可能达不到流畅的60fps

通过DevTools从文档对象模型到像素级别的观察

当你用 Chrome DevTools 的 timeline 来查看的话,你可以看到跟一下相似的模式

浏览器处理的过程很简单:计算元素的样式(重新计算样式),生成每个元素的几何形状和位置(布局),绘制图层中的每个像素(初始化绘图并且进行绘图)并且将图层绘制到屏幕上(图层的合成)。

为了生成流畅的动画,你需要让浏览器尽可能少地工作,而最好的方式就是指改变影响合成的属性——transform 和 opacity。瀑布流越高 ,浏览器为了计算每个像素,就做的越多。

对布局进行动画

当你改变一个元素的时候,浏览器可能需要计算布局(位置和大小),这将影响到所有被这项改变影响的元素。如果你改变了一个元素,那么其他元素的几何结构可能会需要重新计算。举个例子,如果你改变 元素的width属性,那么它所有的子元素都将被影响。由于元素的溢出和相互之间的影响,改变可能导致文档树的布局自下而上地被重新计算。

文档树越大,计算布局所花费的时间就越长。所以你应该尽力避免对那些影响布局的的属性设置动画。

项目中一些常见的,会引起布局变化的属性CSS属性:

width height
padding margin
display border-width
border top
position font-size
float text-align
overflow-y font-weight
overflow left
font-family line-height
vertical-align right
clear white-space
bottom min-height

绘图属性的动画

对一个元素的改变也可能引起绘图,而在现代浏览器中,主要的绘制工作会在软件光栅化中进行。这取决于元素在你的应用中如何分层。挨着被改变的元素旁边的其他元素也可能需要被重新绘制。

有很多属性会引起元素的绘制,但以下这些是最常见的属性:

color border-style
visibility background
text-decoration background-image
background-position background-repeat
outline-color outline
outline-style border-radius
outline-width box-shadow
background-size

如果你在元素中对以上的属性设置动画,那么将会引起重绘,并且元素所属的图层将提交给GPU进行处理。对于移动端设备来说,这代价是非常昂贵的,因为它们的CPU的处理能力明显弱于桌面端。这意味着,任务将用更长的时间来完成;并且CPU和GPU之间的带宽是有限的,所以数据的上传需要花费很长的一段时间。

合成属性的动画

有一个CSS属性,你可能认为它会引起重绘,但有时候并不会。就是:opacity. 当GPU在合成元素的纹理结构的时候,会以一个较低的alpha值去处理opacity的改变。它的条件是,元素必须是图层中唯一的一个元素。如果它和其它的元素组合在一起,那么对opacity的改变也会让GPU(错误地)淡化其它的元素。

在Blink和WebKit内核的浏览器中,对于在CSS的transition或者animation中有opacity的改变的元素,将会为其创建一个图层。但也有很多开发者使用translateZ(0)或者translate3d(0,0,0)来人为地强制性地创建一个图层。 强制创建一个图层可以确保图层被绘制完毕并且可以在动画开始的时候,马上进入就绪状态。

一个元素的变换,归结为改变它的位置,旋转角度和缩放。通常,位置的改变是使用left和top属性来改变的。问题是,如前面所述,left和top都会引起图层的变化,并且它的代价是昂贵的。更好的解决方案是使用不会引起图层变化的translate属性。

命令式动画vs说明式动画

开发者常常需要决定它们的动画用JavaScript(命令式)来完成还是用CSS(说明式)来完成。

命令式(javascript)

命令式动画主要的优点同时也是它主要的缺点的是:它在浏览器主进程的JavaScript中运行。主进程已经忙于运行其他的JavaScript,样式的计算,布局还有绘制。所以进程内存在这资源竞争。这实质上增加了掉帧的风险,可能这一帧是你认为最重要的帧。

JavaScript中的动画可以为你提供更多的控制:开始,暂停,回放,中断和取消等细节。

声明式(CSS)

作为替代的方案,你可以用CSS来实现你的渐变和动画。最主要的好处就是,浏览器会对动画进行优化。如果有需要,它会创建图层。并且可以在主进程之外完成一些操作。它最主要的缺点就是CSS动画相对于Javascript动画而言,缺乏表现力。并且很难有意义地组织动画,这意味着创造动画会带来较高的复杂度和错误率。

总结:目前动画 优先使用 opacity 来改变透明度. transfrom——translate rotate scale

css margin折叠

margin折叠-Collapsing margin

所谓margin折叠(Collapsing margin),就是我们常说的盒子垂直边距叠加,具体是指在同一个块级格式化上下文(简称BFC)中,毗邻的两个或者多个垂直外边距会发生互相折叠的现象。其中所说的毗邻可以归结为以下两点

  • 两个或者多个外边距没有被非空内容、padding、border或者clear分隔开。
  • 两个或者多个外边距所归属的元素都处于普通流中,而非浮动元素或者绝对定位元素。

常见发生margin折叠的情况有:

  • 一个盒子元素的top margin与第一个子盒子元素的top margin。
  • 一个盒子元素的bottom margin与最后一个子盒子元素的bottom margin, 但须在该盒子元素的height为auto的情况下 * 一个盒子元素的bottom margin与紧接着的下一个盒子元素的top margin。
  • 一个盒子元素的top margin与其自身的bottom margin,但须满足没创建 BFC、零min-height、零或者“auto”的height、没有普通流的子盒子元素。

margin折叠的后的外边距计算:

  • 参加折叠的元素指都为正,取其中较大的值;
  • 参加折叠的元素值都为负,取绝对值较大的,然后负向位移;
  • 参加折叠的元素有正有负,取出正值中最大的值+负值中绝对值最大的值;
  • 元素自身的 margin-bottom 和 margin-top 相邻时也会折叠,自身
    margin-bottom 和 margin-top 相邻,只能是自身内容为空,垂直方向上 border、padding 为 0;
  • 相邻的 margin 要一起参与计算,不得分步计算,要注意,相邻的元素不一定非
    要是兄弟节点,父子节点也可以,即使不是兄弟父子节点也可以相邻。而且,在计算时,相邻的 margin 要一起参与计算,不得分步计算。
1
<div style="margin:50px 0; background:green; width:50px;"> 
<div style="margin:-60px 0;"> 
   <div style="margin:150px 0;">A</div> </div> </div> 
<div style="margin:-100px 0;background:green; width:50px;"> 
<div style="margin:-120px 0;"> 
    <div style="margin:200px 0;">B</div> </div> </div>

错误的计算方式:分别算 A 和其父元素的折叠,然后与其父元素的父元素的折叠,这个值算出来之后,应该是 90px。依此法算出 B 的为 80px;然后,A和B折叠,margin 为 90px。 请注意,多个 margin 相邻折叠成一个 margin,计算的时候,应该取所有相关的值一起计算,而不能分开分步来算。 以上例子中,A 和 B 之间的 margin 折叠产生的 margin,是6个相邻 margin 折叠的结果。将其 margin 值分为两组: 正值:50px,150px,200px 负值:-60px,-100px,-120px 根据有正有负时的计算规则,正值的最大值为 200px,负值中绝对值最大的是 -120px,所以,最终折叠后的 margin 应该是 200 + (-120) = 80px。

创建了BFC(块级格式化上下文)的元素,不和内部的子元素发生margin 折叠

在windows 下安装 MongoDb

Starting in version 2.2, MongoDB does not support Windows XP

  • 到官网下载MongoDb
  • 解压到制定目录。我的机器目录如下

    D:
      mongodb
             |mongodb                            
                    |mongod.cfg
                    |README
                    |THIRD-PARTY-NOTICES
                    |bin
    
  • 创建MongoDb的数据存储目录,可以再任意文件夹下创建,这里我在D:/mongodb下创建了D:/mongodb/data/db目录
  • 启动MongoDb服务,打开命令行模式下 进入MongoDb的bin目录 运行mongod.exe命令

    D:\mongodb\mongodb\bin\mongod.exe --dbpath d:\mongodb\data
    

输出的结果如下,表示服务启动成功.然后在浏览器输入localhost:28017会打开一个MongoDb的监控网页

Tue May 06 08:56:28.645 [initandlisten] MongoDB starting : pid=808 port=27017 db
path=d:\mongodb\data 64-bit host=DuBaoKun
Tue May 06 08:56:28.646 [initandlisten] db version v2.4.1
Tue May 06 08:56:28.646 [initandlisten] git version: 1560959e9ce11a693be8b4d0d16
0d633eee75110
********
Tue May 06 08:56:29.056 [initandlisten] command local.$cmd command: { create: "s
tartup_log", size: 10485760, capped: true } ntoreturn:1 keyUpdates:0  reslen:37
290ms
Tue May 06 08:56:29.077 [initandlisten] waiting for connections on port 27017
Tue May 06 08:56:29.077 [websvr] admin web console waiting for connections on po
rt 28017
  • 连接数据库 保持上述的Mongodb服务处于开启状态 在开启一个控制台,运行D:\mongodb\mongodb\bin\mongo.exe命令,则会连接成功了。可以按照这篇文章继续操作

将MongoDb配置为服务

虽然进行了以上的配置,但是之后每次使用Mongodb的时候都要先启动服务,如果配置为windows服务,让其自动启动。

  • 创建日志目录:除了配置数据存储目录外,MongoDb还需要要一个日志存储的目录。在d:\mongodb目录下创建log目录(这个目录是任意的)
  • 创建服务配置文件:在d:\mongodb目录下创建mongod.cfg文件。用记事本打开配置dbpath和logpath

    logpath=D:\mongodb\log\mongo.log
    dbpath=D:\mongodb\data\db
    
  • 安装服务。在命令行中运行如下命令

    D:\mongodb\mongodb\bin\mongod.exe --config D:\mongodb\mongod.cfg --install
    

    运行之后提示如下

    1
    Tue May 06 10:03:12.151 Trying to install Windows service 'MongoDB'
    Tue May 06 10:03:12.358 Service 'MongoDB' (Mongo DB) installed with command line
    'D:\mongodb\mongodb\bin\mongod.exe --config D:\mongodb\mongod.cfg --service'
    Tue May 06 10:03:12.359 Service can be started from the command line with 'net s
    tart MongoDB'

此命令成功之后会安装 Mongo Db的服务。并且开机会自动启动,如果要自己控制启动,在开始栏的搜索框输入services.msc可以到计算机管理服务里找个Mongo Db的服务进行设置.

启动服务 net start MongoDB

停止服务 net stop MongoDB

删除服务 “C:\Program Files\MongoDB\bin\mongod.exe” –remove

  • 连接数据库 保持Mongodb服务处于开启状态 开启一个控制台,运行D:\mongodb\mongodb\bin\mongo.exe命令,则会连接成功了。可以按照这篇文章继续操作

渐进性增强与优雅性降级

每个专业词语的背后是一种思想,理解并在实践中应用才是必要的。

优雅降级(graceful degradation)

一开始就构建站点的完整功能,然后针对浏览器测试和修复,使用优雅降级方案,Web站点在所有新式浏览器中都能正常工作,如果用户使用的是老式浏览器,则代码会检查以确认它们是否能正常工作。由于IE独特的盒模型布局问题,绝大多数Web设计师和开发者都通过专门的样式表或针对不同版本的IE的hack实践过优雅降级了;
使用优雅降级技术时,你必须首先完整的实现了网站,其中包括所有的功能和效果。然后再为那些无法支持所有功能的浏览器增加候选方案,使之在旧式浏览器上以某种形式降级体验却不至于完全失效.

渐进增强(progressive enhancement)

一开始只构建站点的最少特性,然后不断针对各浏览器追加功能。从被所有浏览器支持的基本功能开始,逐步地添加那些只有新式浏览器才支持的功能。渐进增强是值得所有开发者采用的做法。渐进增强方案并不假定所有用户都支持javascript,而总是提供一种候补方法,确保用户可以访问(主要的)内容。

使用渐进增强时,无需为了一个已成型的网站在旧式浏览器下正常工作而做逆向工程。首先,只需要为所有的设备和浏览器准备好清晰且语义化的HTML以及完善的内容,然后再以无侵入(unobtrusive)的方式向页面增加无害于基础浏览器的额外样式和功能。当浏览器升级时,它们会自动地呈现出来并发挥作用。

想让网站在任何环境下看起来都保持一致是不可能的,不管为此付出多少努力,结局依旧会令你失望。与其试图让IE看起来堪比年轻它十岁的浏览器,不如努力改善网站的可访问性,或是进行更多的可用性测试,而不仅仅是让页面看起来更靓一点。

某些CSS3特性在不支持它的浏览器中简直是无法模拟实现的,但若使用渐进增强,就无需为了能让你的网站适合所有人而放弃这些技术。仅仅因为部分人不愿或不能升级浏览器,却让使用新型浏览器的用户无法享受CSS3所提供的伟大技术,这是毫无道理可言的。

我们应该先让网站能够正常工作于尽可能旧的浏览器上,然后不断为它在新型浏览器上实现更多的增强和改进。随着时间的推移,当越来越多的人开始升级浏览器而浏览器本身的支持度也不断提升时,就会有越来越多的人体验到这些增强和改进,它持续有效的使网站越来越好,却如需你刻意做什么。

思考

其实优雅降级和渐进增强都是页面的加分项,是针对技术的一种形而上的要求。保证尽可能多的用户都能正常使用网站是第一步,在此之上才需要考虑降级的极端情形和现代浏览器的体验增强。其实可以定义一个基准线,在此之上的增强叫做渐进增强,在此之下的兼容叫优雅降级。

CSS3 选择器总结

在学习新技术的同时不要忘记复习一下那些最基础的东西

基本选择器

  • 通配符选择器(*) 通配符选择器是用来选择所有元素,,也可以选择某个元素下的所有元素
  • 元素选择器 根据tag名字来选择 比如 p body div 等等
  • 类选择器 .className
  • id选择器 #ID
  • 后代选择器 A B (A、B分别为选择器 中间) 有空格
  • 子元素选择器(A>B)(IE6不支持子元素选择器)。
  • 相邻兄弟元素选择器(E + F)

    相邻兄弟选择器可以选择紧接在另一元素后的元素,而且他们具有一个相同的父元素,换句话说,EF两元素具有一个相同的父元素,而且F元素在E元素后面,而且相邻,这样我们就可以使用相邻兄弟元素选择器来选择F元素。

  • 通用兄弟选择器(E >F)

    通用兄弟元素选择器是CSS3新增加一种选择器,这种选择器将选择某元素后面的所有兄弟元素,他们也和相邻兄弟元素类似,需要在同一个父元素之中,换句话说,E和F元素是属于同一父元素之内,并且F元素在E元素之后,那么E ~ F 选择器将选择中所有E元素后面的F元素。比如下面的代码:

  • 群组选择器(selector1,selector2,…,selectorN)

    群组选择器是将具有相同样式的元素分组在一起,每个选择器之间使用逗号“,”隔开,如上面所示selector1,selector2,…,selectorN。这个逗号告诉浏览器,规则中包含多个不同的选择器,如果不有这个逗号,那么所表达的意就完全不同了,省去逗号就成了我们前面所说的后代选择器,这一点大家在使用中千万要小心加小心。

属性选择器

  • E[attr]:只使用属性名,而不论这个属性值是什么,你就可以使用这个属性选择器
  • E[attr=”value”]:指定属性名,并指定了该属性的属性值为value;
  • E[attr~=”value”]:指定属性名,并且具有属性值,此属性值是一个词列表,并且以空格隔开,其中词列表中包含了一个value词,而且等号前面的“~”不能不写;属性选择器中有波浪(〜)时属性值有value时就相匹配,没有波浪(〜)时属性值要完全是value时才匹配。
  • E[attr^=”value”]:属性值是以value开头的;
  • E[attr$=”value”]:而且属性值是以value结束的;
  • E[attr*=”value”]:而且属值中包含了value;
  • E[attr|=”value”]:属性值是value或者以“value-”开头的值

伪类选择器

动态伪类
  • :hover用于当用户把鼠标移动到元素上面时的效果;
  • :active用于用户点击元素那一下的效果(正发生在点的那一下,松开鼠标左键此动作也就完成了)
  • :focus用于元素成为焦点,这个经常用在表单元素上。
    #####UI元素状态伪类

  • :enabled 表单元素可用(比如”type=”text”有enable和disabled两种状态,前者为可写状态后者为不可状态)

  • :disabled
  • :checked 表单元素选中适用于radio checked

    CSS3的:nth选择器
  • :fist-child选择某个元素的第一个子元素;

  • :last-child选择某个元素的最后一个子元素;
  • :nth-child()选择某个元素的一个或多个特定的子元素;
  • :nth-child(length);参数是具体数字
  • :nth-child(n);参数是n,n从0开始计算
  • :nth-child(n*length)n的倍数选择,n从0开始算
  • :nth-child(n+length);选择大于length后面的元素
  • :nth-child(-n+length)选择小于length前面的元素
  • :nth-child(n*length+1);表示隔n选一
  • :nth-last-child()选择某个元素的一个或多个特定的子元素,从这个元素的最后一个子元素开始算;
  • :nth-of-type()选择指定的元素;
  • :nth-last-of-type()选择指定的元素,从元素的最后一个开始计算;
  • :first-of-type选择一个上级元素下的第一个同类子元素;
  • :last-of-type选择一个上级元素的最后一个同类子元素;
  • :only-child选择的元素是它的父元素的唯一一个了元素;
  • :only-of-type选择一个元素是它的上级元素的唯一一个相同类型的子元素;
  • :empty是用来选择没有任何内容的元素,这里没有内容指的是一点内容都没有,哪怕是一个空格.
否定选择器(:not)selctorA:not(slectorB)

用来过滤元素,比如要选择除了type为button之外的任何input可以使用 input:not([type=”button”])
使用

伪元素

CSS 3之前使用:在CSS3中使用::

  • ::first-line选择元素内容的第一行
  • ::first-letter选择文本块的第一个字母,比如用来实现首字下沉。
  • ::before和::after这两个主要用来给元素的前面或后面插入内容,这两个常用”content”配合使用,比如流行的清除浮动。

    .clearfix:before,
    .clearfix:after {
        content: ".";
        display: block;
        height: 0;
        visibility: hidden;
    }
    .clearfix:after {clear: both;}
    .clearfix {zoom: 1;}
    

    -::selection用来改变浏览网页选中文的默认效果

7行代码实现异步函数顺序执行-No Callback

首先假设我们有两个函数,分别按顺序被调用,但是他们都会操作一个相同的变量,第一个函数式为这个变量赋值,第二个函数是使用这个变量:代码如下

var value;
var A = function() {
    setTimeout(function() {
        value = 10;
    }, 200);
}
var B = function() {
    console.log(value);
}

如果我们先后调用A() ,B() 我们将会得到输出结果为 undefined,因为函数A式异步的设置变量的值,在执行B的时候,还没有为value赋值,但是我们可以给A传递一个回调函数,带赋值之后执行这个函数,如下

var value;
var A = function(callback) {
  setTimeout(function() {
    value = 10;
    callback();
  }, 200);
};
var B = function() {
  console.log(value);
};

A(function() {
  B();
});

这种方式是可行的,但是假设,如果我要执行四个甚至更异步的方法,这样一直传递回调函数的话将会让代码变得杂乱,并且代码也会失去美观,于是这就是传说中的callback hell,就像这样

A(function() {
    B(function(){
         C(function(){
                  D(function(){
                      E();
                  });
              });
          });
        });

可以考虑写一个小的函数,来帮助我们来完成这个过程,首先从最简单的开始

var queue = function(funcs) {

}

我们可以通过queue([A,B]);分别实现A和B的顺序调用。分别获得每个函数,然后执行

var queue = function(funcs) {
    var f = funcs.shift();
    f();
}

如果执行这些代码话,将会得到如下的异常TypeError: undefined is not a function.,因为A需要接受一个回调函数作为参数,但是并没有传递参数,所以会报错了,下面定义一个next函数作为A的参数进行传递

var queue = function(funcs) {
    var next = function() {
        // ...
    };
    var f = funcs.shift();
    f(next);
};

next函数是在A执行结束之后执行的,这样只是执行了函数A,如果要执行数组里面的每一个函数可以做如下调整

var queue = function(funcs) {
    var next = function() {
        var f = funcs.shift();
        f(next);
    };
    next();
};

到此为止我们基本上已经实现了我们的目标了。通过queue([A,B]);可以实现A和B的顺序执行,即A执行完之后才执行B。以上代码中的shift是一个关键的函数,他移除并返回数组中的第一个元素,执行多次之后,这个数组最后悔变为空的,因此最终会导致错误,为了验证这个,假设这里有两个函数,我们不知道他们的执行顺序,它们都会接受一个回调函数,并在函数的末尾执行回调函数。

var A = function(callback) {
    setTimeout(function() {
        value = 10;
        callback();
    }, 200);
};
var B = function(callback) {
    console.log(value);
    callback();
};

如果按照顺序调用queue([A,B])的话,我们会得到TypeError: undefined is not a function。为了避免这种情况的发生,在next函数里面要检查funcs是否为空

var queue = function(funcs) {
    (function next() {
        if(funcs.length > 0) {
            var f = funcs.shift();
            f(next);
        }
    })();
};

下面考虑一下更多的情况,这个函数里面的this是在闭包里面的,默认是指向了全局的window对象,如果我们可以给他绑定我们自己的作用域会更好一些比如:

var queue = function(funcs, scope) {
    (function next() {
          if(funcs.length > 0) {
              var f = funcs.shift();
              f.apply(scope, [next]);
          }
    })();

我用 apply 替换了f(next)来实现设置函数的作用域以及传递next作为参数,最后一步就是考虑参数函数之间的传递,首先我们不知道传递的参数的个数,于是使用arguments变量,这个变量存在每个函数中的,是一个ArrayLike对象、里面存储了执行改函数时传递过来的参数列表,因为apply函数的第二个参数需要接受一个真的数组,来传递参数列表。可以使用Array.prototype.slice.call(arguments, 0) 搞定

var queue = function(funcs, scope) {
    (function next() {
          if(funcs.length > 0) {
              var f = funcs.shift();
              f.apply(scope, [next].concat(Array.prototype.slice.call(arguments, 0)));
          }
    })();
};

到此为止这个功能已经完全实现了让异步函数的同步执行,可以调用方式如下

    var obj = {
        value: null
    };

    queue([
        function(callback) {
            var self = this;
        setTimeout(function() {
            self.value = 10;
            callback(20);
        }, 200);
    },
    function(callback, add) {
        console.log(this.value + add);
        callback();
    },
    function() {
        console.log(obj.value);
    }
], obj);

函数的执行结果 为

30
10

言之就是把这些异步的函数放在一个数组里,然后通过next里面的shift来获取函数并传递next并实现了依次调用、最后列出queue函数的源码

var queue = function(funcs, scope) {
    (function next() {
          if(funcs.length > 0) {
              funcs.shift().apply(scope || {}, [next].concat(Array.prototype.slice.call(arguments, 0)));
          }
    })();
};

原文地址:http://krasimirtsonev.com/blog/article/7-lines-JavaScript-library-for-calling-asynchronous-functions