防抖与节流

防抖

防抖要解决的问题:阻止一个事件被频繁触发。
例如:当我们监听浏览器的滚动条事件时,如果不做防抖,就会发生滚动一次鼠标滚轮,事件被触发6-8次。
应用场景:等用户的鼠标滚轮滚动到某一个地方,停止滚动1秒之后才执行函数。
解决思路:设置一个触发函数执行的「等待时间」,当「等待时间」结束之后才可以被执行。

function debounce(fn, wait) {
  var timer = null;
  return function () {
      var context = this
      var args = arguments
      if (timer) {
          clearTimeout(timer);
          timer = null;
      }
      timer = setTimeout(function () {
          fn.apply(context, args)
      }, wait)
  }
}

var fn = function () {
  console.log('boom')
}

setInterval(debounce(fn,500),1000) // 第一次在1500ms后触发,之后每1000ms触发一次

setInterval(debounce(fn,2000),1000) // 不会触发一次(我把函数防抖看出技能读条,如果读条没完成就用技能,便会失败而且重新读条)

节流

节流要解决的问题:和防抖一样,都是阻止一个事件被频繁触发
区别:防抖是「等待时间」结束之后再执行函数,而节流是先执行函数,然后再计时,确保计时没有结束之前函数不会再次执行。
应用场景:用户在网页的Input表单输入框中按住了某一个按键,但是我们要做到函数不被连续触发,而是每1秒执行1次

function throttle(fn, gapTime) {
  let _lastTime = null;

  return function () {
    let _nowTime = + new Date()
    if (_nowTime - _lastTime > gapTime || !_lastTime) {
      fn();
      _lastTime = _nowTime
    }
  }
}

let fn = ()=>{
  console.log('boom')
}

setInterval(throttle(fn,1000),10)

 

1. 介绍 – 前端设计模式系列教程

学习内容简介

1. 面向对象

Class语法 – webpack和babel搭建es6编译环境
三要素 – 继承、封装、多态
UML类图

2. 设计原则

概念 – 《Linux/Unix设计 哲学》
5大设计原则
从设计到模式

3. 设计模式

优先级 – 创建型、结构型、行为型
核心
结合框架

4. 综合示例

设计方案 – 设计分析,画UML类图
代码演示 – jQuery实现一个购物车
设计模式对应关系 – 总结7种设计模式怎么合起来使用

学习目标

获得面向对象思想,学会画UML类图
理解5大设计原则,23种设计模式
相关面试题
提升设计能力

1. Getting Started

1.1 About Version Control

Local Version Control Systems
The method of choice is to copy files into another directory (perhaps a time-stamped directory, if they’re clever). This approach is very common because it is so simple, but it is also incredibly error prone.

Centralized Version Control Systems
These systems have a single server that contains all the versioned files, and a number of clients that check out files from that central place.

Distributed Version Control Systems
Every client have fully mirror the repository, including its full history.

1.2 A Short History of Git

A Short History of Git
As with many great things in life, Git began with a bit of creative destruction and fiery controversy.

1.3 What is Git?

Snapshots, Not Differences
These other systems store as a set of files and the changes made to each file over time. this is the Differences.

Nearly Every Operation Is Local
you have the entire history of the project right there on your local disk, most operations seem almost instantaneous.

Git Has Integrity
ou can’t lose information in transit or get file corruption without Git being able to detect it.

Git Generally Only Adds Data
in Git, nearly all of them only add data to the Git database.

The Three States
Git has three main states that your files can reside in: modified, staged, and committed:
Modified means that you have changed the file but have not committed it to your database yet.
Staged means that you have marked a modified file in its current version to go into your next commit snapshot.
Committed means that the data is safely stored in your local database.

The basic Git workflow goes something like this:
You modify files in your working tree.
You selectively stage just those changes you want to be part of your next commit, which adds only those changes to the staging area.
You do a commit, which takes the files as they are in the staging area and stores that snapshot permanently to your Git directory.

1.6 Getting Started – First-Time Git Setup

First-Time Git Setup
Git comes with a tool called git config that lets you get and set configuration variables, These variables can be stored in three different places:
[path]/etc/gitconfig file: Contains values applied to every user on the system and all their repositories.
~/.gitconfig or ~/.config/git/config file: Values specific personally to you.
config file in the Git directory (that is, .git/config): Specific to that single repository.
You can view all of your settings and where they are coming from using:

$ git config --list --show-origin

 

 

