Giter Club home page Giter Club logo

blog's People

Contributors

yangkean avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

blog's Issues

初识 parcel

前言

好新好新的轮子,又可以愉快地玩耍了。这篇文章我们简单粗暴些吧!

官网卖的瓜 - parcel 的优势

  • 打包速度炒鸡快 - 多核编译(使用 worker 并行构建),文件系统缓存(重构建会很快)
  • 打包所有资源 - 不需要插件即可打包 JS、HTML、CSS 文件及所有其它文件资源
  • 自动转换 - 代码会在需要时使用 Babel、PostCSS 和 PostHTML 自动转换
  • 0 配置代码分离 - 在你需要加载的时候使用 import() 语法加载包 (bundle) 即可
  • 模块热替换 (HMR) - 在开发期间,文件发生改变就会自动更新浏览器中相应的模块,无需配置
  • 友善的错误报告 - 代码高亮让你准确找到问题

它是如何工作的呢?

简言之,就是它很牛逼,跟市面上那些普(yao)通(yan)家(jian)伙(huo)不一样。

其他打包器都是基于 JavaScript 资源的,其他格式的都要转换为字符串添加到 JavaScript 上,parcel 就不一样了,它把资源树转换为包 (bundle) 树。它先用内置处理器处理各种资源,将它们各自转化为最终编译好的形式,形成资源树。

以下几段自己翻译和看别人的翻译都不是很清晰,遂附上原文

一旦资源树被构建好,资源会被放置在文件束树中。首先一个入口资源会被创建成一个文件束,然后动态的 import() 会被创建成子文件束 ,这引发了代码的拆分。(Once the asset tree has been constructed, the assets are placed into a bundle tree. A bundle is created for the entry asset, and child bundles are created for dynamic import()s, which cause code splitting to occur.)

当不同类型的文件资源被引入,兄弟文件束就会被创建。例如你在 JavaScript 中引入了 CSS 文件,那它会被放置在一个与 JavaScript 文件对应的兄弟文件束中。(Sibling bundles are created when assets of a different type are imported, for example if you imported a CSS file from JavaScript, it would be placed into a sibling bundle to the corresponding JavaScript.)

如果资源被多于一个文件束引用,它会被提升到文件束树中最近的公共祖先中,这样该资源就不会被多次打包。(If an asset is required in more than one bundle, it is hoisted up to the nearest common ancestor in the bundle tree so it is not included more than once.)

开始

$ yarn global add parcel-bundler

同一目录下准备 index.html 和 index.js:

<html>
<body>
  <script src="./index.js"></script>
</body>
</html>
console.log("hello world");

命令行中敲入:

$ parcel index.html

好了,最简单的打包完成。demo 放在 这里

后续继续暗中观察。

Reference

Pug 常用点归纳

概述

官方的瓜:健壮、优雅、功能多的高性能模板引擎。接下来,我按照我认为的理解较好的顺序总结了下它的特性,毕竟官网的顺序太官方了。:)

小览

它写起来,是这样的:

doctype html
html(lang="en")
  head
    title= pageTitle
    script(type='text/javascript').
      if (foo) bar(1 + 5)
  body
    h1 Pug - node template engine
    #container.col
      if youAreUsingPug
        p You are amazing
      else
        p Get on it!
      p.
        Pug is a terse and simple templating language with a
        strong focus on performance and powerful features.

经过编译生成的样子渲染之后的样子是这样的:

注意这个渲染结果是经过美化的 HTML,直接编译渲染是全部缩在一起而没有缩进的,就像压缩过的代码一样

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Pug</title>
    <script type="text/javascript">
      if (foo) bar(1 + 5)
    </script>
  </head>
  <body>
    <h1>Pug - node template engine</h1>
    <div id="container" class="col">
      <p>You are amazing</p>
      <p>Pug is a terse and simple templating language with a strong focus on performance and powerful features.</p>
    </div>
  </body>
</html>

下面讲一下比较常用的语法,显示 pug 代码,然后是编译渲染后的结果 (原始渲染结果,无美化):

标签

直接使用标签名表示标签,借助缩进 (统一风格即可) 来表示标签间的嵌套。

ul
  li Item A
  li Item B
  li Item C
<ul><li>Item A</li><li>Item B</li><li>Item C</li></ul>

纯文本

whitespace 控制

在讲纯文本时,为了避免误解和困惑,我们需要先了解在把 pug 文件渲染成 HTML 时 Pug 是怎么处理 whitespace 字符 的:

  1. 移除缩进和元素 (element) 间的 whitespace 字符:因而渲染好的 HTML 的闭标签会紧挨着下一个开标签
  2. 保留元素 (element) 内部的 whitespace 字符,包括:
    • 一行文本内部所有的 whitespace 字符
    • 一行末尾的 whitespace 字符
    • 纯文本块 (即连续的几行文本) 内部或者连续的管道文本行 (即下文用|开头的行) 之间的换行符
    • 一行中除了块缩进之外的开头的 whitespace 字符

就像上一小节的 Item A ,标签名一个空格之后接着的任何东西会被当做标签内的文本。纯文本中的任何代码或文字都将几乎不经过处理就输出到渲染好的 HTML 中。

p This is plain old <em>text</em> content.
<p>This is plain old <em>text</em> content.</p>

如果一行的开头是左尖括号(<)即原始 HTML,那么整行都会被当作纯文本。

<html>

body
  p Indenting the body tag here would make no difference.
  p HTML itself isn't whitespace-sensitive.

</html>
<html><body><p>Indenting the body tag here would make no difference.</p><p>HTML itself isn't whitespace-sensitive.</p></body></html>

在一行前面加一个管道符号(|)添加的也是纯文本。文本中可嵌入 HTML。

p
  | The pipe always goes at the beginning of its own line,
  | not counting indentation.
<p>The pipe always goes at the beginning of its own line,
not counting indentation.</p>

如果你需要在一个标签里写一个大的纯文本块,而且是在文本块里按照你想写的格式来写,你只需要在标签名后面紧接一个点 .,在标签有属性的时候,则是紧接在闭括号后面。在标签和点之间不要有空格。块内的纯文本内容必须缩进一层。

script.
  if (usingPug)
    console.log('you are awesome')
  else
    console.log('use pug')
<script>if (usingPug)
  console.log('you are awesome')
else
  console.log('use pug')</script>

代码

pug 中要嵌入 JavaScript 代码的话。可以用 - 开始一段不直接进行输出的代码。

- for (var x = 0; x < 3; x++)
  li item
<li>item</li><li>item</li><li>item</li>

= 开始一段带有输出的代码。且输出代码会被转义。

注意等号左侧不能有空格,否则等号连同后面的东西会变成标签内文本

p= 'This code is' + ' <escaped>!'
<p>This code is &lt;escaped&gt;!</p>

注释

带输出的注释,必须单独一行。

// just some paragraphs
p foo
p bar
<!-- just some paragraphs--><p>foo</p><p>bar</p>

不输出的注释。也必须单独一行。

//- will not output within markup
p foo
p bar
<p>foo</p><p>bar</p>

块注释。

