Giter Club home page Giter Club logo

blog's People

Contributors

zsjie avatar

Watchers

 avatar

blog's Issues

一个完整支持unicode的cookie读取/写入器

/*\
|*|
|*|  :: cookies.js ::
|*|
|*|  A complete cookies reader/writer framework with full unicode support.
|*|
|*|  https://developer.mozilla.org/en-US/docs/DOM/document.cookie
|*|
|*|  This framework is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
|*|  Syntaxes:
|*|
|*|  * docCookies.setItem(name, value[, end[, path[, domain[, secure]]]])
|*|  * docCookies.getItem(name)
|*|  * docCookies.removeItem(name[, path], domain)
|*|  * docCookies.hasItem(name)
|*|  * docCookies.keys()
|*|
\*/

var docCookies = {
  getItem: function (sKey) {
    return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null;
  },
  setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) {
    if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; }
    var sExpires = "";
    if (vEnd) {
      switch (vEnd.constructor) {
        case Number:
          sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd;
          break;
        case String:
          sExpires = "; expires=" + vEnd;
          break;
        case Date:
          sExpires = "; expires=" + vEnd.toUTCString();
          break;
      }
    }
    document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : "");
    return true;
  },
  removeItem: function (sKey, sPath, sDomain) {
    if (!sKey || !this.hasItem(sKey)) { return false; }
    document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : "");
    return true;
  },
  hasItem: function (sKey) {
    return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie);
  },
  keys: /* optional method: you can safely remove it! */ function () {
    var aKeys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, "").split(/\s*(?:\=[^;]*)?;\s*/);
    for (var nIdx = 0; nIdx < aKeys.length; nIdx++) { aKeys[nIdx] = decodeURIComponent(aKeys[nIdx]); }
    return aKeys;
  }
};

Nginx 日志格式

nginx 日志格式

nginx 日志模块官方文档

日志变量

nginx 的日志格式可以通过以下日志变量定义:

$remote_addr

IP 地址,从这里可以看到请求从哪个 IP 发出

$remote_user

远程用户

$requst

请求,包括请求方法和 URL

$body_bytes_sent

发送给客户端请求体的 byte 数量

$bytes_sent

发送给客户端的 byte 数量

$connection

连接的序列号

$connection_requests

通过一个连接发送的当前请求数(the current number of requests made through a connection)

$msec

写入日志时的时间,单位为秒,精确毫秒。

$pipe

“p” if request was pipelined, “.” otherwise

$request_length

请求的长度,包括 request line,请求头和请求体

$request_time

处理请求所花时间,从接受到第一个 byte 到最后一个 byte 发送给客户端

$status

响应状态码

$time_iso8601

ISO 8601 标准格式的当地时间

$time_local

普通格式的当地时间

$http_referer

http referer,从这里可以看到请求从哪个域名发出

$http_user_agent

客户端 user agent

默认的日志格式 combined

log_format combined '$remote_addr - $remote_user [$time_local] '
                '"$request" $status $body_bytes_sent '
                '"$http_referer" "$http_user_agent"';

举个例子,打印出来的日志会长这样:

101.33.122.22 - - [19/Nov/2019:00:44:17 +0800] "POST /api/path HTTP/1.1" 200 2 "-" "Mozilla/4.0"

vscode 配置

vscode 配置

Settings Sync

如果有多台工作机器,可以利用 Settings Sync 来同步 vscode 的配置。它可以同步所有插件配置。要注意的是,自动上传和下载都是默认关闭,需要手动开启。

ESLint

语法检查是代码质量的保证之一。ESLint 是比较推荐的 JS 语法检查插件,这个插件依赖 npm eslint 模块,它首先会检查项目中是否安装了 eslint,然后检查全局是否安装了 eslint。所以如果你有使用多个 node 版本,而且并不是每个版本中都安装了 eslint,当你切换没有安装 eslint 的版本,项目中也没有安装 eslint,ESLint 就会失效。

Code Spell Checker

语法检查是不够的,拼写检查可以帮助我们发现一些难以察觉的错误。

Node.js Modules Intellisense

这个插件可以在 require 或者 import 语句中提示 Node.js 模块。

Path Intellisense

提示文件路径。个人认为,vscode 的模块和文件提示触发条件都不够人性化,比如,模块提示需要输入引号才能触发,但删除模块名直至剩下引号则不能触发。

配置 AWS AMI 实例笔记

配置 CentOS 7 实例笔记

下文假设你拿到的是一个刚刚启动的 CentOS 7 实例,然后你为以下目的进行配置:

  • 提高日常运维体验
  • 搭建 nodejs 服务运行环境

安装清单:

  • git
  • oh-my-zsh
  • autojump
  • autosuggestion
  • nginx
  • nodejs
  • yarn

安装 git

sudo yum -y install git

安装 oh-my-zsh

安装 zsh

yum -y install zsh

设置 zsh 为默认 shell

chsh -s /bin/zsh

安装 oh-my-zsh

sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"

安装 autojump

先 clone 仓库

git clone git://github.com/wting/autojump.git

然后到项目根目录执行:

cd autojump

./install.py

安装 autosuggestion

先 clone 仓库到 oh my zsh 的插件目录

git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions

然后在 .zshrc 文件中添加插件

plugins=(zsh-autosuggestions)

安装 nginx

添加 nginx 源

vi /etc/yum.repos.d/nginx.repo

然后输入以下内容:

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/mainline/rhel/7/$basearch/
gpgcheck=0
enabled=1

保存以后执行:

sudo yum install -y nginx

默认情况下,安装成功后,配置文件的路径是 /etc/nginx/nginx.conf

然后可以通过以下命令管理 nginx

# 启动
systemctl start nginx

# 停止
systemctl stop nginx

安装 nodejs 和 yarn

安装 nodejs

# 先添加源。注意 setup_10.x,这里安装的是 10.x 版本
curl --silent --location https://rpm.nodesource.com/setup_10.x | sudo bash -

# 使用 yum 安装
sudo yum install nodejs

安装 yarn

