Giter Club home page Giter Club logo

deep-in-vue's Issues

数据驱动更新原理补充说明

为了更好的了解数据驱动更新原理,我们先来要理解如下几个组成部分:

  • data
  • template
  • directive
  • binding

data -> template

模板使用data中的数据来实现动态渲染,如下所示:

Template

<div id='app'>
      <h2 v-text='hello'></h2>
</div>

Data

new TinyVue(
      data: {
             hello: 'Hello World!'
     }
)

模板中的v-text='hello'会渲染为data.hello的值,记得到如下的html.

<div id='app'>
      <h2 v-text='hello'>Hello World!</h2>
</div>

那么这里两者是怎么关联的呢?就是通过binding来实现的,如下就是一个简单的binding:

Binding

var binding = {
      value: '',
      directives: []
}

每一个binding都会对应一个data中的key,如v-text='hello'中的 hello 就是一个key.

在TinyVue实例中,会将所有的binding以binding对应的key为键值存储起来,这样当key对应的data数据更新的时候就能取出对应的binding,而binding又记录了关联的directive,从而就知道如何去更新UI了。

Bindings对象:

var vm = new TinyVue(
      data: {
             hello: 'Hello World!'
     }
);

var binding = {
      value: '',
      directives: [textDirective]
};

vm._bindings = {
       hello: binding
}

vm.hello = 'hi, girl!';

当执行 vm.hello='hi, girl!'后,会触发hello的setter操作,然后在setter操作中取出对应的binding(已经存储在vm._bindings中),

var binding = vm._bindings['hello'];

得到binding后,就可以执行绑定的指令的更新操作来更新UI了。

binding.directies.forEach(function(directive){
      direcitve.update();
})

如上所示就完成了数据驱动界面的响应。

direcitve 说明

可以简单的将指令(directive)当做一个函数即可,如 text 指令:

    /**
    * 对应于 v-text 指令
    */
    text: function (value) {
        this.el.textContent = value || '';
    }

指令和dom节点的绑定是在解析模板的时候完成得,解析得到directive后,在directive中存储dom节点。

directive.el = el;

即text指令的update函数,当执行 directive.update函数的时候,就会更新关联dom节点的文本内容。

详细实现可参考源码

v-if/v-for指令实现原理

为什么要单独将v-if/v-for指令提出来单独说明,因为与v-text/v-show等指令不同,他们会改变DOM结构,改变DOM结构就会影响到vue的编译过程,同时会涉及到重新编译的过程。

以v-for为例,根据条件的不同,v-for指令会删除或是新增该DOM节点,如果是删除,那么会导致父节点的子元素的个数减少一个,那么久会影响父节点后续节点的编译,新增的过程会更麻烦,因为新增后的节点需要重新编译,为了不影响整个组件,所以需要单独进行编译处理。

DOM结构更改问题

因为指令的预处理会更改节点的结构,所以会对其余节点的编译,所以需要自己了解哪些节点已编译,哪些节点是未编译的,不会少编译也不会重复编译。

如下为compilerNode函数的部分代码:

function  compilerNode (el) {
          for (; el.index < el.childNodes.length; el.index++) {
                /**
                 * el.index 表示当前第几个子元素,在compileNode函数中可能会更改el的子元素结构,
                 * 所以需要el.index来标识编译的节点索引
                 */
                let child = el.childNodes[el.index];
                compileNode(child);
            }
}

如上为一个基本的DOM递归遍历,如果递归的过程中DOM结构没有发生变化,那么是不会有什么问题的,但是当遇到v-for这样的指令时,会对v-for节点进行编译处理,结果就是可能会当前节点元素增量的增加了,那么上述的遍历就会有问题,所以通过el.index来控制编译的流程,比如v-for指令增加了5个节点,那么el.index会加5,从而跳过新增的编译处理后的节点。

局部编译问题

为了提高编译的效率,当v-for相关的节点需要重新编译时不需要整体编译而进行局部编译,所以v-for得到的每一个子元素都会实例化一个viewModel对象,这样它们就可以进行单独编译而不影响整个组件。

这里有一个需要注意的点的,子元素实例化的vm对象应该与整个组件公用上下文(父子组件则是分别具有独立的上下文),即childVm可以访问parentVm的数据。