body
  //-
    Comments for your template writers.
    Use as much text as you want.
  //
    Comments for your HTML readers.
    Use as much text as you want.
  p hello
<body><!--Comments for your HTML readers.
Use as much text as you want.--><p>hello</p></body>

属性

表示 HTML 中的属性。注意所有属性值默认都会被转义。

a(href='google.com') Google
|
|
a(class='button' href='google.com') Google
|
|
a(class='button', href='google.com') Google
<a href="google.com">Google</a>
<a class="button" href="google.com">Google</a>
<a class="button" href="google.com">Google</a>

想要使用变量,要先用-声明变量。此外,样式和类属性也很好写。再者,类可以使用 .classname 语法来定义,ID 可以使用 #idname 语法来定义,不适用标签名则默认是div

- var btnType = 'info'
- var btnSize = 'lg'
button(type='button' class='btn btn-' + btnType + ' btn-' + btnSize)
|
|
button(type='button' class=`btn btn-${btnType} btn-${btnSize}`)
|
|
input(type='checkbox' checked)
|
|
a(style={color: 'red', background: 'green'})
|
|
- var classes = ['foo', 'bar', 'baz']
a(class=classes)
|
|
//- the class attribute may also be repeated to merge arrays
a.bang(class=classes class=['bing'])
<button class="btn btn-info btn-lg" type="button"></button>
<button class="btn btn-info btn-lg" type="button"></button>
<input type="checkbox" checked="checked"/>
<a style="color:red;background:green;"></a>
<a class="foo bar baz"></a>
<a class="bang foo bar baz bing"></a>

分支条件

- var friends = 10
case friends
  when 0
    p you have no friends
  when 1
    p you have a friend
  default
    p you have #{friends} friends
<p>you have 10 friends</p>

条件

- var user = { description: 'foo bar baz' }
- var authorised = false
#user
  if user.description
    h2.green Description
    p.description= user.description
  else if authorised
    h2.blue Description
    p.description.
      User has no description,
      why not add one...
  else
    h2.red Description
    p.description User has no description
<div id="user"><h2 class="green">Description</h2><p class="description">foo bar baz</p></div>

Doctype

doctype html
<!DOCTYPE html>

Include

借助 include 我们可以在一段模板里插入其他文件内容。

//- index.pug
doctype html
html
  include includes/head.pug
  body
    h1 My Site
    p Welcome to my super lame site.
    include includes/foot.pug
//- includes/head.pug
head
  title My Site
  script(src='/javascripts/jquery.js')
  script(src='/javascripts/app.js')
//- includes/foot.pug
footer#footer
  p Copyright (c) foobar
<!DOCTYPE html><html><head><title>My Site</title><script src="/javascripts/jquery.js"></script><script src="/javascripts/app.js"></script></head><body><h1>My Site</h1><p>Welcome to my super lame site.</p><footer id="footer"><p>Copyright (c) foobar</p></footer></body></html>

如果 include 的不是 pug 模板文件,则会作为普通文本内容被导入,比如外部 CSS 和 JavaScript 文件。

嵌入

通过 #{} 把 JavaScript 的值嵌入文本中。

- var title = "On Dogs: Man's Best Friend";
- var author = "enlore";
- var theGreat = "<span>escape!</span>";

h1= title
p Written with love by #{author}
p This will be safe: #{theGreat}
<h1>On Dogs: Man's Best Friend</h1><p>Written with love by enlore</p><p>This will be safe: &lt;span&gt;escape!&lt;/span&gt;</p>

迭代

ul
  for val, index in ['zero', 'one', 'two']
    li= index + ': ' + val
|
|
ul
  for val, index in {1:'one',2:'two',3:'three'}
    li= index + ': ' + val
|
|
- var n = 0;
ul
  while n < 4
    li= n++
<ul><li>0: zero</li><li>1: one</li><li>2: two</li></ul>
<ul><li>1: one</li><li>2: two</li><li>3: three</li></ul>
<ul><li>0</li><li>1</li><li>2</li><li>3</li></ul>

Mixin

mixin 可以让你在 pug 中重复使用一整个代码块。

//- Declaration
mixin list
  ul
    li foo
    li bar
    li baz
//- Use
+list
|
|
+list
<ul><li>foo</li><li>bar</li><li>baz</li></ul>
<ul><li>foo</li><li>bar</li><li>baz</li></ul>

当然这个 mixin 也是可以传入参数。

模板继承

这是一个异常强大的功能,我们在父块中用block来声明在被继承时可被子块覆盖的块,继承时使用extends关键字声明要继承的 pug 文件。

//- layout.pug
html
  head
    - var title = 'yang'
    title My Site - #{title}
    block scripts
      script(src='/jquery.js')
  body
    block content
    block foot
      #footer
        p some footer content
//- page-a.pug
extends layout.pug

block scripts
  script(src='/jquery.js')
  script(src='/pets.js')

block content
  h1= title
  - var pets = ['cat', 'dog']
  for petName in pets
    include pet.pug
//- pet.pug
p= petName
<html><head><title>My Site - yang</title><script src="/jquery.js"></script><script src="/pets.js"></script></head><body><h1>yang</h1><p>cat</p><p>dog</p><div id="footer"><p>some footer content</p></div></body></html>

结尾

这里只是列举了常用的特性的常用方法,如果遇到了比较高级的需求,应该查看英文的官方文档。另外,我觉得官方文档美化 HTML 的输出其实是会误导人的,因为 pug 对于 whitespace 字符的处理是很微妙的 :),所以我还是选择了打印出原始的 HTML。

Reference

用 ArrayBuffer 来截取二进制数据中的字符

前言

大多数时候,用户选取文件后只需要把文件提交给后台,但是,如果需要在选取一个二进制文件后再根据该文件末尾表示的 2 字节短整型数字大小然后往前偏移该数值大小截取所需字符串,该怎么做呢?这些信息是二进制数据,与平常处理的字符串有很大不同。一种方法是,我们将该文件传给后台,后台处理完再发给前台显示。但自从 ArrayBuffer 获得了广泛的浏览器支持,在前端处理二进制数据便不再是一件难事了。ArrayBuffer 的应用场景更多是在网页游戏中的二进制数据交换,但是我们也可以用它来处理文件中的二进制数据。也许上面的场景听起来有点晕,我们一步步地来分解过程。

概述

在讲如何用 ArrayBuffer 操作二进制数据之前,我们需要明确一些与 ArrayBuffer 相关的知识。

我们通常使用 ArrayBuffer 得到系统分配的一段长度为 length 的可以存放二进制数据的连续的内存区域,但是要操作这段内存区域,还得借助 Int8Array、Uint8Array 等九种 TypedArray 的构造器函数来创建这段内存区域的类数组视图,然后就可以借助数组下标来访问内存中的二进制数据,TypedArray 中的 type 指的并不是 JavaScript 中的原生数据类型,而是视图的类型,比如 Int8Array 指的是类数组的每个元素是一个 8 位有符号整数。TypedArray 的类型是需要特别注意的,接下来的文章会告诉你这是为什么。

const arrayBuffer = new ArrayBuffer(10);
arrayBuffer.byteLength; // 10