# 添加源
curl --silent --location https://dl.yarnpkg.com/rpm/yarn.repo | sudo tee /etc/yum.repos.d/yarn.repo
sudo rpm --import https://dl.yarnpkg.com/rpm/pubkey.gpg

# 使用 yum 安装
sudo yum install yarn

参考文档

使用 CSS 实现自适应的正方形

使用 CSS 实现自适应的正方形

在移动端页面中,我们经常要实现一个自适应的正方形 banner。以下是几种实现方式,可以根据项目的实际情况选取合适的方式。

页面结构通常为:

<div class="container">
    <div class="banner"></div>
</div>

利用 vw 单位

这种办法是推荐使用的,毕竟是移动端,几乎不存在 vw 单位的兼容性问题。

.container {
    width: 100%;
    max-width: 32rem;
}
.banner {
    width: 100%;
    height: 100vw;
}

利用 padding-bottom

这里有一个知识点,按照规定,margin, padding 的百分比数值是相对 父元素宽度 的宽度计算的。由此可以发现只需将元素垂直方向的一个 padding 值设定为与 width 相同的百分比就可以制作出自适应正方形了。

.banner {
    width: 100%;
    padding-bottom: 100%;
}

利用伪元素的 margin(padding)-top

.banner {
    width: 100%;
    overflow: hidden;
}
.banner:after {
    content: '';
    display: block;
    margin-top: 100%;
}

值得注意的是,overflow: hidden; 是为了解决容器与伪元素在垂直方向发生了外边距折叠的问题。

用第二和第三种方法实现时,显然 .banner 中的内容会使高度溢出。所以通常我们都会将内容放到独立的内容块中,利用绝对定位消除空间占用。

javascript 正则笔记

JS RegExp

量词

{n}: 某个字符刚好出现 n 次,比如说匹配一个八位数字的字符串,我们可以这样写,/^\d{8}$/

{n,}:某个字符至少出现 n 次,比如说匹配一个开标签,简单情况下可以这样写,/<\w{1,}>/

{n, m}:某个字符至少出现 n 次,至多出现 m 次,比如说匹配6到20位的数字和字母组成的密码,我们可以这样写,/[A-z0-9]{6,20}/

简单量词

一些特殊的字符出现次数可以用以下特殊符号来描述,它们是简单量词。

  • ?:出现 0 次或者 1 次,等价于 {0,1}
  • +:至少出现一次,相当于 {1,}
  • *:出现 0 次或者多次,即任意次,等价于 {0,}

量词的匹配模式

以上提到的简单量词遵循贪婪匹配模式,即尽可能多地匹配字符。在贪婪模式下,表达式会试图匹配整个字符串,如果发现不匹配,就会去掉最后一个字符,然后在进行比较,直到字符串长度为 0 或者匹配成功。

与之相反的是惰性匹配模式,即尽可能少地匹配字符。如果在任何量词 *+?{} 的后面加上 ?,将会使量词变为惰性模式。在惰性模式下,表达式会先匹配字符串的第一个字符,如果发现不匹配就加上第二个字符一起匹配,直到最后一个字符或者匹配成功。如果第一个字符就匹配成功了,那么就直接停止往下匹配了。

举个例子,对 '123abc' 应用 /\d+/ 将会返回 '123',应用 /\d+?/ 则返回 '1'

String.prototype.match 方法

String.prototype.match 用于在字符串中检索指定的值,参数是一个正则表达式对象,如果传入一个非正则表达式对象,则会隐式地使用 new RegExp(obj) 将其转换为正则表达式对象。

返回值:如果在字符串没有找到和参数匹配的内容,String.prototype.match 返回 null,否则返回一个数组,数组内容和参数类型有很大关系,以下举例说明。

当参数是字符串:

var str = "you've got a new message: new topic"
console.log(str.match('new')) // ["new", index: 13, input: "you've got a new message: new topic"]
console.log(str.match('old')) // null

数组第一个元素存放的是匹配文本。此外,数组包含了两个对象属性,index 属性声明的是匹配文本的起始字符在原字符串中的位置,input 属性声明的是对原字符串的引用。

需要注意的是,原字符串中 new 出现了两次,但只匹配到了第一个,因为当参数是字符串时,match 方法只执行一次匹配。

如果参数是正则表达式,那么要分两种情况:regexp 是否有 g 标志。如果没有 g 标志,那么 match 方法只执行一次匹配。没有找到任何匹配的文本,返回 null,否则返回一个数组,举个例子:

var str = '1 plus 2 equal 3',
	reg = /\d+/
console.log(str.match(reg)) // ["1", index: 0, input: "1 plus 2 equal 3"]

可以看到这种情况下返回值和当参数是字符串时是一样的,数组第一个元素存放的是匹配文本,另外包含了两个对象属性,index 属性声明的是匹配文本的起始字符在原字符串中的位置,input 属性声明的是对原字符串的引用。

如果正则表达式中有子表达式,数组会包含更多元素,第一个元素和两个对象属性不变,其余元素存放与子表达式匹配的文本:

var str = "<h2>",
	reg = /<(.+)>/
console.log(str.match(reg)) // ["<h2>", "h2", index: 0, input: "<a>"]

可以看到数组中多了第二个元素 'h2''h2' 正好与 .+ 匹配。

如果 regexp 具有标志 g,则 match 方法将执行全局检索,找到所有匹配子字符串。若没有找到任何匹配的子串,则返回 null。如果找到了一个或多个匹配子串,则返回一个数组。不过全局匹配返回的数组的内容与前者大不相同,它的数组元素中存放的是所有的匹配子串,而且也没有 index 属性或 input 属性。

var str = '1 plus 2 equal 3',
	reg = /\d+/g
console.log(str.match(reg)) // ["1", "2", "3"]

参考:W3School - JavaScript match() 方法
参考:MDN - JavaScript match() 方法

RegExp.prototype.exec 方法

RegExp.prototype.exec 也用于检索字符串的匹配文本,如果存在匹配文本则返回一个数组,否则返回 null。