3 制作第一个游戏 – Cocos 系列教程

下面我们将跟随教程制作一款名叫 “一步两步” 的小游戏。这款游戏考验玩家的反应能力,根据路况选择是要跳一步还是跳两步。
可以在https://gameall3d.github.io/MindYourStep_Tutorial/index.html 体验一下游戏的完成形态。

1 新建项目

新建一个名为 MindYourStep 的空项目

2 创建场景

游戏场景(Scene) 是开发时组织游戏内容的中心,一般会包括以下内容:

  • 场景物体
  • 角色
  • UI
  • 逻辑脚本

当玩家运行游戏时,就会载入游戏场景,游戏场景加载后就会自动运行所包含组件的游戏脚本

  1. 在 资源管理器 中点击选中 assets 目录,新建一个文件夹,命名为 Scenes。
  2. 选中 Scenes 目录,点击鼠标右键,新建一个scence,将它重命名为 Main。

3 添加跑道

我们的主角需要在一个由方块(Block)组成的跑道上从屏幕左边向右边移动。我们使用编辑器自带的立方体(Cube)来组成道路。

  1. 在 层级管理器 中创建一个立方体(Cube),并命名为 Cube。
  2. 选中 Cube,按 Ctrl + D 复制出 3 个 Cube。
  3. 将 3 个 Cube 按以下坐标排列:
    • 第一个节点位置(0,-1.5,0)
    • 第二个节点位置(1,-1.5,0)
    • 第三个节点位置(2,-1.5,0)

4 添加主角

层级管理器中创建一个名为 Player 的空节点;
然后在这个空节点下创建名为 Body 的主角模型节点,为了方便,我们采用编辑器自带的3D对象胶囊体
使用空节点和子节点的方式,把对象分为两个节点的好处是,我们可以使用脚本控制 Player 节点来使主角进行水平方向移动,而在 Body 节点上做一些垂直方向上的动画(比如原地跳起后下落),两者叠加形成一个跳越动画。

5 编写主角脚本

首先在 资源管理器 中右键点击 assets 文Sc件夹,选择 新建 -> 文件夹,重命名为 Scripts。
右键点击 Scripts 文件夹,选择 新建 -> TypeScript,创建一个 TypeScript 脚本。
名字改为 PlayerController,双击这个脚本,打开代码编辑器(例如 VSCode)。
注意:Cocos Creator 中脚本名称就是组件的名称,这个命名是大小写敏感的!
复制下面代码:

import { _decorator, Component, Vec3, systemEvent, SystemEvent, EventMouse, Animation } from 'cc';
const { ccclass, property } = _decorator;

@ccclass("PlayerController")
export class PlayerController extends Component {
    /* class member could be defined like this */
    // dummy = '';

    /* use `property` decorator if your want the member to be serializable */
    // @property
    // serializableDummy = 0;

    // for fake tween
    private _startJump: boolean = false;
    private _jumpStep: number = 0;
    private _curJumpTime: number = 0;
    private _jumpTime: number = 0.1;
    private _curJumpSpeed: number = 0;
    private _curPos: Vec3 = new Vec3();
    private _deltaPos: Vec3 = new Vec3(0, 0, 0);
    private _targetPos: Vec3 = new Vec3();
    private _isMoving = false;

    start () {
        // Your initialization goes here.
        systemEvent.on(SystemEvent.EventType.MOUSE_UP, this.onMouseUp, this);
    }

    onMouseUp(event: EventMouse) {
        if (event.getButton() === 0) {
            this.jumpByStep(1);
        }
        else if (event.getButton() === 2) {
            this.jumpByStep(2);
        }

    }

    jumpByStep(step: number) {
        if (this._isMoving) {
            return;
        }
        this._startJump = true;
        this._jumpStep = step;
        this._curJumpTime = 0;
        this._curJumpSpeed = this._jumpStep / this._jumpTime;
        this.node.getPosition(this._curPos);
        Vec3.add(this._targetPos, this._curPos, new Vec3(this._jumpStep, 0, 0));

        this._isMoving = true;
    }

    onOnceJumpEnd() {
        this._isMoving = false;
    }

    update (deltaTime: number) {
        if (this._startJump) {
            this._curJumpTime += deltaTime;
            if (this._curJumpTime > this._jumpTime) {
                // end
                this.node.setPosition(this._targetPos);
                this._startJump = false;
                this.onOnceJumpEnd();
            } else {
                // tween
                this.node.getPosition(this._curPos);
                this._deltaPos.x = this._curJumpSpeed * deltaTime;
                Vec3.add(this._curPos, this._curPos, this._deltaPos);
                this.node.setPosition(this._curPos);
            }
        }
    }
}

