一、Wind.js 是怎么实现的异步流程控制。

通常我们常见的代码是同步执行的。比如这样:

?
1
2
3
var log = fs.readFile('log.txt');
console.log('log');
console.log('read finished.');

同步代码执行是顺序的,阻塞的,文件内容没有被读取完城,后面的两句 console.log 语句不会被执行。

当遇到异步代码时,情况则发生了变化:

?
1
2
3
4
5
6
7
fs.readFile('log.txt', function(err, content) {
  if (error) return;
  var log = content;
  console.log('log');
  console.log('read finished.');
});
console.log('reading...');

原本顺序执行的代码,必须非放置在 readFile 异步函数的回调内。读取文件函数执行后立即运行的是 console.log(‘reading…’)。很好,它不阻塞了,副作用是破坏了代码的执行顺序。

如果仔细观察两端代码,我们可以发现一些有迹可循的编码原则。当使用异步函数时,红色代码被移动到了回调函数内,其它地方则没有更多的变化。

Wind.js 就是利用了这个编码调制原则。

Wind.js 类库中 Wind.compile 方法先将可能执行的异步代码包装为匿名函数,通过参数传入。 Wind.compile 方法会将传入的函数 toString 后分析代码。函数体内的 $await 函数实际上是充当了代码块位置偏移的标示位,以便分析函数源码时候见到 $await 函数时,就将其后的代码块整体修正到异步回调函数内(实际上 Wind.js 处理的内容非常多,如 $await 函数前后的 JS 语法逻辑变更为异步函数逻辑等,具体可看 老赵 亲自写的讲解文章: )。

看一段简单的异步代码:

?
1
2
3
setTimeout(function() {
  console.log('sleep finished.');
}, 1000);

然后是 Wind,js 封装后的异步代码:

?
1
2
3
4
5
var test = eval(Wind.compile("async", function () {
        $await(Wind.Async.sleep(1000));
        console.log('sleep finished.');
    }
}));

Wind.compile 函数先将匿名函数参数 toString,拆解源码字符串并重新进行语法整合。遇到 $await 函数后,就根据其参数,确定调用具体的何种异步代码模型。这里 Wind.Async.sleep 是对 setTimeout 函数的封装,它告知 $await 与 Wind.compile 需要按照 setTimeout 函数语法的回调形式来重新组织代码。于是, 被重构后的代码可能为如下源码字符串形式:

?
1
2
3
var sourceCode = "setTimeout(function() {
  console.log('sleep finished.');
}, 1000);"

这个源码字符串,将作为 Wind.compile 函数的返回值:

?
1
2
3
4
Wind.compile = function() {
  // 语法重组处理 blablablabla ……
 return sourceCode;
}

之后,eval 此源码串,即可获得原始编写异步代码同效结果。

*注意下,这里仅是阐述原理,Wind.js 返回的代码是被包含在 “function(){ … }” 字符串内的源码,此代码被 eval 后变为真正的 function 对象,此 function 需要被执行才能真正开始执行异步流程控制。

二、$await 为什么是个函数而不是作为一个简单的语法标记存在?

其实这个问题刚才已经诠释过, Wind.compile 在处理代码时,遇到 $await 后需要知道调用何种方式的异步语法。如 setTimeout 异步函数的回调参数为首参,而 addEventListenter 异步函数的回调函数位置在次参数。

?
1
2
setTimeout(function() { ... }, timeout);
DOM.addEventListenter(‘click’, function() {}, false);

Wind.compile 需要明确知道不同异步函数回调语法的确切位置才能准确无误的重组代码。因此 $await 就承担了告知任务,以函数形式来指明不同类型的异步语法。Wind.js 由此也获得了其他兼容异步模型的扩展能力,不同的异步语法可通过扩展绑定 Task 任务模型后交由 $await 识别处理。

三、为什么要用 eval 并且还没有封装它?

如上所述,Wind.js 将 Wind.compile 函数的第二个参数由 function 进行 toString 后得到源码字符串。分析源码字符串后,将此部分代码重新封装为一段异步调用形式的源码字符串。此时,它需要执行此字符串,使它们能成为 JS 执行流中的一部分。

于是,动态执行一段源码串在 JS 内不依赖宿主环境实现的 API 就只有 eval 和 Function 构造函数两种。

先说,Function 构造函数,它的特点是其内源码都在 Global 作用域下被执行。如果此时,源码需要 Wind.compile 函数所在作用域中的某个变量,那么将会出现非预期的问题。如:

?
1
2
3
4
5
var a = 'globlal scope';
(function () {
  var a = 'local scope';
  new Function('console.log(a)')();
}());

此处打印出的结果是 ‘globlal scope’。