如果正则表达式没有 g 标志,此时 exec 方法的行为和 String.prototype.match 方法是一致的。

如果正则表达式有 g 标志,那么 exec 的行为要复杂一点。它会在表达式的 lastIndex 属性(初始值为 0)指定的字符处开始检索字符串。当它找到了与表达式相匹配的文本时,在匹配后,它将把表达式的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。这就是说,我们可以通过反复调用 exec 方法来遍历字符串中的所有匹配文本。当再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。我们可以这么说,在循环中反复地调用 exec 方法是唯一一种获得全局模式的完整模式匹配信息的方法。

var str = '<p>hello</p><article>world</article>'
var reg = /<(.|\s)*?>/g

var res = [], arr
while (arr = reg.exec(str)) {
    res.push(arr)
}
console.log(res) 

常用正则

取出下面标签中的文本

<p id='test'>
	<a href='http://www.example.com/'>blog</a>
	by <em>zhushijie</em>
</p>

var reg = /<(.|\s)*?>/g,
	node = document.getElementById('test')

node.outerHTML.replace(reg, '')

['foo']['bar'] -> .foo.bar

var reg = /\[['"]([^'^"]+)['"]\]/g,
	str1 = "['foo']['bar']",
	str2 = '["foo"]["bar"]'

str1.replace(reg, '.$1') // .foo.bar
str2.replace(reg, '.$1') // .foo.bar

匹配中文

var reg = /[\u4e00-\u9fa5]/

reg.test('中') // true

前端性能优化(一):常见优化方向

前端性能优化(一):常见优化方向

为了提高打开网站的速度,我们通常会从以下几个方面进行优化:

  • 减少请求数量
  • 减少请求带宽
  • 提高请求响应速度
  • 利用缓存
  • 优化页面结构

减少请求数量

减少请求数量可能是最为大家熟知的优化方向之一,常见的手段有:

  • 合并 js 和 css 文件
  • 使用雪碧图或者 svg
  • 拆分初始化负载

合并文件、使用雪碧图算是老生常谈了,这里不展开说,值得提的是拆分初始化负载。拆分初始化负载的目标是将页面一开始加载时不需要执行的资源从所有资源中分离出来,等到需要的时候再加载。举个例子,很多人打开购物网站通常都是直接搜索然后进入搜索结果页,首页首屏以下的内容几乎不会翻看,所以下面的组件依赖的 js 就可以在用户滚屏时再进行加载。

<html>
<head>
    <title>page</title>    
</head>
<body>
    <div class="content"></div>
    <script>
        document.onscroll = function () {
            getJS('a.js')
        }

        function getJS (url) {
            var script = document.createElement('script')
            script.src = url
            document.appendChild(script)
        }
    </script>
</body>
</html>

这段代码只是简单演示了动态加载一个 js 文件的情况,如果需要加载多个 js,则需要做更多的工作来确保插入顺序和执行顺序符合期望。

减少请求带宽

常用手段:

  • 开启 GZip
  • 压缩 js 和 css 文件
  • 图像优化

图像优化不仅仅是进行压缩或者 PNG 转 JPEG,更重要的是要根据需求来采用不同的图片尺寸。最典型的案例是信息流中的图片,由于大部分的配图用户都不会点开去查看大图,如果返回全部完整尺寸的图,将会造成巨大的流量浪费,页面性能也会受影响。所以合理的做法是,缩略图采用高度压缩甚至裁剪过的小图来展示,当用户点击缩略图时再去请求完整的图片。

提高响应速度

使用 CDN。

利用缓存

为了利用缓存,我们通过添加 Expires 头来实现永久缓存,这样可以极大提高第二次及以后打开网站的速度。需要注意的是,既然利用了缓存,那么在发布更新时就要想办法对抗缓存,同时又不破坏网站的可用性。为了对抗缓存,可以在打包资源时在文件名后面添加 MD5 戳。不推荐使用时间戳的方式来使用缓存失效,因为文件名没有改变,新发布的资源会覆盖就得同名资源文件,这时如果页面还没更新,那么旧的页面就会加载新的资源,可能导致页面出错。所以最好采用非覆盖的方式来发布更新,先发布资源,再发布页面。

<script  src="a-felj52as23.js"></script>

优化页面结构

将 css 文件放在顶部,将 js 文件放在底部。

使用 webpack(一):基础

使用 webpack(一):基础

使用 webpack 来对项目进行打包,有利于更好地组织代码,也有利于开发过程中的调试。不过个人感觉 webpack 配置起来还是比较麻烦的,所以我写下这篇文档记录自己的需求,和如何使用 webpack 来满足需求,方便自己回顾。

本文基于 [email protected] 版本。

基础

webpack 需要一个配置文件来告诉它该如何进行打包,在最简单的情境下,我们只需配置好入口文件和打包文件的输出路径。在项目根目录新建 webpack.config.js 如下:

const path = require('path')

module.exports = {
  entry: './src/main.js',
  
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}

这段配置代码的做的事情是:

  • entry:告诉 webpack 入口文件是 src 目录下的 main.js 文件
  • output:打包好的文件命名为 bundle.js 并输出的到 dist 目录下

然后在项目根目录下执行 webpack 命令,你会发现多了一个 dist 目录,里面是打包好的文件。

使用 ES6

借助 babel,我们可以在浏览器全面支持之前用上 ES6 的新特性。webpack 将所有文件(包括 CSS,图片等资源)都当做一个模块,所以为了在 webpack 中使用 babel,我们实际上是在 webpack 中配置一个模块加载器 babel-loader:

npm install --save-dev babel-core babel-loader babel-preset-es2015
module.exports = {
  entry: './src/main.js',
  
  output: { /* ... */ },
  
  module: {
    rules: [
      {
        test: /\.js$/,
        use: 'babel-loader'
      }
    ]
  }
}

这段配置代码的做的事情是:

  • module:webpack 中配置加载器的选项,即声明什么模块使用什么加载器。上面的配置即声明 js 模块(或者说文件)都使用 babel-loader

使用 Sass

