Giter Club home page Giter Club logo

chassis's People

Contributors

miller avatar xspider avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

chassis's Issues

事件

<<返回目录

Events 是一个可以被mix到任意对象的模块,它拥有让对象绑定和触发自定义事件的能力。 事件在被绑定之前是不需要事先声明的,还可以携带参数。

看下面的例子:

var object = Chassis.mixin({}, Chassis.Events);

object.bind("alert", function(msg) {
  alert("Triggered " + msg);
});

object.trigger("alert", "hi,Chassis!");

事件还提供了几种非常有用的API,详细的使用方法可以参考文档API。

在Chassis的里,事件无处不在,ModelView(PageView、SubView、GlobalView)、RouterHistory等都自动继承了Events的各种方法,比如,Model的属性被变更时会触发change事件,PageView切换时会触发onBeforePageIn/onAfterPageIn事件。

<<返回目录

模块化按需加载

<<返回目录

按需加载异步加载是不同的,按需加载是指:当需要某些SubView时,该SubView才及时创建并可用。

按需加载需要基于异步加载,并使用一些简单的方法,如示例中helloRocket里的代码:

(function($){

Chassis.PageView.sayhello = Chassis.PageView.extend({

    el: '#sayhello_page'

    ,init: function(options){
        var me = this,opt;

        opt = $.extend({}, options);

        me.prepend('say_header',opt);
        me.setup('sayhello_content',opt);

        // render异步子模块,也可以这样写:me.renderAsyncSubView(['say_header','sayhello_content']);
        me.renderAsyncSubView();
    }
});

})(Zepto);

代码中所示me.prepend('say_header',opt);me.setup('sayhello_content',opt);不会直接render到页面,只有在显示的调用renderAsyncSubView方法后才会生效。

如果renderAsyncSubView参数为空,则render当前PageView下所有异步的模块。支持指定render特定的异步模块,参数为renderAsyncSubView([asyncModule,...])

按需加载按需渲染可能更恰当一些,因为它并不是等到需要渲染时才异步加载再渲染的,而是提前异步加载,这样有助于减少这类模块下载等待的时间。

<<返回目录

PageView

<<返回目录

PageView是Chassis中非常重要的组成部分,它是APP UI的主要展现载体。PageView与PageView之间是不允许嵌套的,它主要由SubView组成。

PageView的定义

使用以下方式来自定义PageView。

Chassis.PageView.define( 'detail', {
    el: '#detail',
    events: {},
    init: function(){}
} );

这里需要注意的是define方法的第一个参数,它并不是随意定义的,而是与APP的路由规则密切相关。

如果有如下的路由规则:

Chassis.history.start( {
    router: {
        routes: [ 'detail/:id' ]
    } 
} );

则当APP切换到类似http://yourdomain.com/#/detail/1233URL时,路由处理器会在PageView中寻找ID为detail的PageView并自动初始化。

关于路由的更多信息请参考《Chassis的路由使用》

PageView的中可以通过以下的方式来添加SubView:

Chassis.PageView.define( 'detail', {
    el: '#detail',
    events: {},
    init: function(){
        this.append( Chassis.SubView.create( 'detail.section' ) );
    }
} );

此外,还可以调用prepend方法和setup方法。其中appendprepend都会将SubView的DOM结点添加到PageView中,而setup不会执行该操作,只是建立父子关系。如果SubView中的结点是自动创建的话使用前两种方式,如果结点已经在DOM树中了则直接调用setup即可。

关于PageView定义中的其他参数解释请参考《Chassis视图使用指南》

PageView的实例化

通常情况下,PageView的实例化是由Chassis路由自动完成的,但是在某些特殊情况下也可以通过以下方式来实例化一个PageView:

Chassis.PageView.create( 'detail', {
    id: 'detail',
    className: 'detail'
} );

<<返回目录

build

<<返回目录

Chassis的最终代码(完整版、精简版)、API文档等都是通过build工具来构建的。

构建过程请按照以下步骤执行:

  1. 安装nodejs、npm
  2. 安装grunt