把 PlayerController 组件添加到主角节点 Player 上。
在 层级管理器 中选中 Player 节点,然后在 属性检查器 中点击 添加组件 按钮。
选择 自定义脚本 -> PlayerController,为主角节点添加 PlayerController 组件。
为了能在运行时看到物体,我们需要将场景中 Camera 的参数进行一些调整,位置Position 设置为(0,0,13),角度Rotation(0,0,0),背景颜色Color 设置为(50,90,255,255)
然后点击工具栏中心位置的 Play 按钮在浏览器中预览

网页中点击鼠标左键和右键,可以看到胶囊移动

6 添加角色动画

 

 

2 编辑器面板 – Cocos 系列教程

(A)层级管理器:以树状列表的形式展示场景中的所有节点和它们的层级关系,所有在 场景编辑器 中看到的内容都可以在 层级管理器 中找到对应的节点条目,在编辑场景时这两个面板的内容会同步显示,一般我们也会同时使用这两个面板来搭建场景。
(B)资源管理器:显示了项目资源文件夹(assets)中的所有资源。这里会以树状结构显示文件夹并自动同步在操作系统中对项目资源文件夹内容的修改。您可以将文件从项目外面直接拖拽进来,或使用菜单导入资源。
(C)场景编辑器:用于展示和编辑场景中可视内容的工作区域。通过在场景编辑器中搭建场景,即可获得所见即所得的场景预览。Ï
(D)动画编辑器:用于编辑并存储动画数据。
(E)属性检查器:用于查看并编辑当前选中节点和组件属性的工作区域,这个面板会以最适合的形式展示和编辑来自脚本定义的属性数据。
(F)项目预览:在场景搭建完成之后,在 Web 或原生平台预览游戏的运行效果。

1 Hello World – Cocos 系列教程

安装

1 下载 Dashboard
在官网的下载页面 https://www.cocos.com/creator 选择对应的系统版本进行下载

2 安装编辑器
安装完Dashboard之后点击左侧的Editor标签进行编辑器安装,建议选择3.0,因为目前官网文档最高支持到3.0

3 Hello World
选择Dashboard的项目标签;
点击右下角的新建按钮;
上面选择第2个Creator模板(Hello World),然后点击右下角的创建并打开
左下方的资源管理器面板中点击鼠标右键,选择创建-> Scene
在左上方的层级管理器面板中点击右键,选择创建->3D对象 -> Cube立方体
在 资源管理器 面板中点击鼠标右键,选择 创建 -> TypeScript,然后命名为 “HelloWorld”
双击新建的HelloWorld脚本,脚本会自动在脚本编辑器中打开,然后在start方法中添加console语句如下:

start () {
// Your initialization goes here.
console.info('Hello world');
}

在 层级管理器 中选中创建的 Cube 节点,然后在 属性检查器 面板最下方点击 添加组件 -> 自定义脚本 -> HelloWorld

4 预览场景
场景搭建完成后,就可以点击编辑器上方的 预览(三角形图标) 按钮来预览游戏了

5 修改摄像机角度
在预览中我们可以看到立方体似乎有点太小了,这时便可以通过调整场景中的 Camera 来调整场景运行时显示的区域,Camera 代表的是游戏中的玩家视角。
首先在 层级管理器 中选中 Main Camera 节点,场景编辑器 中便会显示变换工具 Gizmo,以及玩家视角的小窗口。
然后在 场景编辑器 中拖动 Gizmo,或者修改 属性检查器 中的 Position 属性,使玩家视角窗口中的立方体显示得更为明显。
再次在浏览器中预览

6 项目结构
初次创建并打开一个 Cocos Creator 项目后,开发者项目文件夹的结构如下:
assets:资源目录
build:构建目录(在构建某平台后会生成该目录)
library:导入的资源目录
local:日志文件目录
profiles:编辑器配置
temp:临时文件目录
package.json:项目配置

7 版本控制
开发者应该注意只需要提交 assets、extensions、settings、package.json,或其它手动添加的关联文件。

语义化版本控制规范(SemVer)

摘要

