Javascript Downcast
jsdc
取自Java多态特性向下转型,意指ES6代码向下转换为ES5兼容代码。
INSTALL
npm install jsdc
使用说明
- jsdc仅提供安全兼容的转换接口,并且不改变你的源代码行数一致性,这使得调试极为便利
- 智能识别es5语法,jsdc不会修改es5的部分
- 无需预置script脚本,绝不更改任何变量
- CommonJS/AMD/CMD自适应
- as simple as possible
- 仅转换可实现的语言部分,扩展库(如
Set
和Map
)请使用es6-shim
之类的库 - 特别注意某些实现依赖
Iterator
,请确保有此扩展
已实现的部分
- 二进制和八进制的Number扩展
- Unicode的字符串增强
- Object属性增强
- block局部作用域
- let/const关键字
- 默认参数赋值
- rest扩展参数和spread扩展参数调用
- template模板
- for of循环
- class类实现
- extends类继承
- module模块
- ArrayComprehension数组推导
- ArrowFunction箭头函数
- yield语句
- Generator生成器函数
- 解构
API
Jsdc
- constructor(code:String = '') 传入需要转换的code
- parse(code:String = null):String 转换code,可以为空,否则会覆盖构造函数里传入的code
- define(d:Boolean):Boolean 读取/设置转换module为CommonJS时是否包裹define(即转为AMD/CMD),默认false
- ast():Object 返回解析后的语法树
- tokens():Array<Object> 返回解析后的词法单元序列
静态属性
- parse(code:String):String 可以直接调用静态方法转换,以省略new一个对象的步骤
- define(d:Boolean):Boolean 读取/设置转换module为CommonJS时是否包裹define(即转为AMD/CMD),默认false
- ast():Object 返回解析后的语法树
- tokens():Array<Object> 返回解析后的词法单元序列
- runtime(flag:Boolean):void 开启/关闭运行时支持,仅限NodeJs。开启后改写require机制,获取module前尝试预编译
Demo
- demo目录下是一个web端的实时转换例子,本地浏览需要
npm install
安装依赖 - 依赖的语法解析器来自于
homunculus
:https://github.com/army8735/homunculus - 在线地址:http://army8735.me/jsdc/demo/
Tools
- 命令行版工具:https://github.com/xudafeng/jsdc-cli
- gulp插件:https://github.com/army8735/gulp-jsdc
License
[MIT License]
语法转换规则
- 以下按实现逻辑顺序排列(如有逻辑关联,如let和block作用域)
- 确保转换后代码执行一致,调试行数一致
Number数字扩展
0b
或0B
开头的二进制将使用parseInt
转换:
var i = 0b010 j = 0B101
var i = j =
0o
或0O
开头的八进制也是如此(有人会用大写的字母O吗,和数字0根本看不出来区别):
var i = 0o456 j = 0O777
var i = j =
Unicode的字符串增强
Unicode
编号大于0xFFFF
的字符将会转移成2个utf-8
拼接:
'\u{10000}'
'\ud800\udc00'
转义符也能正确识别:
'\\u{10000}'
'\\u{10000}'
Object增强
赋值对象时Object
的同名属性可以简写:
var a = o
var a = o:o
方法也是:
var a = {}
var a = {}
甚至可以用[]
表明它是一个表达式:
var a = 'a'+'b' : 1
var {var _0=_1: 1;_0'a'+'b'=_0_1;delete _0_1;return _0}
实现方法是先用个临时唯一变量替换掉表达式,最后再将它还原回来。
var和函数迁移
将var
申明迁移至最近的作用域起始处:
{ iftrue var a = 1; let b = 2; }
{var a; iftrue a = 1; let b = 2; }
仅当必要时才迁移,否则保持原样(比如下面没有let
):
{ iftrue var a = 1; }
示例中
let
和块级作用域尚未处理,后面会提到。
函数和var的性质一样:
{}
!{{}};
{}块级作用域
必要时将{}
替换为function
作用域:
let a = 1; {}
! { let a = 1; {}};
if
语句,iterator
语句和try
/catch
/finally
等也是,注意和纯{}
语句插入匿名函数位置的异同:
iftrue let a = 1;
iftrue ! { let a = 1;};
示例中
let
尚未做处理,后面会提到。
let/const关键字
将let
和const
替换为var
:
let a = 1;const b;
var a = 1;var b;
注意和块级作用域的交互:
var a = 1; let b; const c = 1;
var a;! { a = 1; var b; var c = 1;};
函数和
Generator
函数均默认块级作用域。
默认参数值
根据是否undefined
赋值,它可以有多个:
{}
{ifb ===void 0b=1;}
扩展参数
将扩展参数通过arguments
转换为数组:
{}
{args = slice;}
方法执行则使用apply
调用:
fn
如果调用者是成员表达式,context将从this
变为主表达式:
Math
Mathmax
在数组中则会自动展开,支持string预判断:
var codeUnits = ..."this is a string";var codeUnits = ...a;
var codeUnits = ;var codeUnits = ;
template模板
将模板转换为普通字符串,需要的情况下会包裹括号(确保运算符优先级正确性):
`template`
"template"
模板中的引号将被转义:
`"`
"\""
模板中的变量会被替换:
`b`
a + "b"
注意变量标识符$也可以被转义:
`\${a}b`
"\${a}b"
for of循环
存储被循环体到临时引用:
fora of b
var _0=b;fora of b
将of
改为=
并添加;
补完循环:
fora of b
var _0=b;fora =_0;;
将赋值添加[Symbol.iterator]().next()
并添加.done
结束判断:
fora of b
var _0=b;fora =_0Symboliteratornext;!adone;a=_0next
循环体内先赋值.value
:
fora of b
var _0=b;fora =_0Symboliteratornext;!adone;a=_0nexta=avalue;
var
语句同样处理:
forvar a of b
var _0=b;forvar a =_0Symboliteratornext;!adone;a=_0nexta=avalue;
class类声明
将类声明改为function
声明:
{}
{}
constructor
构造函数可省略,也可以显示声明:
{thisa = a}
//此行是空行,请忽略:由于github会忽略前面的空白,所以用注释代替{thisa = a}
注意行对应关系,省略的话行位置是
class
声明行,否则是constructor
声明行。
方法会改写成prototype
的原型方法:
{}
{}
getter/setter会通过Object.defineProperty
巧妙地设置到原型上:
{} {}
{} Object; Object;
static
静态方法会附加在function
本身:
static {}
{}A{}
extends类继承和super关键字
采用最广泛的寄生组合式继承:
{}
!{var _0=Object;_0constructor=A;Aprototype=_0;};{}Object;
开头会附加上一段
prototype
原型和constructor
构造器,标准的寄生组合式继承方法。
结尾会继承父类的静态属性。
super
关键字直接改写为父类引用:
{super}
!{var _0=Object;_0constructor=A;Aprototype=_0;};{B}Object;
如果不是调用父类构造函数而是方法,则会这样:
{super}
!{var _0=Object;_0constructor=A;Aprototype=_0;};{Bprototypea}Object;
默认构造器函数则会自动调用super()
:
{B}!{var _0=Object;_0constructor=A;Aprototype=_0};Object;
class表达式
和函数表达式一样,class也可以有表达式:
var o = class {}
var {{} _0prototype{}return _0}
由于表达式没有名字(也可以有),所以需要封装成立即执行的匿名函数并返回一个
class
声明。
有名字的话就用原有名字,否则依然临时唯一id。
注意匿名函数的结尾没有分号,因为本身是个
assignmentexpr
。
module
只要出现了module/import/export语句,就认为文件是个模块,用define
封装成AMD/CMD模块:
module circle from "a"
;
注意语句本身尚未做处理,下面会说明。为阅读方便,下面所有都省略了
define
封装。
也可以通过API设置来控制:
jsdc:Boolean
module
转换为require
:
module circle from "a"
var circle=;
import
也会转换为require
:
;
import
可以指定id:
var a;!{var _0=;a=_0a};
类似
_0
变量是自动生成的,数字会自动累加,且不会和已有变量冲突。
在冲突时会自动跳过:
var _0;!{var _1=;_0=_1a};
import
还可以指定多个id:
var a;var b;!{var _0=;a=_0a;b=_0b;};
import
可以用{}
来赋值,注意里面as
声明变量名的方法:
var a;var c;!{var _0=;a=_0a;c=_0b;};
export * from ""
会将模块的导出赋给module.exports:
!{var _0=;Object;};
export
一个var
语句时会自动赋值同名变量:
var a = 1
var a;exportsa=a = 1
export
一个方法或类时也一样:
{}{}
exportsa=a;{}exportsA=A;{}
export default
会赋给exports.default
,这样在使用时会判断是否有default
属性:
moduleexports=avar {var {var _0=;return _0?_0b:_0?_0default:_0}}
注意单id会优先判断使用同名属性,退级使用
default
,最后模块本身
ArrayComprehension数组推导
可以代替Array.map
方法:
var a = fork of ok
var {var k;var _0=;fork in ok=ok;_0return _0}
注意再次出现的临时变量
_0
和上面提到的一致,不会冲突。
if
语句可以替代Array.filter
方法:
var a = fork of oifkk
var {var k;var _0=;fork in ok=ok;ifk_0return _0}
嵌套组合使用也是可以的:
var a = fora of bforc of aifcc
var {var a;var c;var _0=;fora in ba=ba;forc in ac=ac;ifc_0return _0}
ArrowFunction箭头函数
转换为普通函数:
var v
var {return v}
括号形式的参数:
var b + c
var {return b + c}
带{}
的函数体:
var {return b - c}
var {return b - c}
yield语句
yield
作为关键字只能出现在Generator
中,会被替换为return
:
{ }
{ return}
Generator
语句本身尚未做处理,后面会提到。
赋值语句后会加上一个临时唯一id,模拟下次调用next()
传入的一个参数:
{ var a = }
{ var a;return;a=_0}
yield
的返回值将变成一个对象的value
,同时添加done
属性标明是否结束:
{ var a = 1}
{ var a;return value:1done:true;a=_0}
Generator生成器函数
它的实现比较复杂,首先是改写为普通函数:
{ 1 2}
{ returnvalue:1done:false returnvalue:2done:true}
然后包裹:
{ 1 2}
var {return {returnnext:a};{ returnvalue:1done:false returnvalue:2done:true}};
这样每次调用它便能得到像es6中一样的一个具有
next()
方法的对象。
内部的a变量需要改写为一个唯一临时id(为什么后面会提到):
{ 1 2}
var {return {returnnext:_0};{ returnvalue:1done:false returnvalue:2done:true}};
再次添加一个唯一临时id作为state标识,来为实现yield
功能做准备:
{ 1 2}
var {return {var _1=0;returnnext:_0;{ returnvalue:1done:false returnvalue:2done:true}}};
当出现yield
语句时,添加while
和switch
语句来模拟顺序执行:
{ 1 2}
var {return {var _1=0;returnnext:_0;{ while1}}};
注意状态在
switch
各分支语句之间的跳转
同时函数里面的var
声明需要前置,以免每次调用next()
方法时又重新声明一遍失去了状态:
{ var a = 1; a++; a++;}
var {return {var _1=0;returnnext:_0;var a;{ while1}}};
函数则不需要前置。
注意函数内有个同名变量
a
,这就是前面为什么要改函数名的原因。
添加default
语句:
{ var a = 1; a++; a++;}
var {return {var _0=0;returnnext:_1;var a;{ while1}}};
yield
还支持返回一个Generator
,这就是一个递归:
{ b}
var {return {var _0=0;returnnext:_1;{ while1}}};
表达式也一样,没有yield
则不会添加while
和switch
语句:
~{}
~{return {var _0=0;returnnext:_1;{}}}
destructure解构
var
声明变量时可以用数组:
var a = 1
var a;{var _0= 1;a=_00;}
变量名会被前置,同时包裹执行一个匿名函数,将变量名赋值对应到正确的索引。
多个变量一样,注意逗号占位符:
var abc = 1
var c;var b;var a;{var _1= 1;a=_10;b=_11;c=_13}
也可以是对象:
var a = "a":1
var a;{var _0= "a":1;a=_0"a"}
注意变量名和键名要一致。
对象可以用:
号更改引用:
var ab:c = "a":1"b":2
var a;{var _0= "a":1"b":2;a=_0"a";c=_0"b"}
它们甚至可以互相嵌套递归:
var abc:d = 1"b":2"c":3
var d;var b;var a;{var _0= 1"b":2"c":3;a=_00;var _1=_01;b=_1"b";var _2=_02;var _3=_2"c";d=_30}
解构还允许在未定义的情况下默认赋值:
var a=1 =
var a;{var _0= ;a=_00;if_0!=0a=1}
表达式赋值也可以:
a=1 = {}
{var _0= {};a=_0"a";if!_0a=1;return _0}
数组解构最后允许rest
运算符:
var a ...b = 1 2 3
var b;var a;{var _0= 1 2 3;a=_00;b=_0}