曾经苍老,现在风华正茂
🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙
个人博客
曾经苍老,现在风华正茂
🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙 🤙
asdfasdfasdfasdfasfasdfasdfasdfadsfsdafsadfs
文章参考:
https://time.geekbang.org/column/article/126339
mqyqingfeng/Blog#4
**定义:**当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。
调用栈是javascript引擎追踪函数执行的一个机制,通过调用栈就能够了解函数之间的调用关系。javascript利用栈这种数据结构管理执行上下文。
var a = 0;
function add(a + b) {
returan a + b;
}
function sum(c) {
return c + add(2, 3);
}
sum(a);
可以看到调用栈如果不能有序退出那么就会造成栈溢出,这种情况一般会发生在递归调用结束条件有问题情况等等。
作用域决定了代码区块中变量和其他资源的可访问性。
ES6 之前javascript没有块级作用域,只有全局作用域和函数作用域。var、let、const是js定义变量的三个关键词,其中var和let、const有本质不同。let 和 const 都是es6语法。两者都支持块级作用域,并没有变量提升现象,即:不会再编译阶段将声明放置到代码顶部。而在javascript为了加入块级作用域,引入了词法环境这一概念。我们可以简单地认为,var以及function声明的变量加入到环境变量,而let以及const声明的变量加入到词法环境当中。
我们可以通过一个函数的创建执行来分析这两种变量的不同。
function strong(){
var a = 1;
let b = 2;
{
var c = 3;
let d = 4;
console.log(c)
console.log(d)
}
console.log(a)
console.log(b)
console.log(c)
}
foo()
可以看到 let , const 等块级作用域变量会直接放到词法环境中,首先在这里寻找变量,如果没有再去变量环境中寻找。块级代码执行完后,这些变量会被词法环境栈直接弹出。
待填坑
自己对于http/2 的理解还停留在面试死记硬背的水平,所以最近对自己心中一些疑问以及自己的理解做一个总结,这些总结可能会有错误。如果有大佬觉得哪里有什么问题,可以直接说出。
http/2 的完整介绍很多。珠玉在前:http/2 简介 ,就不在赘述了。
对于这个问题,我们首先就要搞清楚什么http1.1的文本协议和htt/p2的二进制协议的不同了。其实我作为一个基础并不牢固的搬砖工,当看到这个二进制的时候我就在想:在计算机世界当中所有的内容不都是二进制吗?为什么还要有二进制以及文本协议之分呢?
带着这个问题,我在网上找到了一些回复,就直接搬下来吧
Binary protocol versus text protocol isn't really about how binary blobs are encoded. The difference is really whether the protocol is oriented around data structures or around text strings. Let me give an example: HTTP. HTTP is a text protocol, even though when it sends a jpeg image, it just sends the raw bytes, not a text encoding of them.
四级低空略过水平的翻译:
二进制协议和文本协议的区别并不是关于二进制blob是如何编码的。关键在于协议是以数据结构为导向还是以文本字符串为导向。
struct request {
int requestType;
int protocolVersion;
char path[1024];
char user_agent[1024];
char host[1024];
long int accept_bitmask;
long int language_bitmask;
long int charset_bitmask;
};
当我们看到http文本协议以及二进制请求的结构之后:
At the core of all performance enhancements of HTTP/2 is the new binary framing layer, which dictates how the HTTP messages are encapsulated and transferred between the client and server.
_
在许多文章中,都说http/2中的二进制分帧层,是http/2性能提升的关键,它给一个tcp链接同时发送多个请求提供了可能。
HTTP/1.1和HTTP/2都是基于TCP的协议,TCP模型是双向数据流,任何在一个TCP连接上处理超过一个请求的协议都需要解决这样两个问题:
在这两个问题中,我觉得第二个往往会让人忽略,就是在http1.1 中,一个tcp请求只能同时完成一个请求响应过程,所以请求以及响应天生一一对应,不存在响应和请求无法匹配的问题。但是当我们打开抓包工具查看相应的相关http报文的时候,会发现其实响应是没有一个字段去和请求一一对应的。这就给http/2的多路复用带来了问题,一个tcp连接中多个请求,多个响应,那么如何将这些响应和请求一一对应呢?只能在二进制分帧层添加字段了,所以在http/2帧的报文格式中,有一个字段是Stream Identifier,这个流ID可以将响应和请求一一对应。这个根本解决了多路复用,请求以及响应匹配的问题。
所以给这个问题来一个总结吧:
在http/2中帧是最小通信单位,我的问题是http/2 中的帧和最小的tcp数据包是什么关系,一一对应的吗?在http/2 的请求中,tcp是如何拆分以及组装消息的?
其中的解释如下:
- 同域名下所有通信都在单个连接上完成,消除了因多个 TCP 连接而带来的延时和内存消耗。
- 单个连接上可以并行交错的请求和响应,之间互不干扰
下面的解释当中,并行交错的请求和响应。其实这个并行请求应该具体为并行http请求,那么tcp可以将请求拆分并行请求吗?
TCP 数据包在 IP 数据包的负载里面。它的头信息最少也需要20字节,因此 TCP 数据包的最大负载是 1480 - 20 = 1460 字节。由于 IP 和 TCP 协议往往有额外的头信息,所以 TCP 负载实际为1400字节左右。因此,一条1500字节的信息需要两个 TCP 数据包
我们假设有两个post请求并行进行请求:
在我看来其实这是不可以的,因为tcp协议是完全按照顺序组装数据包的,如果并行请求并将数据包顺序打乱,那么计算机将不知道如何组装这些数据包。
所以上面的并行请求是不准确的,在计算机内部一定是串行请求,发完一个请求的数据包,在发送下一个请求的数据包,这样才能保证数据包组合正确。
所以http/2中的帧,是按照顺序串行请求的,如果不是这样,数据将无从组装,一一对应。
总结一下吧:
如果有哪些地方理解不到位,请不吝赐教
作用域是在运行时代码中的某些特定部分中变量,函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。
词法作用域又称静态作用域(lexical scoping),与之对应的是静态作用域,这是编程语言常见的两种作用域,javascript采用的是静态作用域,也就是词法作用域。
静态作用域的概念对于理解javascript的作用域以及闭包等概念非常重要。下面就用两个例子说明静态作用域以及动态作用域的区别。
const result = 1;
function sub() {
console.log(result);
}
function func() {
const result = 2;
sub();
}
func();
// 输出结果 1
#!/bin/bash
result=1
function sub () {
echo $result;
}
function func () {
local $result=2;
foo;
}
bar
// 输出结果 2
其实这就是静态作用域与动态作用域的区别:
听到链这个词,估计很多前端同学都很熟悉,因为javascript不仅有作用域链还有原型链等等,作用域链其实比较简单,下面我们就用几个图,来说明作用域链的知识点。
上方是简单的作用域链的示意图,可以看到变量的访问是根据函数定义时就确定的。
function out() {
const out = 3;
return function inner() {
debugger;
const inner = 4;
return out + inner;
}
}
我们可以看到闭包函数作用域链是 Local => Closure => Global
每一个页面都有一个渲染主线程,会处理很多任务,比如:DOM、样式计算、布局等等。如此多的任务就需要一个消息队列来进行管理。这些任务的类型,就是我们通常所说的宏任务以及微任务。
其实宏任务以及微任务的概念在前端已经是很普及的了,相关文章链接。但是有以下几个问题一直困扰着我:
所以研究总结如下关系:
宏任务和他所产生的微任务是绑定的,一个宏任务执行完成后,这个宏任务所产生的微任务,以及微任务产生的微任务全部执行完后。才会执行下一个宏任务。如果这些任务耗时不长,那么一帧16ms内,可以执行多个宏任务。
如果一个宏任务以及宏任务产生的微任务耗时过长,超过16ms,那么就会造成UI线程渲染阻塞。其中如果一个宏任务耗时过长,也会等待其所产生的微任务执行完成后再进行UI线程渲染页面。
每一个任务的执行当中,有可能会产生新的任务,那么这些新的任务有两种插入消息队列的方式:
这也主要是宏任务和微任务的区别,在任务执行过程中:
可以看出微任务的存在主要是保证任务执行的时效性,而宏任务就是正常的直接插入消息队列尾部。
Vite是一个构建工具,旨在为现代web项目提供更快、更精简的开发体验。
vite主要分两个模块:
vite 和 webpack 开发环境最大的区别就是vite 在开发环境抛弃了打包这一个理念,直接在开发环境使用Javascript module,减少打包带来的时间损耗,极大地方便了本地开发。
对于vite的学习,我主要总结了以下四个模块进行总结。
对于一个native es module服务系统而言,不同模块的路径解析非常重要,这里面有以下几个问题:
vite的解决方式:
通过不同格式的处理,我们可以理解类似于webpack loader对于不同文件是如何处理的, 了解vite工作机制。
对于不同格式的文件,vite统一都处理成javascript格式,在返回的response 中添加
Content-Type: application/javascript; charset=utf-8
vue 的组件是一个单文件组件的机制。一个vue组件的定义基本分三个部分:
<template></template>
<script></script>
<style></style>
编译器会将一个vue组件的三部分分别处理。在vite中,请求一个组件的资源:
// 此文件可以理解为一个组件的script逻辑部分
import string from '/src/string.js'
const __script = {
name: 'HelloWorld',
props: {
msg: String
},
data() {
return {
age: 123
}
}
}
// 这里引入组件的template部分
import "/src/components/HelloWorld.vue?type=style&index=0"
// 这里引入组件的style部分
import {render as __render} from "/src/components/HelloWorld.vue?type=template"
__script.render = __render
__script.__hmrId = "/src/components/HelloWorld.vue"
__script.__file = "/Users/lizhuang/gitcode/vite-test/src/components/HelloWorld.vue"
export default __script
import { updateStyle } from "/vite/client"
const css = "\nh1 {\n background: red;\n}\n"
updateStyle("62a9ebed-0", css)
export default css
import {
toDisplayString as _toDisplayString,
createVNode as _createVNode,
createTextVNode as _createTextVNode,
Fragment as _Fragment,
openBlock as _openBlock,
createBlock as _createBlock
} from "/@modules/vue.js"
const _hoisted_1 = /*#__PURE__*/
_createVNode("p", null, "string1", -1 /* HOISTED */
)
const _hoisted_2 = /*#__PURE__*/
_createVNode("p", null, [/*#__PURE__*/
_createTextVNode("Edit "), /*#__PURE__*/
_createVNode("code", null, "components/HelloWorld.vue"), /*#__PURE__*/
_createTextVNode(" to test hot module replacement.")], -1 /* HOISTED */
)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(),
_createBlock(_Fragment, null, [_createVNode("h1", null, _toDisplayString($props.msg), 1 /* TEXT */
), _createVNode("button", {
onClick: _cache[1] || (_cache[1] = $event=>($data.count++))
}, "count is: " + _toDisplayString($data.count), 1 /* TEXT */
), _hoisted_1, _createVNode("p", null, _toDisplayString($data.string), 1 /* TEXT */
), _hoisted_2], 64 /* STABLE_FRAGMENT */
))
}
通过 rollup-pluginutils 的dataToEsm方法
{
custom: 'data',
to: ['treeshake']
}
转变为:
export const custom = 'data';
export const to = ['treeshake'];
export default { custom, to };
其实在vue的样式部分已经有所涉及。
import { updateStyle } from "/vite/client"
const css = "\nh1 {\n background: red;\n}\n"
updateStyle("62a9ebed-0", css)
export default css
其中 updateStyle 是更新样式的关键函数,我们进行分析:
/**
* content: css文件内容
*/
function updateStyle(id, content) {
...
if (!style) {
style = new CSSStyleSheet()
style.replaceSync(content)
document.adoptedStyleSheets = [...document.adoptedStyleSheets, style]
} else {
style.replaceSync(content)
}
...
}
vite利用** CSSStyleSheet **代表一个样式表,利用javascript的接口编辑或者添加相关的样式。
当然还有一个特殊的情况就是css文件中有@import 等操作, 这种特殊的情况,vite直接使用style标签进行样式插入。
cosnt style = document.createElement('style')
style.setAttribute('type', 'text/css')
style.innerHTML = content
document.head.appendChild(style)
vite1 代码较少,这可以让我们低成本的学习一个开发环境热更新的具体细节
其中第四部处理不同文件的方式,列在了下方:
async function handleMessage(payload: HMRPayload) {
const { path, changeSrcPath, timestamp } = payload as UpdatePayload
switch (payload.type) {
case 'connected':
console.log(`[vite] connected.`)
break
case 'vue-reload':
queueUpdate(
import(`${path}?t=${timestamp}`)
.catch((err) => warnFailedFetch(err, path))
.then((m) => () => {
__VUE_HMR_RUNTIME__.reload(path, m.default)
console.log(`[vite] ${path} reloaded.`)
})
)
break
case 'vue-rerender':
const templatePath = `${path}?type=template`
import(`${templatePath}&t=${timestamp}`).then((m) => {
__VUE_HMR_RUNTIME__.rerender(path, m.render)
console.log(`[vite] ${path} template updated.`)
})
break
case 'style-update':
// check if this is referenced in html via <link>
const el = document.querySelector(`link[href*='${path}']`)
if (el) {
el.setAttribute(
'href',
`${path}${path.includes('?') ? '&' : '?'}t=${timestamp}`
)
break
}
// imported CSS
const importQuery = path.includes('?') ? '&import' : '?import'
await import(`${path}${importQuery}&t=${timestamp}`)
break
.... 还有很多,就不一一列举了
}
}
vite的预打包优化手段其实和小程序页面预加载技术,以及网页的prefetch,preload等的原理是基本一致的,当我们尽量少的打包过后,那么预打包那些没有处理的文件就是优化的手段之一。
vite 会去分析package.json 当中的依赖项,会将依赖进行打包并缓存:
其中lodash较为特殊,因为其文件众多,如果不进行预打包的话,开发项目将会请求很多相关文件,造成网页reload时性能衰减。所以vite预打包的另外一个重要的功能就是通过rollup或者esbuild(vite 不同版本实现不同),将过于零散的文件打包,减少网络请求,提高页面reload性能。
2
当你的页面仅支持http1.x的时候,浏览器进程可能会和服务器建立多个tcp连接。用不同的端口号区分tcp连接。
TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接。
断开连接分两种情况:
首先还是正常的tcp进行连接。
然后进行的就是tls的握手:
上面只是对各种包的一个大概的说明。
asdfadsfadsfdsafadsfasdf
cookie 的英文意思有两个一个是饼干,另一个是坚强的人。将数据信息命名为 cookie 的原因可以看这个
cookie 是服务器产生的数据信息(部分 cookie 也可能是由网页 javascript 代码注入),存储在浏览器中,常用来存储用户信息等。
cookie 的存储是通过一个字符串来完成的
"name=xxx;age=123"
既然 cookie 这个概念代表一部分数据,那么下面我们将讨论数据的来源、存储以及应用
cookie 的存储以及修改会涉及到 cookie 的配置问题,下面对相关配置进行总结
cookie 的最长有效时间,如果没有设置这个属性,浏览器关闭时 cookie 会被清除
经过多少秒 cookie 过期,如果也存在 Expires 属性,已 Max-Age 为准。也就是说 Max-Age 比 Expires 权重要高。
域名规定 cookie 可被发送的域名,如果不设置,默认为当前页面的 host。如果设置了,子域名都是允许的。
比如 Domain = a.com,那么域名 b.a.com 就是允许访问这个 cookie 的。
页面路径和域名的设置原理基本一致,/ 字符被认为是文件名的分隔符。 如果你设定 Path=/docs 那么
设定之后,只有是 http 协议才能访问这个 cookie
设置后 javascript 无法访问这个 cookie
在浏览器存储由接口返回的 cookie 后,此后的每一个请求,只要是满足 cookie 的设置,cookie 都会被 http 请求携带。
我在这一部分会重点对跨域请求的 cookie 携带做一个总结。
对于cookie 的使用,一个主动地使用场景就是请求跨域的接口,如果这个跨域的接口需要携带 cookie 有以下两个方面需要考虑
跨域请求接口,浏览器默认是不会携带 cookie 的
在调用接口后,我们需要指定一个属性 withCredentials
const invocation = new XMLHttpRequest();
const url = 'https://bar.other/resources/credentialed-content/';
function callOtherDomain() {
if (invocation) {
invocation.open('GET', url, true);
// 在指定这个属性后,接口调用才有可能携带 cookie
invocation.withCredentials = true;
invocation.onreadystatechange = handler;
invocation.send();
}
}
cors 跨域请求分两种一种是简单请求,一种是复杂请求 (不理解请自行baidu)
SameSite 属性是浏览器除了 withCredentials 判断跨域请求是否可以携带此 cookie 的另一个机制。
浏览器的跨域请求必须同时满足这两个机制,才能将 cookie 发出。
除了我们在 javascript 中使用 XMLHttpRequest 以及 fetch。我们还会在 html 中引入一下标签,常用的比如 img、iframe 等。其中这些资源也会涉及到 cookie 发送的问题,这些问题往往被我们所忽视:
请求类型 | 示例 | strict | None | lax |
---|---|---|---|---|
链接 | < a href=""> | 否 | 是 | 是 |
预加载 | < link ref="prerender" href="" /> | 否 | 是 | 是 |
get 表单 | < form method="get"> | 否 | 是 | 是 |
post 表单 | < form method="post"> | 否 | 是 | 否 |
iframe | < iframe src=""> </iframe> | 否 | 是 | 否 |
AJAX | get("") | 否 | 是 | 否 |
image | < img src=""> | 否 | 是 | 否 |
其中需要特别注意的就是 a 标签,当点击标签后,如果 same-site 属性为 strict,跳转过去的页面是不会携带 cookie 的。所以会造成很多网页的登录状态失效,这个要特别注意。
所以我觉得默认的 lax 其实是比较合适的,在安全性以及易用性上保持平衡
在学习使用 egg 的过程中,会涉及到一些关于 cookie 安全的问题。下面对这些 cookie的安全问题做一个总结
csrf (cross-site request forgery) 跨站请求伪造。一句话解释就是,攻击者会利用 cookie 信息,在第三方网页上伪造真实请求,进行网络攻击。
特点:
第一种就是 same-site 属性。当然只有支持这个属性的浏览器才可以。如果是版本较老的浏览器就不行了。
第二种方式就是 csrf-token
csrf-token 简单说就是在 cookie 中存储一个新的字段,通过网络请求页面时服务器注入一个加密的字符串,在之后的网络请求时,都会携带这个字符串,如果服务器判断字符串不合法,就不会返回真实数据以及数据交互。
我比较喜欢第二种方式,因为可以减轻服务器的压力,实现也比较简单。
cookie 存储在浏览器中,javascript、用户手动都是可以进行修改的。
为了防止 cookie 被篡改,可以在服务器下发 cookie 的时候,同时下发一个根据内容加密的 cookie 字段。
set-cookie name=008; path=/; httponly
set-cookie name.sig=BKz_FtEld6gVjNwSzdNZAXZCq3n2Vf7VcHISiEBp7oc; path=/; httponly
其中 name.sig 就是防止篡改的字段。当服务器接收到 name 时会同时和 name.sig 进行比对,如果不一致,就意味着 cookie 被篡改了
这篇文章对 esbuild 在 vite 中的应用做一个总结
esbuild 是 vite 性能快的关键。esbuild 在 vite 中主要被使用在以下场景:
需要处理 node_modules 的原因:
require('esbuild').buildSync({
entryPoints: ['index.html'], // is ok
bundle: true,
write: true,
outdir: 'out',
})
if (resolved.includes('node_modules') || include?.includes(id)) {
if (OPTIMIZABLE_ENTRY_RE.test(resolved)) {
// esbuild 插件中直接判断引入的包是否是 node_modules 中的依赖
depImports[id] = resolved
}
}
const regex = isHtml ? scriptModuleRE : scriptRE
const scriptModuleRE =
/(<script\b[^>]*type\s*=\s*(?:"module"|'module')[^>]*>)(.*?)<\/script>/gims
export const scriptRE = /(<script\b(?:\s[^>]*>|>))(.*?)<\/script>/gims
vite 内部通过编写一个简单的 esbuild 插件,直接利用正则表达式将 script 标签内部内容截取出来,作为 vue 文件的内容。这样 esbuild 就可以将 vue 文件当成 js 文件处理依赖树了。
当用户第一次请求业务文件的时候,浏览器以及 vite 应用内部均没有缓存,这个时候就要倚靠 esbuild 对部分业务文件进行编译。
esbuild 默认会编译 ts、tsx、jsx 文件。
其中 js 文件默认不会被编译。所以当选择一些新语法开发时,要慎重
就像刚才说的问题,普通的 js 文件以及 vue 文件中的 js script 内容,是不会被 esbuild 处理编译的,所以不稳定的新语法是不能在 vite 环境下使用的。
esbuild 作为新起的打包工具,他对部分语法是不支持的,比如 js 文件中使用装饰器。这个场景 esbuild 就是不兼容的。
所以语法兼容问题要考虑两个问题:
学习笔记来自这两篇博客
发送方和接收方需要持有同一把密钥,发送消息和接收消息均使用该密钥。
相对于非对称加密,对称加密具有更高的加解密速度,但双方都需要事先知道密钥,密钥在传输过程中可能会被窃取,因此安全性没有非对称加密高。
接收方在发送消息前需要事先生成公钥和私钥,然后将公钥发送给发送方。发送放收到公钥后,将待发送数据用公钥加密,发送给接收方。接收到收到数据后,用私钥解密。
在这个过程中,公钥负责加密,私钥负责解密,数据在传输过程中即使被截获,攻击者由于没有私钥,因此也无法破解。
非对称加密算法的加解密速度低于对称加密算法,但是安全性更高。
这个没什么好说的,就是用户在浏览器里输入一个https网址,然后连接到server的443端口。
采用HTTPS协议的服务器必须要有一套数字证书,可以自己制作,也可以向组织申请。区别就是自己颁发的证书需要客户端验证通过,才可以继续访问,而使用受信任的公司申请的证书则不会弹出提示页面(startssl就是个不错的选择,有1年的免费服务)。这套证书其实就是一对公钥和私钥。如果对公钥和私钥不太理解,可以想象成一把钥匙和一个锁头,只是全世界只有你一个人有这把钥匙,你可以把锁头给别人,别人可以用这个锁把重要的东西锁起来,然后发给你,因为只有你一个人有这把钥匙,所以只有你才能看到被这把锁锁起来的东西。
这个证书其实就是公钥,只是包含了很多信息,如证书的颁发机构,过期时间等等。
这部分工作是有客户端的TLS来完成的,首先会验证公钥是否有效,比如颁发机构,过期时间等等,如果发现异常,则会弹出一个警告框,提示证书存在问题。如果证书没有问题,那么就生成一个随即值。然后用证书对该随机值进行加密。就好像上面说的,把随机值用锁头锁起来,这样除非有钥匙,不然看不到被锁住的内容。
这部分传送的是用证书加密后的随机值,目的就是让服务端得到这个随机值,以后客户端和服务端的通信就可以通过这个随机值来进行加密解密了。
服务端用私钥解密后,得到了客户端传过来的随机值(私钥),然后把内容通过该值进行对称加密。所谓对称加密就是,将信息和私钥通过某种算法混合在一起,这样除非知道私钥,不然无法获取内容,而正好客户端和服务端都知道这个私钥,所以只要加密算法够彪悍,私钥够复杂,数据就够安全。
这部分信息是服务段用私钥加密后的信息,可以在客户端被还原
客户端用之前生成的私钥解密服务段传过来的信息,于是获取了解密后的内容。整个过程第三方即使监听到了数据,也束手无策。
非对称加密算法的性能是非常低的,原因在于寻找大素数、大数计算、数据分割需要耗费很多的CPU周期,所以一般的HTTPS连接只在第一次握手时使用非对称加密,通过握手交换对称加密密钥,在之后的通信走对称加密。
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.