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生成器函数
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目录下是一个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 = parseInt("0b010", 2), j = parseInt("0B101", 2)
0o
或
0O
开头的八进制也是如此(有人会用大写的字母O吗,和数字0根本看不出来区别):
var i = parseInt("0o456", 8), j = parseInt("0O777", 8)
Unicode的字符串增强
Unicode
编号大于
0xFFFF
的字符将会转移成2个
utf-8
拼接:
转义符也能正确识别:
Object增强
赋值对象时
Object
的同名属性可以简写:
方法也是:
甚至可以用
[]
表明它是一个表达式:
var a = function(){var _0={
_1: 1
};_0['a'+'b']=_0._1;delete _0._1;return _0}()
实现方法是先用个临时唯一变量替换掉表达式,最后再将它还原回来。
var和函数迁移
将
var
申明迁移至最近的作用域起始处:
function() {
if(true) {
var a = 1;
let b = 2;
}
}
function() {var a;
if(true) {
a = 1;
let b = 2;
}
}
仅当必要时才迁移,否则保持原样(比如下面没有
let
):
function() {
if(true) {
var a = 1;
}
}
示例中
let
和块级作用域尚未处理,后面会提到。
函数和var的性质一样:
!function(){function a(){}}();
{}块级作用域
必要时将
{}
替换为
function
作用域:
{
let a = 1;
function b(){}
}
!function() {
let a = 1;
function b(){}
}();
if
语句,
iterator
语句和
try
/
catch
/
finally
等也是,注意和纯
{}
语句插入匿名函数位置的异同:
if(true) {!function() {
let a = 1;
}();}
示例中
let
尚未做处理,后面会提到。
let/const关键字
将
let
和
const
替换为
var
:
注意和块级作用域的交互:
{
var a = 1;
let b;
const c = 1;
}
var a;!function() {
a = 1;
var b;
var c = 1;
}();
函数和
Generator
函数均默认块级作用域。
默认参数值
根据是否
undefined
赋值,它可以有多个:
function method(a, b = 1) {
}
function method(a, b) {if(b ===void 0)b=1;
}
将扩展参数通过
arguments
转换为数组:
function method(a, ...args) {
}
function method(a, args) {args = [].slice.call(arguments, 1);
}
方法执行则使用
apply
调用:
fn.apply(this, [a,b].concat(Array.from(c)))
如果调用者是成员表达式,context将从
this
变为主表达式:
Math.max.apply(Math, [].concat(Array.from(a)))
在数组中则会自动展开,支持string预判断:
var codeUnits = [..."this is a string"];
var codeUnits = [...a];
var codeUnits = [].concat("this is a string".split(""));
var codeUnits = [].concat(Array.from(a));
template模板
将模板转换为普通字符串,需要的情况下会包裹括号(确保运算符优先级正确性):
模板中的引号将被转义:
模板中的变量会被替换:
注意变量标识符$也可以被转义:
for of循环
存储被循环体到临时引用:
将
of
改为
=
并添加
;
补完循环:
将赋值添加
[Symbol.iterator]().next()
并添加
.done
结束判断:
var _0=b;for(a =_0[Symbol.iterator]().next();!a.done;a=_0.next()){
}
循环体内先赋值
.value
:
var _0=b;for(a =_0[Symbol.iterator]().next();!a.done;a=_0.next()){a=a.value;
}
var
语句同样处理:
var _0=b;for(var a =_0[Symbol.iterator]().next();!a.done;a=_0.next()){a=a.value;
}
class类声明
将类声明改为
function
声明:
constructor
构造函数可省略,也可以显示声明:
class A{
constructor(a){this.a = a}
}
function A(a){this.a = a}
注意行对应关系,省略的话行位置是
class
声明行,否则是
constructor
声明行。
方法会改写成
prototype
的原型方法:
function A{}
A.prototype.method=function(){}
getter/setter会通过
Object.defineProperty
巧妙地设置到原型上:
class A{
get b(){}
set c(d){}
}
function A(){}
Object.defineProperty(A.prototype, "b", {get :function(){}});
Object.defineProperty(A.prototype, "c", {set :function(d){}});
static
静态方法会附加在
function
本身:
function A(){}
A.F=function(){}
extends类继承和super关键字
采用最广泛的寄生组合式继承:
class A extends B{
constructor(){}
}
!function(){var _0=Object.create(B.prototype);_0.constructor=A;A.prototype=_0;}();
function A(){}
Object.keys(B).forEach(function(k){A[k]=B[k]});
开头会附加上一段
prototype
原型和
constructor
构造器,标准的寄生组合式继承方法。
结尾会继承父类的静态属性。
super
关键字直接改写为父类引用:
class A extends B{
constructor(){super()}
}
!function(){var _0=Object.create(B.prototype);_0.constructor=A;A.prototype=_0;}();
function A(){B.call(this)}
Object.keys(B).forEach(function(k){A[k]=B[k]});
如果不是调用父类构造函数而是方法,则会这样:
class A extends B{
constructor(){super.a()}
}
!function(){var _0=Object.create(B.prototype);_0.constructor=A;A.prototype=_0;}();
function A(){B.prototype.a.call(this)}
Object.keys(B).forEach(function(k){A[k]=B[k]});
默认构造器函数则会自动调用
super()
:
function A(){B.call(this)}!function(){var _0=Object.create(B.prototype);_0.constructor=A;A.prototype=_0}();
Object.keys(B).forEach(function(k){A[k]=B[k]});
class表达式
和函数表达式一样,class也可以有表达式:
var o = class{
method(){}
}
var o = function(){function _0(){}
_0.prototype.method = function(){}
return _0}()
由于表达式没有名字(也可以有),所以需要封装成立即执行的匿名函数并返回一个
class
声明。
有名字的话就用原有名字,否则依然临时唯一id。
注意匿名函数的结尾没有分号,因为本身是个
assignmentexpr
。
module
只要出现了module/import/export语句,就认为文件是个模块,用
define
封装成AMD/CMD模块:
define(function(requrie,exports,module){module circle from "a"});
注意语句本身尚未做处理,下面会说明。为阅读方便,下面所有都省略了
define
封装。
也可以通过API设置来控制:
jsdc.define(wrap:Boolean):Boolean
module
转换为
require
:
import
也会转换为
require
:
import
可以指定id:
var a;!function(){var _0=require("a");a=_0.a}();
类似
_0
变量是自动生成的,数字会自动累加,且不会和已有变量冲突。
在冲突时会自动跳过:
var _0;!function(){var _1=require("a");_0=_1.a}();
import
还可以指定多个id:
var a;var b;!function(){var _0=require("a");a=_0.a;b=_0.b;}();
import
可以用
{}
来赋值,注意里面
as
声明变量名的方法:
import {a,b as c} from "a"
var a;var c;!function(){var _0=require("a");a=_0.a;c=_0.b;}();
export * from ""
会将模块的导出赋给module.exports:
!function(){var _0=require("a");Object.keys(_0).forEach(function(k){module.exports[k]=temp[k];});}();
export
一个
var
语句时会自动赋值同名变量:
export
一个方法或类时也一样:
export function a(){}
export class A{}
exports.a=a;function a(){}
exports.A=A;function A(){}
export default
会赋给
exports.default
,这样在使用时会判断是否有
default
属性:
export default a
import b from "a"
module.exports=a
var b=function(){var b=function(){var _0=require("a");return _0.hasOwnProperty("b")?_0.b:_0.hasOwnProperty("default")?_0.default:_0}()}()
注意单id会优先判断使用同名属性,退级使用
default
,最后模块本身
ArrayComprehension数组推导
可以代替
Array.map
方法:
var a = function(){var k;var _0=[];for(k in o){k=o[k];_0.push(k)}return _0}()
注意再次出现的临时变量
_0
和上面提到的一致,不会冲突。
if
语句可以替代
Array.filter
方法:
var a = [for(k of o)if(k)k]
var a = function(){var k;var _0=[];for(k in o){k=o[k];if(k)_0.push(k)}return _0}()
嵌套组合使用也是可以的:
var a = [for(a of b)for(c of a)if(c)c]
var a = function(){var a;var c;var _0=[];for(a in b){a=b[a];for(c in a){c=a[c];if(c)_0.push(c)}}return _0}()
ArrowFunction箭头函数
转换为普通函数:
var a = function(v) {return v}
括号形式的参数:
var a = function(b, c) {return b + c}
带
{}
的函数体:
var a = (b, c) => {return b - c}
var a = function(b, c) {return b - c}
yield语句
yield
作为关键字只能出现在
Generator
中,会被替换为
return
:
Generator
语句本身尚未做处理,后面会提到。
赋值语句后会加上一个临时唯一id,模拟下次调用
next()
传入的一个参数:
function *a() {
var a = yield
}
function *a(_0) {
var a;return;a=_0
}
yield
的返回值将变成一个对象的
value
,同时添加
done
属性标明是否结束:
function *a() {
var a = yield 1
}
function *a(_0) {
var a;return {value:1,done:true};a=_0
}
Generator生成器函数
它的实现比较复杂,首先是改写为普通函数:
function *a(){
yield 1
yield 2
}
function a(){
return{value:1,done:false}
return{value:2,done:true}
}
然后包裹:
function *a(){
yield 1
yield 2
}
var a=function(){return function(){return{next:a}};function a(){
return{value:1,done:false}
return{value:2,done:true}
}}();
这样每次调用它便能得到像es6中一样的一个具有
next()
方法的对象。
内部的a变量需要改写为一个唯一临时id(为什么后面会提到):
function *a(){
yield 1
yield 2
}
var a=function(){return function(){return{next:_0}};function _0(){
return{value:1,done:false}
return{value:2,done:true}
}}();
再次添加一个唯一临时id作为state标识,来为实现
yield
功能做准备:
function *a(){
yield 1
yield 2
}
var a=function(){return function(){var _1=0;return{next:_0};function _0(){
return{value:1,done:false}
return{value:2,done:true}
}}}();
当出现
yield
语句时,添加
while
和
switch
语句来模拟顺序执行:
function *a(){
yield 1
yield 2
}
var a=function(){return function(){var _1=0;return{next:_0};function _0(){
while(1){switch(_1){case 0:_1=1;return{value:1,done:false}
case 1:_1=-1;return{value:1,done:true}}}
}}}();
注意状态在
switch
各分支语句之间的跳转
同时函数里面的
var
声明需要前置,以免每次调用
next()
方法时又重新声明一遍失去了状态:
function *a(){
var a = 1;
yield a++;
yield a++;
}
var a=function(){return function(){var _1=0;return{next:_0};var a;function _0(){
while(1){switch(_1){case 0:a = 1;
_1=1;return{value:a++,done:false};
case 1:_1=-1;return{value:a++,done:true;}}}
}}}();
函数则不需要前置。
注意函数内有个同名变量
a
,这就是前面为什么要改函数名的原因。
添加
default
语句:
function *a(){
var a = 1;
yield a++;
yield a++;
}
var a=function(){return function(){var _0=0;return{next:_1};var a;function _1(_2){
while(1){switch(_0){case 0:a = 1;
_0=1;return{value:a++,done:false};case 1:
_0=-1;return{value:a++,done:true};default:return{done:true}}}
}}}();
yield
还支持返回一个
Generator
,这就是一个递归:
function *a(){
yield * b
}
var a=function(){return function(){var _0=0;return{next:_1};function _1(_2){
while(1){switch(_0){case 0:_0=1;var _3=b();if(!_3.done)_0=0;return _3;default:return{done:true}}}
}}}();
表达式也一样,没有
yield
则不会添加
while
和
switch
语句:
~function(){return function(){var _0=0;return{next:_1};function _1(){
}}}()
destructure解构
var
声明变量时可以用数组:
var a;(function(){var _0= [1];a=_0[0];}).call(this)
变量名会被前置,同时包裹执行一个匿名函数,将变量名赋值对应到正确的索引。
多个变量一样,注意逗号占位符:
var c;var b;var a;(function(){var _1= [1];a=_1[0];b=_1[1];c=_1[3]}).call(this)
也可以是对象:
var a;(function(){var _0= {"a":1};a=_0["a"]}).call(this)
注意变量名和键名要一致。
对象可以用
:
号更改引用:
var {a,b:c} = {"a":1,"b":2}
var a;(function(){var _0= {"a":1,"b":2};a=_0["a"];c=_0["b"]}).call(this)
它们甚至可以互相嵌套递归:
var [a,{b},{c:[d]}] = [1,{"b":2},{"c":[3]}]
var d;var b;var a;(function(){var _0= [1,{"b":2},{"c":[3]}];a=_0[0];var _1=_0[1];b=_1["b"];var _2=_0[2];var _3=_2["c"];d=_3[0]}).call(this)
解构还允许在未定义的情况下默认赋值:
var a;(function(){var _0= [];a=_0[0];if(_0.indexOf(a)!=0)a=1}).call(this)
表达式赋值也可以:
((function(){var _0= {};a=_0["a"];if(!_0.hasOwnProperty('a'))a=1;return _0}).call(this))
数组解构最后允许
rest
运算符:
var [a, ...b] = [1, 2, 3]