npm uninstall -g grunt
npm install -g grunt-cli
  1. 下载最新代码并解压。
  2. 进入Chassis的源码根目录,执行以下命令
npm install
  1. 构建
grunt

项目构建时导出来的命名空间是Chassis,可以根据自己的需求配置新的命名空间

打开根目录下的文件Gruntfile.js,修改第15行即可:

          return src.replace( /__Chassis__/g, '新的命名空间' );

_常见问题_

  1. 默认的第三方开源库经常会被墙,可以更改设置
npm config set registry "http://npm.tuna.tsinghua.edu.cn/registry"
  1. 安装phantomjs出错时,可以手动更新包
    下载phantomjs-1.9.1-windows.zip到当前盘符的/tmp/phantomjs/目录下。然后执行
npm install phantomjs

<<返回目录

构架失败

使用grunt构建时出现错误,以下截取部分失败时才错误。
Linting src/view/view.fx.slider.js...ERROR
[L104:C32] Unexpected space after '('.
setTimeout( function() {
[L103:C28] Unexpected space after '('.
setTimeout( function() {
[L62:C24] Unexpected space after '('.
setTimeout( function() {

Linting src/view/view.js...ERROR
[L22:C33] Unexpected space after '('.
this.cid = Chassis.uniqueId( 'view' );
[L22:C40] Unexpected space after 'view'.
this.cid = Chassis.uniqueId( 'view' );
[L184:C35] Missing space after '('.
Chassis.$(window[ selector ])
[L184:C53] Missing space after ']'.
Chassis.$(window[ selector ])
[L371:C35] Unexpected space after '('.
attrs = Chassis.mixin( {}, this.attributes || {} );
[L412:C29] Unexpected space after '('.
throw new Error( 'view is not an instance of Chassis.View.' );
[L412:C72] Unexpected space after 'view is not an instance of Chassis.View.'.
throw new Error( 'view is not an instance of Chassis.View.' );
[L415:C2] Unexpected space after '}'.
} );
[L495:C2] Unexpected space after '}'.
} );

Linting src/view/view.loading.js...ERROR
[L20:C23] Unexpected space after '('.
Chassis.$( 'body' ).append( $el );
[L20:C30] Unexpected space after 'body'.
Chassis.$( 'body' ).append( $el );
[L152:C14] Unexpected space after '}'.
} );

Warning: Task "jsbint:all" failed. Use --force to continue.

Aborted due to warnings.

PageView回收

<<返回目录

在SPA类APP应用中,除了SubPage的回收外,还有PageView的回收。PageView的回收是指:当创建的PageView数量大于设定的上限值时,就会回收一些PageView,以减少对内存的使用、加快应用可操作性的速度。

回收的策略是:

  1. 被回收的PageView是非活动的。
  2. 当前的PageView数量已达设定的上限。(默认20)
  3. 每次仅回收SubView数量最多的PageView
  4. 被回收的PageView会在其活动时重新创建。

所以,回收的过程是透明的,不需要业务端额外的代码。

<<返回目录

Chassis视图使用指南

<<返回目录

View是Chassis中非常重要的组成部分,APP中可以没有Model但是不会没有View。View在Chassis中担当的职责包括:数据读写、UI渲染、页面切换以及用户交互。由此可见,Chassis中的View并不是传统MVC中的 V ,而是相当于 Controller 的角色。

分类

在Chassis中,按功能划分View被分为PageView、SubView以及GlobalView:

  • PageView: 页面视图,代表的是一个完整的逻辑页面,是APP路由的对象;
  • SubView: 子视图,代表的是逻辑页面中的某个子模块,PageView可以包含零个或多个子视图;子视图可以被定义为子页面(SubPage)并进行子页面路由处理;
  • GlobalView: 全局视图,代表的是APP中全局固定的部分,例如全局导航栏等;全局视图可以监听路由事件,也可以向指定的PageView派发事件,但是本身不参与任何路由逻辑;

在Chassis的内部实现中,PageView、SubView以及GlobalView都继承于Chassis.View类,而且绝大部分实现代码也都位于基类中,因此接下来我们主要会针对Chassis.View进行分析。