版本格式:主版本号.次版本号.修订号,版本号递增规则如下:

  1. 主版本号:当你做了不兼容的 API 修改,
  2. 次版本号:当你做了向下兼容的功能性新增,
  3. 修订号:当你做了向下兼容的问题修正。
    先行版本号及版本编译元数据可以加到“主版本号.次版本号.修订号”的后面,作为延伸。

规范

以下关键词 MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、SHOULD NOT、 RECOMMENDED、MAY、OPTIONAL 依照 RFC 2119 的叙述解读。(译注:为了保持语句顺畅, 以下文件遇到的关键词将依照整句语义进行翻译,在此先不进行个别翻译。)

  1. 使用语义化版本控制的软件必须(MUST)定义公共 API。该 API 可以在代码中被定义或出现于严谨的文件内。无论何种形式都应该力求精确且完整。

  2. 标准的版本号必须(MUST)采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数,且禁止(MUST NOT)在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须(MUST)以数值来递增。例如:1.9.1 -> 1.10.0 -> 1.11.0。

  3. 标记版本号的软件发行后,禁止(MUST NOT)改变该版本软件的内容。任何修改都必须(MUST)以新版本发行。

  4. 主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。

  5. 1.0.0 的版本号用于界定公共 API 的形成。这一版本之后所有的版本号更新都基于公共 API 及其修改内容。

  6. 修订号 Z(x.y.Z | x > 0)必须(MUST)在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。

  7. 次版本号 Y(x.Y.z | x > 0)必须(MUST)在有向下兼容的新功能出现时递增。在任何公共 API 的功能被标记为弃用时也必须(MUST)递增。也可以(MAY)在内部程序有大量新功能或改进被加入时递增,其中可以(MAY)包括修订级别的改变。每当次版本号递增时,修订号必须(MUST)归零。

  8. 主版本号 X(X.y.z | X > 0)必须(MUST)在有任何不兼容的修改被加入公共 API 时递增。其中可以(MAY)包括次版本号及修订级别的改变。每当主版本号递增时,次版本号和修订号必须(MUST)归零。

  9. 先行版本号可以(MAY)被标注在修订版之后,先加上一个连接号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且禁止(MUST NOT)留白。数字型的标识符禁止(MUST NOT)在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。范例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。

  10. 版本编译元数据可以(MAY)被标注在修订版或先行版本号之后,先加上一个加号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且禁止(MUST NOT)留白。当判断版本的优先层级时,版本编译元数据可(SHOULD)被忽略。因此当两个版本只有在版本编译元数据有差别时,属于相同的优先层级。范例:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。

  11. 版本的优先层级指的是不同版本在排序时如何比较。
    判断优先层级时,必须(MUST)把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较(版本编译元数据不在这份比较的列表中)。
    由左到右依序比较每个标识符,第一个差异值用来决定优先层级:主版本号、次版本号及修订号以数值比较,例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。
    当主版本号、次版本号及修订号都相同时,改以优先层级比较低的先行版本号决定。例如:1.0.0-alpha < 1.0.0。
    有相同主版本号、次版本号及修订号的两个先行版本号,其优先层级必须(MUST)透过由左到右的每个被句点分隔的标识符来比较,直到找到一个差异值后决定:
    只有数字的标识符以数值高低比较,有字母或连接号时则逐字以 ASCII 的排序来比较。
    数字的标识符比非数字的标识符优先层级低。
    若开头的标识符都相同时,栏位比较多的先行版本号优先层级比较高。
    范例:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0。

为什么要使用语义化的版本控制?

这并不是一个新的或者革命性的想法。实际上,你可能已经在做一些近似的事情了。问题在于只是“近似”还不够。如果没有某个正式的规范可循,版本号对于依赖的管理并无实质意义。将上述的想法命名并给予清楚的定义,让你对软件使用者传达意向变得容易。一旦这些意向变得清楚,弹性(但又不会太弹性)的依赖规范就能达成。

举个简单的例子就可以展示语义化的版本控制如何让依赖地狱成为过去。假设有个名为“救火车”的函式库,它需要另一个名为“梯子”并已经有使用语义化版本控制的包。当救火车创建时,梯子的版本号为 3.1.0。因为救火车使用了一些版本 3.1.0 所新增的功能, 你可以放心地指定依赖于梯子的版本号大等于 3.1.0 但小于 4.0.0。这样,当梯子版本 3.1.1 和 3.2.0 发布时,你可以将直接它们纳入你的包管理系统,因为它们能与原有依赖的软件兼容。

