Leon82 发布于 11月01, 2018

从一道面试题谈起

人生无根蒂,飘如陌上尘。分散逐风转,此已非常身。落地为兄弟,何必骨肉亲!得欢当作乐,斗酒聚比邻。盛年不重来,一日难再晨。及时当勉励,岁月不待人。 晋.陶渊明《杂诗》

笔者做面试官时候,除了考察常规的前端技能、框架的特性外,经常会出一些很简单的算法题目。这种经典题目的好处是言简意赅,在很短的时间就可以完成。但却能考察出工程师的基本计算机功力,以及知识储备。特别对于Javascript而言,还可以避免技术栈不同对于工程师能力的误判。

下面就是一道笔者经常考核的题目:

创建一个函数来判断给定的表达式中的大括号是否闭合,返回True/False,对于空字串,返回True:

var expression = "{{}}{}{}"
var expressionFalse = "{}{{}";

function isBalanced(exp) {}

题目本身比较简单。不同的同学拿到题目会有不同的第一反应。

有的同学仅仅查找各种括号的个数,这种是不可以的,这种括号是不匹配的:}{

有很多同学试图使用正则表达式解决问题。其思路是找到所有邻接的匹配的括号对,然后将其替换为空,直到不能替换为止。此时如果最后得到的字符串为空即为匹配,否则不匹配,代码很简洁,如下:

function isBalanced(exp) {
  var reg =  /\{\}/g, len;
  do{
      len  = exp.length;
      exp = exp.replace(reg, "")
  } while (len != exp.length)
  return exp.length === 0
}

好像也是可以的。但是问题点至少有二:

第一、此算法的最坏的时间复杂度是O(n^2)级别的,对于长篇大论是不友好的。

第二、此算法的正则表达式普适性较差,对于表达式含有其他干扰字符时候需要频繁修改正则表达式。当正则表达式过于复杂时候,反过来又会影响到检索效率。

其实,有时候最淳朴的做法,可能会更有效。

大家在数据结构课程中曾经学习过“栈”这种数据结构。“栈”是一种满足后进先出的抽象数据结构。这个结构在这道题目中可以帮助到我们。

思路如下:

  1. 巡检字符串,将括号分类,一类是左括号、一类是右括号。
  2. 左括号看作是入栈信号,右括号是出栈信号。
  3. 当出栈时,如果栈定没有与之匹配的元素,则宣告不匹配。
  4. 当巡检完毕,如果得到空栈,则匹配,否则不匹配。

写成代码大致就是这样,时间复杂度是O(n):

function isBalanced(exp){
    let info = exp.split("")
    let stack = []

    for(let i = 0; i < info.length; ++i){
                let el = info[i]
        if (el == "{"){
            stack.push("{")
        }
        else if (el == "}"){
            if(stack.length === 0){
                return false;
            }
            stack.pop()
        }
    }

    return stack.length === 0
}

在Javascript里,Array可以很方便的模拟栈的行为。读者有兴趣也可以自己实现一个简单的栈,用以丰富自己的代码工具箱。

另外,由于题目中的括号情形比较简单,很多同学用标志位来解决,即当为左括号时候,置标志位,为右括号时候,当标志位存在,取消标志位,否则判定不匹配。最后没有标志位则为匹配。这种解法,对于题目中的状况是可以的。

我们还可以把题目再向前面推进一步,看一看更一般的括号和字符串的情形:

实现函数isBalanced,用true或false表示给定的字符串的括号是否平衡(一一对应)。注意了是要支持三种类型的括号{},[],和()。带有交错括号的字符串应该返回false。

isBalanced('(foo { bar (baz) [boo] })') // true
isBalanced('foo { bar { baz }')         // false
isBalanced('foo { (bar [baz] } )')      // false 

基本思路和上面的解法是类似的。只是这里面需要注意两点:

  1. 过滤掉非括号的干扰字符。
  2. 每一种右括号有一种唯一的左括号与之对应。出现右括号时候,栈顶的左括号必须是和它匹配的。

对于第二种,大家应该很自然的联想到用JSON对象控制,这个是可以的。其实在ES6中,有Map这样的数据结构作为更专业的键值对存储结构可以使用。同时,一些好玩的语法和特性如扩展语法,箭头函数可以让我们把程序写得更加一气呵成。下面是一个可行的代码:

const isBalanced = (str) => {
    const map = new Map([
        ["{", "}"],
        ["[", "]"],
        ["(", ")"]
    ])

    let stack = [];

    for(let i = 0 ; i < str.length; ++i){
        let node = str[i]

        if (map.has(node)){
            stack.push(node)    
        }
        else if ([...map.values()].includes(node)){
            if (stack[stack.length - 1] !== 
                                [...map.entries()].filter(el=>el[1]===node).pop().shift()
                         ){
                return false
            }
            stack.splice(stack.length-1, 1)
        } 
    }    

    return stack.length === 0
}

很喜欢ES6的这些扩充,和这些新的数据结构。使得撰写Javascript有一种特别愉悦的体验。

让我们再次扩充一下这道题目。要求严格限制括号的顺序,即中括号外围只能是大括号,内部只能是小括号。也即:括号只能以大括号、中括号、小括号的顺序只能前面的包含后面的,不能后面的包含前面的,用代码来表示一下:

isStrictBalanced('foo { bar (baz) [boo] }')  // true
isStrictBalanced('(foo { bar (baz) [boo] })')  // false

这样的需求怎么解决呢?

聪明的你可能已经想出了答案,其实就是在入栈时候判断一下当前的优先级就好了。这里可能需要判断一下当前的字符和栈顶元素的关系。这里,我们如果用Array的pop API,则会破坏stack的结构。上例中,我们用length来取得,这里我们用...语法来实现数组的复制,同时在上例基础上,进行一些重构,得到下列代码:

const isStrictBalanced = (str) => {
    const map = new Map([
        ["{", "}"],
        ["[", "]"],
        ["(", ")"]
    ])
    let stack = [], keys = [...map.keys()], values = [...map.values()];
    for(let i = 0 ; i < str.length; ++i){
        let node = str[i]
        if (map.has(node)){
            if (stack.length){
                let arr = [node, [...stack].pop()]
                    .map(el => keys.indexOf(el))

                if (arr[0] < arr[1]){
                    return false
                }
            }
            stack.push(node)    
        }
        else if (values.includes(node)){
            let needKey = [...map.entries()].filter(el=>el[1]===node).pop().shift()
            if ([...stack].pop() !== needKey){
                return false
            }
            stack.pop()
        } 
    }    
    return stack.length === 0
}

从括号匹配的最简单情形,我们已经扩展到了比较复杂的括号匹配情形。至此,在逐步迭代的需求中给大家展示了栈的实际应用以及ES6中Map和扩展语法的使用。在面试的实际,即时反应很重要,也许并不拘泥于一种解法,从一点开始,逐步深入,慢慢延展,方能达到融会贯通,活学活用,这恐怕是面试官比较看中的。

阅读全文 »

Leon82 发布于 10月01, 2018

给你的动画加点料:谈谈AnimationWorklet

残阳西入崦,茅屋访孤僧。落叶人何在,寒云路几层。 独敲初夜磬,闲倚一枝藤。世界微尘里,吾宁爱与憎。 ——唐.李商隐 《北青萝》

引子

浏览器接收到 HTML 代码后,需要将其显示到屏幕上。不同的浏览器的处理方案有一定的差异。对此,谷歌工程师 Paul Lewis 给出了一个简明的 Rendering Pipeline 模型来说明浏览器解析流程。 据此,Paul 在这篇文章中, 详细地分析了浏览器每一步的职责以及几种常见的运行模式。

做为一个立志给用户最好的浏览体验的 Web 工程师,肯定希望在上述必要的步骤中和浏览器配合,作出必要的优化。

开发者可以通过 JavaScript 或者 CSS 来控制 DOM 和 CSSOM。对应于上图,也就是前两个阶段。在这两个阶段,开发者几乎可以随心所欲地控制 DOM 和 CSSOM。但是,后三个阶段,对 Web 开发者是黑盒。它们的行为,是由浏览器自动完成的,开发者只能通过前两个阶段的操作,间接地影响后面的阶段。

这不是我们想要的结果。

其实,各大浏览器厂商也意识到了这个问题的存在。因此,W3C 的 CSS 工作组,有一个活跃的工作小组,称为 CSS Houdini。CSS Houdini 是由一群来自 Mozilla、Apple、Opera、Microsoft、HP、Intel、IBM、Adobe 与 Google 等公司的工程师所组成的工作小组。这个小组希望指定一系列 API。通过这些 API,Web 开发者也能介入到浏览器的 CSS 解析引擎,从而和浏览器更好的配合,来解决性能、一致性等问题。这些 API 实现后,也许 CSS 的某些新标准也可以借助 Polyfill 在尚未实现的浏览器中体验了。

来自爱尔兰都柏林的资深工程师、开源软件组织成员Serg Hospodarets在 2018 年 3 月进行了一个关于 CSS Houdini 的演讲。演讲文稿中,对 Houdini 的任务模块做了如下的划分:

在这张图中,读者可以看到,浏览器渲染引擎的“白盒化”的标准,大致分了几个类:有对自定义属性的规范,有给 CSSOM 引入类型系统的规范,有解析 CSS 的规范,有字体属性与测量的规范,也有对 Layout、paint 和 Composite 的接口规范。

这里 Layout、Paint 和 Composite 的接口主要是通过 Web Worker 的机制实现的。关于 Layout、Paint 的 API,奇舞周刊后面的文章会给读者做详细的介绍,这篇文章,笔者将和读者主要谈一谈 Composite 相关的 CSS Animation Worklet API。

终于,本文的主角要登场了。没错,就是它,Animation Worklet。

什么是 Animation Worklet

那么 Animation Worklet 到底是做什么的呢?

首先,大家来回想下,一般是怎么制作动画效果的?

最简单的做法,我们使用 setTimeout 和 setInterval 来做动画,动画以时间间隔做为变化的依据。

但很快,人们发现当帧率过快时候,比如小于 16.7ms 一帧的时候,动画会发生丢帧的情况。张鑫旭老师的这篇文章非常形象和趣味地解释了这个问题。

随后,浏览器厂商开放了一个更有效率的 API:requestAnimationFrame。之所以更有效率,在于这实际上是一个基于动画帧发生的回调。这种设计的高明之处在于把帧与时间解耦,从而不再受制于显示器的刷新率。

这个结果似乎已经很完美了,不过这里还有一点问题。以 Chrome 浏览器为例,渲染引擎分主线程和 Compositor 线程。Layout 完成之后,主线程维护了一份 Layer 树,Compositor 维护了一个副本。如果我是主线程,我一定会对这个分担我的运行压力,并且定期和我同步、听候我同步指令的线程兄弟,欣赏有加的。然而,requestAnimationFrame是运行在主线程的。一旦主线程非常繁忙,动画的效率会大打折扣。

怎么才能让 Compositor 线程分担主线程的压力呢?读者不妨参考https://csstriggers.com/ 。只要不触发 Layout 或 Paint 的属性改变,就可以让 Compositor 线程独立工作,并择机同步给主线程。CSS3 动画引入的 transform 就是这个原理。使用了 transform 的动画,往往会比 JavaScript 动画更流畅。对啊,那可是多人干活啊。

好了,如果是这样,我们就尽量研习规范,把工作丢给 Compositor 线程就好了呗。但是,理想总是美好的,现实总是骨感的:能确定丢给 Compositor 线程独立工作,主线程无需参与的,现在还不太丰富。

Animation Worklet 的出现,就是为了满足这个痛点出现的。一方面,可以借助 JavaScript 的威力制作强大而精确控制的动画,一方面可以在 Compositor 线程独立工作。它的目标旨在兼容目前的 Web Animations 标准,尽可能的使用现有的结构,以其使用这组 API 能平衡动画的性能、丰富性和合理性。

Animation Worklet 最早曾经被称为 Compositor Worklet。顾名思义,之前的这些代码是可以直接执行在 Compositor 线程的。直到 2016 年,Houdini 小组在 TPAC 会议(TPAC 会议,W3C 的年度重要技术会议之一。参会者在五天的时间里,共同协调未来开放 Web 平台的技术方向,讨论 W3C 的组织策略)期间,提出对 API 的新建议,主要的变化是不再允许用户代码直接运行在 Compositor 线程。这种变化的原因在于,游览器厂商担心如果有大量的或者低效的用户代码阻塞住了 Compositor 线程,页面将没有反应,动画也将停滞。

新的 API 被重新命名为:Animation Worklet。在 Animation Worklet 里,用户代码不会直接运行在 Compositor 线程。Compositor 线程会尽力和这个线程保持同步。所以,如果代码效率实在“惨不忍睹”,Compositor 线程可能会对其有一定的弱化处理。

为了说明 Animation Worklet,下面先简单讲讲 Worklet 和 Web Animations。

什么是 Worklet

前面提到过,Layout、Paint 和 Composite 的接口主要是通过 Web Worker 的机制实现的。而 Worklet 的接口是一个轻量版的 Web Worker 的版本。借助这个接口,Web 开发者可以获得渲染引擎的底层部分的访问权限,从而使得高性能的诉求称为可能。

Worklet 被严格限制用途,不能随意创建。目前有四类 Worklet:PaintWorklet、AudioWorklet、AnimationWorklet 和 LayoutWorklet。

各种 let 只有一个公用的方法:addModule,返回一个 Promise。Promise 的 resolve 时,已将 URL 所示的脚本模块加载到了当前的 Worklet。

interface Worklet {
    [NewObject] Promise<void> addModule(USVString moduleURL, optional WorkletOptions options);
};

dictionary WorkletOptions {
    RequestCredentials credentials = "same-origin";
};

上面的接口定义了一个标准的 Worklet 接口,读者可在这里 阅读 Worklet 标准的所有细节。

什么是 Web Animations

那么,做为动画本身也有自己的标准,就是:Web Animations。Web Animations 本身又被分为 CSS Animations 和 CSS Transitions 两类。未来这些 API 会在这些接口的基础上丰富更多的功能。使用它们,不需要再去针对不同浏览器作出 hack,也不需要额外的 requestAnimationFrame。理想的目标是,调用这些接口,浏览器可以使用内部的优化机制,达到最优的体验。

Web Animations 定义了 Animation、KeyframeEffect、AnimationTimeline、AnimationEvent、DocumentTimeline、EffectTiming 等接口。是 Animation Worklet 运行动画的基础。

MDN 的这篇文章详细地描述了标准的分类、对象、主要方法,并给出了几个有意思的例子。

细看 Animation Worklet

除了主线程、Compositor 线程,我们又引入了 Animation Worklet 线程。在 Animation Worklet 中,动画运行在 Animation Worklet 线程上下文中,并且,暴露 Web Animations 标准中定义的接口。为了显示的最终一致性,这里存在一个主线程、Compositor 线程和 Animation Worklet 线程同步的流程。

上面这幅图简单的说明了,独立的 Animation Worklet 线程、Compositor 线程是如何与主线程同步的流程。这里以 Chrome 的实现的数据流向为例:从宏观上,主线程发送动画的状态如创建、删除、当前时间偏移等等信息,最终到达 Animation Worklet 线程。Animation Worklet 线程将实际起作用的动画时间回传给主线程。

这里为了不互相阻塞,主线程和 Animation Worklet 线程中间存在一个 Compositor 线程。主线程和 Compositor 线程,Compositor 线程和 Animation Worklet 线程之间于是存在有有两类的数据同步流程:

第一、Animation Worklet 接收到更新可视元素的信号,在动画播放器上运行每一个动画。所有动画播放完毕,记录下每一个动画时间,发给 Compositor 线程。Compositor 线程接收到消息后按时序生成一个新的状态记录。 第二、主线程在文档生命周期中每一次运行动画帧之前,需要和 Compositor 线程进行一次动画属性状态的同步。

Animation Worklet 给出的接口定义如下:

[Exposed=Window]
partial namespace CSS {
    [SameObject] readonly attribute Worklet animationWorklet;
};

最终实现的接口挂在全局的 CSS 对象上。

[Exposed=AnimationWorklet, Global=AnimationWorklet]
interface AnimationWorkletGlobalScope : WorkletGlobalScope {
    void registerAnimator(DOMString name, VoidFunction animatorCtor);
};

方法registerAnimator定义一个动画执行器,动画执行器的名称可以为一个字符串;animatorCtor为一个动画类。

前文曾经提到的数据同步图,每一个黑色的实心原点代表一个动画类实例。将它展开,就是下面这个图。每个动画类都包含一个 animate 方法,这个方法是动画执行的入口,Worklet 通过这个入口控制动画的执行。

在 Chrome 的实现下,上面的两图可以合成如下一幅图:

Animation Worklet 的使用场景都有哪些呢?

  1. 大多数的滚动相关的场景的动画和效果都是 Animation Worklet 的用武之地,比如滚动条、滚动过程中的快照、平滑的滚动动画、固定滚动栏等等。
  2. 可以使用硬件加速的 CSS 属性变化动画,也即单独使用 Compositor 线程可以完成的动画。如:opacity 的变化。

演示例子

让我们来看一个形象的例子。

这个例子里,我们要实现的是:随着滚动条的滚动,页面上部的进度条不断前进;同时,在第一屏以下,逐渐显示订阅按钮,按钮逐渐变大,opacity 值越来越高。

打开 devtools 我们可以看到,动画的发生,不会触发 Layout 和 Paint,这是我们能够顺利使用 Animation Worklet 的前提。同时,我们需要实时获取滚动的进度。Animation Worklet 的 API 正好可以满足我们的需求。

在主页面上,我们引入 Polyfill 和动画文件:

<!-- HTML (scripts) -->
<!-- Polyfill checks and loads (if needed)
    both Web Animation API and Animation Worklet polyfills -->
<script src="polyfill/anim-worklet.js"></script>

<script src="animator.js"></script>

这里animator.js的代码如下:

/* animator.js (load a Worklet module) */
window.animationWorkletPolyfill.addModule('worklet.js')
    .then(()=> {
        onWorkletLoaded()
    }).catch(console.error);

这里说明一下,目前 Polyfill 的实现,会挂载在 window 的animationWorkletPolyfill对象上,如果今后原生支持了,对象一般会通过 CSS.animationWorklet 访问。这段的实现代码大致是:

...
    // Returns true if AnimationWorklet is natively supported.
    function hasNativeSupport() {
      for (var namespace of [scope, scope.CSS]) {
        if (namespace.animationWorklet && namespace.animationWorklet.addModule)
          return true;
      }
      return false;
    }
...
  // Create scope.CSS if it does not exist.
  scope.CSS = scope.CSS || {};

  // Create a polyfill instance but don't export any of its symbols.
  scope.animationWorkletPolyfill = MainThreadAnimationWorklet();

  scope.animationWorkletPolyfill.load();

接下来我们来看看,worklet 里面的写法。

/* worklet.js - register and apply animations */
// Animators are classes registered in the worklet execution context
registerAnimator(
    'scroll-position-animator',// animator name
    class { // extends Web Animation
        constructor(options) {
            this.options = options;
        }

        // currentTime, KeyframeEffect and localTime concepts
        // from Web Animation API
        // animate function with animation frame logic
        animate(currentTime, effect) {
            // scroll position can be taken from option params
            // const scrollPos = currentTime * this.options.scrollRange;

            // each effect will apply the animation options
            // from 0 to 100% scroll position in the scroll source
            effect.children.forEach((children) => {
                // currentTime is a Number,
                // which represent the vertical scroll position
                children.localTime = currentTime * 100;
            });
        }
});

定义完 worklet,我们回过头来补充 animate.js

/* animator.js (onWorkletLoaded() ) */
const scrollPositionAnimation = // animator instance
    new WorkletAnimation(
        'scroll-position-animator', // animation animator name
        [ // animation effects
            new KeyframeEffect(scrollPositionElement, [ // scroll position
                {'transform': 'translateX(-100%)'}, // from
                {'transform': 'translateX(0%)'} // to
                ],
                {duration: 100, iterations: 1, fill: 'both'} // options
            ),
            new KeyframeEffect(subscribeElement, [ // size and opacity
                {'transform': 'scale(0.5)', 'opacity': 0}, // from
                {'transform': 'scale(1)','opacity': 1} // to
                ],
                {duration: 100, iterations: 1, fill: 'both'}) // options
        ],
        new ScrollTimeline({ // animation timeline
            scrollSource: document.querySelector('.page-wrapper'),
            orientation: 'vertical'
        })
    );
scrollPositionAnimation.play(); // start and apply the animation

现在的状态

目前,各大主流浏览器都在努力对 Houdini 标准进行实现,这里面 Chrome 浏览器是最积极的一个。下面这张图,说明了各个浏览器对 API 的支持情况,以及 W3C 组织在各组 API 上的进展。

我们来仔细看下这张图。绿色的是已经有完整的实现;浅黄色的是有部分的实现;深黄色是正在开发中;紫色的是已经有实现的意愿;红色的是暂时还没有进度。

具体到 Animation Worklet,目前标准正在非常活跃的迭代中,直到本文撰写时刻(2018 年 9 月),已经有 6 次工作草案的发布,最近一次为 2018 年 9 月 6 日,这里是最新的标准版本。

在实现方面,暂时还只有 Chrome 有部分支持,不过不要紧,已经有比较成熟的Polyfill来实现这些既有的标准特性,而且,主流的现代浏览器都可以比较完美的支持。

做为 Animation Worklet 标准的活跃推进者,Chrome 已经计划在未来支持除了 CSS 加速属性之外的所有 CSS 属性动画,如果实现,这将是更加令人兴奋的特性。未来会怎样?让我们拭目以待。

Houdini 的官方给出了一些例子供大家感受,读者可以尝试这些例子。这里需要注意一点,这些 API 只对 localhost 域名和 https 的域名可见。这两种情况之外,这些 API 不可用。目前有 Polyfill 可用。如果需要一些原生的支持,目前(2018 年 10 月)请下载最新的 Canary 版 Chrome,同时,打开--enable-experimental-web-platform-features选项。

Web 工程师现在能做什么

包括 Animation Worklet 在内的 Houdini 标准还是在扫雷和实验阶段。虽然已经达成了广泛的共识,并且有主流的浏览器和 W3C 不断的推进,但是对于大规模生产实践应用,还尚有距离,标准不排除会有变化的可能。

笔者衷心感谢可以坚持读到这里的读者。大家是真心关心 Web 标准并对技术有着前瞻性了解希望的,这也是标准得以发展并推进的原始动力之一。包括 Houdini 在内的 Web 标准发展需要广大 Web 开发者、浏览器供应商的共同努力。

笔者认为,大家可以在以下几个方向给 Houdini 的发展作出贡献。

  1. 关心 Houdini 的发展,可以在他们的 github 上提出 issue、PR 等等帮助他们提出问题、解决分歧。也可以开发一些真实可用案例,去实现一些在如今难以被实现的样式或布局。
  2. 同一般软件产品不一样,浏览器的用户分为最终用户和 Web 工程师两类。浏览器厂商需要倾听 Web 工程师方面的痛点、需求。集中反馈的需求可能得到优先的响应。
  3. 关注官方提供的一些例子。为将来到来的 Houdini 特性做好准备,当然也可以帮助他们纠错:-)