生命周期管理

在View的基类中有三个类方法以及一个实例方法直接参与着View的生命周期管理。需要注意的是,基类是一个虚类,不要直接使用基类创建类实例或者自定义视图类。

自定义视图类

在使用Chassis开发APP时,通常都是通过继承方式来自定义视图类,这里会用到基类中的define方法。

使用define方法定义一个PageView类:

Chassis.PageView.define( 'home', {
    el: '#home',
    events: {},
    init: function(){}
} );

define执行时实际上是在Chassis.PageView命名空间下新增了一个key为home的Chassis.View子类。

实际上它等同于:

Chassis.PageView.home = Chassis.PageView.extend( {
    el: '#home',
    events: {},
    init: function(){}
} );

而在实际开发中,你确实也可以使用第二种方法来自定义类,但是我们还是强烈建议你使用define。一方面代码会更简洁,另一方面如果key中包含特殊字符时也会更便利,此外对于PageView而言,key就是路由中的action,因此必须得小写,通过define的方式可以避免大小写不一致的问题。

此外,SubView和GlobalView都有define方法,因此通过相同的方式自定义视图。

实例化视图

通过前面的介绍大家知道了,视图的定义实际上是通过继承的方式得到一个新的视图类,并放在了对应视图类的命名空间下,那么实例化视图可以简单的使用这种方式:

var homeView = new Chassis.PageView.home( {} );

不过,我们还是强烈推荐使用以下方式来创建实例:

var homeView = Chassis.PageView.create( 'home', {} );

获取视图类

通过define( id, definition );可以定义一个视图类,相应的通过get( id ); 接口可以从相应的视图空间中获得类定义。

var HomeView = Chassis.PageView.get( 'home' );

当然也可以直接获取:

var HomeView = Chassis.PageView.home;

但是同样会有大小写问题,或者有特殊字符需要使用引号访问的问题。

销毁视图

当你需要销毁某个视图时,务必记得使用视图实例上的destroy接口。调用该接口不仅仅会销毁视图对应的DOM结点,还包括所有通过events对象注册的事件以及当前视图中的所有子视图。

var homeView = Chassis.PageView.create( 'home', {} );
homeView.destroy();

层级管理

Chassis的视图支持多级嵌套,这样便于开发者灵活的拆分模块。为了实现上的简洁性,层级管理的接口也是在基类中,包括以下几个接口:

  • append(view): 将目标视图的DOM结点append到当前视图的DOM结点中,同时建立视图层级关系;
  • prepend(view): 将目标视图的DOM结点prepend到当前视图的DOM结点中,同时建立视图层级关系;
  • setup(view): 仅建立视图层级关系;

这里的视图关系包括:

  • 将目标视图的parent属性指向当前视图;
  • 将目标视图放入当前视图的children属性中;

如果你的SubView是新创建的DOM结点,而并不是在已有DOM上操作时,创建层级关系需要调用appendprepend方法,否则可以使用setup方法。

这里需要注意,虽然接口在基类中,但是实际上PageView是不允许相互嵌套的,同时PageView和GlobalView也不应该相互嵌套。除此之外,PageView和GlobalView都可以可以嵌套多个SubView,而SubView中可以继续嵌套SubView。

GlobalView PageView
    |          |
 SubView    SubView
               |
            SubView

事件管理

events对象

当你自定义视图类或者创建视图类的实例时都可以定义一个events对象,例如:

Chassis.PageView.define( 'home', {
    el: '#home',
    events: {
        'click .btn': 'onBtnClick' 
    },
    init: function(){},
    onBtnClick: function(){}
} );

Chassis.PageView.define( 'home', {
    el: '#home',
    init: function(){},
    onBtnClick: function(){}
} );

var homeView = Chassis.PageView.create( 'home', {
    events: {
        'click .btn': 'onBtnClick' 
    }
} );

在视图中,通过events对象几乎可以将所有的事件注册都通过配置的方式来实现,像上面的例子所述,events对象的格式如下:

{ 'type target': 'handler' || handler }

