Code Monkey home page Code Monkey logo

web-frontend-magic's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

web-frontend-magic's Issues

如何让 JS 调用空函数也能报错?

先来看个 JS 思考题 —— 如何获取闭包内 key 变量的值:

// 挑战目标:获取 key 的值
(function() {
  // 一个内部变量,外部无法获取
  var key = Math.random()

  console.log('[test] key:', key)

  // 一个内部函数
  function internal(x) {
    return x
  }

  // 对外暴露的函数
  apiX = function(x) {
    try {
      return internal(x)
    } catch (err) {
      return key
    }
  }
})()

// 你的代码写在此处:
// ...

利用 JS 函数柯里化,实现高性能虚拟机(1)

前言

元旦期间,相信大家都被微信小游戏刷屏了,各种玩法攻略铺天盖地。当得知这是用 JS 开发时,立马想把过去写的 JS 游戏都翻新一遍。于是打开小游戏的官网,查看开发文档。

然而遗憾的是,小游戏虽然是用 JS 写的,但它并非运行在浏览器环境里,因此和 HTML 并不兼容。此外,在官网文档上,还看见一条奇葩的规则:

...
2.与小程序一样,小游戏每次发布需要经过审核。我们在小程序和小游戏中都移除了动态执行代码的能力,包括以下调用方式:
(1)eval 函数
(2)setTimeout、setInterval 函数第一个参数传入代码字符串执行
(3)使用 Function 传入字符串构造函数
(4)使用 GeneratorFunction 传入字符串构造生成器函数
...

咋一看貌似有些道理,要是开放 eval 的话,代码可以从网络上更新,就能绕过官方审核了。

但是,开发者若真想动态执行,那根本就是拦不住的 —— 在代码里藏一个虚拟机不就可以了吗。之前我们探讨《使用 VM 壳混淆 JavaScript 代码》时,就讲解了 JS 虚拟机的概念,让程序根据不同的数据,执行不同的操作。虽然性能不高,但用于简单临时的场合,还是没问题的。

这时冒出个想法:要是能把性能优化得足够好的话,是不是可以让整个小游戏都由虚拟指令实现,这样程序只需发布一次就再也不用审核了呢?

于是开始探索,一个不用 eval、而是由 JS 自我驱动的图灵机,性能最高能达到多少。

有什么场合,必须使用 ES6 的 Reflect?

大家都知道 JS 非常灵活,很多原生 API 都可以被开发者重写。

然而有时我们希望调用的 API 是原生的(至少在我们程序运行后不再有变化)。常见的做法,就是在程序运行时将原始接口进行备份:

(function(Date_now) {
  function call_later() {
    Date_now();
  }
  // ...
})(Date.now);

这样,即使后续程序修改了 Date.now,我们程序内部的 Date_now 引用的仍是原始版本。

当然这个案例比较简单。下面思考一个更复杂的,如果换成 document.getElementById,又改如何实现?
也许你首先会想到这样:

(function(fn) {
  fn('id')
  // ...
})(document.getElementById);

但是 document.getElementByIdDate.now 不同,这个 API 并不是静态函数,它依赖 this。直接调用 fn 的话,就会抛出 Illegal invocation 错误。(当然很久以前的古老 IE 浏览器可以这么调用,这里不扯远)

当然你会说,把 document 也进行备份,然后通过 call/apply 就可以了:

(function(document, fn) {
  fn.apply(document, [...]);
  // ...
})(document, document.getElementById);

从工程角度来看,到此确实可以了。但从理论上说,此处的 apply 其实并不能保证 100% 调用就是原生 document.getElementById。换言之,执行 apply 是有可能存在副作用的!

仔细想想,所谓的 apply 其实就是 Function 类的一个方法而已,即 Function.prototype.apply。如果把它重写了,那么 fn.apply() 就是调用重写后的函数!

所以,调用 call/apply 是无法保证绝对可靠的。

那么,能否把原生的 apply 也备份起来呢?可以。但是为了调用原生的 apply,你仍得使用 apply,于是陷入一个死循环。。。

为了解决这个窘境,是时候派上 Reflect 了。Reflect 提供了一个 apply 方法,它比 Function.prototype.apply 更底层,所以不会受到 Function 重写的影响。

更好的是,Reflect 提供的函数都是静态的,如同之前提到的 Date.now 一样!

因此,我们只需备份 Reflect.apply 即可。 上述案例即可这样实现:

(function(apply, document, fn) {
  apply(fn, document, ...);
  // ...
})(Reflect.apply, document, document.getElementById);

演示:http://jsfiddle.net/9m2a7fts/1/

2014-11-13 Hack小技巧:

2014-11-13
Hack小技巧:检测控制台是否被打开

演示:https : //www.etherdream.com/FunnyScript/console_detect/

这个对火狐没用,研究研究如何才能检测 火狐的 独立弹出 控制台,非独立弹出检测浏览器可视区域宽高就行了,

利用 JS 函数柯里化,实现高性能虚拟机(2)

前言

上一篇,我们讲解了如何将指令转换成函数,从而减少运行时开销。

然而光有转换还是不够的,如何将转换后的函数高效运行起来,才是真正的挑战。

下面开始我们的探索。。。

流程控制

计数器

传统虚拟机的流程控制,大多依靠程序计数器(program counter)实现,用以指向当前指令的位置。

计数器的方案,同样适用于我们的函数列表:

fn_list = [fn, fn, ......]
pc = 0

do {
  f = fn_list[pc++]
  f()
} while (......)

正常情况下,我们顺序执行列表中的函数;如果需要跳转,则可提供一些「能修改计数器」的指令,以实现分支、循环等效果。

计数器的原理很简单,但其效率并不高。因为每执行一条指令,都得访问数组、更新计数器、终止判断,从而产生数倍的性能开销。