如下为v-for子实例的简单创建:

let vm, node = this.el.cloneNode(true);

        this.parent.insertBefore(node, this.endRef);
        this.parent.index++;

        /**
         * array item data process
         */
        let data = {
            $index: index
        };
        data[this.subKey] = item;
        vm = new Vm({
            el: node,
            data: data
        }, this.vm);
        //父子实例数据共享
        vm.__proto__ = this.$vm;

        vm.appendTo(this.vm);
        this.childElements[index] = node;
        this.childVms[index] = vm;

这里巧用了原型链的功能,达到了父子实例公用上下文的目的。

the super tiny vue.js

the super tiny vue.js

一个极简版vue的实现,希望对于想了解vue内部实现原理以及学习其源码而又无从下手的人有所帮助。

实现功能:

  • v-text、v-model、v-on-[event]指令
  • seter/getter数据监测,自动更新
  • mvvm简易模型

数组类型响应式实现原理分析

按索引取值监测

因为数组属于对象类型,所以对于arr[0]形式取值与普通对象无异,都可以通过Object.defineProperty来对其进行处理,从而达到响应式的目的。

如下所示:

var person = [{}]
			var p = []
			Object.defineProperty(person, '0', {
				get: function () {
					return p[0];
				},
				set: function (val) {
					p[0] = val;
                                        //directive.update();
					console.log('0', person[0]);
				}
			})
			person[0] = {};
			person[0] = {a: 1}
			person[0].a = 2
			console.log('1', person[0])

实现了person数组第一个元素的响应式处理。

数组实例方法监测

数组的操作除了数组元素的取值赋值外,还需要考虑数组本身的更改。数组的下面这八个实例方法会改变数组本身,所以需要监测其更改操作。

        'pop',
        'push',
        'reverse',
        'shift',
        'unshift',
        'splice',
        'sort'

我们来看如下代码(为vue初期源码):

var proto = Array.prototype,
    slice = proto.slice,
    mutatorMethods = [
        'pop',
        'push',
        'reverse',
        'shift',
        'unshift',
        'splice',
        'sort'
    ]

module.exports = function (arr, callback) {
    mutatorMethods.forEach(function (method) {
        arr[method] = function () {
            proto[method].apply(this, arguments)
            callback({
                event: method,
                args: slice.call(arguments),
                array: arr
            })
        }
    })
}

可以看到,这里是对需要监测得数组的这八个实例方法进行了重写,重写之后的方法除了进行原有的数组操作之外会执行一个回调函数,即只要发生了pop,posh等方法调用,回调函数就会调用,这就达到了检测的目的,将该回调函数和对应的指令更新操作绑定在一起即实现了数组的响应式。

length属性不支持响应式

为什么说length属性不支持响应式,因为这个属性不能够重定义,即不能通过Object.defineProptery进行重写,所以无法实现对length属性的监测。

                         var length = 0;
                         Object.defineProperty(person, 'length', {
				get: function () {
					return length;
				},
				set: function (val) {
					length = val;
				}
			})

这段代码会报类型错误:Uncaught TypeError: Cannot redefine property: length.

about render function auto render

recently , i wrote a lot of components .
but i always thinking this is trouble to me.
so i think move all component to vuex store . render all element with one component , but always fail

//home store