其中handler表示的是事件的callback函数,可以是字符串,也可以是直接的函数定义。如果是字符串的话会通过this[handler]来查找callback。callback被调用时的参数为事件参数,执行context为当前视图;

type表示事件名,根据不同的target而不同。

target为需要监听的对象,这里包括多种类型:

  • dom selector: 表示监听指定的DOM结点;这类事件注册时,会将当前视图元素中selector所选中DOM结点的事件通过代理的方式注册到当前视图的元素中;即当前视图所在元素会代理一切此类DOM事件;例如'click .btn',会将this.$el中所有class为.btn的元素的click事件代理到this.$el上;此外,如果selector为空则表示直接注册到this.$el上;
  • view: 表示监听当前视图所触发的事件,例如beforepagein view
  • model: 表示监听当前视图中的model所触发的事件,例如change model
  • window: 表示监听window上的事件;
  • document:表示监听document上的事件;

例如:

{
    'mousedown .title': 'edit',
    'click .button': 'save',
    'click .open': function( e ){},
    'orientationchange window': 'refresh',
    'click document': 'close',
    'beforepagein view': 'onBeforePageIn',
    'change model': 'render'
}

在定义视图时配置events对象以及在实例化时传入events对象都能使视图自动进行事件注册,这种方式是强烈推荐的,因为在视图销毁时会自动解除这一过程中绑定的事件,如果使用其他方法注册则无法保证能自动解除。

delegateEvents方法

如果需要在视图运行中注册事件,同样建议调用delegateEvents(events)方法来处理,因为该方法会用相同的方法来注册事件,在视图销毁时可以确保事件被解除。

触发的事件

视图本身在不同的状态下也会触发一些事件。

beforepagein

在视图切换中,当前视图即将进入可视区之前会触发该事件,如果要监听可以使用以下方式:

Chassis.PageView.define( 'home', {
    events: {
        'beforepagein view': 'onBeforePageIn' 
    },
    onBeforePageIn: function(){}
} );

但实际上,由于该事件监听比较频繁,Chassis对此做了简化,无需在events对象再进行配置,可以直接定义onBeforePageIn方法。即上述代码等同于:

Chassis.PageView.define( 'home', {
    onBeforePageIn: function(){}
} );

afterpagein

beforepagein对应,在视图已经完全进入可视区时触发,同样可以省略events对象的配置,可以直接定义onAfterPageIn方法。

beforedestroyafterdestroy

在视图销毁之前以及销毁完成后触发。
如果要响应beforedestroy事件可以直接定义onDestroy方法,而无需配置events对象。

实例化参数

在实例化视图时可以配置的参数包括:modelelidattributesclassNametagNameevents

  • model: 视图对应的Model实例;
  • events: 事件注册配置;

其他的都与视图所在的DOM结点相关:

  • el: 视图对应的DOM元素,可以是$对象,也可以是html代码;
  • id: DOM ID;
  • attributes: DOM属性;
  • className: DOM CSS class;
  • tagName: 自动创建DOM时的tagName;

这里需要注意的是,其中el参数和其他参数是互斥的,如果设置了el参数则会直接将el转换成this.$el而不会进行DOM属性的设置;如果未设置el则视图会自动创建DOM并设置DOM属性。

其他方法与属性

  • $el属性: 通过this.$el可以获取到当前视图对应的DOM结点;
  • $方法: 通过this.$方法可以在当前DOM结点中进行元素查找;

<<返回目录

Chassis的路由使用---pushState

<<返回目录

pushState的使用和hash没什么太大的区别,使用上非常简单:

Chassis.history.start( {pushState:true} );

默认会使用当前路径作为pushState的根目录,比如http://example.com/music/index,pushS
tate会使用/music/作为根目录。整个路由都会被控制在http://example.com/music/下。

比如Chassis.navigate( 'info' ),地址就会变为http://example.com/music/info

如果想指定新的root,可以这样:

Chassis.history.start( {pushState:true,root:'/'} );

有个问题是:当页面刷新后,因为当前地址并不存在,所以需要做一些重写。

<<返回目录

模块化异步加载

<<返回目录