const uInt8Array = new Uint8Array(arrayBuffer); 
uInt8Array; // Uint8Array(10) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

读取文件

在前端的应用场景中,我们通常会利用 type 为 file 的表单读取一个文件,然后在 onload 事件中获取到该文件对象并对文件进行操作。我们也采取这种做法,不过我们要将文件对象读取为 ArrayBuffer 对象,方便我们后面对字节数组的操作。

const file = document.querySelector('input[type="file"]');
const reader = new FileReader();

reader.addEventListener('load', (event) => {
  const buffer = event.target.result; // 我们得到了转化为 ArrayBuffer 对象的文件对象
});

reader.readAsArrayBuffer(file.files[0]);

截取文件对象中的字节

现在,我们得到了转化为 ArrayBuffer 对象的文件对象,接下来就是从对象中截取字节了。假定我们要截取文件对象的最后两个字节。

const uInt8Array = new Uint8Array(buffer);
const size = uInt8Array.length;

const lastTwoBytes = uInt8Array.slice(size - 2, size);

类型数组的大多方法与普通数组是类似的,所以我们可以借助 slice 截取字节数组中的最后两个字节。现在,我们得到了包含两个元素且每个元素是一个字节大小的数组,需要注意的是,我们这里说的一个字节的大小,指的是每个元素的数值最大不能大于 1 个字节 (即每个元素的值的范围在 0 ~ 255),并不是说每个元素是二进制的表示法,恰恰相反,每个元素是十进制的表示法。还有一个容易忽略的点是,我们使用 slice 截取数组的一部分并作为新数组返回,而这个新数组依然是原类型的类型数组,而不是普通数组,不注意这个的话,你就会无形之中被坑了。

console.log(lastTwoBytes); // Uint8Array(2) [1, 126]

从十进制元素到二进制字符串元素

上面我们说了类型数组中的数值是十进制的表示法,因为我们要获取的是最后两个字节表示的短整型 (也就是 16 位的整型) 数字,我们需要把最后 2 个元素转换为二进制字符串再合并并最终转换为十进制数字,就是我们要的短整型数字。

const shortIntString = Array.from(lastTwoBytes, (item) => item.toString(2).padStart(8, '0')).join('');

我们来解释下这行代码所做的事,首先,我们用 Array.from 将 lastTwoBytes 这个类型数组对象转换为普通数组,因为我们说过,TypedArray 存储元素时使用的是十进制的形式,我们需要将十进制表示的数转换为字符串表示并合并,而 Uint8Array 会将字符串表示在合并之前又转换成十进制表示,并且还有其他麻烦的问题 (见下面的附注),所以要先转换为普通数组 (普通数组每个元素可以存储包括 4 字节大小的数字)。

附注:

如果给 TypedArray 构造器函数传入一个字符串数组,字符串元素会按照十进制被转换为数字。如果类型数组的一个元素大于这个类型数组的每个元素允许的最大值,比如 Uint8Array 的每个元素最大为 255,则存储时存的是这个数求余 2^8 得到的数,比如,10000100 存的就是 10000100 % 256 = 228,所以 new Uint8Array(['10000100']) 得到的会是 Uint8Array [228] 而不是 Uint8Array [132]。

其次,Array.from 的第二个参数容易被人忽略,它是一个 map 函数,可以对生成的数组进行 map 操作,非常方便。对每个元素,我们使用数字的 toString 方法转换为二进制字符串。但是还没完,数字的 toString 方法有个极其坑爹的地方,如果一个转换好的二进制字符串有前缀 0,这个前缀 0,乍一看,对单个二进制数来说是没有什么问题的,但当两个二进制数作为一个整体时,这少掉的前缀 0 将是致命的,直接导致合并后的二进制数转换的十进制数是错误的,因此我们要给二进制字符串补前缀 '0',麻烦的方法是根据字符串应有的长度进行遍历补 '0'。好消息是,ES8 中有个很棒的方法 padStart,传入应达到的长度 8 和前置补充的字符 '0',就解决这个问题了。最后用 join 将两个字符串合并,便得到了短整型数字的二进制字符串表示,离我们的目标更近了!

得到短整型数和我们需要的字符串

上面我们得到了短整型数字的二进制字符串表示,要转换为十进制数字很容易,用 parseInt 即可。

const goToPosition = Number.parseInt(shortIntString, 2);

现在 goToPosition 就是我们要的偏移值了,借助这个偏移值,便可以得到指定范围的字符串了。

const sliceStart = size - 2 - goToPosition;
const sliceEnd = size - 2;
const resultArray = uInt8Array.slice(sliceStart, sliceEnd);
const result = String.fromCharCode(...resultArray);

String.fromCharCode 可以将范围在 0 ~ 65535 的最常用的 Unicode 码转换为对应的字符。终于,我们得到了隐藏在二进制中的字符串信息。下面是完整的处理代码:

  /**
   * 给定 ArrayBuffer 对象,返回该对象转换为 Uint8Array 类数组对象时其内部指定范围的字符串表示
   * 
   * @param {ArrayBuffer} buffer - 操作的 ArrayBuffer 对象
   * @return {string} 给定 ArrayBuffer 对象的内部指定范围的字符串表示
   */
  function getStringFromArrayBuffer(buffer) {
    const uInt8Array = new Uint8Array(buffer);
    
    // 这里也许有解压操作...

    // 取得末尾两个字节的二进制字符串表示
    const lastTwoBytes = uInt8Array.slice(size - 2, size);

    const shortIntString = Array.from(lastTwoBytes, (item) => item.toString(2).padStart(8, '0')).join('');
    
    // 将二进制字符串转换为十进制数字
    const goToPosition = Number.parseInt(shortIntString, 2);
    
    const sliceStart = size - 2 - goToPosition;
    const sliceEnd = size - 2;
    const resultArray = uInt8Array.slice(sliceStart, sliceEnd);
    const result = String.fromCharCode(...resultArray);

    return result;
  }

值得一提的是,二进制数据有时是经过压缩的,这种情况下我们可以借助 pako 这样的解压缩库解压一波再操作,赞!

Gitlab CI/CD 初探

Gitlab CI/CD 初探

探索 gitlab CI/CD 的一篇小文,包含了 gitlab 的安装和 CI/CD 的研究。

gitlab 安装及配置

官网有很多种安装方式,方便起见,不想做过多配置,直接走 Docker 安装

Mac 如果是较新的系统可以直接按 文档 来。旧系统只能找对应支持的 旧版本 Docker,新版本不兼容。

装好 Docker 后,需要装 gitlab 的 社区版本,商业版本毕竟要钱。命令行输入:

$ docker pull gitlab/gitlab-ce

参照 文档 配置 gitlab,下面均默认 Mac 系统。

命令行执行:

$ export GITLAB_HOME=$HOME/gitlab

用于 gitlab 容器中相关数据的存储。

执行:

sudo docker run --detach \
  --hostname gitlab.yangkean.com \
  --publish 443:443 --publish 8000:8000 --publish 2222:22 \
  --name gitlab \
  --restart always \
  --volume $GITLAB_HOME/config:/etc/gitlab \
  --volume $GITLAB_HOME/logs:/var/log/gitlab \
  --volume $GITLAB_HOME/data:/var/opt/gitlab \
  gitlab/gitlab-ce