那么,是否有更快的流程控制方案呢?

指令分离

既然我们要追求极致的性能,那只能牺牲一些灵活性。

冯诺伊曼结构的虚拟机,指令和数据是合为一体的,这显然非常灵活。例如,程序可以跳到指令区外,将动态数据当做指令运行;可以跳到指令中间,将半个指令当做新指令执行;甚至还可以自修改,运行时改变指令数据。

然而这并不常用。我们不妨抛弃这些小众特性,尝试将指令和数据分离,这样是否能玩出新花样?

柯里化

现在,指令始终是固定的。因此不妨将多个指令事先绑在一起,这样就能一次调用多个指令。

例如,我们捆绑这两个指令:

mod r3, r5, r7
xor r0, r0, r1

根据上一篇文章,它们先被转换成两个闭包函数:

fn_list[0] = OP2(mod, set_r3, get_r5, get_r7)
fn_list[1] = OP2(xor, set_r0, get_r0, get_r1)

现在我们需要另一个函数,用于两者的绑定。这时,柯里化又派上用场了:

function wrap2(f1, f2) {
  return function() {
    f1(); f2()
  }
}

我们将 fn_list 作为参数列表传给 wrap2,即可得到合二为一的结果:

f = wrap2.apply(null, fn_list)

之后,只需 f() 即可触发 f1()、f2(),从而实现一次调用两个指令!

模板

如果想绑定 3 个、5 个指令,又该如何实现?很简单,接着实现 wrap3、wrap5 即可。

function wrap5(f1, f2, f3, f4, f5) {
  return function() {
    f1(); f2(); f3(); f4(); f5()
  }
}

我们可提供多个版本,例如从 wrap2 直到 wrap16。这样 16 个指令以内的,即可直接绑定;超过 16 个,则可通过组合实现。

例如绑定 18 个指令,可以这样实现:

f = wrap16(x1, x2, ......, x15, wrap3(x16, x17, x18) )

因为绑定后的结果也是一个函数,所以能多层嵌套。

这样,我们就能使用链式结构,将任意多的指令绑在一起,从而实现一次调用、批量执行!

动态 vs 静态

也许你在想,为什么绑定的个数非得固定,而不动态获取呢。例如这样岂不更简单:

function wrapN() {
  const arr = arguments
  return function() {
    for (let i = 0; i < arr.length; i++)
      arr[i]()
  }
}

从做逻辑上说,这确实没问题。然而,动态对于优化是不利的,可能会导致性能降低。

我们写个简单的案例,观察动态的性能开销:

let a = 0
function inc() { a++ }

// 硬编码
console.time('t0')
for (let i = 0; i < 100000000; i++) {
  inc(); inc();
  inc(); inc();
}
console.timeEnd('t0')


// 固定个数
const f = wrap4(inc, inc, inc, inc)
console.time('t1')

for (let i = 0; i < 100000000; i++) {
  f()
}
console.timeEnd('t1')


// 动态个数
const f = wrapN(inc, inc, inc, inc)
console.time('t2')

for (let i = 0; i < 100000000; i++) {
  f()
}
console.timeEnd('t2')

在线运行:https://jsfiddle.net/jcktof8b/

由此可见,固定个数的 wrap4 耗时和硬编码相差无几,而动态个数的 wrapN 则要慢上 3 倍!

分支流程

固定分支

我们实现了顺序流程,现在来考虑分支流程。

由于没有计数器,因此像 goto 这样的跳转,显然不易实现了。不过一般情况下,我们很少会用 goto,尤其像 JS 本来就不支持,只能用 break、continue 等控制流程,跳到块头或块尾。

既然我们的目标是高性能,那不妨再牺牲一些灵活性,只提供固定的流程控制!

例如条件判断,我们通过预制的模板来实现:

function br_if(src, exp1, exp2) {
  return function() {
    src() ? exp1() : exp2()
  }
}

这样,提供条件源、分支 1、分支 2,即可生成一个「带判断功能」的闭包函数。

类似的,循环也可以这样实现。例如,一个固定次数的循环模板:

function loop(N, exp1) {
  return function() {
    for (let i = 0; i < N; i++) {
      exp1()
    }
  }
}

不过,怎样才能将现有的指令块,作为参数传给分支模板呢?

看来,我们得设计一种特殊的指令结构。

指令结构

既然程序没有 goto 这样的任意跳转,那平坦型的指令结构不再有意义,不如使用树结构:

01  add ...
02  loop 1000
03    sub ...
04    br_if r1
05      add ...
06      sub ...
07    else
08      mul ...
09      div ...

虚拟机预处理时,将 同级 的指令合在一起,作为 上级 流程模板的参数。

例如,上述 5~6 行的指令合成 f56 函数,8~9 行合成 f89

f56 = wrap2(f5, f6)
f89 = wrap2(f8, f9)

然后通过分支模板,生成 4~9 行的分支闭包:

f49 = br_if(get_r1, f56, f89)

由于该分支与第 3 行指令位于同一级,于是合成 f39

f39 = wrap2(f3, f49)

然后通过循环模板,生成 2~9 行的循环闭包:

f29 = loop(1000, f39)

由于该循环与第 1 行指令位于同一级,于是合成 f19

f19 = wrap2(f1, f29)

这就是最终的根节点。调用它,即可驱动整个程序!

小结

到此,顺序流程和分支流程已实现,这个「柯里化虚拟机」总算是图灵完备了。

既然我们的目标是高性能,那么其中显然还有不少值得推敲优化的地方。下一篇,我们继续探索。

简单演示:https://www.etherdream.com/FunnyScript/CurryVM/www/

这个 Demo 很不完善,现在已有很大变化~ 当然主要分享的是思路

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.