blog's People
blog's Issues
设计模式 - 单例模式
单例模式
单例模式的定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象。
实现
var Singleton = function (name) {
this.name = name
this.instance = null
}
Singleton.prototype.getName = function () {
alert(this.name)
}
Singleton.getInstance = function (name) {
if (!this.instance) {
this.instance = new Singleton(name)
}
return this.instance
}
var a = new Singleton('sven1')
var b = new Singleton('sven2')
console.log(a === b) // true
当然还可以把getInstance改写为:
Singleton.getInstance = (function () {
var instance = null
return function (name) {
if (!instance) {
instance = new Singleton(name)
}
return instance
}
})()
var a = new Singleton('sven1')
var b = new Singleton('sven2')
console.log(a === b) // true
虽然现在已经完成了一个单例模式的编写,但这段单例模式代码意义不大。我们需要实现一个“透明”的单例类,用户从这个类中创建对象的时候,可以像使用其他任务普通类一样。
var CreateDiv = (function () {
var instance
var CreateDiv = function (html) {
if (instance) {
return instance
}
this.html = html
this.init()
return instance = this
}
CreateDiv.prototype.init = function () {
var div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
return createDiv
})()
var a = new CreateDiv('sven1')
var b = new CreateDiv('sven2')
console.log(a === b) // true
用代理实现单例模式
var CreateDiv = function (html) {
this.html = html
this.init()
}
CreateDiv.prototype.init = function () {
var div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
var ProxySingletonCreateDiv = (function () {
var instance
return function (html) {
if (!instance) {
instance = new CreateDiv(html)
}
return instance
}
})()
var a = new ProxySingletonCreateDiv('sven1')
var b = new ProxySingletonCreateDiv('sven2')
console.log(a === b) // true
通过引入代理类的方式,我们同样完成了一个单例模式的编写,跟之前不同的是,我们把负责管理单例的逻辑移到了proxySingletonCreateDiv中。这样一来,CreateDiv就变成了一个普通的类,它跟proxySingletonCreateDiv组合起来可以达到单例模式的效果。
使用命名空间
适当地使用命名空间,并不会杜绝全局变量,但可以减少全局变量的数量。
最简单的方法依然是对象字面量的方式:
var namespace1 = {
a: function () {
alert(1)
},
b: function () {
alert(2)
}
}
动态创建命名空间:
var MyApp = {}
MyApp.namespace = function (name) {
var parts = name.split('.')
var current = MyApp
for (var i in parts) {
if (!current[parts[i]]) {
current[parts[i]] = {}
}
current = current[parts[i]]
}
}
MyApp.namespace('event')
MyApp.namespace('dom.style')
/**
上述代码等价于
var MyApp = {
event: {},
dom: {
style: {}
}
}
*/
使用闭包封装私有变量
var user = (function () {
var __name = 'sven',
__age = 29
return {
getUserInfo: function () {
return __name + '-' + __age
}
}
})()
通用的惰性单例
var createIframe = (function () {
var iframe
return function () {
if (!iframe) {
iframe = document.createElemnt('iframe')
iframe.style.display = 'none'
document.body.appendChild(iframe)
}
return iframe
}
})()
单例创建不变的逻辑:
var obj
if (!obj) {
obj = xxx
}
把管理单例的逻辑从原来的代码中抽离出来,这些逻辑封装在getSingle函数内部,创建对象的方法fn被当成参数动态传入getSingle函数:
var getSingle = function (fn) {
var result
return function () {
return result || (result = fn.apply(this, arugments))
}
}
var createLoginLayer = function () {
var div = document.createElement('div')
div.innerHTML = '我是登录窗'
div.style.display = 'none'
document.body.appendChild(div)
return div
}
var createSingleLoginLayer = getSingle(createLoginLayer)
document.getElementById('loginBtn').onClick = function () {
var loginLayer = createSingleLoginLayer()
loginLayer.style.display = 'block'
}
此外getSingle还可用于绑定一次事件的场景。
var bindEvent = getSingle(function () {
document.getElementById('div1').onClick = function () {
alert('click')
}
return true
})
var render = function () {
console.log('开始渲染列表')
bindEvent()
}
render()
render()
render()
总结
单例模式是一种简单但非常实用的模式,特别是惰性单例技术,在合适的时候才创建对象,并且只创建唯一的一个。以上单例还结合了代理模式,使得代码更加灵活。
「依赖->请求」避坑
最近做业务碰到一种场景,也有了一些总结。
场景:
一个页面有左右两个块,第一个块中有table,第二个块中可以也是个table,第一个table选中某一行会去导致第二个table去请求数据。Emmm… 光看描述还是再普通不过的场景了,其实陷阱还是有的。
预料到的问题:
- 第一个table选中某一行会导致第二个table请求数据。那如果我在单位时间内做选中行切换,那发出的2个请求得坐下控制。
解决方案:
- 对选中行的操作做一个debounce:
在网络环境好的情况还是有点作用,如果考虑那种差的网络环境还是会出现以上场景中的问题 - 设置一个变量作为阈值来控制请求,这个和切换选中行咩有直接联系,主要是限制请求的发送,在来回切换选中行时,当一个请求发送之后,阈值设置为true,等到响应来了再发送下一个选中行的请求。这个基本能解决响应慢的问题。
- 设置 cancel token取消还未得到响应的请求。这个方法比较粗暴,当切换选中行时,如果上一个请求还在进行,直接cancel掉,并发送下一个选中行的请求。
相同场景有很多,我们还是得注意一些细节问题。
element ui dialog form 「新增」「编辑」组件复用
最近业务上表单的添加编辑逻辑已经是家常便饭了,经常遇到一种场景:
- 新增 -> 弹出一个dialog,填写好表单内容,提交,完事
- 编辑 -> 在已有的列表项里选中某一项,编辑,弹出一个和新增一样的dialog,更改表单中的内容,保存后退出。
由于Element ui在业务上使用还是挺多的,经常需要用到Element ui的表单组件来完成以上逻辑,不过使用Element表单,给人一种方便中带有嫌弃的感觉...废话不多说了,还是来总结下代码:
<el-dialog title="新增" :visible.sync="visible" :before-close="handleClose">
<el-form :model="form" label-width="100px" ref="form">
<el-form-item props="name" label="姓名">
<el-input v-model="form.name"></el-input>
</el-form-item>
<el-form-item props="sex">
<el-select v-model="form.sex">
<el-option v-for="item in sexList" :label="item.label" :value="item.value"></el-option>
</el-select>
</el-form-item>
<el-button type="primary" @click="submit">提交</el-button>
<el-button @click="handleClose">取消</el-button>
</el-form>
</el-dialog>
export default {
name: 'form',
data () {
return {
this.sexList = [{
label: '男',
value: 'male'
}, {
label: '女',
value: 'female'
}]
form: {
name: '',
sex: ''
}
}
},
methods: {
submit () {
...
},
handleClose () {
...
},
resetFields () {
this.$refs.form.resetFields()
}
}
}
以上就是个典型的dialog表单,用Element表单组件写起来确实很方便,新增很好应对,编辑是对新增的复用。往往需要接受一个props属性作为框里已有的值:
export default {
name: 'form',
props: {
pName: String,
pSex: String
},
watch: {
pName (val) {
this.name = val
},
pSex (val) {
this.sex = val
}
},
methods: {
handleClose () {
this.$refs.form.reset()
},
submit () {
this.$refs.form.validate(valid => {
...
})
}
}
}
当然也可以传入一个对象,监听对象后进行赋值。但在多个项切换或者新增和编辑切换经常会出现输入框选择框出现上一次操作的结果,即name和sex会重新被置为上一次的结果,而不是我们想要的结果。
查阅一下element ui表单部分的源码,有这么一段:
form-item.vue
export default {
...
mounted () {
if (this.prop) {
...
let initialValue = this.fieldValue
if (Array.isArray(initialValue)) {
intialValue = [].concat(initialValue)
}
Object.defineProperty(this, 'initalValue', { value: initialValue })
}
},
methods: {
resetField () {
this.validateState = ''
this.validateMessage = ''
let model = this.form.model
let value = this.fieldValue
let path = this.prop
if (path.indexOf(':') !== -1) {
path = path.replace(/:/, '.')
}
let prop = getPropByPath(model, path, true)
if (Array.isArray(value)) {
this,.validateDisabled = true
prop.p[prop.k] = [].concat(this.initialValue)
} else {
this.validateDisabled = true
prop.o[prop.k] = this.initialValue
}
}
}
}
form.vue
export default {
...
methods: {
resetFields () {
if (!this.model) {
process.env.NODE_ENV !== 'production' && console.warn('Element Warn][Form]model is required for resetFields to work.')
}
this.fields.forEach(field => {
field.resetField()
})
}
}
}
粗略看了一把,就是把现有的值重置为mounted中赋的值。
了解原理之后,我们需要使编辑与新增之间互不干扰,编辑与编辑之间互不干扰,其实本质上也不是新增和编辑的问题,而是在关闭dialog之后需要reset这一步导致。
解决方案:
-
如果props传入的是诸如name和sex这类单一的属性,我们可以再传入一个”flag“监听flag的变化去操作:
child.vue
export default {
props: {
flag: Boolean,
pName: String,
pSex: String
},data () { return { name: '', sex: '' } }, watch: { flag (val) { if (val) { this.name = pName this.sex = pSex } else { this.name = '' this.sex = '' } } } methods: { handleClose() { this.$emit('close-dialog') this.$refs.form.resetFields() } } }
parent.vue
<child :flag="flag" :p-name="name" :p-sex="sex" @close-dialog="handleClose">
<script> export default { name: 'parent', data () { return { name: '小明', sex: '男', flag: } }, methods: { handleClose () { this.flag = false }, // 新增 openDialog () { this.flag = true // 当然还伴随这visible等true显示dialog的操作,这里就不写了 this.name = '' this.sex = '' }, // 编辑 edit () { this.flag = true this.name = 'xxxx' this.sex = 'xxxx' } } } </script>
-
传入一个props对象,代理flag,关闭dialog的时候直接通知父级把这个对象赋值为null,其他步骤和👆类似
设计模式 - 代理模式
代理模式
代理模式是为一个对象提供一个代替品或占位符,以便控制对它的访问。
例子:小明追MM代理
var Flower = function () {}
var xiaoming = {
sendFlower: function (target) {
var flower = new Flower()
target.receiveFlower(flower)
}
}
var B = {
revceiveFlower: function (flower) {
A.listenGoodMood(function () { // 监听A的好心情
A.receiveFlower(flower)
})
}
}
var A = {
receiveFlower: function (flower) {
console.log('收到花' + flower)
},
listenGoodMood: function (fn) {
setTimeout(function () { // 假设10秒之后A的心情变好
fn()
}, 10000)
}
}
xiaoming.sendFlower(B)
保护代理和虚拟代理
代理B可以帮助A过滤掉一些请求,比如送花的人年龄太大或者没有宝马,这种请求就可以直接在代理B处被拒绝掉。这种代理叫做保护代理。
另外,new Flower的操作交给代理B去执行,代理B会选择在A心情好时再执行new Flower,这是代理模式的另一种形式,叫做虚拟代理。
var B = {
receiveFlower: function (flower) {
A.listenGoodMood(function () { // 监听A的好心情
var flower = new Flower() // 延迟创建flower对象
A.receiveFlower(flower)
})
}
}
保护代理用于控制不同权限的对象对目标对象的访问,但在Javascript并不容易实现保护代理,因为我们无法判断谁访问了对象。而虚拟代理是最常用的一种代理模式。
虚拟代理实现图片加载
var myImage = (function () {
var imgNode = document.createElment('img')
document.body.appendChild(imgNode)
return {
setSrc: function (src) {
imgNode.src = src
}
}
}) ()
myImage.setStr('https://xxxxxxx')
现在开始引入代理对象proxyImage,通过这个代理对象,在图片被真正加载好之前,页面中将出现一张占位的菊花图Loading.gif提示用户图片正在加载。
var myImage = (function () {
var imgNode = document.createElment('img')
document.body.appendChild(imgNode)
return {
setSrc: function (src) {
imgNode.src = src
}
}
}) ()
var proxyImage = (function () {
var img = new Image
img.onload = function () {
myImage.setSrc(this.src)
}
return {
setSrc: function (src) {
myImage.setSrc('xxxxxx')
img.src = src
}
}
})()
proxyImage.setSrc('xxxxxx')
代理的意义
代理的意义在于面向对象设计的原则 —— 单一职责原则。
单一职责原则指的是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象承担了多项职责,就意味着整个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分部到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏。
缓存代理
计算乘积:
var mult = function () {
cosnole.log('开始计算乘积')
var a = 1
for (var i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i]
}
return a
}
var proxyMulti = (function () {
var cache = {}
return function () {
var args = Array.prototype.slice.call(arguments)
if (args in cache) {
return cache[args]
}
return cache[args] = mult.apply(this, arguments)
}
})()
proxyMult(1, 2, 3, 4) // 输出: 24
proxyMult(1, 2, 3, 4) // 输出: 24
总结
代理模式包 许多小分类,在 JavaScript 开发中最常用的是 拟代理和 存代理。虽然代理模式非常有用,但我们在编写业 代码的时候,往往不需要去 先 测是否需要使用代理模式。当 正发现不方便直接 问某个对象的时候,再编写代理也不迟。
设计模式 - 策略模式
策略模式
定义:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
以年终奖计算为例进行介绍:
很多公司的年终奖是根据员工的工资基数和年底绩效情况来发放的。例如,绩效为S的人年终奖有4倍工资,绩效为A的人年终奖有3倍工资,而绩效为B的人年终奖是2倍工资。假设财务部要求我们提供一
段代码,来方便他们计算员工的年终奖。
代码实现:
var calculateBonus = function (performanceLevel, salary) {
if (performanceLevel === 'S') {
return salary * 4
}
if (performanceLevel === 'A') {
return salary * 3
}
if (performanceLevel === 'B') {
return salary * 2
}
}
calculateBouns('B', 2000) // 输出4000
calculateBonus('S', 6000) // 输出24000
代码十分简单,但是存在显而易见的缺点。
- calculateBonus函数比较大,包含了很多if-else语句,语句覆盖所有的逻辑分支
- calculateBonus函数缺乏弹性,如果增加了一种新的绩效等级C,或者改动其他绩效奖金的计算方法,就需要深入calculateBonus的内部实现
- 算法复用性差。
使用组合重构代码
var performanceS = function (salary) {
return salary * 4
}
var performanceA = function (salary) {
return salary * 3
}
var performanceB = function (salary) {
return salary * 2
}
var calculateBonus = function (performanceLevel, salary) {
if (performanceLevel === 'S') {
return performanceS(salary)
}
if (performanceLevel === 'A') {
return performanceA(salary)
}
if (performanceLevel === 'B') {
return performanceB(salary)
}
}
calculateBnus('A', 10000) // 输出:30000
使用策略模式重构代码
一个策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类Context,Context接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明Context中药维持对某个策略对象的引用。
var performanceS = function () {}
performanceS.prototype.calculate = function (salary) {
return salary * 4
}
var performanceA = function () {}
performanceA.prototype.calculate = function (salary) {
return salary * 3
}
var performanceB = function () {}
performanceB.prototype.calculate = function () {
return salary * 2
}
// 定义奖金类Bonus
var Bonus = function () {
this.salary = null // 原始工资
this.strategy = null // 绩效等级对应的策略对象
}
Bonus.prototype.setSalary = function (salary) {
this.salary = salary
}
Bonus.prototype.setStrategy = function (strategy) {
this.strategy = strategy
}
Bonus.prototype.getBonus = function () { // 取得奖金数额
return this.strategy.calculate(this.salary) // 把计算奖金的操作委托给策略对象
}
var bonus = new Bonus()
bonus.setSalary(100000)
bonus.setStrategy(new PerformanceS()) // 设置策略对象
console.log(bonus.getBonus()) // 输出:40000
bonus.setStrategy(new performanceA()) // 设置策略对象
console.log(bonus.getBonus()) // 输出:30000
定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。
说得更详细点,就是:定义一系列的算法,把它们各自封装成策略类,算法被封装在策略类内部的方法。在客户对Context发起请求的时候,Context总是把请求委托给这些策略对象中间的某个进行计算。
函数即对象,所以更简单和直接的做法是把strategy直接定义为函数:
var strategies = {
'S': function (salary) {
return salary * 4
},
'A': function (salary) {
return salary * 3
},
'B': function (salary) {
return salary * 2
}
}
var calculateBonus = function (level, salary) {
return strategies[level](salary)
}
console.log(calculateBonus('S', 20000)) // 输出:80000
console.log(calculateBonus('A', 10000)) // 输出:30000
策略模式重构表单校验
var strategies = {
isNotEmpty: function (value, errorMsg) {
if (value === '') {
return errorMsg
}
},
minLength: function (value, length, errorMsg) {
if (value.length < length) {
return errorMsg
}
},
isMobile: function (value, errorMsg) {
if (!/^1[3|5|8][0-9]{9}/.test(value)) {
return errorMsg
}
}
}
var validataFunc = function () {
var validator = new Validator() // 创建一个validator对象
/* 添加一些校验规则 */
validator.add(registerForm.userName, 'isNotEmpty', '用户名不能为空')
validator.add(registerForm.password, 'minLength:6', '密码长度不能少于5位')
validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确')
var errorMsg = validator.start()
return errorMsg
}
var registerForm = document.getElementById('registerForm')
registerForm.onsubmit = function () {
var errorMsg = validataFunc()
if (errorMsg) {
alert(errorMsg)
return false // 阻止表单提交
}
}
var Validator = function () {
this.cache = []
}
Validator.protoype.add = function (dom, rule, errorMsg) {
var ary = rule.split(':')
this.cache.push(function () {
var strategy = ary.shift()
ary.unshift(dom.value)
ary.push(errorMsg)
return strategies[strategy].apply(dom, ary)
})
}
Validator.prototype.start = function () {
for (var i = 0, validatorFuc; validatorFunc = this.cache[i++]) {
var msg = validatorFunc()
if (msg) {
return msg
}
}
}
给某个文本输入框添加多种校验规则
validator.add(registerForm.userName, [{
strategy: 'isNotEmpty', errorMsg: '用户名不能为空'
}, {
strategy: 'minLength: 6',
errorMsg: '用户名长度不能小于10位'
}])
// 改写add方法
Validator.prototype.add = function (dom, rules) {
var _self = this
for (var i = 0, rule; rule = rules[i++]) {
(function (rule)) {
var strategy = rule.strategy.split(':')
var errorMsg = rule.errorMsg
_self.cache.push(function () {
var strategy = rule.strategyAry.shift()
strategyAry.unshift(dom.value)
return strategies[strategy].apply(dom, strategyAry)
})
}(rule)
}
}
策略模式的优缺点
- 策略模式利用组合、委托和多态技术和**,可以有效地避免多重条件选择语句。
- 策略模式提供了对开放-封闭原则的完美支持,将算法封装在独立的strategy中,使得他们易于切换,易于理解,易于扩展。
- 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制黏贴工作。
- 在策略模式中利用组合和委托来让Context拥有执行算法的能力,这也是继承的一种更轻便的替代方案。
一个简单的数据监测 - 类Vue
理解Vue的observer机制,实现一个简单数据观测。
难点解析:
- 对象的属性赋值(考虑对象的属性可能也是对象等要素,获取路径)
- 数组的内置方法(push, pop, splice ... )
- 缓存旧值 -> 当有一个新的赋值操作时,则与旧值做比较,经判断后执行回调
简单的赋值操作监听:
var data = {
x: 100
}
function callback (val) {
console.log('val', val)
}
Object.defineProperty(data, 'x', {
get: function () {
return data['x']
},
set: function (newVal) {
callback(newVal)
}
})
data.x = 200
通过Object.defineProperty方法我们能做到非常简单的赋值监听,结合以上的难点解析,慢慢深入实现一个数据监听。
源码:
/*
* Object 原型
*/
const OP = Object.prototype;
/*
* 需要重写的数组方法 OAR 是 overrideArrayMethod 的缩写
*/
const OAM = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
export class Jsonob{
constructor(obj, callback){
if(OP.toString.call(obj) !== '[object Object]'){
console.error('This parameter must be an object:' + obj);
}
this.$callback = callback;
this.observe(obj);
}
/**
* 监测数据对象
* @param {Object} obj [要监测的数据对象]
* @param {Array} path [属性路径]
*/
observe(obj, path){
if(OP.toString.call(obj) === '[object Array]'){
this.overrideArrayProto(obj, path);
}
Object.keys(obj).forEach(function(key, index, keyArray){
var oldVal = obj[key];
var pathArray = path && path.slice(0);
if(pathArray){
pathArray.push(key);
}else{
pathArray = [key];
}
Object.defineProperty(obj, key, {
get: function(){
return oldVal;
},
set: (function(newVal){
if(oldVal !== newVal){
if(OP.toString.call(newVal) === '[object Object]' || OP.toString.call(newVal) === '[object Array]'){
this.observe(newVal, pathArray);
}
this.$callback(newVal, oldVal, pathArray);
oldVal = newVal;
}
}).bind(this)
});
if(OP.toString.call(obj[key]) === '[object Object]' || OP.toString.call(obj[key]) === '[object Array]'){
this.observe(obj[key], pathArray);
}
}, this);
}
/**
* 重写数组的方法
* @param {Array} array [数组字段]
* @param {Array} path [属性路径]
*/
overrideArrayProto(array, path){
var originalProto = Array.prototype,
overrideProto = Object.create(Array.prototype),
self = this,
result;
OAM.forEach(function(method, index, array){
var oldArray = [];
Object.defineProperty(overrideProto, method, {
value: function(){
oldArray = this.slice(0);
var arg = [].slice.apply(arguments);
result = originalProto[method].apply(this, arg);
self.observe(this, path);
self.$callback(this, oldArray, path);
return result;
},
writable: true,
enumerable: false,
configurable: true
});
}, this);
array.__proto__ = overrideProto;
}
}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<script type="module">
import { Jsonob } from './jsonob.js'
var callback = function(newVal, oldVal, path){
console.log('新值:' + newVal + '----' + '旧值:' + oldVal + '----路径:' + path);
};
var data = {
a: 200,
level1: {
b: 'str',
c: [{w: 90}, 2, 3],
level2: {
d: 90
}
}
}
var j = new Jsonob(data, callback);
data.level1.c.push(4);
data.level1.c[0].w = 100;
data.level1.b = 'sss';
data.level1.level2.d = 'msn';
</script>
</body>
</html>
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.