部分说明:

--detach 在后台中运行容器

--hostname 设置容器主机名

--publish 2222:22 把容器的端口22映射到外部2222端口,其他类似。需要注意的是 http(s) 绑定新端口时需要 端口一致

—name gitlab 容器的名称, 可用于命令中,如重启容器:docker restart gitlab

--restart always 自动重启

—volume 将容器的路径映射到容器外部

几个常用命令:

$ docker ps # 列举所有容器,可以查看容器 ID
$ docker restart [container name] # 重启相应的容器
$ docker kill [container id] # 干掉当前在跑的某个容器
$ docker image ls # 列举所有已安装镜像
$ docker stats # 容器运行时的资源使用情况

接下来是需要修改配置文件,找到 $GITLAB_HOME/config/gitlab.rb,找到并修改如下几行:

# 访问 gitlab 的地址,端口默认为 80
external_url 'http://127.0.0.1:8000'

# 设置 ssh 服务的访问地址
gitlab_rails['gitlab_ssh_host'] = '127.0.0.1'

# 设置 ssh 服务访问的端口
gitlab_rails['gitlab_shell_ssh_port'] = 2222

# nginx 端口设置
nginx['listen_port'] = 8000

访问 127.0.0.1(或者自行在 hosts 文件里配置域名的映射)即可看到 gitlab。管理员账号是 root,密码在 $GITLAB_HOME/config/initial_root_password 文件里,登录后自行修改。或可直接 设置初始密码

CI/CD

Gitlab CI/CD 是 gitlab 内置的用于持续集成和持续部署的工具,每次推送代码到仓库中都会根据根目录下的 .gitlab-ci.yml 文件执行一系列脚本,完成构建、测试、验证、发布等任务。我们可以使用 gitlab 提供的 shared runner 来执行 .gitlab-ci.yml 中的任务,也可以使用 gitlab 提供的 gitlab-runner 程序在我们选择的机器上执行。我这里使用 gitlab-runner 的方式。

安装 gitlab runner

方式也很多,这里使用 Docker 方式安装 gitlab runner

$ docker volume create gitlab-runner-config
$ docker run -d --name gitlab-runner --restart always \
    -v /var/run/docker.sock:/var/run/docker.sock \
    -v gitlab-runner-config:/etc/gitlab-runner \
    gitlab/gitlab-runner:latest

注册 runner:

$ docker run --rm -it -v gitlab-runner-config:/etc/gitlab-runner gitlab/gitlab-runner:latest register

按照提示输入,URL 和 token 可以在项目的设置中的 CI/CD 页查看到。需要注意的是,URL 建议使用 外网 IP 或 内网 IP,而不是 localhost 之类的 IP,会有问题。

编写 .gitlab-ci.yml

一个简单的 gitlab-ci 配置文件是这样的:

stages: # 分阶段
    - install
    - eslint
    - build
    - deploy

cache: # 缓存,为了避免在执行新 stage 时以下的文件夹被清空无法找到
    paths:
        - node_modules
        - build

install-job:
    tags: # 这里的 tag 是我们在 runner 中定义的 tag,只有有 tag 才会被相应的 runner 执行
        - demo
    stage: install # 所属的 stage
    script: # 当前 job 执行的脚本,可以有多段脚本
        - npm install

eslint-job:
    tags:
        - demo
    stage: eslint
    script:
        - npm run lint

build-job:
    tags:
        - demo
    stage: build
    script:
        - npm run build

deploy-job:
    tags:
        - demo
    stage: deploy
    script:
        - echo "start to deploy"

这个文件放在项目根目录中,gitlab runner 会根据这个这个文件来执行 pipeline,也就是管道流程,它包含多个 stage,每个 stage 代表一个阶段,可以理解为是我们在软件开发中的一些阶段,安装、lint、构建、部署等,每个 stage 都可以包含多个 job,每个 job 完成所属 stage 的一些任务,所有的 job 完成之后,整个 pipeline 就完成啦。

现在可以提交代码到仓库中看看效果啦!

Reference

谈谈 git-flow

There's only one rule: anything in the master branch is always deployable. -- Github

What & Why

一个人开发程序久了,习惯性地就会直接提交到 master 分支。但当项目逐渐大型且团队合作开发的人数逐渐变多时,才发现有必要规范下整个代码版本控制的流程,否则,混乱将是不可避免的,而 git-flow,就是这样一个规范代码版本控制的工作流程 (workflow)。git-flow 并没有改变基本的 git 命令,它只是通过脚本的方式将标准的 git 命令组合起来,配以清晰的引导提示和描述,让你通过几个简单的命令就可以完成一些麻烦的操作。

How to use it

下面我们来安装它吧:

$ brew install git-flow-avh

其它的安装方式或不同平台上的安装方法请参见 Installing git-flow

现在我们可以用 git-flow 来规范我们的版本控制了,cd 进入到你的项目,然后初始化它:

$ git flow init
Initialized empty Git repository in /Users/yangshao/Desktop/test/.git/
No branches exist yet. Base branches must be created now.
Branch name for production releases: [master]
Branch name for "next release" development: [develop]

How to name your supporting branch prefixes?
Feature branches? [feature/]
Bugfix branches? [bugfix/]
Release branches? [release/]
Hotfix branches? [hotfix/]
Support branches? [support/]
Version tag prefix? []
Hooks and filters directory? [/Users/yangshao/Desktop/test/.git/hooks]

配置推荐使用默认的,所以一路回车即可。另外,如果你的项目不是一个 git 仓库,init 命令会帮你初始化的。

项目会有两个在项目的整个生命周期中长生不死的分支:

  • master
  • develop

按照国际惯例,master 只能放产品代码,master 不能直接提交代码,代码提交要放到短命的分支上。develop 是进行任何新的开发的基础分支,功能分支的代码都是要合并到这个分支的,这个分支汇集所有已经完成的功能,并等待被整合到 master 分支中。

了解下那些活不久的分支

功能分支 (feature branch)

该分支用于新功能的开发。

开启一个分支:

$ git flow feature start rss-feed
Switched to a new branch 'feature/rss-feed'

Summary of actions:
- A new branch 'feature/rss-feed' was created, based on 'develop'
- You are now on branch 'feature/rss-feed'

Now, start committing on your feature. When done, use:

     git flow feature finish rss-feed

完成该功能的开发时:

$ git flow feature finish rss-feed
Switched to branch 'develop'
Already up-to-date.
Deleted branch feature/rss-feed (was b8dfaaa).

Summary of actions:
- The feature branch 'feature/rss-feed' was merged into 'develop'
- Feature branch 'feature/rss-feed' has been locally deleted
- You are now on branch 'develop'

finish 该分支时该分支会被自动合并到 develop 分支。

预发布分支 (release branch)

当当前在 develop 分支的代码已经是个成熟的 release 版本时,即包含所有新的功能和必要的修复且被彻底地测试过了,这时候就可以生成一个 release 分支。

$ git flow release start 1.1.5
Switched to a new branch 'release/1.1.5'