前面提到 webpack 将所有文件(包括 CSS,图片等资源)都当做一个模块,所以我们配置一个 sass loader 即可:

npm install --save-dev style-loader css-loader sass-loader node-sass
module.export = {
  ...
  module: {
    rules: [
      {
        test: /\.scss$/,
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader' },
          { loader: 'sass-loader' }
        ]
      }
    ]
  }
}

加载 CSS 需要 css-loader 和 style-loader,他们做两件不同的事情,css-loader会遍历 CSS 文件,然后找到 @importurl() 表达式然后处理他们,style-loader 会把原来的 CSS 代码插入页面中的一个 style 标签中。sass-loader 会将 sass 编译成 CSS。注意,scss 文件流经这三个加载器的顺序是 sass -> css -> style。

使用 webpack-dev-server

webpack-dev-server 的 hot module reload 属性使我们可以在修改代码后不用手动刷新浏览器也可以看到更新。webpack-dev-server 在配置文件中有一个专门的 devServer 属性用于设置相关选项:

npm install --save-dev webpack-dev-server
module.export = {
  ...
  devServer: {
    host: '127.0.0.1',
    port: 8937
  }
}

然后可以在命令行中执行:

webpack-dev-server -d --inline --hot --open

说一下命令中的各个选项:

  • -d:等于 NODE_ENV=development
  • --inline:区别于 iframe 模式,在 iframe 模式下,页面会被嵌套在一个 iframe 标签中,iframe 上会有一个 notification bar 提示页面状态;在 inline 模式下在直接在浏览器中渲染页面
  • --hot:使用 hot module reload 特性
  • --open:在浏览器中打开 URL

Linux 系统中查看磁盘信息的命令

在日常运维中,经常需要查看机器磁盘的信息,或者文件夹和文件的大小,以便进行文件清理,为程序运行、发布和备份工作留出空间。我现在整理一下相关的命令,方便查阅。

df

df 用于显示文件系统上已使用和剩余的磁盘空间。语法:

df [option]... [file]...

不传参数时,df 会显示所有挂载的文件系统的使用和剩余空间,否则显示包含参数 file 的文件系统的信息。

磁盘空间的显示单位默认是 1024 bytes,即 1KB,但可以通过 --block-size 参数指定。不足一单位时向上取整。

选项:

‘-a’
’--all‘
列出所有的文件系统

-B size
--block-size=size
打印磁盘空间的单位(参考 Block size),比如 -BG 打印的单位是 1,073,741,824 bytes

-h
--human-readable
给磁盘空间大小数值加上单位。取整的单位是 1024,不是1000。这个选项等价于 --block-size=human-readable。 如果你更倾向于使用 1000 作为取整单位的话,可以使用 --si 选项。

-H
等价于 --si

-i
--inodes
显示 inode 使用信息。inode,index node,包含文件的 owner,权限,时间戳和在磁盘上的位置等信息。

-k
以 1024-byte 为单位打印磁盘空间,会覆盖掉默认单位。等价于 --block-size=1K

-l
--local
只显示本地文件系统。默认情况下,远程文件系统也会显示。

--no-sync
在执行 df 命令前不要调用 sync 指令。这个可以使得 df 在挂载了很多磁盘的系统上运行得较快。这个选项为预设值。

关于 sync 指令

Linux 系统为了提高写入效率,会先将数据写到 buffer 中。所以在写磁盘时并不是立即将数据写到磁盘中,写入文件的时候磁盘的可用空间并不是立即变化。

sync 命令用来 flush 文件系统 buffer,这样数据才会真正的写到磁盘中。Linux 系统会周期性地去调用 sync 指令来写入数据。

--output
--output[=field_list]
使用 field_list 定义的格式来输出信息,否则输出所有的列。列的顺序入下面的列表所示。

--output 这几个选项 -i-P-T 是互斥的,同时使用的话会提示如下:

$ df -i --output=iuse

df: options -i and --output are mutually exclusive
Try 'df --help' for more information.

field_list field 之间用逗号,分隔,可以根据需要控制 field 的顺序,每个 field 只能指定一次。有效的 field name 如下:

  • source:挂载点的来源,通常是一台设备
  • fstype:文件系统类型
  • itotal:inodes 总数
  • iused:已使用的 inode 数量
  • iavail:可用的 inode 数量
  • ipcent:已使用的 inode 所占百分比
  • size:磁盘空间
  • used:已使用磁盘空间
  • avail:可用磁盘空间
  • pcent:已使用磁盘空间所占百分比
  • file:指定的文件名
  • target:挂载点

field_list 可以分开指定:

$ df --out=target --output=pcent,ipcent

-P
--portability
使用 POSIX 的输出格式。基本上就是默认的格式了,除了有以下几点不同:

  1. 每个文件系统的信息都严格打印在一行;一个挂载设备不会单独放在一行。这意味着,如果挂载设备名超过20个字符长(比如,一些网络挂载),列就会对不齐。
  2. 某些 field name 会被修改以符合 POSIX 标准
  3. 默认的输出单位不会受到 DF_BLOCK_SIZEBLOCK_SIZEBLOCKSIZE 环境变量的影响,但是还是会受到 POSIXLY_CORRECT 的影响:如果 POSIXLY_CORRECT 为 true,那么默认单位是 512,否则是 1024。

关于 POSIX

POSIX,Portable Operation System Interface,即操作系统接口标准,Unix 和 Linux,包括 macOS 都遵循这个标准。

很多语言(Go、Java、Python、Ruby等)都是跨平台的,我们在使用这些语言时很少去考虑系统调用的兼容性。实际上这是因为 POSIX 提供了这些语言上跨平台的语义。

--si
使用 1000 为单位,而不是 1024。

--sync
在执行 df 命令前强制调用 sync 指令。

--total
打印每一项值的总和,可以用来统计所有的磁盘空间。

-t fstype
--type=fstype
指定输出特定类型的文件系统。

-T
--print-type
打印出文件系统的类型。

-x fstype
--exclude-type=fstype
排除输出特定类型的文件系统。默认情况下,所以类型的文件系统都会打印出来。