致谢

设计师王旋 mm,为本文设计的精美题图,在此表示诚挚的谢意。

参考资料

  1. https://developers.google.com/web/fundamentals/performance/rendering/
  2. https://blog.csdn.net/Joel_h/article/details/72400100
  3. https://github.com/w3c/css-houdini-drafts/tree/master/css-animationworklet
  4. https://www.zhangxinxu.com/wordpress/2013/09/css3-animation-requestanimationframe-tween-%E5%8A%A8%E7%94%BB%E7%AE%97%E6%B3%95/
  5. https://javascript.ruanyifeng.com/htmlapi/requestanimationframe.html
  6. https://segmentfault.com/q/1010000000645415
  7. https://css-tricks.com/myth-busting-css-animations-vs-javascript/
  8. http://www.chinaw3c.org/member-meetings.html
  9. https://developer.mozilla.org/en-US/docs/Web/API/Worklet
  10. https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API/Using_the_Web_Animations_API
  11. https://drafts.css-houdini.org/css-animationworklet/
  12. http://dassur.ma/things/animworklet/
  13. https://css-houdini.rocks/
  14. https://googlechromelabs.github.io/houdini-samples/
  15. https://docs.google.com/document/d/1MdpvGtnK_A2kTzLeTd07NUevMON2WBRn5wirxWEFd2w/edit#heading=h.w09fmb4pxgin
  16. https://slides.com/malyw/houdini#/46
  17. https://ishoudinireadyyet.com/
  18. https://chromium.googlesource.com/chromium/src/third_party/+/master/blink/renderer/modules/animationworklet/README.md
  19. https://qiita.com/taichitk/items/010c154c407a7b22dfc4
  20. http://www.w3cplus.com/css/css-houdini.html