Summary of actions:
- A new branch 'release/1.1.5' was created, based on 'develop'
- You are now on branch 'release/1.1.5'

Follow-up actions:
- Bump the version number now!
- Start committing last-minute fixes in preparing your release
- When done, run:

     git flow release finish '1.1.5'

然后做最后的版本号更新等编辑工作即可完成该分支:

$ git flow release finish 1.1.5
Switched to branch 'master'
Deleted branch release/1.1.5 (was b8dfaaa).
1.1.6

Summary of actions:
- Release branch 'release/1.1.5' has been merged into 'master'
- The release was tagged '1.1.5'
- Release branch 'release/1.1.5' has been locally deleted
- You are now on branch 'master'

finish 该分支时该分支会被自动合并到 master 分支。

补丁分支 (hotfix branch) master

在已发布版本发现 bug 时使用该分支。

就语义来说,bugfix 是修复开发和测试时发现的 bug,hotfix 是修复当前已发布版本发现的 bug,前者用的比较少。

$ git flow hotfix start missing-link
Switched to a new branch 'hotfix/missing-link'

Summary of actions:
- A new branch 'hotfix/missing-link' was created, based on 'master'
- You are now on branch 'hotfix/missing-link'

Follow-up actions:
- Start committing your hot fixes
- Bump the version number now!
- When done, run:

     git flow hotfix finish 'missing-link'
$ git flow hotfix finish missing-link
Switched to branch 'develop'
Deleted branch hotfix/missing-link (was b8dfaaa).

Summary of actions:
- Hotfix branch 'hotfix/missing-link' has been merged into 'master'
- The hotfix was tagged 'missing-link'
- Hotfix branch 'hotfix/missing-link' has been locally deleted
- You are now on branch 'develop'

finish 该分支时该分支会被自动合并到 master 分支。

结语

当然,除了 git-flow,还有 github-flow、gitlab-flow,它们各有优缺点,但不管怎样,都是为了提高团队开发时代码管理的高效性。

Reference

如何在 JavaScript 中拷贝对象

本文为不完全拷贝对象总结

最简单的方法

创建一个新的空对象,然后直接遍历属性(比如用Object.keys,不过这个方法只列出自身对象的所有可列举属性)并赋值。

浅拷贝(shallow copy)

let b = {
  shallow: {
    test: 'OK'
  }
}

let a = {}
a.shallow = b.shallow

b.shallow.test // "OK"

a.shallow.test = 'NOPE'

// 受到影响
b.shallow.test // "NOPE"

// 或者可以用 for...in,但是 for...in 会迭代一个对象及其原型链上的所有可列举属性
function shallowCopy(oldObj) {
  let newObj = {}
  for (let i in oldObj) {
    if (oldObj.hasOwnProperty(i)) {
      newObj[i] = oldObj[i]
    }
  }
  return newObj
}

深拷贝(deep copy)

let b = {
  shallow: {
    test: 'OK'
  }
}

let a = {}
a.shallow = {}
a.shallow.test = 'NOPE'

b.shallow.test // "OK"

Object.assign(target, source1[, sourceN])

Notes

  • ES6 才开始支持
  • 实行的是浅拷贝
  • Symbol类型的 key 也会被复制
  • 对象的所有访问器(accessor),例如属性的 getter 和 setter,都将调用 getter 复制为数据,而且复制时不设置属性任何的 descriptor
  • key 的定义顺序可能影响到其他 key 的值

复制 source1、source2... 对象的可列举属性到 target 对象,并根据 source 从右到左从高到低的优先级合并相同属性。返回复制并合并好后的对象。

let a = {}
let b = {
  a: {
    b: true
  },
  b: 2
}
let c = {
  a: {
    c: true
  },
  c: 3
}

Object.assign(a, b, c)

a.b // 2
a.c // 3

a.a === c.a // true

a.a.test = 'wow'
c.a.test // "wow"

let uid = Symbol('uid')
let special = {
  get next() {
    return ++this.counter
  },
  counter: 0
}

special[uid] = Math.random()

let notSpecial = Object.assign({}, special)


notSpecial.counter // 1
                   // next 在 counter 之前复制,所以 counter 增 1
                   // 如果 next 在 counter 之后复制则 counter 为 0

notSpecial.next // 1
notSpecial[uid] === special[uid] // true 

改良版 Object.assign

相比原本的 Object.assign,改良版保持浅拷贝、忽略 Symbol 类型的键、拷贝 accessor。不过这个方法没有复制目标对象上已有的属性,可以自己加逻辑去覆盖已有属性。

function assign(target) {
  for (let
    hOP = Object.prototype.hasOwnProperty,
    copy = function (key) {
      if (!hOP.call(target, key)) {
        Object.defineProperty(
          target,
          key,
          Object.getOwnPropertyDescriptor(this, key)
        )
      }
    },
    i = 0;
    ++i < arguments.length;
    Object.keys(arguments[i]).forEach(copy, arguments[i])
    ){}
    return target
}

let special = {
  get next() {
    return ++this.counter
  },
  counter: 0
}
let reSpecial = assign({}, special)

reSpecial.counter;  // 0
reSpecial.next;     // 1
reSpecial.next;     // 2
reSpecial.next;     // 3

special.counter;    // 0
special.next;       // 1
special.next;       // 2

Reflect.ownKeys

Note

  • ES6 才开始支持
  • 浅拷贝
  • 复制所有属性,不管是否可列举及是否是Symbol
// 相当于 Reflect.ownKeys
function ownKeys(object) {
  return Object.getOwnPropertyNames(object).concat(
    Object.getOwnPropertySymbols(object)
  )
}

function getOwnPropertyDescriptors(object) {
  return ownKeys(object).reduce(function (d, key) {
    d[key] = Object.getOwnPropertyDescriptor(object, key)
    return d
  }, {})
}

let target = {}
let source = {
  name: 'Samyang',
  gender: 'male',
  height: 172,
  [Symbol('only')]: Math.random()
}

Object.defineProperties(
  target,
  getOwnPropertyDescriptors(source)
)
// {name: "Samyang", gender: "male", height: 172, Symbol(only): 0.483625340619384}

JSON.parse(JSON.stringify(obj))

  • 深拷贝
  • 对象中的 undefined、Symbol、function 会被忽略,若存在数组则数组中这3个值会被替换为null,循环结构的嵌套会报TypeError
  • 忽略所有非可列举属性
let source = {
  name: 'Samyang',
  g: {
    a: 999
  },
  gender: 'male',
  height: undefined,
  [Symbol('only')]: Math.random()
}
Object.defineProperty(source, 'gender', {
  enumerable: false
})

let target = JSON.parse(JSON.stringify(source))
target // {name: "Samyang", g: {…}}
target.g.a = 'hhh'
source.g.a // 999

递归法

  • 深拷贝
function deepCopy(oldObj) {
  let newObj = oldObj
  if (oldObj && typeof oldObj === 'object') {
    newObj = Object.prototype.toString.call(oldObj) === "[object Array]" ? [] : {}
    for(let i in oldObj) {
      newObj[i] = deepCopy(oldObj[i])
    }
  }
  return newObj
}
let source = {
  name: 'Samyang',
  g: {
    a: 999
  },
  gender: 'male',
  height: undefined,
  [Symbol('only')]: Math.random()
}
Object.defineProperty(source, 'gender', {
  enumerable: false
})