-v
忽略;只是为了兼容常见选项。

du

统计文件所占用的磁盘空间,如果是文件夹,则会地递归列出所有文件。语法:

du [OPTION]... [FILE]...

# or
du [OPTION]... --files0-from=F

选项:

-0
--null
NUL 符号结束一行,而不是换行符。可以理解为输出信息时不换行。

-a
--all
输出所有文件的大小,不仅仅是目录。

--apparent-size
打印 apprent size。apparent size 是指文件中实际内容的大小,通常比所占用的磁盘空间大小要小,但也可能更大,比如,文件有内部碎片。

-B size
--block-size=size
打印磁盘空间的单位(参考 Block size),比如 -BG 打印的单位是 1,073,741,824 bytes

-b
--bytes
等价于 --apparent-size --block-size=1

-c
--total
打印每一项值的总和,可以用来统计所有文件的大小

-D
--dereference-args
显示指定符号链接的源文件大小

-d N
--max-depth=N
最多递归多少层文件夹。N0 是等价于 --summarize

--files0-from=F
TODO

-H
等价于 --dereference-args

-h
--human-readable
在输出的数值后面加上单位(以提高可读性)

--inodes
列出 inode 的使用情况,而不是磁盘空间

-k
等价于 --block-size=1K

-L symbolic_link
--dereference=symbolic_link
显示选项中所指定符号链接的源文件大小

-l
--count-links
重复计算硬链接的文件

-m
等价于 --block-size=1M

-P
--no-dereference
不显示符号链接的源文件大小,此为预设值

-S
--separate-dirs
不要统计子目录

--si
-h 类似,但使用 1000 作为幂,不是 1024

-t size
--threshold=size
不统计小于 size 的文件或者目录

--time[=word]
如果不指定参数,打印文件的最后修改时间。可选参数:atimeaccessusectimestatus

--time-style=style
输出的时间风格。可选参数:full-isolong-isoiso

--exclude-from=file
从指定目录中排除文件

--exclude=pattern
排除符合 pattern 的文件

-x
--one-file-system
以一开始处理时的文件系统为准,若遇上其它不同的文件系统目录则略过

fdisk

fdisk 是查看实体磁盘上的分区最常用的命令,也可对硬盘分区。这个命令可以列出所有分区和其他细节,比如文件系统类型,但是不会展示分区的大小。

$ fdisk -l

WARNING: GPT (GUID Partition Table) detected on '/dev/sda'! The util fdisk doesn't support GPT. Use GNU Parted.


Disk /dev/sda: 2398.2 GB, 2398202363904 bytes
255 heads, 63 sectors/track, 291564 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

   Device Boot      Start         End      Blocks   Id  System
/dev/sda1               1      267350  2147483647+  ee  GPT

Vue (0.10)中 vm 的实例化过程

Vue (0.10)中 vm 的实例化过程

本文所指的 Vue 版本为 0.10

总的来说,当我们实例化一个 Vue 对象时,构造函数会做以下三件事情:

  • 建立数据监听机制,提供订阅某项数据变化的能力
  • 编译模板,找出其中的指令及其依赖的数据源
  • 将指令依赖的数据订阅在对应的数据项上,并初始化视图

完成以上三项工作是一个复杂的过程,逐行逐段地分析源码不是本文的目的,我试图以图解结合必要的代码分析来大致描绘 Vue 内部的架构。

几个主要对象

实例化的工作涉及以下几个主要的对象:

  • vm:Vue 实例
  • compiler:源码中 Compiler() 构造函数的实例,实例化的工作主要由该函数来完成
  • Observer:建立数据监听机制
  • data:被监听的数据

以下是实例化后几个对象之间的相互关系,蓝色的箭头表示数据发生变化时,数据和事件在内部的传递关系。

vue vm 7

第一步,setupObserver()

回到开始,为了可以监听事件,我们为 compiler 设置一个 observerobserver 是源码中 Emitter() 构造函数的实例,拥有抛出事件和监听事件的能力。Emitter() 构造函数是一个典型的发布者/订阅者模式实现,具体实现可以看src/emitter.js 文件。

step 1

observer 监听了 get/set/mutate 事件,对于 set/mutate 事件来说,回调函数的工作是,如果某个 key 尚未存在对应的 binding 对象,则创建 binding 对象并利用其更新所有依赖该 key 的指令和计算属性。

// code from src/compiler.js
function onSet (key, val, mutation) {
    observer.emit('change:' + key, val, mutation)
    check(key)
    bindings[key].update(val)
}

第二步,extend(vm, data)

data 中的数据复制到 vm 上,这就是为什么在 vmmethods 中可以通过 this 获取到 data 中的数据。个人认为这是个非常优雅的特性,因为这样获取数据的方式非常符合直觉,如果非得通过 this.$data.someKey 才能获取到就显得太啰嗦了。

step 2

第三步,observeData(data)

如何监听对象上属性的改变?Object.defineProperty 这个 API 可以帮到我们,为这个属性设置一个特殊的 getter/setter,然后在 setter 中触发一个函数就可以达到监听的效果。所以我们需要将 data 上的 key 转换成 getter/setter。这部分工作是由 Observer 对象来完成,除了进行转换,它还会抛出第一批 set 事件,set 事件会反过来创建绑定关系。同时,Observer 会为 compiler.observer 创建 proxy 来转发事件。

step 3.1

这里有必要说一下 binding 对象,因为它是 Vue 中绑定关系的体现。一般来说,data 中的 key 都会有一个对应的 binding 对象,所有依赖该 key 的指令和计算指令会登记到对象上,当 key 的值发生变化时,就调用 binding.update() 方法来更新视图。

BindingProto.update = function (value) {
    if (!this.isComputed || this.isFn) {
        this.value = value
    }
    if (this.dirs.length || this.subs.length) {
        var self = this
        bindingBatcher.push({
            id: this.id,
            execute: function () {
                if (!self.unbound) {
                    self._update()
                }
            }
        })
    }
}

