jsdc v0.6.13
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\ 返回解析后的词法单元序列
静态属性
- parse(code:String):String 可以直接调用静态方法转换,以省略new一个对象的步骤
- define(d:Boolean):Boolean 读取/设置转换module为CommonJS时是否包裹define(即转为AMD/CMD),默认false
- ast():Object 返回解析后的语法树
- tokens():Array\ 返回解析后的词法单元序列
- runtime(flag:Boolean):void 开启/关闭运行时支持,仅限NodeJs。开启后改写require机制,获取module前尝试预编译
Demo
- demo目录下是一个web端的实时转换例子,本地浏览需要
npm install
安装依赖 - 依赖的语法解析器来自于
homunculus
:https://github.com/army8735/homunculus - 在线地址:http://army8735.me/jsdc/demo/
Tools
License
语法转换规则
- 以下按实现逻辑顺序排列(如有逻辑关联,如let和block作用域)
- 确保转换后代码执行一致,调试行数一致
Number数字扩展
0b
或0B
开头的二进制将使用parseInt
转换:
var i = 0b010, j = 0B101
var i = parseInt("0b010", 2), j = parseInt("0B101", 2)
0o
或0O
开头的八进制也是如此(有人会用大写的字母O吗,和数字0根本看不出来区别):
var i = 0o456, j = 0O777
var i = parseInt("0o456", 8), j = parseInt("0O777", 8)
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 = {o(){}}
var a = {o:function(){}}
甚至可以用[]
表明它是一个表达式:
var a = {
['a'+'b'] : 1
}
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 a(){}}
!function(){function a(){}}();
{}块级作用域
必要时将{}
替换为function
作用域:
{
let a = 1;
function b(){}
}
!function() {
let a = 1;
function b(){}
}();
if
语句,iterator
语句和try
/catch
/finally
等也是,注意和纯{}
语句插入匿名函数位置的异同:
if(true) {
let a = 1;
}
if(true) {!function() {
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;!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(a, b, ...c)
fn.apply(this, [a,b].concat(Array.from(c)))
如果调用者是成员表达式,context将从this
变为主表达式:
Math.max(...a)
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模板
将模板转换为普通字符串,需要的情况下会包裹括号(确保运算符优先级正确性):
`template`
"template"
模板中的引号将被转义:
`"`
"\""
模板中的变量会被替换:
`${a}b`
(a + "b")
注意变量标识符$也可以被转义:
`\${a}b`
"\${a}b"
for of循环
存储被循环体到临时引用:
for(a of b){
}
var _0=b;for(a of b){
}
将of
改为=
并添加;
补完循环:
for(a of b){
}
var _0=b;for(a =_0;;){
}
将赋值添加[Symbol.iterator]().next()
并添加.done
结束判断:
for(a of b){
}
var _0=b;for(a =_0[Symbol.iterator]().next();!a.done;a=_0.next()){
}
循环体内先赋值.value
:
for(a of b){
}
var _0=b;for(a =_0[Symbol.iterator]().next();!a.done;a=_0.next()){a=a.value;
}
var
语句同样处理:
for(var a of b){
}
var _0=b;for(var a =_0[Symbol.iterator]().next();!a.done;a=_0.next()){a=a.value;
}
class类声明
将类声明改为function
声明:
class A{}
function A(){}
constructor
构造函数可省略,也可以显示声明:
class A{
constructor(a){this.a = a}
}
//此行是空行,请忽略:由于github会忽略前面的空白,所以用注释代替
function A(a){this.a = a}
注意行对应关系,省略的话行位置是
class
声明行,否则是constructor
声明行。
方法会改写成prototype
的原型方法:
class A{
method(){}
}
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
本身:
class A{
static F(){}
}
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()
:
class A extends B{
}
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模块:
module circle from "a"
define(function(requrie,exports,module){module circle from "a"});
注意语句本身尚未做处理,下面会说明。为阅读方便,下面所有都省略了
define
封装。
也可以通过API设置来控制:
jsdc.define(wrap:Boolean):Boolean
module
转换为require
:
module circle from "a"
var circle=require("a");
import
也会转换为require
:
import "a"
require("a");
import
可以指定id:
import a from "a"
var a;!function(){var _0=require("a");a=_0.a}();
类似
_0
变量是自动生成的,数字会自动累加,且不会和已有变量冲突。
在冲突时会自动跳过:
import _0 from "a"
var _0;!function(){var _1=require("a");_0=_1.a}();
import
还可以指定多个id:
import a,b from "a"
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:
export * from "a"
!function(){var _0=require("a");Object.keys(_0).forEach(function(k){module.exports[k]=temp[k];});}();
export
一个var
语句时会自动赋值同名变量:
export var a = 1
var a;exports.a=a = 1
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 = [for(k of o)k]
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 = v => v
var a = function(v) {return v}
括号形式的参数:
var a = (b, c) => b + c
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
:
function *a() {
yield
}
function *a() {
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 *(){
}
~function(){return function(){var _0=0;return{next:_1};function _1(){
}}}()
destructure解构
var
声明变量时可以用数组:
var [a] = [1]
var a;(function(){var _0= [1];a=_0[0];}).call(this)
变量名会被前置,同时包裹执行一个匿名函数,将变量名赋值对应到正确的索引。
多个变量一样,注意逗号占位符:
var [a,b,,c] = [1]
var c;var b;var a;(function(){var _1= [1];a=_1[0];b=_1[1];c=_1[3]}).call(this)
也可以是对象:
var {a} = {"a":1}
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=1] = []
var a;(function(){var _0= [];a=_0[0];if(_0.indexOf(a)!=0)a=1}).call(this)
表达式赋值也可以:
({a=1} = {})
((function(){var _0= {};a=_0["a"];if(!_0.hasOwnProperty('a'))a=1;return _0}).call(this))
数组解构最后允许rest
运算符:
var [a, ...b] = [1, 2, 3]
var b;var a;(function(){var _0= [1, 2, 3];a=_0[0];b=_0.slice(1)}).call(this)
8 years ago
8 years ago
8 years ago
8 years ago
9 years ago
9 years ago
9 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
10 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
12 years ago
12 years ago
12 years ago