let yyy = deepCopy(source)
yyy // {name: "Samyang", g: {…}, height: undefined}

就酱😝

Reference

有只老鼠叫 EditorConfig

EditorConfig 是啥?

如果你有幸在诸如 VSCode、Sublime Text、Atom、Notepad++、IDEA、Webstorm 等各种编辑器或 IDE 上写过代码,你就会发现它们各自的默认代码风格的配置其实是不同的,那么问题来了,作为一名有追求的程序猿,怎么可以忍受代码风格的不统一,于是你就疯狂地每换一个编辑器就改配置,mmp,实在太烦了,这时候,EditorConfig 就像神一样来拯救我们,帮我们在不同的编辑器或 IDE 上定义和维护一致的代码风格。

怎么用?

EditorConfig 提供了一个 .editorconfig 文件用于定义代码风格,这个文件的栗子看起来像下面这样:

# 其他没设置的地方就会默认使用编辑器或 IDE 的设置

# 表明这是最顶层的 EditorConfig 文件
root = true

# 每个文件都是 Unix-style 的新行且文件末尾以新行结束
[*]
end_of_line = lf
insert_final_newline = true

# 设置多个文件类型的字符编码
[*.{js,py}]
charset = utf-8

[*.py]
indent_style = space
indent_size = 4

# 设置了 indent_style 为 tab 后习惯上不设置 indent_size 以便阅读者使用它们喜欢的缩进宽度
[Makefile]
indent_style = tab

# lib 目录下的 JS 文件的缩进配置
[lib/**.js]
indent_style = space
indent_size = 2

[{package.json,.travis.yml}]
indent_style = space
indent_size = 2

当在一个编辑器或 IDE 中打开一个文件时,EditorConfig 插件会在当前目录和每个父目录下寻找这个 .editorconfig 文件,这种寻找会直到到达根路径或者找到包含 root=true 的 .editorconfig 文件。当有多个 .editorconfig 文件时,会从更上层的目录往下读取,如果是相同的属性,那么离当前文件更近的 .editorconfig 文件里的相同属性具有更高的优先级。

上面文件中的模式匹配符的规则如下:

字符 含义
* 匹配除 /(斜杠)外的任意字符串
** 匹配任意字符串
? 匹配任意单个字符
[name] 匹配 name 的每个字符,用于匹配文件名
[!name] 匹配不是 name 的字符的其他任意单个字符
{s1,s2,s3} 匹配所给的任意字符串
{num1..num2} 匹配 num1 ~ num2 之间的任意整数数字

EditorConfig 主要属性 (properties) 如下:

详细的属性列表见 EditorConfig Properties

  • root - 特殊属性,应定义在 .editorconfig 文件的顶部,true 表示在当前文件停止搜索 .editorconfig 文件
  • indent_style - tab/space,缩进风格
  • indent_size - 缩进数
  • end_of_line - lf/cr/crlf,定义如何表示换行符
  • charset - latin1/utf-8/utf-16be/utf-16le,定义文件的字符编码
  • trim_trailing_whitespace - true/false,true 的时候移除移除一行尾部的空白字符
  • insert_final_newline - true/false,true 表示一个文件以一个新行结束

写好了规则,接下来我们就要借助 EditorConfig 插件了,有些平台、IDE 或者 编辑器自带绑定就不用我们安装插件,而没有绑定的下载插件也是 so easy! 见 Download a Plugin。愉快地玩耍吧:)

Reference

Sass 入门

前言

我终于开始正式学这个老早就出来的东西了。

来,介绍下自己

Sass (Syntactically Awesome StyleSheets) 是一个可以被编译成 CSS 的脚本语言,即 SassScript。Sass 有两种语法,最早的语法是“缩进语法”,使用缩进去分离代码块和新行的字符以区分不同的规则。新语法就是“SCSS”,使用和 CSS 一样的块语法,即用花括号去表示一个代码块,用分号去分隔不同的行。两种语法的文件的扩展名分别是 .sass.scss

使用 Sass

先按照 安装说明 安装 Sass。然后写个 demo1.scss,用 sass 编译了下:

$ sass scss/demo1.scss ../dist/css/demo1.css

除 CSS 文件还生成了了 demo1.css.map 文件,这是个 sourcemap 文件,便于浏览器用调试工具调试代码。

变量 (variables)

变量用于存储值,增强代码的复用性,使用 $ 来表示变量。变量类型可以是:数字、字符串、颜色、布尔型、null、数组(list,用空格或逗号分隔的一系列的值)、maps(相当于 JavaScript 的 object,写为 (key1: value1, key2: value2))。

$width: 5em;

#main {
  width: $width;
}

嵌套 (nested rules)

Sass 允许将一个 CSS 样式嵌套进另一个样式中,内层的样式将它外层的选择器作为父选择器。

.parent {
  color: blue;
  .child {
    font-size: 12px;
  }
}

有时要直接使用嵌套外层的父选择器,这时可使用 & 代表嵌套外层的父选择器。

a {
  font-weight: bold;
  text-decoration: none;
  &:hover {
    text-decoration: underline;
  }
  body.firefox & {
    font-weight: normal;
  }
}

嵌套不仅只用于子选择器上,还可以使用在同一个命名空间中的属性上。

.funky {
  font: {
    family: fantasy;
    size: 30em;
    weight: bold;
  }
}
// 会被转译为:
// .funky {
//   font-family: fantasy;
//   font-size: 30em;
//   font-weight: bold; 
// }

混入 (mixins)

我们可以借助 @mixin 指令定义可重复使用的样式片段,当要使用该片段时,用 @include 指令指定片段名即可。

@mixin large-text {
  font: {
    family: Arial;
    size: 20px;
    weight: bold;
  }
  color: #ff0000;
}
.page-title {
  @include large-text;
  padding: 4px;
  margin-top: 10px;
}

运算 (Operations)

Sass 支持 +-*/%<><=>===!= 运算符。

对于 /,需要注意只有以下三种情况才会视为是除法运算符:

  • 如果值,或值的一部分,是变量或者函数的返回值
  • 如果值被圆括号包裹
  • 如果值是算数表达式的一部分
p {
  font: 10px/8px;             // 原生 CSS,不作为除法
  $width: 1000px;
  width: $width/2;            // 使用了变量,作为除法
  width: round(1.5)/2;        // 使用了函数,作为除法
  height: (500px/2);          // 用圆括号包裹,作为除法
  margin-left: 5px + 8px/2px; // 使用了 +,作为除法
}

引用 (import)

当一个 scss 文件只是用于导入到其他 scss 文件而不需要编译为 css 文件时,只需要在这个文件名前添加下划线即可,但是导入语句则不需要添加下划线。

可以使用 @import 引入其他的 scss 或 sass 文件。另外,被导入的文件中所包含的变量或者混合指令 (mixin) 都可以在导入的文件中使用。

@import 'foo'; // 这会导入 `foo.scss` 或 `foo.sass`

在以下情况下,@import 仅作为普通的 CSS 语句,不会导入任何 Sass 文件。

  • 文件拓展名是 .css
  • 文件名以 http:// 开头
  • 文件名是 url()
  • @import 包含 media queries

选择器继承 (selector inheritance)

我们可以使用 @extend 实现将一个选择器下的所有样式继承给另一个选择器。

.error {
  border: 1px #f00;
  background-color: #fdd;
}
.seriousError {
  @extend .error;
  border-width: 3px;
}

这个特性是可以帮助我们解决在针对一个元素应用普通样式和特殊样式时要写两个 class 的问题,现在只需要写 seriousError 的类名就可以了。

咦?@extend@mixin 怎么感觉用起来的效果一样。其实不然,

  • @mixin 定义的代码段不会编译到最终的 css 文件中,@extend 中被继承的代码片段则会
  • @extend 会聪明地对选择器进行合并,节省代码量,@mixin 相当于把一段代码复制粘贴到别处
  • @mixin 还可以传入参数,还有默认值,跟函数一样一样的

工具

结合 webpack 用就好了,compass 就不要用了,已经被作者抛弃好久了==!

结尾

其实 Sass 写起来的感觉跟 JavaScript 蛮像的。另外,这里介绍的只是很少的但是很常用的特性,如果在写的过程中感觉有不合理的或者不爽的地方,不妨看看 Sass 官方文档,说不定就找到高级的写法了:)。