作为一位负责任的开发者,你理当确保每次包升级的运作与版本号的表述一致。现实世界是复杂的,我们除了提高警觉外能做的不多。你所能做的就是让语义化的版本控制为你提供一个健全的方式来发行以及升级包,而无需推出新的依赖包,节省你的时间及烦恼。
如果你对此认同,希望立即开始使用语义化版本控制,你只需声明你的函式库正在使用它并遵循这些规则就可以了。请在你的 README 文件中保留此页连结,让别人也知道这些规则并从中受益。

FAQ

在 0.y.z 初始开发阶段,我该如何进行版本控制?

最简单的做法是以 0.1.0 作为你的初始化开发版本,并在后续的每次发行时递增次版本号。

如何判断发布 1.0.0 版本的时机?

当你的软件被用于正式环境,它应该已经达到了 1.0.0 版。如果你已经有个稳定的 API 被使用者依赖,也会是 1.0.0 版。如果你很担心向下兼容的问题,也应该算是 1.0.0 版了。

这不会阻碍快速开发和迭代吗?

主版本号为零的时候就是为了做快速开发。如果你每天都在改变 API,那么你应该仍在主版本号为零的阶段(0.y.z),或是正在下个主版本的独立开发分支中。

对于公共 API,若即使是最小但不向下兼容的改变都需要产生新的主版本号,岂不是很快就达到 42.0.0 版?

这是开发的责任感和前瞻性的问题。不兼容的改变不应该轻易被加入到有许多依赖代码的软件中。升级所付出的代价可能是巨大的。要递增主版本号来发行不兼容的改版,意味着你必须为这些改变所带来的影响深思熟虑,并且评估所涉及的成本及效益比。

为整个公共 API 写文件太费事了!

为供他人使用的软件编写适当的文件,是你作为一名专业开发者应尽的职责。保持专案高效一个非常重要的部份是掌控软件的复杂度,如果没有人知道如何使用你的软件或不知道哪些函数的调用是可靠的,要掌控复杂度会是困难的。长远来看,使用语义化版本控制以及对于公共 API 有良好规范的坚持,可以让每个人及每件事都运行顺畅。

万一不小心把一个不兼容的改版当成了次版本号发行了该怎么办?

一旦发现自己破坏了语义化版本控制的规范,就要修正这个问题,并发行一个新的次版本号来更正这个问题并且恢复向下兼容。即使是这种情况,也不能去修改已发行的版本。可以的话,将有问题的版本号记录到文件中,告诉使用者问题所在,让他们能够意识到这是有问题的版本。

如果我更新了自己的依赖但没有改变公共 API 该怎么办?

由于没有影响到公共 API,这可以被认定是兼容的。若某个软件和你的包有共同依赖,则它会有自己的依赖规范,作者也会告知可能的冲突。要判断改版是属于修订等级或是次版等级,是依据你更新的依赖关系是为了修复问题或是加入新功能。对于后者,我经常会预期伴随着更多的代码,这显然会是一个次版本号级别的递增。

如果我变更了公共 API 但无意中未遵循版本号的改动怎么办呢?(意即在修订等级的发布中,误将重大且不兼容的改变加到代码之中)

自行做最佳的判断。如果你有庞大的使用者群在依照公共 API 的意图而变更行为后会大受影响,那么最好做一次主版本的发布,即使严格来说这个修复仅是修订等级的发布。记住, 语义化的版本控制就是透过版本号的改变来传达意义。若这些改变对你的使用者是重要的,那就透过版本号来向他们说明。

我该如何处理即将弃用的功能?

弃用现存的功能是软件开发中的家常便饭,也通常是向前发展所必须的。当你弃用部份公共 API 时,你应该做两件事:(1)更新你的文件让使用者知道这个改变,(2)在适当的时机将弃用的功能透过新的次版本号发布。在新的主版本完全移除弃用功能前,至少要有一个次版本包含这个弃用信息,这样使用者才能平顺地转移到新版 API。

语义化版本对于版本的字串长度是否有限制呢?

没有,请自行做适当的判断。举例来说,长到 255 个字元的版本已过度夸张。再者,特定的系统对于字串长度可能会有他们自己的限制。

第1章 Emscripten 快速入门

1.1 使用emsdk命令行工具安装Emscripten

Emscripten包含了将C/C++代码编译为WebAssembly所需的完整工具集(LLVM、Node.js、Python、Java等),它不依赖于任何其他的编译器环境。