step 3.2

第四步,compile(el, true)

这一步包含两部分,编译 DOM 和绑定指令。对于编译 DOM,主要任务是找出指令的类型和依赖的数据,比如 <input type='text' v-model='message'>,编译的结果是找到指令类型是 model,其依赖 message ,如果需要了解编译的细节,可以去阅读 src/compiler.js 中的 compile() 函数。指令的实现在 src/dirctives 目录下,每个指令都是一个独立的实现,拥有自己的绑定和更新方法。绑定过程中会第一次触发更新函数,从而实现视图的初始化。

step 4

// code from src/compiler.js
CompilerProto.bindDirective = function (directive, bindingOwner) {
    // some other code...

    var value = binding.val()
    // invoke bind hook if exists
    if (directive.bind) {
        directive.bind(value)
    }
    // set initial value
    directive.$update(value, true)
}

以上就是 Vue 中 vm 的实例化,当然这只是非常简化的过程,真正的实现要更加复杂精细,如果需要了解 Vue 的所有工作原理,还是要仔细地去看源码,本文只能提供一个大概的思路。

数组方法笔记

数组方法笔记

关于遍历方法的遍历元素范围:遍历的元素的范围在第一次调用 callback 时就已经确定了。在调用遍历方法后被添加到数组中的值不会被 callback 访问到。如果数组中存在且还未被访问到的元素被 callback 改变了,则其传递给 callback 的值是遍历方法访问到它那一刻的值。

工具类方法:

  • Array.from()
  • Array.isArray()
  • Array.of()