阅读全文 »

Leon82 发布于 09月25, 2018

关于逆回购

猿鸟犹疑畏简书,风云常为护储胥。徒令上将挥神笔,终见降王走传车。管乐有才终不忝,关张无命欲何如。他年锦里经祠庙,梁父吟成恨有余。 ——唐.李商隐 《筹笔驿》

近几年,每到长假之前,逆回购就是一个热门的话题。利用闲置的资金稳健地获取更高的收益,这是一个很自然的理性选择。本文就来探讨下这个问题。

什么叫逆回购

前文所说的逆回购,一般指发生在证券交易所的国债的逆回购。

一般讲,一笔买卖,都有买方和卖方。站在不同的角度,收进和让渡的东西是不一样的。 狭义的逆回购,实际买方“购”的是现金的使用权。因此可描述国债逆回购为:卖方(通常指个体投资人)卖“货币”使用权给买方(拥有国债等有价证券的个体或机构),并从中赚取利息的过程,或者说就是卖方借钱给买方。在实际操作中,一个完整的逆回购会发生两笔关联操作,一个是卖方“逆回购”买方的债券、证券等,让渡货币使用权;另一个是买方按照约定时间、约定利率归还货币(使用权)给卖方,同时收回债券、证券等。两笔交易通常会在交易所的监督下完成,买方须以持有的国债做为抵押物。