export default {
    state: {
        elements: function (h) {
            return [
                h('Nav', {width: '100%', height: '40px', backgroundColor: 'green', position: 'top'},
                    [
                        h('Menu',{
                            tag: "ul",
                            grid: "column",
                            device: "phone tables desktop tv",
                            width: "0 12 12 12",
                            direction: "row",
                        })
                    ])
            ]

        },
    }

//home.js

    export default {
        render(create){
            return create('div', {
                attrs: {
                    id: 'app'
                }
            },this.$store.state.home.elements(create))
        }
    }

fail reason : render function or template not defined in component: anonymous

ok ! this is no good ,so i move h to render

//home store

export default {
    state: {
        elements: [
            {
                name: 'Nav',
                props: {width: '100%', height: '40px', backgroundColor: 'green', position: 'top'},
                parent: undefined,
                children: [
                    {
                        name: 'Menu',
                        props: {
                            tag: "ul",
                            grid: "column",
                            device: "phone tables desktop tv",
                            width: "0 12 12 12",
                            direction: "row",
                        },
                        children: undefined,
                        parent: 'Nav'
                    }
                ]
            },
        ]
    }
}

//home.js

    export default {
        computed: {
            home(){
                return this.$store.state.home.elements
            }
        },
        render(create){
            return create('div', {
                attrs: {
                    id: 'app'
                }
            }, innerDom.call(this, create))
        }
    }

    function innerDom(create) {
        let element = this.home
        function loop() {
             element.forEach(el=>{
                if(el.children){
                    element = el
                }
// I almost tried all the array logic
            })
        }
        loop()
    }

fail reason : vnode can not correct append parent vonde or can not render to body

how can fixed this .?

组件系统实现原理

组件系统功能说明:

  • 全局组件
  • 局部组件
  • 组件间参数传递(props)

组件系统结构

组件按父子关系形成一个组件树,父子组件具有自己的上下文即他们有自己的viewModel结构(可能是一个单独的vm实例,也可能是vm树形结构),父子之间的数据是不能直接访问的。

当数据发生变化时,组件单独更新。

组件编译

<hello-world></hello-world>组件为例,当解析该组件的时候,对该组件进行编译并将编译后的结果替换<hello-world></hello-world>自定义标签,这样就将父子组件关联起来。

说明,因为是自定义标签,所以需要声明组件名,以此和自定义标签名关联起来。因为html规范中不区分大小写,所以只能以中横线的形式命名,考虑到编码方面的便利,需要在驼峰和中横线直接进行转换。

组件间通信

<hello-world  name='hello-world-component' :msg='message'></hello-world>`

通过props传递信息,支持静态属性和动态属性。

如上:msg即为动态属性,message对于父组件的一个属性,当message发生变更时,子组件能够监听到变化并自动更新。

具体实现还是通过$watch方法来监听即可,在子组件中watch父组件的值,当发生变化时触发自身指令的更新即可。

谈谈get/set与getState/setState的优劣

vue数据响应式特性

vue通过Object.defineProperty方法实现对数据更改的监测来实现数据的响应式,这是vue比较让人着迷的一个特性,刚接触vue的时候真的太为它着迷了,但也有它自身的问题。

Object.defineProperty简要说明:

function bindAccessors (vm, key, binding) {
    Object.defineProperty(vm, key, {
        get: function () {
            return binding.value;
        },
        set: function (value) {
            binding.value = value;
            binding.directives.forEach(function (directive) {
                directive.update(
                    directive.el,
                    value,
                    directive.argument,
                    directive,
                    vm,
                    key
                )
            })
        }
    })
}

如上所示,通过对vm对象的key属性set操作的拦截,实现了vm.key属性的监测和响应,当vm[key] = 1代码片段执行的时候,会监测到该属性的赋值操作(这里不能说监测到值的更改,因为如果原有值也为1的话,那么就不能算更改,因为最后的值为1),然后执行directive.update更新操作。

get/set监测数据更新存在的问题

(这里并不是说vue响应式特性存在的问题,因为vue在此基础上进行了不少的优化,这里讨论的是set/get监测数据存在的问题,毕竟优化问题和消灭问题区别还是很大的)

  • 频繁的set操作会造成更新的浪费
    在人眼可感知的阈值内的set操作可能会是不必要的更新资源浪费。
  • 可能会出现无意义的更新操作
    如下所示:
var data = {
    value: 1
}
data.value = 1;
data.value = 1;

如果两次set的值是一样的,那么会触发两次作用一样的更新操作,无疑造成了资源的浪费。

如果想通过比较连续两次值是否相等来过滤掉其中一次无意义的更新操作所带来的成本是很大的,因为属性的值可能会是对象、函数、数组等复合对象,要判断此次更新对页面是否有副作用是很困难的。

  • 数组、对象子属性的响应问题
    数组的push/pop/unshift/shift/filter等操作都需要单独处理,因为set是无法监测这些操作的;
    执行了set的属性的子属性如果没有进行set的处理那么是没有响应式特性的;
    (vue在这点的处理上还是比较好的,基本解决了这两个问题。)

getState/setState式更新

这种方式的更新不是监测赋值操作而是将触发的时机交给了开发者.

因为需要开发者手动更新,所以与get/set的方式相比会显得繁琐,但是却没有上面提及的那几个问题(复合对象值比较的问题依然不能解决,但是可以将其留给开发者来决定是否需要更新)。

指令式声明的替代解决方案

指令式声明

指令式声明以其简洁、复合html语法、易于学习等特点为其带来很大的优势,但在可编程性方面却很差。

jsx/hyperscript

  • jsx
    在保留了HTML似的模板特性的基础上,增加了逻辑编程的能力,即结合了html和javascript两者的有点。
  • hypescript
    采用函数式的编程方式,完全放弃了模板特性,充分发挥逻辑编程的能力。

HTML一直在web编程中占有很大的地位,HTML这种标记类型的语言的确经受住了万维网发展的考验,在文本传输和显示上如鱼得水。但现在对网页的要求已不再是内容的展示,UI交互已成为重点,而这点上,HTML显得很乏力。

从组件化的角度来考虑,HTML也有局限,复用性和正交性比较弱,而这在组件化方案中是很受重视的。

函数式编程**逐渐得到大众认可,其优点也逐渐凸显,基于函数式的UI生成方式也成为一种不错的选择.

函数指令解决方案

  • 一组标签原子API,类似于hyperscript
  • 函数指令API,如 vfor, vif等

采用函数生成dom而不是解析dom的形式,这样可以避免dom解析遍历资源浪费的问题,因为dom是通过相应的函数指令生成的,所以viewModel可以精确的追踪view中的相关节点。

待更新。。。

支持watch监测、计算属性、自定义指令

源码:https://github.com/xiaofuzi/re-vue/tree/9de26c017dc937e19faec6b962d28a444cea7af4

API

  • el
    Type: String | Node

根节点选择器或是根节点dom元素。

  • data
    Type: Object

初始化响应式数据模型

  • computed
    Type: Object

计算属性,每一个元素对应一个函数

注:
* computed属性依赖于data中的响应式数据
* computed属性可依赖computed属性
* computed禁止赋值操作

  • methods
    Type: Object
    每一个元素对应一个函数,支持响应式替换

  • watch
    Type: Object

监测对象,监测对应的响应式数据,当数据发生更改时执行回调.

  • $watch
    Type: Function
    监测某一数据的响应式变化

如:

var vm = new TinyVue({
    data: {
        info: {
            age: 18
        }
    }
});
vm.$watch('info', function (info) {
    
});

vm.$watch('info.age', function (age) {
    
})

* $directive
Type: `Function`

自定义指令

如:
```js
vm.$directive('text', function (text) {
    this.el.textContent = text;
});
  • beforeCompiler
    生命周期函数,编译前执行

  • ready
    生命周期函数,渲染完毕后执行

example:

<div id="app">
    <h2 v-text='hello' v-visible='isShow'></h2>
    <input type="text" v-model='counter'>
    <button v-on:click='add' type="button">add</button>        
    <button v-on:click='toggle' type="button">toggle</button>
    <p v-text='counter'></p>
    <p v-text='info.age'></p>
    <p v-text='wellcome.text'></p>
</div>
var mvvm;
var opts = {
    el: '#app',
    data: {
        isShow: false,
        counter: 1,
        hello: 'ahahah!',
        info: {
            age: 18
        },
        person: {
            weight: 20,
            height: 170
        }
    },
    computed: {
        wellcome () {
            return {text: this.hello + '---' + this.info.age};
        }
    },
    methods: {
        add: function () {
            this.counter += 1;
            this.info.age += 1;
        },
        toggle: function () {
            this.isShow = !this.isShow;                    
        }
    },
    watch: {
        counter (val) {
            console.log('counter: ', val);
        },
        info (info) {
            console.log('info: ', info);
        },
        'info.age' () {

        },
        wellcome () {
            console.log('wellcome: ', this.wellcome);
        }
    },
    ready () {
        let self = this;
        self.hello = 'Ready, go!';
        
        setTimeout(function () {
            self.hello = 'Done!';
        }, 1000)
    }
}

TinyVue.$directive('visible', function (value) {
    this.el.style.visibility = value ? 'visible' : 'hidden';
})
mvvm = new TinyVue(opts);

watch监测、计算属性实现原理

(注:这里是笔者自己尝试的一种实现方式,也许与vue的实现方式会有所区别)

这里对数据的响应式定义进行了调整,之前的tinyVue采用的是遍历 DOM 节点,获取指令对应的key然后将获得的key所对应的对象进行了getter/setter操作的处理,这种实现方式对watch功能和计算属性的实现不便,所以更改为直接将 option.data 对象转换为响应式对象,这样就不会受限于模板中声明的指令对应key的限制,从而未绑定指令的key也可以转换为可监测的。

tinyVue中是通过给每个key建立对应的binding对象来实现响应式的(setter/getter监测由binding对象内部实现)。tinyVue则维护数据模型得到的所有的binding,存储在_bindings中(关于binding的详细说明可参考这里)。

什么是watch监测?

简单的说就是监测到某一属性的变化后执行相应的回调。

例如:

var vm = new TinyVue({
     data: {
        name: 'xiaofu',
        info: {
            height: 170
        }
     },
     watch: {
        name: function (newValue, oldValue) {
            console.log(newValue); 
        },
        info: function (info, oldInfo) {
            console.log(info);
        },
        'info.height': function (height, oldHeight) {
            console.log(height);
        }
     },
     ready () {
        this.name = 'xiaoyang';
        this.info.height = 180;
     }
});

上述例子中,分别定义了name,info,info.height的watch函数,watch回调函数包含两个参数,分别为新值和旧值。

  • 值变化时触发watch回调
  • 子属性值变化时触发回调
  • 父属性变化会触发子属性值的回调(具体可看深层次响应式的实现,父属性赋值会触发为子属性的辅助)
  • 可直接监测子属性的值
  • 新值与旧值相等则不会触发回调

因为子属性的变化也需要触发父属性的回调,所以在这里采用冒泡的实现方式,即当一个属性监测到它发生了变化时,它会通知它的父级发生了变化,父级再往上传,从而实现了当监测info的时候,如果info.height发生了变化,那么info也会知道已发生了变化。

计算属性的实现

这里完全禁止了计算属性的赋值操作,因为给计算属性赋值其实是没必要的,用了反而会影响逻辑,因为赋值操作比较分散,增加理解的难度。

  • 其值由其它值计算而得到
  • 其值会动态的监测依赖值的变化并及时更新
  • 当其值变化后,会更新与其相关的指令
  • 计算属性可以依赖于计算属性

这里比较难以实现的是依赖的收集,这里先将计算属性对应的求值函数定义为该计算属性的getter函数,如下所示:

defineComputedProperty () {
        let key = this.key,
            obj = this.vm,
            self = this;

        def(obj, key, {
            get () {
                let getter = self.vm._opts.computed[key];
                if (isFunc(getter)) {
                    self.value = getter.call(self.vm);

                    return self.value;
                }
            },
            set () {
                //console.warn('computed property is readonly.');
            }
        });
    }

当我们对计算属性取值时,会调用对应的求值函数来得到计算属性的值,依赖的收集也可以在这里进行,当求值函数执行的时候,求值的过程中会发生依赖项的getter操作,通过监测发生的getter操作即可得到依赖项。

如下所示:

def(obj, key, {
            get () {
                observer.isObserving && observer.emit('get', self);
                return self.value;
            },
            set (value) {
                if (value !== self.value) {
                    self.oldValue = self.value;
                    if (!isObj) {
                        self.value = value;
                        self.update(value);
                    } else {
                        for (let prop in value) {
                            self.value[prop] = value[prop];
                        } 
                    }
                    observer.emit(self.key, self);
                    self.refresh();
                }
            }
        });

observer.isObserving && observer.emit('get', self);
首先通过 observer.isObserving来标识一次计算属性的取值过程,然后监测 emit 的 get 事件,事件传回来的self即该计算属性的依赖项,这样就得到了计算属性的依赖项,收集完毕后我们只要监测在setter中触发的emit事件即可实现计算属性的更新(与watch的实现是有所区别的)。

在指令更新上计算属性也需要单独处理,当计算属性更新后,需要通知其所有子属性绑定的指令执行更新操作。

如下所示:

_bind (el, directive) {
        el.removeAttribute(prefix + '-' + directive.name);
        directive.el = el;

        let key = directive.key,
            binding = this._bindings[key];

        if (!binding) {
            /**
             * computed property binding hack
             * 针对计算属性子属性
             */

            //get computed property key
            let computedKey = key.split('.')[0];
            binding = this._bindings[computedKey];
            if (binding.isComputed) {
                binding.directives.push(directive);
            } else {
                console.error(key + ' is not defined.');
            }
        } 
        
        binding.directives.push(directive);
    } 

在_bing函数中收集计算属性相关的所有指令并存储下来,然后在更新的时候动态的更新指令。

待更新。。。

对vue响应式数据更新的误解

对于刚接触vue的同学会经常遇到数据更新了但是模板没有更新的问题,下面将结合vue的响应式特性以及异步更新机制分析常见的错误:

异步更新带来的数据响应式误解

异步数据的处理基本是一定会遇到的,处理不好就会遇到数据不更新的问题,但有一种情况是在未正确处理的情况下也能正常更新,这就会造成一种误解,详情如下所示:

  • 模板
<div id="app">
        <h2>{{dataObj.text}}</h2>
</div>
  • js
new Vue({
            el: '#app',
            data: {
                dataObj: {}
            },
            ready: function () {
                var self = this;

                /**
                 * 异步请求模拟
                 */
                setTimeout(function () {
                    self.dataObj = {}; 
                    self.dataObj['text'] = 'new text';
                }, 3000);
            }
        })

上面的代码非常简单,我们都知道vue中在data里面声明的数据才具有响应式的特性,所以我们一开始在data中声明了一个dataObj空对象,然后在异步请求中执行了两行代码,如下:

self.dataObj = {}; 
self.dataObj['text'] = 'new text';

首先清空原始数据,然后添加一个text属性并赋值。到这里为止一切都如我们所想的,数据和模板都更新了。

那么问题来了,dataObj.text具有响应式的特性吗?

--

模板更新了,应该具有响应式特性,如果这么想那么你就已经走入了误区,一开始我们并没有在data中声明.text属性,所以该属性是不具有响应式的特性的。

但模板切切实实已经更新了,这又是怎么回事呢?

那是因为vue的dom更新是异步的,即当setter操作发生后,指令并不会立马更新,指令的更新操作会有一个延迟,当指令更新真正执行的时候,此时.text属性已经赋值,所以指令更新模板时得到的是新值。

具体流程如下所示:

  • self.dataObj = {};发生setter操作
  • vue监测到setter操作,通知相关指令执行更新操作
  • self.dataObj['text'] = 'new text';赋值语句
  • 指令更新开始执行

所以真正的触发更新操作是self.dataObj = {};这一句引起的,所以单看上述例子,具有响应式特性的数据只有dataObj这一层,它的子属性是不具备的。

注:其实vue文档中已经有说明,对于新增以及删除的属性,vue是无法监测到的。

var a = {};

a.b = 0;    //新增b属性
a = {
    c: 0
};              //更改a属性的值

上述两种赋值方式对vue造成的影响是不同的。

对比示例:

模板

<div id="app">
        <h2>{{dataObj&&dataObj.text}}</h2>
</div>

js

new Vue({
            el: '#app',
            data: {
                dataObj: {}
            },
            ready: function () {
                var self = this;

                /**
                 * 异步请求模拟
                 */
                setTimeout(function () {
                    self.dataObj['text'] = 'new text';
                }, 3000);
            }
        })

上述例子的模板是不会更新的。

Vue.$set

通过$set方法可以将添加一个具备响应式特性的属性,并且其子属性也具备响应式特性,但是必须是新属性才可以,如果是本身已有的属性该方法是不起作用的。

new Vue({
            el: '#app',
            data: {
                dataObj: {}
            },
            ready: function () {
                var self = this;

                /**
                 * 异步请求模拟
                 */
                setTimeout(function () {
                    var data = {
                        name: 'xiaofu',
                        age: 18
                    };
                    var data01 = {
                        name: 'yangxiaofu',
                        age: 19
                    };
                    self.dataObj['person'] = {};
                    self.$set('dataObj.info', data);
                    self.$set('dataObj.person', data01); 
                }, 3000);
            }
        })

如上所示,.person属性是不具备响应式特性的。

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.