遍历方法:

  • Array.prototype.forEach
  • Array.prototype.map
  • Array.prototype.reduce
  • Array.prototype.reduceRight
  • Array.prototype.every
  • Array.prototype.some
  • Array.prototype.entries(ES6
  • Array.prototype.keys(ES6
  • Array.prototype.values(ES6
  • Array.prototype.filter

查找方法:

  • Array.prototype.find(ES6
  • Array.prototype.findIndex(ES6
  • Array.prototype.indexOf
  • Array.prototype.lastIndexOf
  • Array.prototype.includes(ES6

增删元素方法:

  • Array.prototype.pop
  • Array.prototype.push
  • Array.prototype.unshift
  • Array.prototype.shift
  • Array.prototype.splice

截取/拼接数组方法:

  • Array.prototype.concat
  • Array.prototype.slice

排序方法:

  • Array.prototype.reserve
  • Array.prototype.sort

其他:

  • Array.prototype.copyWithin(ES6
  • Array.prototype.fill(ES6
  • Array.prototype.join
  • Array.prototype.toLocalString
  • Array.prototype.toString

数组方法的参数和返回值

/***********************************
 * 工具方法
 ***********************************/

// from 方法可以将一个类数组对象或可遍历对象转换成真正的数组
function fromTest () {
    // 字符串对象既是类数组又是可迭代对象
    console.log(Array.from("foo"))                      // ["f", "o", "o"]

    // 使用 map 函数转换数组元素
    console.log(Array.from([1, 2, 3], x => x + x))      // [2, 4, 6]

    // 生成一个数字序列
    console.log(Array.from({length: 5}, (v, k) => k))    // [0, 1, 2, 3, 4]
}

// of 方法会将它的任意类型的多个参数放在一个数组里并返回
// Array.of() 和 Array 构造函数不同的是:
// 在处理数值类型的参数时,Array.of(42) 创建的数组只有一个元素,即 42, 
// 但 Array(42) 创建了42个元素,每个元素都是undefined。
function ofTest () {
    Array.of(1);         // [1]
    Array.of(1, 2, 3);   // [1, 2, 3]
    Array.of(undefined); // [undefined]
}

/***********************************
 * 遍历方法
 ***********************************/

// forEach 方法,对数组中每一个元素执行一次 callback,返回值是 undefined
// 不会改变原数组
var arr = ['a', 'b', 'c']
var obj = {
    a: 'foo',
    b: 'bar',
    c: 'log'
}
function forEachTest () {
    arr.forEach(function(ele, index, array) {
        console.log(this[ele])
    }, obj)
}

// map 方法返回数组中每一个元素调用 callback 产生的返回值组成的新数组
// 不会改变原数组
function mapTest () {
    var newArr = arr.map(function(ele, index, array) {
        return ele + '1'
    })
    console.log(newArr)
}

// every 方法测试数组中每一个元素是否通过指定函数的测试
// 如果全部通过,则返回 true,否则返回 false
// 不会改变原数组
function everyTest () {
    arr.every(function (ele, index, array) {
        return ele.length > 1
    })
}

// some 方法测试数组是否有元素通过指定函数的测试
// 如果找到元素通过测试,立即返回 true,否则返回 false
// 不会改变原数组
function someTest () {
    arr.some(function (ele, index, array) {
        return ele.length > 1
    })
}

// entries 方法返回一个 Array Iterator 对象,该对象包含数组中每一个索引的键值对。
function entriesTest () {
    var iterator = arr.entries(),
        item

    while (item = iterator.next().value) {
        console.log(item)
    }
}

// keys 方法返回一个数组索引的迭代器
function keysTest () {
    var iterator = arr.keys(),
        item

    while ((item = iterator.next().value) != undefined) {
        console.log(item)
    }
}

// values 方法返回一个新的 Array Iterator 对象,该对象包含数组每个索引的值
function valuesTest () {
    var  iterator = arr.values() // 当前 chrome 版本未支持该方法
    for (let item of iterator) {
        console.log(item)
    }
}

// reduce 方法接收一个函数作为累加器(accumulator),
// 数组中的每个值(从左到右)开始合并,最终为一个值
// 两个参数,callback 和 initValue
// 其中 callback 又有四个参数:
// - preValue, 上一次调用返回的值
// - curValue,当前正在处理的元素
// - index,当前元素的索引
// - array,原数组
// reduce 方法和 reduceRight 的区别是,reduceRight 从右到左遍历
function reduceTest () {
    var arr = [2, [3, 4], 1, 5, [7, 8, 10], 9]
    var res = arr.reduce(function (pv, cv, i, array) {
        if (Array.isArray(cv)) pv = pv.concat(cv)
        else pv.push(cv)

        return pv
    }, [])
    console.log(res)
}

// filter 方法使用指定的函数测试所有元素,并创建一个包含所有通过测试的元素的新数组
// filter 不会改变原数组。
function filterTest () {
    var arr = [12, 5, 8, 130, 44]
    var res = arr.filter(function (ele, index, array) {
        return ele > 10
    })
    console.log(res) // [12, 130, 44]
}

/***********************************
 * 查找方法
 ***********************************/

// find 方法,如果数组中某个元素满足测试条件,find 方法就会返回那个元素的值,
// 如果没有满足条件的元素,则返回 undefined
// 区别:findIndex 方法返回的是满足条件的元素的索引,而非它的值
function findTest () {
    var inventory = [
        {name: 'apples', quantity: 2},
        {name: 'bananas', quantity: 0},
        {name: 'cherries', quantity: 5}
    ];

    function findCherries(fruit) {
        return fruit.name === 'cherries';
    }

    console.log(inventory.find(findCherries)); // { name: 'cherries', quantity: 5 }
    console.log(inventory.findIndex(findCherries)); // 2
}

// indexOf 方法返回给定元素能找在数组中找到的第一个索引值,否则返回-1
// 可以指定开始查找的位置,如果该索引值大于或等于数组长度,意味着不会在数组里查找,返回-1
// lastIndexOf 则返回在数组中找到的最后一个索引值
function indexOfTest () {
    var array = [2, 5, 9];
    array.indexOf(2);     // 0
    array.indexOf(7);     // -1
    array.indexOf(9, 2);  // 2
    array.indexOf(2, -1); // -1
    array.indexOf(2, -3); // 0
}

// includes 方法用来判断当前数组是否包含某指定的值,如果是,则返回 true,否则返回 false
function includesTest () {
    [1, 2, 3].includes(2);     // true
    [1, 2, 3].includes(4);     // false
    [1, 2, 3].includes(3, 3);  // false
    [1, 2, 3].includes(3, -1); // true
    [1, 2, NaN].includes(NaN); // true
}

/***********************************
 * 增删元素方法
 ***********************************/

// pop 方法删除一个数组中的最后一个元素,并返回该元素
// pop 被有意设计成具有通用性,
// 该方法可以通过 call 或 apply 方法应用于一个类数组(array-like)对象上。
// pop 会修改原数组
function popTest () {
    var myFish = ["angel", "clown", "mandarin", "surgeon"];

    console.log("myFish before: " + myFish);

    var popped = myFish.pop();

    console.log("myFish after: " + myFish);
    console.log("Removed this element: " + popped);
}

// push 方法添加一个或多个元素到数组的末尾,并返回数组新的长度(length 属性值)
// push 会修改原数组
function pushTest () {
    var sports = ["soccer", "baseball"];
    var total = sports.push("football", "swimming");

    console.log(sports); // ["soccer", "baseball", "football", "swimming"]
    console.log(total);  // 4
}

// unshift 方法在数组的开头添加一个或者多个元素,并返回数组新的 length 值
// unshift 会修改原数组
function unshiftTest () {
    var arr = [1, 2];

    arr.unshift(0); //result of call is 3, the new array length
    //arr is [0, 1, 2]

    arr.unshift(-2, -1); // = 5
    //arr is [-2, -1, 0, 1, 2]

    arr.unshift( [-3] );
    //arr is [[-3], -2, -1, 0, 1, 2]
}

// shift 方法删除数组的 第一个 元素,并返回这个元素
// shift 会修改原数组
function shiftTest () {
    var myFish = ['angel', 'clown', 'mandarin', 'surgeon'];

    console.log('调用 shift 之前: ' + myFish);
    // "调用 shift 之前: angel,clown,mandarin,surgeon"

    var shifted = myFish.shift();

    console.log('调用 shift 之后: ' + myFish);
    // "调用 shift 之后: clown,mandarin,surgeon"

    console.log('被删除的元素: ' + shifted);
    // "被删除的元素: angel"
}

// splice 方法用新元素替换旧元素,以此修改数组的内容
// array.splice(start, deleteCount[, item1[, item2[, ...]]])
// start: 从数组的哪一位开始修改内容。
// 如果超出了数组的长度,则从数组末尾开始添加内容;如果是负值,则表示从数组末位开始的第几位。
// deleteCount: 整数,表示要移除的数组元素的个数。
// 如果 deleteCount 是 0,则不移除元素。这种情况下,至少应添加一个新元素。
// 如果 deleteCount 大于 start 之后的元素的总数,则从 start 后面的元素都将被删除(含第 start 位)
// itemN: 要添加进数组的元素。如果不指定,则 splice() 只删除数组元素
function spliceText () {
    var myFish = ["angel", "clown", "mandarin", "surgeon"];

    //从第 2 位开始删除 0 个元素,插入 "drum"
    var removed = myFish.splice(2, 0, "drum");
    //运算后的 myFish:["angel", "clown", "drum", "mandarin", "surgeon"]
    //被删除元素数组:[],没有元素被删除

    //从第 3 位开始删除 1 个元素
    removed = myFish.splice(3, 1);
    //运算后的myFish:["angel", "clown", "drum", "surgeon"]
    //被删除元素数组:["mandarin"]

    //从第 2 位开始删除 1 个元素,然后插入 "trumpet"
    removed = myFish.splice(2, 1, "trumpet");
    //运算后的myFish: ["angel", "clown", "trumpet", "surgeon"]
    //被删除元素数组:["drum"]

    //从第 0 位开始删除 2 个元素,然后插入 "parrot", "anemone" 和 "blue"
    removed = myFish.splice(0, 2, "parrot", "anemone", "blue");
    //运算后的myFish:["parrot", "anemone", "blue", "trumpet", "surgeon"]
    //被删除元素的数组:["angel", "clown"]

    //从第 3 位开始删除 2 个元素
    removed = myFish.splice(3, Number.MAX_VALUE);
    //运算后的myFish: ["parrot", "anemone", "blue"]
    //被删除元素的数组:["trumpet", "surgeon"]
}

/***********************************
 * 截取/拼接数组方法
 ***********************************/

// concat 方法将传入的数组或非数组值与原数组合并,组成一个新的数组并返回。可以传入多个数组
// 不会修改原数组
function concatTest () {
    var alpha = ["a", "b", "c"],
        numeric = [1, 2, 3]

    // 组成新数组 ["a", "b", "c", 1, 2, 3]; 原数组 alpha 和 numeric 未被修改
    var alphaNumeric = alpha.concat(numeric)
}

// slice 方法会浅复制(shallow copy)数组的一部分到一个新的数组,并返回这个新数组
// 不会修改原数组
// slice(begin, end) // [begin, end)
function sliceTest () {
    var arr = ['apple', 'orange', 'banana', 'mango']
    console.log(arr.slice(1, 3)) // ['orange', 'banana']
}

/***********************************
 * 排序方法
 ***********************************/

// reserve 方法颠倒数组中元素的位置。第一个元素会成为最后一个,最后一个会成为第一个
// sort 法对数组的元素做原地的排序,并返回这个数组。 sort 排序可能是不稳定的。
// 默认按照字符串的Unicode码位点(code point)排序
// compareFunction: 可选。用来指定按某种顺序进行排列的函数。
// 如果省略,元素按照转换为的字符串的诸个字符的Unicode位点进行排序。
function sortTest1 () {
    var fruit = ['cherries', 'apples', 'bananas'];
    fruit.sort(); // ['apples', 'bananas', 'cherries']

    var scores = [1, 10, 2, 21];
    scores.sort(); // [1, 10, 2, 21]
    // Watch out that 10 comes before 2,
    // because '10' comes before '2' in Unicode code point order.

    var things = ['word', 'Word', '1 Word', '2 Words'];
    things.sort(); // ['1 Word', '2 Words', 'Word', 'word']
    // In Unicode, numbers come before upper case letters,
    // which come before lower case letters.
}

// 如果指明了 compareFunction ,那么数组会按照调用该函数的返回值排序。
// 记 a 和 b 是两个将要被比较的元素:
// 如果 compareFunction(a, b) 小于 0 ,那么 a 会被排列到 b 之前;
// 如果 compareFunction(a, b) 等于 0 , a 和 b 的相对位置不变。
// 如果 compareFunction(a, b) 大于 0 , b 会被排列到 a 之前。
// compareFunction(a, b) 必须总是对相同的输入返回相同的比较结果,否则排序的结果将是不确定的。
// 希望比较数字而非字符串,比较函数可以简单的以 a 减 b,如下的函数将会将数组升序排列

function sortTest2 () {
    var numbers = [4, 2, 5, 1, 3];
    numbers.sort(function(a, b) {
        return a - b;
    });
    console.log(numbers); // [1, 2, 3, 4, 5]

    // 对象可以按照某个属性排序:
    var items = [
        { name: 'Edward', value: 21 },
        { name: 'Sharpe', value: 37 },
        { name: 'And', value: 45 },
        { name: 'The', value: -12 },
        { name: 'Magnetic' },
        { name: 'Zeros', value: 37 }
    ];

    items.sort(function (a, b) {
        if (a.value > b.value) {
            return 1;
        }
        if (a.value < b.value) {
            return -1;
        }
        // a 必须等于 b
        return 0;
    });
}

/***********************************
 * 其他
 ***********************************/

// copyWithin 方法浅拷贝数组的部分元素到同一数组的不同位置,且不改变数组的大小,返回该数组
// arr.copyWithin(target[, start[, end]])
// target: 0 为基底的索引,复制序列到该位置。
// 如果是负数,target 将从末尾开始计算。如果 target 大于等于 arr.length,将会不发生拷贝。
// 如果 target 在 start 之后,复制的序列将被修改以符合 arr.length。
// start: 0 为基底的索引,开始复制元素的起始位置。
// 如果是负数,start 将从末尾开始计算。如果 start 被忽略,copyWithin 将会从0开始复制。
// end: 0 为基底的索引,开始复制元素的结束位置。
// copyWithin 将会拷贝到该位置,但不包括 end 这个位置的元素。
// 如果是负数, end 将从末尾开始计算。如果 end 被忽略,copyWithin 将会复制到 arr.length。
function copyWithin () {
    [1, 2, 3, 4, 5].copyWithin(-2);
    // [1, 2, 3, 1, 2]

    [1, 2, 3, 4, 5].copyWithin(0, 3);
    // [4, 5, 3, 4, 5]

    [1, 2, 3, 4, 5].copyWithin(0, 3, 4);
    // [4, 2, 3, 4, 5]

    [1, 2, 3, 4, 5].copyWithin(0, -2, -1);
    // [4, 2, 3, 4, 5]
}

// 使用 fill() 方法,可以将一个数组中指定区间的所有元素的值, 都替换成或者说填充成为某个固定的值
// fill 方法接受三个参数 value, start 以及 end. start 和 end 参数是可选的, 
// 其默认值分别为 0 和 this 对象的 length 属性值。
// 具体要填充的元素区间是 [start, end) , 一个半开半闭区间
function fillTest () {
    [1, 2, 3].fill(4)            // [4, 4, 4]
    [1, 2, 3].fill(4, 1)         // [1, 4, 4]
    [1, 2, 3].fill(4, 1, 2)      // [1, 4, 3]
    [1, 2, 3].fill(4, 1, 1)      // [1, 2, 3]
    [1, 2, 3].fill(4, -3, -2)    // [4, 2, 3]
    [1, 2, 3].fill(4, NaN, NaN)  // [1, 2, 3]
    Array(3).fill(4);            // [4, 4, 4]
    [].fill.call({length: 3}, 4) // {0: 4, 1: 4, 2: 4, length: 3}
}

// join,toLocalString 和 toString 比较简单,就不展开讲了

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.