经常有资料把这个业务和央行的逆回购混淆,其实这两者是不同的。央行的逆回购一般不会直接针对个人,一般是针对商业银行的。 简单理解,央行逆回购的语境里,可以把前文的卖方替换为央行。

根据上面的解释,国债逆回购的实质是短期贷款。个人投资者主要是通过这一操作达到资金增值,央行主要的目的是轻量化的货币调控。 这一点也有所不同。

逆回购信息

产品分类

一般股票交易软件和股票信息网站都可以查询到逆回购信息。以雪球网为例:https://xueqiu.com/S/SH204001 是上证的1日逆回购的利息波动图。实际上,沪市、深市都有一系列逆回购的产品。按照实际使用资金的时间,一般分为1天、2天、3天、4天、7天、14天、28天、91天、182天等。

交易代码

沪市的代码一般为:GC***,星星的位置以天数代替,不足3位的补零。如14天的代码为GC014。

深市的代码一般为:R-***, 规则同沪市。

交易限额

沪市的以10万为单位,一个10万元为一份,交易只能“按份”递增,至少一“份”。

深市的以一千为单位,一个一千元为一份,交易也只能“按份”递增,至少一“份”。

怎么操作

  1. 在券商开户
  2. 向券商声明开通国债逆回购项目
  3. 在券商账户内放入一定的余额
  4. 搜索交易代码(规则见前面)
  5. 进入交易界面,点“委托卖出”
  6. 默认有委托价格,可以修改,越高收益越好。不过好不好出手就不好说了:-(
  7. 确认,如果委托交易成功,就等待收益吧。

收益怎么算

  1. 利息为委托交易的年化利息。
  2. 交易会扣除一定量的手续费
  3. 资金可用时间为交易日,如果周五进行一日逆回购交易,则也要等周一方可使用资金。所以实际收益率会打折扣。
  4. 交易前注意看收益天数,节前一般会有机会一日逆回购,一周左右收益。
  5. 综上,实际收益为 = 年化利息 × 投资金额 × 天数 / 365 / 100 - 手续费
  6. 一般重大节日前、季度末经常出现高收益国债逆回购。同时如果跨越长假,在长假期间均有收益。

阅读全文 »

Leon82 发布于 09月11, 2018

树莓派,不玩不知道(一)

何处望神州?满眼风光北固楼。千古兴亡多少事?悠悠。不尽长江滚滚流。年少万兜鍪,坐断东南战未休。天下英雄谁敌手?曹刘。生子当如孙仲谋。 ——宋·辛弃疾《南乡子·登京口北固亭有怀》

源起

2016 年前后,在技术论坛里面看到 Raspberry Pi(树莓派)的广告的时候,这种小巧、廉价、便携的设备,深深打动了笔者。积习慢慢侵袭,二话不说果断地入手一台。在短暂的折腾了一阵之后,就放在了一边。近期,WoT 大潮正以迅雷不及掩耳之势席卷而来,笔者想到这个躲在角落吃灰的玩意儿,立马翻出来,吹一吹上面的灰尘......

半个小时后,当尘烟渐渐散去,一个干净如新的 Raspberry Pi 2 跃然眼前,虽然型号已经有一些过时,但是,鉴于硬件架构是类似的,这个“中年”还是可以充分发挥余热的。是的,正如网上流行的观点,“不要大声责骂年轻人,他们会立刻辞职的,但是你可以往死里骂那些中年人”。那吾辈在此,不妨往死里糙磨这个“中年”树莓派。

但是,这番折腾,还是需要一些目标的。这样,我们就可以不断地从目标的达成中,获得正反馈,进而给坚持不断探索以坚实的理由——在用拖把擦拭地板时候,我这样想。

创意总是丰富的,搜索知乎上可以找到不少相关的问题,比如这个:有哪些对树莓派的有趣改造和扩展应用。笔者从这里找到很多好玩的创意,非常希望能将这些创意在树莓派上实现。为此,开设这个系列,希望能够记录一些小创意的动手实现过程,并且给读者今后做这些应用一些借鉴。

认识树莓派

树莓派,最早由英国剑桥某实验室由物理学家埃本·阿普顿花费六年时间研制,于 2012 年 3 月开始正式发售。树莓派外形仅有一张信用卡大小,却具有电脑的所有基本功能。

大多数的树莓派都支持多个 USB 接口,以及以太网接口。树莓派的 CPU 架构为 ARM,与传统意义上的个人电脑的 x86 架构不同。这篇文章详述了两种架构的区别。体积小、低功耗、低成本的 ARM 架构 CPU 在移动设备、单片机等设备上已经得到了广泛使用。ARM 架构的这些优点也正是和树莓派的研发目的高度契合的。

树莓派最新的版本是 3B+,有着更强劲的处理器,更快的网络支持,同时直接整合了蓝牙适配器,支持蓝牙 4.2 协议。目前很多物联网的研究也基于树莓派实现。

作为一个单板计算机,树莓派有很多操作系统可供使用。这里推荐树莓派官方的 Raspbian,这个系统基于 Linux 的发行版 Debian 进行了深度定制。安装上这个系统,你的树莓派可以获得 Linux 的系统能力、基础设备的驱动,以及浩如烟海的高质量软件包。

树莓派包含有 40 组 GPIO 输入输出引脚,通过高低电平的变化感知,可以与传感器配合做出一系列非常有意思的应用。很多高级语言(环境)如 C/C++、Python 和 NodeJS 都支持对 GPIO 操控的封装。这也是树莓派的一个非常吸引人的特色。

除了树莓派,还有很多类似的单板计算机产品,如香蕉派、Arduino 等,有兴趣的读者可以延伸阅读相关资料。

需要购置的最少设备

让我们开始我们的树莓派之旅。

不同于其他的软件实践,第一个拦路虎就是需要购置一定的设备。还是之前的原则,新手上路,可以循序渐进。先让树莓派“跑起来”,然后在不断的正向反馈中,慢慢入坑。

为了确定最少的设备投入,需要先来确认这一次要做什么,达成哪些目标。

经过一番审慎的计划,笔者把这次的目标定为:使用树莓派,搭建一个内网可以访问的 NodeJS 网站开发环境。

首先,我们需要一个树莓派主机,即一块单板,最新版的是 3B+。笔者这边是之前的树莓派 2。

其次,为了保证主机可以免受周围环境的影响,同时也保证取用放置方便,所以我们需要给主机一个外壳。

再有,为了散热方便,你可以购置一些散热片或散热风扇。考虑散热效果和操作便利性,建议购买散热风扇。

另外,树莓派的系统一般烧录在 SD 卡上。卡的读取速度,会影响整个树莓派的运行效率,因此建议使用高速卡。卡的大小最好大于 4GB(话说现在已经很少见到这个容量以下的卡了,也很少见到低速卡了),同时还需要一个读卡器。如果笔记本电脑已经配备了读卡器,则这项开支时可以省下的。

为了第一次将树莓派连上网,请准备一条网线。

同时,如果有无线路由器,为了操作方便,建议再购置一个无线网卡。

最后,如果你不想每次关机就大把抓拔电源,可以买一个带开关的电源。

OK 了,上述东西已经足够完成这次的目标了。

读者可以从某宝某东上搜索相关的产品进行购买,或者从官网的购买链接点击到对应购买页面,原则上这些东西 200-400 大洋就能搞定。

来吧,上面这些东西来个开箱照片。

将它们攒起来,还是很妥帖的。

当然,后面我们的系列文章中会出现传感器、跳线等小元件。笔者感觉周期性的购置那些,才是最烧钱的环节:-)