Reference

谈谈 ES7、ES8、ES9 和 Stage 3 的那些事儿

前言

我在下面对于标准和实现的说法可能并不规范,但为了便于大家理解,我模糊了标准和实现之间的区别。另外,除了新增特性外,ECMAScript 标准还会对语言本身原有的语法等杂项 (misc) 进行微调,为了保持本文的主线一致,我将忽略这些杂项,只讲述每个 ECMAScript 标准新增的特性 (feature) 的我感觉比较有用的内容 (没错,我就是想偷懒),如果你对杂项有兴趣的话,可以参见 es2016plus 的 misc 栏进行了解和学习,另外也可以参考其中的各种兼容性情况。

ES6 作为多年来 JavaScript 的重大版本变革,受到 JavaScript 开发者们的普遍欢迎。我在 16 年底第一次接触到 ES6 的时候也是欣喜若狂,这比徐公 (ES5) 美多了!经过一个月的学习就立即在个人项目中使用了起来,十分酸爽,而后无论个人项目还是公司项目中我都使用 ES6。也正是从 ES6 (ES2015) 开始,JavaScript 版本发布变为年更,即每年发布一个新版本,以年号标识版本,也就有了这篇文章要讲的 ES7 (ES2016)、ES8 (ES2017)、ES9 (ES2018),以及什么是 Stage 3。不要紧张,我知道很多人可能连 ES6 都还没学完,但其实除了 ES6,后面的这几个版本增加的内容并不多,况且有些内容还蛮有用的,所以,不妨了解下吧。:)

有请一号男嘉宾 —— ES7 出场

孟x旁白:这是由 ECMA International (ECMA 国际) 向我们推荐的男嘉宾。

大家好,我今年 2 岁,来自日内瓦,有两项主要技能,很高兴认识大家。

孟x旁白:请说说你的两项主要技能,请大家为男嘉宾打 call。

好的,我分别介绍一下。

Array.prototype.includes(searchElement[, fromIndex])

在 ES6 中我们有 String.prototype.includes() 可以查询给定字符串是否包含一个子串,而在 ES7 中,我们在数组中也可以用 Array.prototype.includes 查找是否包含某个元素了。元素比较用的是 ===,值得一提的是,即使实际上 NaN !== NaN,但常识告诉我们他们应该相等,所以用这个函数测试 NaN 时,我们很高兴地得到了我们应得到的值。但素,另一个让大家可能会奇怪的是,这个函数认为 +0 和 -0 是相等的,真是表里不一啊,差评!我要给委员会寄刀片!!!脱离这苦海无望了。

'Sam Yang'.includes('Yang') // true

const arr = [1, 3, 5, 2, '8', NaN, -0]
arr.includes(1) // true
arr.includes(1, 2) // false
arr.includes('1') // false
arr.includes(NaN) // true
arr.includes(+0) // true

幂操作符 - **

相信在你要计算某个数的 x 次方时,一定用过 Math.pow()。现在,我们终于可以只用一个普通的操作符就实现幂运算了。虽然这个操作符为二元操作符中最高的 (但低于一元操作符),但是诸如 2 * 5 ** 2 的操作我还是建议用括号括起来,这样会更加一目了然。和除 ++—- 的一元操作符一起使用时,都必须使用括号明确意图,否则会因为歧义而报错,同样的,我建议都使用括号明确意图,炫技在工程上并不是聪明的举动。

Math.pow(5, 2) // 25
5 ** 2 // 25

// 相当于 2 * (5 ** 2)
2 * 5 ** 2 // 50

点评

孟 x:让我们来看看是否有人留灯。(噔噔~)全灰,很遗憾,希望你能找到你的归宿!我们来看下一位。

有请二号男嘉宾 —— ES8 出场

孟x旁白:这也是由 ECMA International 向我们推荐的男嘉宾。

大家好,我今年 1 岁,来自日内瓦,有三项主要技能,很高兴认识大家。接下来我介绍下自己。

对象新增的静态方法

1.Object.values(obj)

返回一个由 给定对象自有可枚举 属性值组成的数组。值的顺序是 for...in 遍历对象时的顺序。

const object = {
  a: 'somestring',
  b: 42,
  c: false
};

Object.values(object) // ["somestring", 42, false]

2.Object.entries(obj)

和 Object.values(obj) 是类似的,只是返回的数组的元素是键值对数组。

const object = {
  a: 'somestring',
  b: 42,
  c: false
};

Object.entries(object) // [["a", "somestring"], ["b", 42], ["c", false]]

3.Object.getOwnPropertyDescriptors(obj)

返回一个由 给定对象 的所有 自有 属性的属性描述符组成的对象。

const object = {
  a: 42
};

Object.getOwnPropertyDescriptors(object)
// {a: {value: 42, writable: true, enumerable: true, configurable: true}}

String padding

这是我觉得的一个很有趣的特性。

1.String.prototype.padStart(targetLength [, padString])

用 padString 这个字符串重复填充当前的字符串的头部,以至当前字符串能达到给定长度,即 targetLength。padString 默认使用的是空格。

'Sam Yang'.padStart(15, 'good') // "goodgooSam Yang"
'Sam Yang'.padStart(5, 'good') // "Sam Yang"
'Sam Yang'.padStart(10) // "  Sam Yang"

2.String.prototype.padEnd(targetLength [, padString])

与 String.prototype.padStart 是类似的,只是这是插在当前字符串尾部而已。

'Sam Yang'.padEnd(15, 'good') // "Sam Yanggoodgoo"

有人可能会说,这有啥用捏?还真别说,我遇到了。一般情况下这个方法可以用来格式化字符串,比如让字符串拥有统一的缩进。但我之前遇到了合并字符串时的问题,像 (126).toString(2) 在转化为二进制字符串时,竟然把最前面的 0 去掉了,这有时是很坑爹的,详见我的文章 用 ArrayBuffer 来截取二进制数据中的字符