直接调用 eval 函数在运行源码串,则可以避免由 Function 构造函数带来的问题。它的执行作用域在当前作用域内,就是说它能获取当前作用域内变量值以及上层闭包内变量。如:

?
1
2
3
4
5
var a = 'globlal scope';
(function () {
  var a = 'local scope';
  eval('console.log(a)');
}());

此处打印出的结果是 ‘local scope’。

需要注意的是,这里说明的是直接调用。如果 eval 被赋值到某个别名中,则为非直接调用。那么它就与 Function 构造函数运行方式相同,在 Global 作用域内执行代码。如:

?
1
2
3
4
5
6
var a = 'globlal scope';
(function () {
  var a = 'local scope';
  var myEval = eval;
  myEval('console.log(a)');
}());

此处打印出的结果是 ‘globlal scope’。

因此,直接的 eval 调用是 Wind.js 所预期的结果,它必须这用 eval 来达到功能实现的目的。

特殊说明:直接与非直接 eval 调用时 ES5 标准定义的,因此它有不同时代的浏览器实不一致问题。显然这也不是 Wind.js 所希望的。

四、为什么没有将 eval 封装在 Wind.js 的某个函数内,不必让用户知道它的存在呢?

同上分析,这么做也是不行的。因为,JS 的作用域是静态的,在代码被解析时,作用域已经被确定,运行时无非被修改(只有 JS 引擎级别 API 才可以动态修改作用域)。如果这么做,eval 所处的作用域将为封装它的函数所在作用域,而不是此函数被执行时的作用域,同样会出现非预期问题。如:

?
1
2
3
4
5
6
7
8
var a = 'globlal scope';
function run(code) {
  return eval(code);
}
(function () {
  var a = 'local scope';
  run('console.log(a)');
}());

此处打印出的结果是 ‘globlal scope’。

因此,希望将 eval 封装起来使用是行不通的,那样做将无法获取代码所在作用域中变量值。

Wind.js 就说这么多,如果有兴趣,请访问它的 官网 ,或者在微博咨询作者 @老赵 。

下面开始简要说说 berserkJS。

五、berserkJS 是干什么的,原理是什么?

berserkJS 是一个前端页面性能自动化分析工具,它的特点是可以使用 JS 操作页面导向,获取所需性能数据,编写自己的检测分析规则。它基于 QT 开发,理论上可以跨平台使用,前提是在目标平台编译并部署 QT 运行环境。

针对 berserkJS 编写检测脚本十分简单,因为它是使用 JS 语法的,比如获取某页面的网络性能数据仅需要几行代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 开启监听
App.netListener(true);
// 访问页面
App.webview.open('http://weibo.com');
// 页面加载完成后获取数据
App.webview.addEventListener('load', function() {
  // 获取数据并序列化
  var data = JSON.stringify(App.networkData(), undefined, 2);
  // 写入文件
  App.writeFile(App.path + 'demo1.txt', data);
  // 关闭监听
  App.netListener(false);
  // 退出应用
  App.close();
});

如果你细心可以发现,berserkJS 内写文件语法是同步的(node.js 是异步的),对于 webview 事件监听则是异步的。事实上,berserkJS 作为工具程序,追求的是编码简洁易维护调整,而非高性能(又不用它作为服务组件)。因此,实现时尽可能避免如今非常流行的异步 io 语法设计,将可能实现为同步的 API 都做了同步实现,如 readFile、writeFile、httpReuqest、process 等都为强制同步实现。

但是,唯独 webview 的事件机制无法采用同步 API(同步 UI 事件编码的想法简直是太匪夷所思鸟),这导致使用 berserkJS 在根据 UI 事件模拟用户操作时必须使用异步编码方法。它与最初追求的 ”编码简洁易维护调整“ 理念背道而驰,毕竟异步代码容易被拆的很散,无法维护。