安装系统

你的树莓派和你的小宇宙都准备好爆发了么?在爆发之前,先把操作系统安好。

如前文所说,建议先安装官方推荐的 Raspbian。读者可以去这里下载安装包。目前有两个版本,一个是包含桌面版的,安装包比较大,如果你配置好了相关的显示器和键盘鼠标可以考虑这一版。如果暂时不需要桌面支持(需要时候可以再安装),可以考虑下载 Lite 版。

这里,我们以 Lite 版为例。

下载回来的是一个 Zip 包,借助压缩包查看工具看到,压缩包里面包含一个.img 文件,我们将把它烧录到 SD 卡上面。

完成这项工作,需要借助一些工具。这里,官方推荐的是 Etcher。这个软件是一个图形化的写卡工具,有 Mac、Linux 以及 Windows 的对应版本。读者遵循下列步骤即可完成烧录。

  1. 这里下载符合你的系统的 Etcher,并安装之。
  2. 连接读卡器,插入待写入的 SD 卡
  3. 运行 Etcher,选择刚刚下载的 Zip 文件或解压过的.img 文件。
  4. 选择待写入的 SD 卡。
  5. 点击“Flash”,然后去泡一杯茶或者咖啡,等待烧录完成。

初始化系统

确认 IP

装好了系统之后,我们将其通过网线接入路由器。此时,树莓派会自动连接上网络。因为没有购买显示屏和键盘鼠标,可以通过 SSH 远程连接到树莓派进行操作。现在的问题在于如何确认树莓派的 IP。