由于SPA类应用的特殊性,为了提高首屏可操作性的速度,大部分的模块都可以使用异步加载来完成。

异步加载框架可以使用常见的seajs或百度的FIS异步加载包。Chassis本身不包含这类框架。Chassis对异步加载框架的需求仅是:能异步的获取代码并执行,对是否满足AMD/CMD没有要求。

在使用异步加载之前,需要先做一个简单的配置,以指定异步加载框架及指定异步加载的路径。

F.load = seajs.use;

//如果需要特殊的指定加载路径规则,可以这样
Chassis.load.config.ruler = function( pkg ){
                return '../test/data/' + pkg;
};

下面展示subview异步加载的一个例子:

(function($){

Chassis.PageView.sayhello = Chassis.PageView.extend({

    el: '#sayhello_page'

    ,init: function(options){
        var me = this,opt;

        opt = $.extend({}, options);

        //异步加载subview
        me.prepend('say_header',opt);
        me.setup('sayhello_content',opt);

        /* 这样写就不能使用异步和按需加载
        me.setup(new rocket.subview.sayhello_header(
            $.extend({}, options)
            ,me
        ));

        me.setup(new rocket.subview.sayhello_content(
            $.extend({}, options)
            ,me
        ));
        */

        // render异步子模块,也可以这样写:me.renderAsyncSubView(['say_header','sayhello_content']);
        me.renderAsyncSubView();
    }

});

})(Zepto);

异步加载唯一的要求就是:不要自己生成subview的实例,交给框架来做。

再比如SubPage也是如此:

