captain1023 / front-end-interview Goto Github PK
View Code? Open in Web Editor NEW希望大家都能成功上岸实习、秋招👊
希望大家都能成功上岸实习、秋招👊
JavaScript共有八种数据类型,分别是 Undefined、Null、Boolean、Number、String、Object、Symbol、BigInt。
其中 Symbol 和 BigInt 是ES6 中新增的数据类型:
这些数据可以分为原始数据类型和引用数据类型:
两种类型的区别在于存储位置的不同:
堆和栈的概念存在于数据结构和操作系统内存中,在数据结构中:
(1)typeof
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof undefined); // undefined
console.log(typeof null); // object
其中数组、对象、null都会被判断为object,其他判断都正确。
(2)instanceof
instanceof
可以正确判断对象的类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。
console.log(2 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
instanceof 原理是判断一个对象在其原型链中是否存在一个构造函数(Array,Map,Set)的prototype属性
/**
* isntanceof的原理是:
* 只要右边变量的prototype在左边变量的原型链上即可
* 左边变量是个实例对象,所以使用__proto__
*/
let a = [];
function myInstanceof (a,B){
let aProto = a.__proto__
let BProto = B.prototype
while(true){
if(aProto == null){
return false
}
if(aProto === BProto){
return true
}
aProto = aProto.__proto__
}
}
console.log(myinstance(a,Array))
可以看到,instanceof
只能正确判断引用数据类型,而不能判断基本数据类型。instanceof
运算符可以用来测试一个对象在其原型链中是否存在一个构造函数的 prototype
属性。
参考blog:https://juejin.cn/post/6844903613584654344
(3) constructor
console.log((2).constructor === Number); // true
console.log((true).constructor === Boolean); // true
console.log(('str').constructor === String); // true
console.log(([]).constructor === Array); // true
console.log((function() {}).constructor === Function); // true
console.log(({}).constructor === Object); // true
constructor
有两个作用,一是判断数据的类型,二是对象实例通过 constructor
对象访问它的构造函数。需要注意,如果创建一个对象来改变它的原型,constructor
就不能用来判断数据类型了:[扩展]
Object 的每个实例都具有下列属性和方法。
function Fn(){};
Fn.prototype = new Array();
var f = new Fn();
console.log(f.constructor===Fn); // false
console.log(f.constructor===Array); // true
(4)Object.prototype.toString.call()
Object.prototype.toString.call()
使用 Object 对象的原型方法 toString 来判断数据类型:
var a = Object.prototype.toString;
console.log(a.call(2));
console.log(a.call(true));
console.log(a.call('str'));
console.log(a.call([]));
console.log(a.call(function(){}));
console.log(a.call({}));
console.log(a.call(undefined));
console.log(a.call(null));
同样是检测对象obj调用toString方法,obj.toString()的结果和Object.prototype.toString.call(obj)的结果不一样,这是为什么?
Array、function等类型作为Object的实例,都重写了toString方法。根据原型链定义,不会查找到object.prototye上的toString方法了.
Object.prototype.toString.call(obj).slice(8,-1) === 'Array';
obj.__proto__ === Array.prototype;
Array.isArray(obj);
obj instanceof Array
Array.prototype.isPrototypeOf(obj)
首先 Undefined 和 Null 都是基本数据类型,这两个基本数据类型分别都只有一个值,就是 undefined 和 null。
undefined 代表的含义是未定义,null 代表的含义是空对象。一般变量声明了但还没有定义的时候会返回 undefined,null主要用于赋值给一些可能会返回对象的变量,作为初始化。
undefined 在 JavaScript 中不是一个保留字,这意味着可以使用 undefined 来作为一个变量名,但是这样的做法是非常危险的,它会影响对 undefined 值的判断。我们可以通过一些方法获得安全的 undefined 值,比如说 void 0。
当对这两种类型使用 typeof 进行判断时,Null 类型化会返回 “object”,这是一个历史遗留的问题。当使用双等号对两种类型的值进行比较时会返回 true,使用三个等号时会返回 false。
// 模拟私有属性:function 内部定义私有属性,返回可以访问内部属性的方法
function getGeneratorFunc () {
var _name = 'John';
var _age = 22;
return function () {
return {
getName: function () {return _name;},
getAge: function() {return _age;}
};
};
}
var obj = getGeneratorFunc()();
obj.getName(); // John
obj.getAge(); // 22
obj._age; // undefined
//单例模式
function Singleton(){
this.data = 'singleton';
}
Singleton.getInstance = (function () {
var instance;
return function(){
if (instance) {
return instance;
} else {
instance = new Singleton();
return instance;
}
}
})();//是个立即执行函数,所以调用的时候用一个括号就可以了
var sa = Singleton.getInstance();
var sb = Singleton.getInstance();
console.log(sa === sb); // true
console.log(sa.data); // 'singleton'
原型是一个prototype对象,用于表示类型之间的关系。原型对象的用途是为每个实例对象存储共享的方法和属性,它仅仅是一个普通对象而已。并且所有的实例是共享同一个原型对象,因此有别于实例方法或属性,原型对象仅有一份。
每个对象的__proto__指向创建它的构造函数(注意指向的是构造函数的prototype a.__proto__ == A.prototype)
的prototype,而构造函数的prototype也有__proto__指向他的父辈或者是Object,当查找一个对象中不存在的属性时,会去它的__proto__、__proto__中的__proto__中进行寻找,直到找到或者是null为止
===
严格相等,会比较两个值的类型和值
==
抽象相等,比较时,会先进行类型转换,然后再比较值
==
规则如下
常问到ajax,axios,fetch的区别和实现,以及用promise封装
扩展 此问题可以深入涉及浏览器原理
浏览器包括浏览器进程,GPU进程、网络进程、插件进程、渲染进程
浏览器进程:负责控制浏览器除标签外的页面,包括地址栏、书签、前进后端按钮、以及负责与其他进程的协调工作,同时提供存储功能
GPU进程:负责整个浏览器界面渲染.
网络进程:负责发起和接收网络请求
插件进程:通过插件进程来隔离,以保证插件崩溃也不会对浏览器和页面造成影响
渲染进程:负责控制显示tab标签页内的所有内容,核心任务是将HTML、CSS、JS转为用户可以与之交互的网页,排版引擎Blink和JS引擎V8都是运行在改进程中,默认情况下,Chrome会为每个Tab标签页创建一个渲染进程
渲染进程中的线程
原文传送门👉我是传送门🚪
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象发生了改变,另一个对象(对象属性等)也会发生改变,基本类型改变不受影响(因为基本类型直接储存在栈中)
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。
//递归实现深拷贝,此处为个人简化版
function isObject(target){
const type = typeof target
return target !== null &&(type === 'object' || type ==='function')
}
function getInit(target){
//保留原型链上的数据
const constructor = target.__proto__.constructor
return Object.create(constructor.prototye)
}
function clone(target,map = new WeakMap()){
//基础数据类型
if(target == null) return target
if(!isObject(target)) return target
if(target instanceof Date) return new Date(target)
if(target instanceof RegExp) return new RegExp(target)
const type = Object.prototype.toString().call(target).slice(8,-1);
const cloneTaget = getInit(target)
if(map.get(target)){
return map.get(target)
}
map.set(target,cloneTaget)
if(type == "Set"){
target.forEach((ele,index)=>{
cloneTarget.add(clone(ele),map)
})
}
if(type == "Map"){
target.forEach((ele,index)=>{
cloneTarget.set(index,clone(ele,map))
})
}
if(type == "Object"){
const keys = Object.keys(target)
keys.forEach((ele,index)=>{
cloneTarget[ele] = clone(target[ele],map)
})
}
if(type =="Array"){
target.forEach((ele,index)=>{
cloneTarget[index] = clone(target[index],map)
})
}
}
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = {...outObj}
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
Object.assign():
let outObj = {
inObj: {a: 1, b: 2}
}
let newObj = Object.assign({}, outObj)
newObj.inObj.a = 2
console.log(outObj) // {inObj: {a: 2, b: 2}}
//一次打平
function flat(arr) {
let result = [];
arr.forEach(item=>{
if (Array.isArray(item)) {
result = result.concat(arrayFlattern(item));
} else {
result.push(item);
}
});
return result;
}
//k控制打平,采用闭包
function flat1(arr,k){
let counter = 0
function flat(arr)(){
let res = []
arr.forEach(item=>{
if(Array.isArray(item) && counter < k){
res = res.concat(flat(item))
}else{
res.push(item)
}
})
}
}
//reduce实现,简化版,没有考虑数组为空的情况,使用hasOwnProperty解决
Array.prototye.myReduce = function(reducer,initValue){
let hasInitial = arguments.length >1
let ret = hasInitial ? initValue:this[0]
for(let i = hasInitial?0:1;i<this.length;i++){
if(!this.hashOwnproperty(i)) continue;
ret = reducer.call(undefined,ret,this[i],i,this);
}
return ret
}
传送门👉知乎
//map实现
Array.prototype.map = function(cb,thisArg){
if(! Array.isArray(this) || typeof cb!= 'function'){
throw new Error('error')
}
let res = []
for(let i = 0;i<this.length;i++){
if(!this.hashOwnproperty(i)) continue;
res[i] = cb.call(thisArg,this[i],i,this)
}
return res
}
//forEach实现 和map相比只是少了返回的数组
//forEach是无法在内部打断的
Array.prototype.forEach = function(cb,thisArg){
if(!typeof cb === "function") throw new Error("error")
for(let i = 0 ;i<this.length;i++){
if(!this.hashOwnproperty(i)) continue;
cb.call(thisArg,this[i],i,this)
}
}
Array.prototype.filter = function(cb,thisArg){
let res = []
for(let i = 0;i<this.length;i++){
if(!this.hashOwnproperty(i)) continue;
res[i] = cb.call(thisArg,this[i],i,this)
}
return res
}
总结:filter和map,reduce 不会改变原数组,所以cb需要有返回值用来创建新数组、forEach会改变原数组
Function.prototype.myBind = function(asThis,...args1){
let fn = function(this,...args2){
return fn.call(this instanceof fn? this:asThis,...args1,...args2)
}
let fnNo = function(){}
fnNo.prototype = object.create(fn.prototype)//不用new 因为不用执行构造函数
return fn
}
思路 curry要求我们返回一个函数,如果这个函数的参数列表等于原来函数的参数列表,则绑定最后一次执行的this且执行,如果参数列表长度小于原函数则返回一个函数,递归调用curried函数
function curry(fn){
return function curried(...args1){
if(args.length >= fn.length){
return fn.apply(this,args1)
}else{
return function (...args2){
return curried.apply(this,args2.concat(args1))
}
}
}
}
fucntion sum(a,b){
return a + b;
}
let curriedSum = curry(sum);
alert(curriedSum(1)(2));
防抖: n秒内,如果有一次调用则取消之前的调用,重新计时
function debounce(fn,delay){
let timmer = null
return function(){
let self = this;
let args = Array.prototype.slice.call(arguments)
if(timer) clearTimeout(timer)
timer = setTimeout(()=>{
fn.apply(self,args)
},delay)
}
:第一次执行后的n秒内,如果还有执行则不调用
function throttle(fn,delay){
let timer = null
if(!timer){
fn.apply(this,arguments)
timer = setTimeout(()=>{
timer = null
},delay)
}
}
其实js没有并行的概念,只是同时创建了promise同时执行,并行可以用佛forEach和promiseall 👉原文传送门🚪 最后两种方式还不是很能理解
串行执行,将上一个promise执行后的值,作为下一个promise参数传入执行,arr是个包含promise的数组
方式一.reduce
//思路:在前一个promise的then里面去调用下一个promise,失败就调用Promise.resolve()终止
//因为promise创建即执行,所以需要封装一层
function delay(time){
return new promise((resolve,reject)=>{
console.log(`wait ${time}s`)
setTimeout(()=>{
console.log('execute');
resolve()
},time*1000)
})
}
arr.reduce((prev,curr)=>{
return prev.then(()=>delay(v),Promise.resolve)
})
方式二. async 和await
async function(){
for(const v of arr){
await delay(v)
}
}()
方式三.普通循环
其实仔细想想方式1的本质是使用一个中间变量(上一次执行结果)来保存链式Promise, 那我们举一反三, 换别的循环也可以实现
let p = promise.resolve()
for(const v of arr){
p = p.then(()=>{delay(i)})
}
方式四.递归
function dispatch(i,p=Promise.resolve()){
if(!arr[i]) return Promise.resolve()
return p.then(()=> dispatch(i+1,delay(arr[i])))
}
方式五.for wait of(迭代器的异步调用,之后会讲)
function createAsyncInterable(arr){
return {
[Symbol.asyncInterator](){
return {
i:0,
next(){
if(this.i<arr.length){
return delay(arr[this.i].then(()=>{value:this.i++,done:false}))
}
}
}
}
}
}
(async function(){
for await(i of createAsyncIterable(arr)){
}
})()
*方式6.generator(其实相当于async和await)
function *gen(){
for(const v of arr){
yield delay(v)
}
}
function run(gen){
const g = gen()
function next(data){
const result = g.next(data)
if(result.done) return result.value
result.value.then(function(data){
next(data)
})
}
next()
}
run(gen)
{
tag: 'DIV',
attrs:{
id:'app'
},
children: [
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] }
]
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}
]
}
把上诉虚拟Dom转化成下方真实Dom
<div id="app">
<span>
<a></a>
</span>
<span>
<a></a>
<a></a>
</span>
</div>
// 真正的渲染函数
function _render(vnode) {
// 如果是数字类型转化为字符串
if (typeof vnode === "number") {
vnode = String(vnode);
}
// 字符串类型直接就是文本节点
if (typeof vnode === "string") {
return document.createTextNode(vnode);
}
// 普通DOM
const dom = document.createElement(vnode.tag);
if (vnode.attrs) {
// 遍历属性
Object.keys(vnode.attrs).forEach((key) => {
const value = vnode.attrs[key];
dom.setAttribute(key, value);
});
}
// 子数组进行递归操作
vnode.children.forEach((child) => dom.appendChild(_render(child)));
return dom;
}
new的实现原理
new操作符的实现步骤如下:
* 创建一个空对象 a
* 设置这个对象的原型 a.proto
* 将fn的this绑定为这个空对象,并执行构造函数(赋值的过程this.a = a)
* 判断返回类型,如果构造函数内有返回值我们是返回构造函数的属性,如果没有我们返回我们创建的空对象
/**
* 例子: let a = new A();
* 1. 创建一个空对象 a
* 2. 设置这个对象的原型 a.__proto__
* 3. 将fn的this绑定为这个空对象,并执行(赋值的过程this.a = a)
* 4. 判断返回类型,如果构造函数内有返回值我们是返回构造函数的属性,如果没有我们返回我们创建的空对象
*/
function myNew(){
let a = {};
let args = object.prototype.slice.call(argumetns)
let fn = args.shift();
a.__proto__ = fn.prototye;
const ret = fn.apply(a,args)
return ret || a
}
/**
* return的时候为什么需要判断,因为如果fn 返回一个对象,则按照这个对象构造
*/
function Product(name, price) {
this.name = name;
this.price = price;
return {
name: this.name,
color: 'red'
}
}
var newProduct = new Product('name', 'price');
console.log(newProduct)//{ name: 'name', color: 'red' }
[扩展]
promise的相关实现
promise常见考点,手写promise,promiseAll,promise实现并发数量控制
这里给出了最终版答案,且这版的resolvePromise的判断细节不完整,个人觉得面试写到这个程度足够了,如果想了解如果实现,可以移步掘金原文 传送门➡️掘金,只有这篇是使用了微任务队列,其余大部分都是使用setTimeout()方法去模拟微任务队列,如果小伙伴们有更好的资源可以替换
const PENDING = "PENDING"
const FULFILLED = "FULFILLED"
const REJECTED = "REJECTED"
class myPromise(){
constructor(executor){
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
// 这个executor中的括号内部的箭头函数,相当于我们new promise((resolve,reject)=>{})3
try{
executor(this.resolve,this.reject)
}catch(err){
this.reject(err)
}
}
//当前的status
status = PENDING
//失败成功的返回值
value = null
reason = null
//储存回调函数
onFulfilledCallbacks = []
onRejectedCallbacks = []
resolve = (value)=>{
if(this.status === PENDING){
this.status = FULFILLED
this.value = value
while(this.onFulfilledCallbacks.length){
this.onFulfilledCallbacks.shift()(value)
}
}
}
reject = (reason) =>{
if(this.status === PENDING){
this.status === REJECTED
this.reason = reason
while(this.onRejectedCallbacks.length){
//执行回调函数
this.onRejectedCallbacks.shift()(reason)
}
}
then(onFulfilled,onRejected){
const realOnFulfilled = typeof onFulfilled === 'function'?onFulfilled:value=>value
const realOnRejected = typeof onRejected === 'function' ? onRejected:reson=>{throw reason}
const promise2 = new myPromise((resolve,reject)=>{
const fulfilledMicrotask = ()=>{
queueMicrotask()=>{
try{
const x = realOnFulfilled(this.value)
resolvePromise(promise2,x,resolve,reject)
}catch(error){
reject(error)
}
}
}
const rejectedMicrotask = ()=>{
queueMicrotask()=>{
try{
const x = rejectedMicrotask(this.value)
resolvePromise(promise2,x,resolve,reject)
}catch(error){
reject(error)
}
}
}
if(this.status === FULFILLED){
fulfilledMicrotask()
}else if(this.status === REJECTED){
rejectedMicrotask()
}else if(this.status === PENDING){
onFulfilledCallbacks(fulfilledMicrotask)
onRejectedCallbacks(onRejectedCallbacks)
}
})
return promise2
}
static resolve(para){
if(para instanceof myPromise){
return para
}
return new Promise(resolve=>{
resolve(para)
})
}
static reject(reason){
return new myPromise((resolve,reject) =>{
reject(reason)
})
}
}
}
function resolvePromise(promise2,x,resolve,reject){
if(promise2 === x){
return reject(new Typeerror('Chaining cycle detected for promise #<Promise>'))
}
//实现链式调用
if(x instance myPromise){
x.then(resolve,reject)
}else{
resolve(x)
}
}
promise.all接收一个数组,返回一个promise,promise中的value是一个包含执行结果的数组
function promiseAll(promises){
return new Promise((resolve,reject)=>{
let resolverCounter = 0
let promiseNum = promises.length
let result = new Array(promise.length)
for(let i = 0;i<promiseNum;i++){
promises[i].then(val=>{
resolverCounter++;
result[i] = val
if(resolverCounter == promiseNum){
return result
}
},err=>{
return reject(err)
})
}
})
}
promise.race和all一样接受一个promises数组,promise中的value是第一个完成promise的结果,有一个完成则完成
function race(promises){
return new Promise((resolve,reject)=>{
for(let i = 0;i<promises.length;i++){
promises[i].then(val=>{
return resolve(val)
},err=>{
return reject(err)
})
}
})
}
// JS实现一个带并发限制的异步调度器Scheduler,保证同时运行的任务最多有两个。完善下面代码的Scheduler类,使以下程序能够正常输出:
class Scheduler {
add(promiseCreator) { ... }
// ...
}
const timeout = time => new Promise(resolve => {
setTimeout(resolve, time);
})
const scheduler = new Scheduler();
const addTask = (time,order) => {
scheduler.add(() => timeout(time).then(()=>console.log(order)))
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
// output: 2 3 1 4
//思路:使用队列去并判断队列大小去执行队列控制
class Scheduler{
constructor(){
this.queue = []
this.maxCount =2;
this.runCounts = 0
}
add(promiseCreator){
this.queue.push(promiseCreator)
}
taskStart(){
for(let i = 0;i<this.maxCount;i++){
this.request()
}
}
request(){
if(!this.queue || !this.queue.length||this.runCounts >= this.manxCount){
return
}
this.runCounts++;
this.queue.shift()().then(()=>{
this.runCounts--;
this.request();
})
}
}
const timeout = time => new Promise(resolve => {
setTimeout(resolve, time);
})
const scheduler = new Scheduler();
const addTask = (time,order) => {
scheduler.add(() => timeout(time).then(()=>console.log(order)))
}
addTask(1000, '1');
addTask(500, '2');
addTask(300, '3');
addTask(400, '4');
scheduler.taskStart()
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
this.current = this.from;
return this;
},
next() {
if (this.current <= this.to) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
for (let num of range) {
console.log(num)
}
generator函数
https://juejin.cn/post/6844903593976266760
async和await,原文更容易理解和记忆 传送门👉掘金
function fn(num) {
return new Promise(resolve => {
setTimeout(() => {
resolve(num)
}, 1000)
})
}
function* gen() {
yield fn(1)
yield fn(2)
return 3
}
const g = gen()
console.log(g.next()) // { value: Promise { <pending> }, done: false }
console.log(g.next()) // { value: Promise { <pending> }, done: false }
console.log(g.next()) // { value: 3, done: true }
const g = gen()
const next1 = g.next()
next1.value.then(res1 => {
console.log(next1) // 1秒后输出 { value: Promise { 1 }, done: false }
console.log(res1) // 1秒后输出 1
const next2 = g.next()
next2.value.then(res2 => {
console.log(next2) // 2秒后输出 { value: Promise { 2 }, done: false }
console.log(res2) // 2秒后输出 2
console.log(g.next()) // 2秒后输出 { value: 3, done: true }
})
})
function fn(nums){
return new Promise(resolve=>{
setTimeout(()=>{
resolve(nums*2)
},1000)
})
}
//
function* gen(){
const num1 = yield fn(1)
const nums2 = yield fn(num1)
const nums3 = yiled fn(num2)
return nums3
}
//generatortoAsync期望返回一个function,且这个asynFunction执行后返回一个promise
function generatorToAsync(generatorFn){
return function(){
return new Promise((resolve,reject)=>{
const gen = generatorFn.apply(this,arguemtns)//gen有可能传递参数,this指向调用async函数的作用域 这个相当于是generator返回一个迭代器
function go(key,arg){
let res
try{
//gen在之前apply的时候被赋值,此时gen应该是一个迭代器对象,定义了next()方法
// key是传入的值,一般为next,就是调用next方法的返回值
res = gen[key](arg)//有可能执行返回reject状态的promise,
}catch(err){
return reject(err)//报错走catch,直接reject
}
const{value,done} = res
if(done){
return resolve(value)
}else{
//使用promise.resolve()对value的值进行了过滤,value可能是常量,promise,promise有可能是成功或失败,go将第一个promise的返回值传入了第二个promise,不传递可以不传val
return Promise.resolve(value).then(val=>go('next',val),err=>go('throw',err))
}
}
go("next")
})
}
}
const asyncFn = generatorToAsync(gen)
前端路由实现 👉掘金
算法45度打印数组
setTimeout模拟setInterval
为什么要setTimeout去模拟setInterval? 因为setInterval是间隔一定的时间向任务队列添加时间,如果之前添加的任务还没执行完则不会添加了,setTimeout会稳定添加
let timer = null
function interval(func,delay){
let inner = function(){
func();
timer = setTimeout(inner,delay)
}
timer = setTimeout(inner,delay)
}
class A{}
class B extends A{
constructor(){
super()
}
}
class A{
p(){
return 2;
}
}
class B extends A{
constructor(){
super();
console.log(super.p())//2
}
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.