async 函数

哇!这个不得了,好多人好早就估计开始用了,用了都说爽,简直是异步编程神器,不过它在 ES8 才入标准就是了。这个我提一提,展开了讲可以讲很多。

这个函数可以说是 Promise 和 Generator 的结合体,因为我们可以同时得到 Promise 的可信任性和 Generator 的同步性,做到以同步的方式写可以信任的异步代码。

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

// 用 async 声明异步函数
async function asyncCall() {
  console.log('calling');
  var result = await resolveAfter2Seconds(); // await 会等到它后面的表达式执行完毕才继续执行
  console.log(result);
}

asyncCall();
// calling
// resolved

更具体的使用方法可以参见 async function

点评

孟 x:让我们来看看是否有人留灯。(噔噔~)Java 为你亮了灯,你要选择他吗?

ES8:我拒绝。

孟 x:好,希望你能找到你的归宿。我们有请下一位。

有请三号男嘉宾 —— ES9 出场

孟x旁白:这还是由 ECMA International 向我们推荐的男嘉宾。

Hello, everyone. My name is ES9 and I am from Geneva. I am zero year old this year. 所以我现在还是个宝宝!谢谢大家!(全场传来阵阵掌声)

因为我还是个宝宝,所以在长大中,会有一些新东西不断加到我身上,我先说下我现在有的吧。而且因为我刚出来没多久,知道我的人并不多,所以介绍我的资料比较少点,接下来基本是我的自嘲。

对象的 rest/spread 属性

rest 属性跟数组及函数中的 rest 运算符用法很类似,是用 ... 将剩余的属性放入一个对象中。

const {a, ...rest} = {a: 1, b: 2, c: 3}
rest // {b: 2, c: 3}

const spread = {b: 2, c: 3}
const obj = {a: 1, ...spread}
obj // {a: 1, b: 2, c: 3}

Promise.prototype.finally(onFinallyFunc)

Promise 新增的这个实例方法跟 try...catch...finally 里 的 finally 很像,就是无论一个 promise 是被 fulfilled 还是被 rejected,都将执行传给这个方法的 onFinallyFunc 函数。

Promise.resolve(1)
.then((val) => {
	console.log(`fulfilled: ${val}`)
})
.catch((err) => {
	console.log(`rejected: ${err}`)
})
.finally(() => {
	console.log('hello world')
})
// fulfilled: 1
// hello world

Promise.reject(1)
.then((val) => {
	console.log(`fulfilled: ${val}`)
})
.catch((err) => {
	console.log(`rejected: ${err}`)
})
.finally(() => {
	console.log('hello world')
})
// rejected: 1
// hello world

其实还有很多东西等着加入我。(台下私语:看起来是个潜力股)

点评

孟 x:让我们来看看留灯情况。(噔噔~)TypeScript、React、Angular、Vue 均为你爆灯,你可以选择他们中的其中一个。

ES9(思考良久):我的选择是 —— React。

孟 x:让我们祝福他们!

ES9 & React:xx 蒸蛋糕,是真的爱你❤️。

Stage 3

孟 x:

这一期节目,我们见识了三位新人,他们各怀绝技,有惊喜有遗憾。但是,有些嘉宾可能还在娘胎,他们也有可能很优秀,让我们来了解下从娘胎到出生的五个阶段:Strawman(Stage 0,稻草人),Proposal(Stage 1,提议),Draft(Stage 2,草案),Candidate(Stage 3,候选)以及 Finished(Stage 4,完成)。分别对应着:有机会写入标准,讨论实现方法和可能存在的挑战,明确地用正式文档语言描述语法和语义,需要实现和用户的反馈来做细微的改进,准备好进入 ECMAScript 标准。前面三种处于细胞状态,最后一种意味着已经在标准中了,而 Stage 3 则是意味着很大程度能进入标准,也就是极有可能成为我们的下一位男嘉宾。当前的 Stage 3 基本没有被什么浏览器实现。我就说下其中一个较多浏览器实现的吧。

string trimming

这个是用来给给定字符串清除字符串周围指定方向的 whitespace 字符 的,但不影响原字符串。因为 trim 方法会清除两边,而有时我们可能只需要清除一边。

let str = '  Sam Yang  '
str.trim() // "Sam Yang"
str.trimLeft() // "Sam Yang  "
str.trimRight() // "  Sam Yang"
str // '  Sam Yang  '

结尾

就是这样,欢迎大家收看这期的《非 ES 勿扰》。

Reference

Stylus 笔记

这篇文章不适合不了解 Sass 的读者阅读,对初学者不友好,因为我只是在列举一些差异和在 Sass 的基础上讲解一些厉害的用法

前言

这个 CSS 预处理器看起来强大得有点厉害了。不过很多与 Sass 是类似的,我挑选一些我觉得比较好的记录一下。

选择器

让人感觉很不同的是 stylus 采用的是像 Python 一样的缩进来写样式块。

body
  color: #fff

会被编译为这样:

body {
  color: #fff;
}

其他一些写法是:

// 多个选择器
textarea
input
  border: 1px solid #eee

// 父级引用
textarea
input
  color: #a7a7a7
  &:hover
    color: #000

变量

font-size = 14px // 声明变量

body
  font: font-size Arial, sans-serif

// 属性查找:借助 `@` 使用前面已经定义过的属性
#logo
  position: absolute
  top: 50%
  left: 50%
  width: 150px
  height: 80px
  margin-left: -(@width / 2)
  margin-top: -(@height / 2)

插值

// 使用 `{}` 来包围表达式进行插值
table
  for row in 1 2 3 4 5
    tr:nth-child({row})
      height: 10px * row

操作符

基本你能想到的都能用,有些你想不到的也能用==!我说个有点有趣的,虽然我还不知道这鬼东西哪里用得上orz。

1..5 // => 1 2 3 4 5
1...5 // => 1 2 3 4
5..1 // => 5 4 3 2 1

Mixin

基本用法和 Sass 是一样的,而且不需要用关键字声明。使用时也不需要用关键字。

border-radius(n)
  -webkit-border-radius: n
  -moz-border-radius: n
  border-radius: n

form input[type=button]
  border-radius(5px)

函数

这个定义时的写法和 mixin 是一样的,不同的是函数会返回一个值。这函数的参数还能有默认值和借助name...使用 rest 参数,内部还能用 arguments。stylus 还提供了一大堆的內建函数。

// 定义一个函数
add(a, b)
  a + b

body 
  padding: add(10px, 5)

结尾

这样,我大概有幸触摸了 1% 的特性:)。最后,给 TJ 大神磕头了,一想到用过的那么多东西都是你写的,我竟无语凝噎。我还有个大胆的想法,竟然这些预处理器写得越来越像 JavaScript,干脆把 CSS 和 JavaScript 合并,一起写吧,但我还是太天真了,把 CSS 写入 JavaScript 早已有了,而且很多,其中一个就是 styled-components,○| ̄|_。

Reference

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.