(function($){

Chassis.SubView.say_content = Chassis.SubView.extend({

    className: 'say_page_content'

    ,init: function(options){
        var me = this,
            title = options.title,
            subView,
            spm;

        this.spm = new Chassis.SubPageMgr({
          owner: this,
          max: 15,
          klass: 'say_content_detail' //rocket.subview.say_content_detail
        });
    }

    , onBeforePageIn : function(){
        this.$el.show();

    }

});

<<返回目录

源码结构介绍

<<返回目录

.
|--Chassis
   |---docs API文档目录
   |---examples 示例目录
       |---fis-project 使用百度fis工具构建的项目示例
       |---sample 一个非常简单的示例
   |---src 源码目录
       |---base
          |---base.js
          |---lang.js
          |---event.js
       |---model
       |---router
       |---view
   |---test 测试用例目录
   |---Gruntfile.js grunt文件
   |---.jshintrc grunt构建选项jshint配置文件

<<返回目录

SubView复用

<<返回目录

在SPA类应用中,会常常使用一些相同的SubView,Chassis提供了内部的机制来完成SubView的复用。

SubView的复用不需要使用额外的代码或则特殊处理,只需要按照模块的异步加载要求即可:不要手动的产生新实例(TODO去掉这一要求,使得每次产生相同的实例)

(function($){

Chassis.PageView.sayhello = Chassis.PageView.extend({

    el: '#sayhello_page'

    ,init: function(options){
        var me = this,opt;

        opt = $.extend({}, options);

        me.prepend('say_header',opt);
        me.setup('sayhello_content',opt);

        /* 这样写就不能异步加载
        me.setup(new rocket.subview.sayhello_header(
            $.extend({}, options)
            ,me
        ));

        me.setup(new rocket.subview.sayhello_content(
            $.extend({}, options)
            ,me
        ));
        */
    }

});

})(Zepto);

<<返回目录

Chassis的路由使用

<<返回目录

Router 为SPA类应用提供前进、后退、刷新以及可分享、收藏的URLs的功能。

示例

看一个简单的例子:

假设我们的webapp应用有两个页面,首页(index)和详情页(info),满足以下规则时则实例化对应的页面类:

页面 URLs PageView
首页 http://example.com/# Chassis.PageView.index
详情页 http://example.com/#info/(id) Chassis.PageView.info

相应的路由规则写法如下:

var Router = {
    routes :[ "info/:id" ]
};

Chassis.history.start( {router:Router} );

routes配置指定了路由的规则,info/:id的配置更有趣,它映射形式如#info/100这样的路由到info类,在info类里,参数值(100)会被当作参数传入。

另外,routes仅指定了一个配置,实际上还隐含的指定了一个空配置,指向index类。代码等同于下面这段代码:

routes :[ "","info/:id" ]

这段短小的代码其实还隐藏了一些秘密

其一是页面切换的顺序,Chassis的一大亮点是页面的动画切换,两个页面之间存在着先与后的顺序问题,当从index切换至info页面时,页面会从右切换至左边(也可能是从下切换到上等),回退时则从左切换到右。routes的数组值隐含的指定了这个顺序。如果你期望的顺序恰好相反,可以这样:

var Router = {

    routes : {
        ''          : 'index',  //空或'#'的写法效果是一样的
        'info/:id'  : 'info'
    },

    pageOrder: [ 'info', 'index' ]

};

其二是自动实例化相应的View类。当路由规则匹配info时,框架自动实例化Chassis.PageView.info(即:new Chassis.PageView.info),如果它存在的话。如果你期望在实例化之前做点什么,比如修正参数之类的什么,可以这样:

var Router = {

    routes :[ "", "info/:id" ],

    index : function(){ 
        // new Chassis.PageView.index( this.Request );
        new Chassis.PageView.home( this.Request );
    },

    info : function(){
        if( !this.request.id ){
            new Chassis.PageView[ '404' ];
            return false; // return false后将不会继续调用默认的Chassis.PageView.info
        }
        new Chassis.PageView.info( this.Request );
    }

};

其三是默认使用 hash的路由方案,如果想使用pushState的话,可以这样:

Chassis.history.start( {router:Router,pushState:true} );

pushState的使用和hash没什么太大的区别,使用上非常简单:

Chassis.history.start( {pushState:true} );

默认会使用当前路径作为pushState的根目录,比如http://example.com/music/index,pushS
tate会使用/music/作为根目录。整个路由都会被控制在http://example.com/music/下。

比如Chassis.navigate( 'info' ),地址就会变为http://example.com/music/info

如果想指定新的root,可以这样:

Chassis.history.start( {pushState:true,root:'/'} );

有个问题是:当页面刷新后,因为当前地址并不存在,所以需要做一些URL Rewrite。

其四是默认自动触发当前路由,如果直接打开http://example.com/#info/100这类的地址,info类就会自动被实例化。你可以传入trigger参数阻止默认行为的发生:

Chassis.history.start( {router:Router,trigger:false} );

<<返回目录

模型

<<返回目录

Model 是SPA类应用程序的核心,包括数据的获取、转换、验证、访问控制等。Model 也提供了一组基本的管理变化的功能。

下面的示例演示了如何定义一个模型,包括自定义方法、设置属性、以及触发该属性变化的事件。

Chassis.Model.define( 'sidebar', {
  promptColor: function() {
      var cssColor = prompt( '请输入一个CSS颜色值:' );
      this.set({ color: cssColor });
  }
} );

var sidebar = Chassis.Model.create( 'sidebar' );

sidebar.bind('change:color', function(model, color) {
  $('#sidebar').css({background: color});
});

sidebar.set({color: 'white'});

sidebar.promptColor();

<<返回目录

Hello,Chassis!

创建index.html:

<!doctype html>
<html>
<head>
  <meta charset='utf8'>
  <title>Hello,Chassis!</title>
</head>
<body>
    <div id="example"></div>

  <script type="text/template" id="demo">
    hello,<%= name %>!
  </script>
  <script src="lib/baidutemplate.js"></script>
  <script src="../../build/chassis.js"></script>
  <script src="lib/app.js"></script>
</body>
</html>

创建lib/app.js:

var Router = Chassis.Router.extend({
    routes : [ '' ]
});


Chassis.PageView.index = Chassis.PageView.extend({
    el: '#example',

    init : function(){
        var tpl = $('#demo').html(), data;
        this.model = new Chassis.Model( {name : 'Chassis'} );

        data = this.model.toJSON();

        this.$el.html( baidu.template( tpl,  data ) );
    }
});

var router = new Router;
Chassis.history.start();

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.