比如一段登录微博账号的自动化操作代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 实现微博登录
var loginWeibo = function() {
  w.removeEventListener('load', loginWeibo);
  // 输入账号等 1s 因为表单是异步的
  setTimeout(function() {
    w.execScript(function() {
      // 输入测试账号密码
      document.querySelectorAll('input[node-type=loginname]')[0].value = 'xxx';
      document.querySelectorAll('input[node-type=password]')[0].value = '123';
    });
    // 点下登录按键
    w.sendMouseEvent(w.elementRects('a[node-type=submit]')[0]);
    // 开启网络监听
    w.netListener(true);
    // ...
}
var w = App.webview;
w.addEventListener('load', loginWeibo);

由于页面内登陆模块异步渲染等原因,登录微博操作变成一个异步执行流,回调嵌套使代码变得很凌乱。此时,berserkJS 急需一个异步控制类库来解决由于 UI 事件异步以及页面实现异步等原因导致的代码组织分散问题, 自然 Wind.js 就成了一个很好的选择。

叽歪:至于 berserkJS 的再具体功能内容请看官们去看下其 文档页 。

六、 berserkJS 中无缝使用 Wind.js 以及原理

前戏终于整完,絮絮叨叨好大一坨,现在终于到了关键点,两者怎么结合使用。

在 berserkJS 中挂载 Wind.js 十分简单:

1、你可以通过 App.httpRequest 方法抓取远程的 Wind.js 源码并用 eval 执行它。

2、也可以使用 App.readFile 方法读取本地 Wind.js 源码并用 eval 执行它。

3、当然,对于本地源码你还可以调用 App.loadScript 方法将本地 Wind.js 直接挂载进 berserkJS 内。

挂着完成 Wind.js 后,使用 $await 的辅助功能函数 Wind.Async.onEvent 就可以流程化 App.webview 的异步事件代码了。

比如,有这样一个检测需求:先登录到微博页面,点击某个连接,然后每隔 200 ms 对页面渲染情况经行截图保存,持续截取一段时间的页面渲染情况。如果使用原始代码编写,片段如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// blablabla …… 之前还有些常量
var COUNT = 15;
var SCREEN = {width: 960, height: 600};
var DELAY = 10000;
var TIMEOUT = 200;
var num = 0;
var timeId = 0;
var w = App.webview;
  
var toImg = function() {
  if (num >= COUNT) {
  clearInterval(timeId);
  App.close();
  return;
 }
 num++;
 w.saveImage((isBp ? BP_PATH : NOBP_PATH) + (num < 10 ? ('0' + num) : num)  + '.png', 'png', '100', {
  x:0, y:0, width: SCREEN.width, height: SCREEN.height
 });
};
  
var clickLink = function() {
 toImg();
 w.sendMouseEvent(w.elementRects('a[bpfilter="main"][action-type="leftNavItem"]')[1]);
 timeId = setInterval(toImg, TIMEOUT);
}
  
w.setCookiesFromUrl(App.readFile(WB_COOKIE), 'http://www.weibo.com');
w.open(isBp ? wb_bp : wb_nobp);
w.addEventListener('load', function(url) {
 setTimeout(clickLink, DELAY);
});

原本 toImg 函数仅需实现保存页面截图功能,但是由于执行异步,它必须耦合一些截图次数的判断以及对定时器处理的脏逻辑。同样,clickLink 本应是实现模拟点击操作的而已,为了截图和启用定时,它也被迫加入其它代码逻辑。

加入 Wind.js 后,代码情况得以改善:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 读取远程 Wind.js 源码字符串并 eval 执行。
// 提醒:其中 URL 是存在的,仅为代码示意
eval(App.httpRequest('get', 'http://www.windjs.org/wind.7.0.js'));
// blablabla …… 一些常量定义
var COUNT = 15;
var SCREEN = {width: 960, height: 600};
var TIMEOUT = 200;
var timeId = 0;
var num = 0;
var w = App.webview;
var toImg = function() {
 w.saveImage((isBp ? BP_PATH : NOBP_PATH) + (num < 10 ? ('0' + num) : num)  + '.png', 'png', '100', {
  x:0, y:0, width: SCREEN.width, height: SCREEN.height
 });
};
w.setCookiesFromUrl(App.readFile(WB_COOKIE), 'http://www.weibo.com');
w.open(isBp ? wb_bp : wb_nobp);
  
var test = eval(Wind.compile('async', function() {
 $await(Async.onEvent(w, 'load'));
 $await(Async.sleep(DELAY));
 toImg(num);
 w.sendMouseEvent(w.elementRects('a[bpfilter="main"][action-type="leftNavItem"]')[1]);
 for (var i = 0; i < COUNT; ++i) {
  $await(Wind.Async.sleep(TIMEOUT));
  toImg(++num);
 }
 App.close();
}));
test().start();

toImg 函数功能变得纯粹,clickLink 完全可以去除原本的无用封装,蜕变为简明的 sendMouseEvent 调用。

同时,很奇妙不是么?Wind.Async.onEvent 对于 App.webview 事件是天然支持的。

显然这不是实现这两个完全不同功能方向的库事前商量好的。实际原因是由于 Wind.Async.onEvent 内会检测对象是否存在 W3C DOM 的标准事件监听方法 addEventListener。如果有,则将此操作进行异步流程控制。

恰巧,berserkJS 的 App.webview 事件处理方法,完全遵从了标准事件 API 设计,所以可被 Wind.js 无缝处理。

显然我们该庆幸 berserkJS 为 webview 对象实现了标准 API,如果同 JQuery 一样使用 on、bind 之类简化 API 名,自然就无法享受到与 Wind.js 天然衔接的好处。

原文出处:http://tech.weibo.com/?p=1819

berserkJS 使用 Wind.js 保证序顺执行流程相关推荐

  1. js学习笔记(执行上下文、闭包、this部分)

    1.函数的准备工作 函数在执行会进行一些准备工作,如创建一个"执行上下文"环境:执行上下文可以理解为当前代码的执行环境,它会形成一个作用域: 每个碰到可执行代码的时候都会进行这些& ...

  2. JS引擎线程的执行过程的三个阶段

    浏览器首先按顺序加载由<script>标签分割的js代码块,加载js代码块完毕后,立刻进入以下三个阶段,然后再按顺序查找下一个代码块,再继续执行以下三个阶段,无论是外部脚本文件(不异步加载 ...

  3. 请求异步js,请求完成后执行代码

    要确定请求完成 js 文件,才执行相关的代码. 场景,引用了百度地图的 js-sdk, 需要实例化 SDK 对象,但是这个引用加载JS-SDK文件其实是异步的,在没请求完成之前就实例化对象就会报错,提 ...

  4. JavaScript异步精讲,让你更加明白Js的执行流程!

    JavaScript异步精讲,让你更加明白Js的执行流程! 问题点 什么是单线程,和异步有什么关系 什么是 event-loop jQuery的Deferred Promise 的基本使用和原理 as ...

  5. 码农干货系列【17】--Wind.js与Promise.js

    示例 先引入wind.js与promise.js: <script src="wind-all-0.7.3.js"></script> <script ...

  6. 【vue】使用Promise方法保证按顺序执行

    [vue]使用Promise方法保证按顺序执行 使用Promise方法可以叠加.then方法,来保证执行顺序. queryTasksNum(){// TODO: 如果和queryImg都放在mount ...

  7. js中(function(){…})()立即执行函数写法理解

    js中(function(){-})()立即执行函数写法理解 javascript和其他编程语言相比比较随意,所以javascript代码中充满各种奇葩的写法,有时雾里看花,当然,能理解各型各色的写法 ...

  8. JS引擎线程的执行过程的三个阶段(二)

    继续 JS引擎线程的执行过程的三个阶段(一) 内容, 如下: 三. 执行阶段 1. 网页的线程 永远只有JS引擎线程在执行JS脚本程序,其他三个线程只负责将满足触发条件的处理函数推进事件队列,等待JS ...

  9. 深入浅出JS—20 生成器控制函数执行

    上一篇文章介绍并实现了迭代器,生成器是一种特殊的迭代器, 特殊在于生成器是由生成器函数得到的,并不是我们构造的对象.对于生成器函数,ES6有一套专门的规范和关键字定义 思考:JS中函数执行可以中断吗? ...

最新文章

  1. 稳健地估计单应性矩阵,需要几个特征点?
  2. EL:谁说N素含量高就不固氮了
  3. IMAX融资5000万美元,三年内要打造25个VR项目
  4. [Apple开发者帐户帮助]三、创建证书(3)创建企业分发证书
  5. Istio Pilot架构解析
  6. 最新的ndkr20编译c_NDKr20使用clang编译ffmpeg
  7. 5 万条微信语音升入太空;阿里京东否认停止社招;雷军开怼华为 | 极客头条...
  8. jvm垃圾收集器与内存分配策略
  9. 2017蓝桥杯B组:最长公共子序列(动态规划详解(配图))
  10. [Bhatia.Matrix Analysis.Solutions to Exercises and Problems]ExI.1.1
  11. MySQL新增用户以及数据库访问授权
  12. 集成电路实践----D触发器
  13. 18岁少年辍学组建黑客俱乐部 已覆盖62所学校
  14. 补点C#基础_022_json校验和json在线编辑器-bejson
  15. 利用VSCode+platformio学习esp32开发
  16. 虚拟机类加载机制(类加载过程)
  17. 简单通用QQ/微信跳转浏览器打开代码
  18. 超详细版-计算网络地址、子网、广播地址、主机数
  19. Log4j日志框架介绍
  20. 最近感冒恢复中,读了《平凡的世界》,写点感受。

热门文章

  1. 射频电子标签 购物绿色通道
  2. IP-Guard桌面安全解决方案
  3. 创业必须的一些网站和博客导航
  4. 锂电池充电——NTC温度控制电路
  5. 下行控制信息 - 上行DCI
  6. 【每天学点管理】—如何提高团队的执行力
  7. 复旦大学苏教授火了!扯出600多个假博士
  8. AR路由器web界面每IP限速配置方法
  9. oracle 11g第二版课后答案,oracle 11g(钱慎一)课后习题答案
  10. J storm战队成员_DOTA2J.Storm战队介绍-DOTA2MD迪士尼Major预选赛J.Storm战队介绍_牛游戏网攻略...