有几种方案,这里介绍一种最简便的方案:用手机或 PC(如果没有......,那还是给树莓派配上显示器和键盘鼠标吧)打开路由器的管理界面(不了解这个界面的,可以咨询路由器厂商。如果没有特殊设置过,一般是 http://192.168.0.1/admin ),并转到设备管理页面,此时会发现名为 raspberry 设备,此时可以记下它的 IP (如 192.168.0.10)

用户设置

现在可以用 SSH 客户端连接树莓派,如果读者使用的是 Mac 或者 Linux ,系统会自带 SSH 连接终端,如果是 windows,请读者下载安装 Putty 等 SSH 连接软件。

如果树莓派的 SSH 服务没有打开,在烧录好镜像之后,在boot分区中新建一个名为ssh的文件夹,正常开机,通过 SSH 即可连接到树莓派。

默认地,树莓派分配了一个用户 pi,默认密码是:raspberry。因为是默认的,为了安全第一步应该把登录上去把它的密码修改掉。

ssh pi@192.168.0.10

在提示符下我们可以输入 raspberry 完成登录。立即修改密码。

passwd

此时,根据弹出的提示,修改掉默认的密码。这样可以在一定程度上,提高树莓派的安全性。

另外,建议新建一个用户作为常用账户,如:

sudo adduser leon
sudo passwd leon

这样就建立了一个名为 leon 的新用户,同时可以为 leon 设置账号密码。

为了能够进行系统级的操作,可以将 leon 加入 sudoer。

sudo visudoer

在编辑界面下,将 leon 加入列表并保存。以后,SSH 登录树莓派就可以使用 leon 作为用户名了。

配置工具

树莓派本身也提供一个配置工具界面。在命令行下键入命令sudo raspi-config。显示如下图。

更换源

在中国大陆的同学,建议更换国内的源,以保证更快的下载速度。这里使用阿里的源,如果读者有更好的源,可以类似地更换。

因为我们安装的系统是以 Debian 发行版作为基础。所以 Debian 的软件包管理工具在这里是可用的。同理可知,换源的方法也是类似的。

# 先备份,后悔药先备上
sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak

# 打开软件源列表文件
sudo vim /etc/apt/sources.list

下面,注释掉原来的源,添加以下两行

deb http://mirrors.aliyun.com/raspbian/raspbian/ wheezy main non-free contrib
deb-src http://mirrors.aliyun.com/raspbian/raspbian/ wheezy main non-free contrib

这里需要注意的是,不同的 Raspbian 系统版本, wheezy这一列可能会变成 Jessies。造成这种变化的原因是,基于不同的 Debian 系统,系统的名称不同。稳妥的办法是参照原来的源的写法,仅仅修改掉域名即可。

修改完毕后,可以运行sudo apt-get upgrade完成软件包资源索引的更新。成功更新后,就可以高速下载对应的软件了。

关于中文乱码

解决的办法是安装中文字库和输入法,同时修改系统的默认字符集。

sudo apt-get install ttf-wqy-zenhei
sudo apt-get install scim-pinyin

在上面所说的配置工具中,选择第 5 项,根据提示进行配置。接下来的屏幕,选择 Change Locale

在接下来的屏幕中,去掉 en_GB.UTF-8 UTF-8,选中“en_US.UTF-8 UTF-8”、“zh_CN.UTF-8 UTF-8”和“zh_CN.GBK GBK”。并在下一屏幕默认语言选 zh_CN.UTF-8。

重启树莓派,使得结果生效。

更换时区

默认地,树莓派的时区是 UTC0,对于时间相关的应用会造成问题。中国大陆位于东八区,我们可以通过sudo raspi-config来设置。

选择 亚洲 > ShangHai 切回主页面后按 Finish 保存退出。 即可完成设置。

无线网卡配置

如果你没有移动树莓派的需求,那么插着网线就能解决网络的问题了,如果是这种情况,读者可以跳过这一节。不过,如果今后有想法做需要移动的应用,或者不喜欢插网线,还是推荐装好无线网卡。

首先,你需要确认你的无线网卡已经被正确驱动。可以通过连接树莓派,输入ifconfig命令查看所有树莓派的网络设备。不能找到网卡的情况很少见,如果不幸遇到,请先确认网卡可用,并寻找对应的驱动程序安装。

下面,准备好相应的 WIFI 信息。编辑wpa_supplicant.conf文件。sudo vim /etc/wpa_supplicant/wpa_supplicant.conf,在此文件结尾增加,如下代码:

network={
    ssid="WIFI名称"
    key_mgmt=WPA-PSK
    psk="WIFI密码"
}

这里注意:

  1. ssid 名称不推荐中文
  2. 最好不要带有.等特殊符号

此时即可重启网络 /etc/init.d/networking restart

可以再次使用ifconfig查看网络。同时可以打开上面提到的路由器管理页面,查看是否有新设备加入。如果你的无线网卡被发现了,你就可以:

  1. 放心的拔掉网线
  2. 连接 IP,使用无线网卡的 IP。

安装 Web 服务器软件

上面我们已经把系统的主要功能做了配置。下面我们开始进行 Web 服务器软件的配置。这里我们使用 Nginx 作为服务器软件。

因为系统是基于 Debian 的,并且已经配置了较快的软件源,所以下面的操作是比较愉快的。

sudo apt-get install nginx -y

此时,Nginx 的默认 www 根目录在/usr/share/nginx/www,你可以修改 /etc/nginx/sites-available/default 来配置网站的目录、端口、代理等等。

配置 NodeJS 开发环境

理论上,可以通过sudo apt-get install nodejs来安装。但是,这种方法安装的 NodeJS 版本可能会比较旧。如果从源码安装,鉴于树莓派的处理能力比较弱,故而不推荐。

这里推荐一种比较省力和完善的方法。

  1. 使用uname -a 查询系统版本,找到 arm***字样。
  2. 打开 https://nodejs.org/dist/latest/ 找到符合这个“arm”的软件包,下载之。
  3. 解压缩。
  4. 将解压过的 node 文件夹,如“node-v10.10.0-linux-armv7l”移动到/usr/local/目录下,将其重命名为 node。
  5. 运行echo PATH=$PATH:/usr/local/node/bin >> ~/.bashrc; source ~/.bashrc

如此即可完成 NodeJS 的安装。

安装完成后,我们就可以自由发挥创造力来构建基于 NodeJS 的网站。这里推荐一下奇舞团出品的 NodeJS 框架 ThinkJS,最新的版本是 3.2.8。官网是:https://www.thinkjs.org/ 。我们基于这个框架可以快速的搭建网站。

看,下面就是 ThinkJS 的表演时间,美滋滋 :-)