emsdk是一组基于Python 2的脚本,Mac系统自带Python,无需安装

1.1.1 新建一个demo文件夹,然后通过git下载安装工具包

git clone https://github.com/juj/emsdk.git

1.1.2 安装并激活

进入到安装包的文件夹,分别执行以下命令

git pull
./emsdk install latest
./emsdk activate latest

把emcc命令添加至环境变量,新建一个控制台,切换至emsdk所在的目录,执行以下命令:

source ./emsdk_env.sh

注意:每次重新登陆或者新建 Shell 窗口,都要执行一次这行命令source ./emsdk_env.sh,然后本控制台窗口才能使用emcc命令。

1.1.3 验证安装是否成功

emcc是Emscripten的核心命令,正确安装激活并设置环境变量后,执行emcc -v可以查看版本信息:

> emcc -v
emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 2.0.17
clang version 13.0.0 (/opt/s/w/ir/cache/git/chromium.googlesource.com-external-github.com-llvm-llvm--project 3b677b81cec7b3c5132aee8fccc30252d87deb69)
Target: wasm32-unknown-emscripten
Thread model: posix
InstalledDir: /Users/liuxiaofan/WorkSpace/vipkid/emscripten/emsdk/upstream/bin

1.2 Hello, wolrd!

先跑一个最基本的demo

1.2.1 生成wasm

新建一个名为hello.cc的C源文件,为了正确标识中文字符串,将其保存为UTF8编码:

//hello.cc
#include <stdio.h>

int main() {
    printf("Hello, wolrd!");
    return 0;
}

执行编译命令生成wasm文件:

emcc hello.cc

目录下将得到两个文件:a.out.wasm以及a.out.js。其中a.out.wasm为C源文件编译后形成的WebAssembly汇编文件;a.out.js是Emscripten生成的胶水代码。

使用-o选项可以指定emcc的输出文件,执行下列命令:

emcc hello.cc -o hello.js

注意:现在如果改变了hello.cc文件再次编译的话会报错。因为编译出的wasm默认情况下不会退出运行时,所以每次编译的时候加上-s EXIT_RUNTIME=1 即可。如下:

emcc index.cc -o index.js -s EXIT_RUNTIME=1

1.2.2 在网页中测试
新建一个index.html的网页文件:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Emscripten:你好,世界!</title>
  </head>
  <body>
    <script src="hello.js"></script>
  </body>
</html>

本地起一个web服务,让localhost的目录指向本demo的目录,然后打开控制发现成功打印出来Hello, wolrd!
例如全局安装live-server,然后执行live-server –port=8888启动一个本地服务

npm i -g live-server
live-server --port=8888

1.2.3 在Node.js中测试

Emscripten自带了Node.js环境,因此我们可以直接使用node来测试刚才的程序:

> node hello.js
Hello, wolrd!

1.3 胶水代码初探

简单了解一下Emscripten生成的JavaScript胶水代码hello.js

1.3.1 WebAssembly汇编模块载入

WebAssembly汇编模块(既.wasm文件)的载入后经过一系列的操作,最终被实例化,然后赋值了给全局对象Module的子对象asm。

function receiveInstance(instance, module) {
      ... ...
      Module['asm'] = exports;
      console.log(Module['asm']);  //我们在这添加一行Log在控制台里面打印出来看看这个对象里面都有哪些属性和方法
      ... ...

我当前的版本打印出来的结果是这样的,以后随着版本的升级可能会有变化。注意里面有个main方法

1.3.2 导出函数封装

在Emscripten中,C函数导出时,函数名前会添加下划线“_”,由此可知上述代码分别提供了main()以及malloc()函数的封装;我们可以在浏览器控制台中手动执行_main()以及Module._main()对此进行检验:如果报错,请忽略,接着往下看就明白了。

1.3.3 异步加载

经过我的测验,直接在控制台中执行_main()以及Module._main()会报错

Assertion failed: you need to wait for the runtime to be ready (e.g. wait for main() to be called)

解决的基本思路是在Module初始化前,向Module中注入一个名为onRuntimeInitialized的方法,Emscripten的Runtime就绪后,将会回调该方法。

<body>
    <script>
      Module = {};
      Module.onRuntimeInitialized = function() {
        //do sth.
        Module._main();
      }
    </script>
    <script src="hello.js"></script>
  </body>

如上面的代码,先监听RuntimeInitialized,再执行Module._main()就好了。