Javascript Downcast (es6 to es5)
JavaScript HTML Makefile
Latest commit 92fbf06 Jan 6, 2017 @army8735 scope this
Permalink
Failed to load latest commit information.
bin #29 May 9, 2015
demo 避免使用.关键字在IE8下报错 May 20, 2015
src scope this Jan 6, 2017
tests scope this Jan 6, 2017
web scope this Jan 6, 2017
.coveralls.yml use homunculus as a syntax parser Jun 6, 2014
.gitattributes init Mar 12, 2013
.gitignore jsdc-node #29 May 8, 2015
.npmignore 添加命令行工具 Sep 12, 2014
.travis.yml sudo: false Aug 4, 2015
Makefile 5000ms May 1, 2015
README.md 解构function表达式的this指向保持之前 Dec 25, 2015
gulpfile.js 无需换行 Oct 16, 2014
index.js typo Jun 10, 2014
package.json scope this Jan 6, 2017

README.md

Javascript Downcast

jsdc取自Java多态特性向下转型,意指ES6代码向下转换为ES5兼容代码。

NPM version Build Status Coverage Status Dependency Status

INSTALL

npm install jsdc

使用说明

  • jsdc仅提供安全兼容的转换接口,并且不改变你的源代码行数一致性,这使得调试极为便利
  • 智能识别es5语法,jsdc不会修改es5的部分
  • 无需预置script脚本,绝不更改任何变量
  • CommonJS/AMD/CMD自适应
  • as simple as possible
  • 仅转换可实现的语言部分,扩展库(如SetMap)请使用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

Tools

License

[MIT License]

语法转换规则

  • 以下按实现逻辑顺序排列(如有逻辑关联,如let和block作用域)
  • 确保转换后代码执行一致,调试行数一致

Number数字扩展

0b0B开头的二进制将使用parseInt转换:

var i = 0b010, j = 0B101
var i = parseInt("0b010", 2), j = parseInt("0B101", 2)

0o0O开头的八进制也是如此(有人会用大写的字母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关键字

letconst替换为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语句时,添加whileswitch语句来模拟顺序执行:

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则不会添加whileswitch语句:

~function *(){
}
~function(){return function(){var _0=0return{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)