小结

本篇文章中,笔者带大家简单了解了树莓派,并详述了系统安装和一些系统初始化配置,安装了网站服务器环境和 NodeJS 开发环境。借此完成了树莓派的新手上路。有关传感器等更加有趣的内容,将在今后的系列文字中不断出现。笔者也会加紧时间,给大家呈现更有意思的树莓派。

致谢

感谢何文力为本文提出的修改建议。设计师王旋 mm,为本文设计的精美题图,在此一并表示诚挚的谢意。

阅读全文 »

Leon82 发布于 08月08, 2018

CSS Grid 28关通关秘籍

十年磨一剑,霜刃未曾试。今日把示君,谁有不平事。 —— 唐·贾岛《剑客》

游戏概况

Grid GardenCodepip创建的一款寓教于乐的在线网页游戏,游戏共有28关。玩家可以通过过关的方式掌握CSS最新标准CSS Grid。

游戏的设定是一个花园种植胡萝卜的场景,玩家通过在代码区填写CSS Grid的相关代码完成除草、浇水等任务。通过玩家的辛勤劳作,一定能够吃上纯天然、无公害的胡萝卜。

打开游戏,我们发现,游戏存在多语言版。在左侧底部就可以切换各种语言。事实上,笔者对自己的英语水平是非常有信心的,所以毫不犹豫的切换到——简体中文版。

除了代码区和任务区,玩家可以在选关区选择28关的任意一关来挑战;当玩家在代码区敲入代码时候,右侧的任务和结果展示区会实时根据代码展现结果。如果代码完成了任务,则点击提交按钮,会进入下一关,如果玩家通关的话,则展示通关特效;如果代码不能完成任务,提交按钮会灰掉。如果玩家硬来,代码区会有一个错误特效供玩家欣赏:-(

除了游戏本身,游戏的目的是加深玩家对CSS Grid的理解。说到CSS Grid,这可是一种强大的Web页面布局方式。恰当的使用CSS Grid,可以高效地解决很多常见的布局问题,而且优雅、简洁。完整的CSS Grid属性参考,可查阅这里。由于CSS Grid标准尚属CR(CR,Candidate Recommendation)阶段,如果你是最新标准的爱好者,还可以跟进CSS工作组关于CSS Grid的最新进展

尽管如此,现在主流浏览器都已经有了不同程度的支持,支持度如下图所示:

说到这里,各位都迫不及待地想要在游戏中一试身手了吧,那么话不多说,Let's Go。

过关实录

跟网格项玩耍

也许各位玩家完全没有接触过CSS Grid,刚刚进来可能会有些不知所措。我们姑且认为前面几关是教学关。一般游戏的教学关都会有一个人物在屏幕上蹦来蹦去外加叨逼叨来普及各种概念和操作。那么笔者现在就来饰演一下这个人物。

  1. CSS Grid元素主要分为两大类:网格容器网格项网格容器是父元素,网格项是子元素。
  2. 对于网格容器网格项各有不同的属性修饰。
  3. 声明Grid布局要做的事情是在网格容器的CSS代码中指定display: grid;display: inline-grid;或者display: subgrid;
  4. 网格线构成网格结构的分界线,是定位网格项的参照。下图就是行row和列column的第一个网格线的位置。换句话说,对于一个每行有5列的网格,它的每一行总共有6个网格线。如果这点看不清楚,那可能需要复习植树问题了:-(

  1. 网格轨道是指相邻的网格线之间的部分,下图箭头所指是一个网格轨道。

掌握了这些知识我们就可以开始过关之旅了。

第1关到第11关设置主要是针对网格项属性grid-column-startgrid-column-end展开的,相当的简单,相信玩家一定可以很快的完成。

下面简单总结一下第1-11关:

  1. grid-column-startgrid-column-end作用于网格项
  2. 上述值可配合使用来解决跨行跨列问题。
  3. grid-column-startgrid-column-end中,start不一定比end小,逆向是被允许的。
  4. 可以设置负值,负值的意思是从最后一个网格线算起的数值。
  5. 除了取数值外,还可以使用span关键字。格式是span <number>意思是跨越多少个网格轨道
  6. 可以使用grid-column: <start>/<end>来简写, span关键字适用此缩写。

上面可能出错的地方在于,设置数值时候,是确定网格线的顺序而非网格轨道的顺序,尤其是负数时候,而span后面跟着的数字是网格轨道的个数。只要牢记这点就很容易。

第12关与第13关,主要展示了CSS Grid在行row上设置的能力,二维空间的设置是Grid布局比flex布局拓展的一个方面。

这两关也比较简单。

从第14关开始,我们开始综合运用行与列的属性。 第14和15关的过关,需要灵活利用上述关键字。规范中还可以给轨道线命名,这里暂时没有遇到,我们先不使用“命名”这个利器。

第16关的意思是可以行列的简写方式,依然可以使用grid-area属性再次化简,grid-area接收4个由/隔开的值,依次为:grid-row-start, grid-column-start, grid-row-end, grid-column-end

第17关告诉我们,重迭覆盖是不影响计算机制的。

依然很简单,过。

针对17关的重叠,第18-19关引入了属性关键字:orderorder类似于z-index,表明叠放顺序,数值越大,越在上。允许负数。

很简单是不是。

跟网格容器玩耍

上面我们对网格项的“一波操作猛如虎”,下面我们再来看一看,对于网格容器的操作,能不能“横扫千军我做主”。

第20关到第22关主要针对网格容器的属性grid-template-columnsgrid-template-rows展开的。

下面简单总结一下第20-22关:

  1. grid-template-columnsgrid-template-rows用于设置Grid布局的行列中网格轨道的大小
  2. repeat函数可以简化多个同值,格式为repeat(N, value),其中N是个数,value是值。repeat可以与其他值混用,如:grid-template-columns: repeat(N-1, value) value
  3. 定义上述属性时,允许长度单位混合使用。

第23-25关,主要说明了关键字fr的使用。

下面总结一下第23-25关:

  1. fr是“分数”的英文单词fraction的简写。
  2. fr用于等分等分网格容器剩余空间。那么fr是怎么分配空间的呢?举个例子说明:设有A、B、C三个网格轨道,他们的grid-template-columns的设置依次是1fr2fr3fr。那么他们共同把一个行分为6等分,则A,B,C的空间就依次获得了这一行的1/6、2/6和3/6。
  3. fr是可以和其他单位混用的,如grid-template-columns: 1fr 50px 1fr 1fr;。计算优先级记住一点即可:除了auto之外,先计算所有固定值(包括百分数)后,剩下的空间再计算fr

第26关介绍grid-template-rows与前面的grid-template-columns语法类似。留给玩家尝试。

第27关介绍了grid-template-columnsgrid-template-rows的简写方式grid-template,写法是:grid-template-rows / grid-template-columns

经历了百转千回,我们终于来到了关底,我们来看看大BOSS的尊容:

WTF?只能写一行代码么? 仔细想想:grid-template最简洁,格式是/隔开的先行后列。 先解决行:需要把50px先分出去,后面100%给到花草。再解决列,列的场景是典型的fr使用场景,杂草占空间的1/5,胡萝卜占4/5。

于是代码是:grid-template: 1fr 50px/1fr 4fr;

Bingo!恭喜你,通关成功!

结语

是的,我们已经最快速度领略了CSS Grid的风采。然而,对于整个的CSS Grid我们仅仅做了最常用的展示,更多的好玩的做法,还要等待大家的发掘,以及标准的演进。

致谢

感谢李松峰老师、高峰、刘博文对本文修订提出的中肯意见。 设计师王旋美眉帮忙设计了精美的题图。 在此诚挚的表示感谢。

阅读全文 »

Leon82 发布于 07月31, 2018

20分钟上手WebAssembly

背景

Web应用的蓬勃发展,使得JavaScript、Web前端,乃至整个互联网都发生了深刻的变化。前端开始承担起了更多的职责,于是对于执行效率的诉求也就更为急迫。除了在语言本身的进化,Web从业者以及各大浏览器厂商,也在不停地进行探索。2012年Mozillia的工程师提出了Asm.js和Emscripten,使得C/C++以及多种编程语言编写的高效程序转译为JavaScript并在浏览器运行成为可能。

更进一步地,WebAssembly(简称wasm)技术被提出,并迅速成立了各种研发组织,各种周边工具链的不断完善,相关实验数据也有力地佐证了这条优化和加速路线的可行性。

特别是2018年,W3C的WebAssembly工作组发布了第一个工作草案,包含了核心标准JavaScript API以及Web API。另外,除了C/C++和Rust之外,Golang语言也正式支持了wasm的编译。我们罕见的看到,各大主流浏览器一致表示支持这一新的技术,也许一个崭新的Web时代即将到来。

阅读全文 »