Skip to content

TEAMPRODUCTs/jsdc

 
 

Repository files navigation

Javascript Downcast

compiler ecmascript6 to ecmascript5

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 返回解析后的词法单元序列

    静态属性

    • 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

      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的性质一样,除了迁移还会改写为var形式:

      {function a(){}}
      var a;{a=function (){}}

      {}块级作用域

      必要时将{}替换为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(function(){var _0=[],_1;while(!(_1=c.next()).done)_0.push(_1.value)return _0}()))

      如果调用者是成员表达式,context将从this变为主表达式:

      Math.max(...a)
      Math.max.apply(Math, [].concat(function(){var _0=[],_1;while(!(_1=a.next()).done)_0.push(_1.value)return _0}()))

      在数组中则会自动展开,支持string预判断:

      var codeUnits = [..."this is a string"];
      var codeUnits = [...a];
      var codeUnits = [].concat("this is a string".split(""));
      var codeUnits = [].concat(Object.prototype.toString.call(a)=="[object String]"?a.split(""):a);

      template模板

      将模板转换为普通字符串,需要的情况下会包裹括号(确保运算符优先级正确性):

      `template`
      "template"

      模板中的引号将被转义:

      `"`
      "\""

      模板中的变量会被替换:

      `${a}b`
      (a + "b")

      注意变量标识符$也可以被转义:

      `\${a}b`
      "\${a}b"

      for of循环

      of改为=并添加;补完循环:

      for(a of b){
      }
      for(a =b;;){
      }

      将赋值添加.next()并添加.done结束判断:

      for(a of b){
      }
      for(a =b.next();!a.done;a=b.next()){
      }

      循环体内先赋值.value

      for(a of b){
      }
      for(a =b.next();!a.done;a=b.next()){a=a.value;
      }

      var语句同样处理:

      for(var a of b){
      }
      for(var a =b.next();!a.done;a=b.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(){var _1=0;return function(){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(){var _1=0;return function(){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(){var _1=0;return function(){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(){var _0=0;return function(){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(){var _0=0;return function(){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(){var _0=0;return function(){return{next:_1}};function _1(){
      }}()

      destructure解构

      var声明变量时可以用数组:

      var [a] = [1]
      var a;!function(){var _0= [1];a=_0[0];}()

      变量名会被前置,同时包裹执行一个匿名函数,将变量名赋值对应到正确的索引。

      多个变量一样,注意逗号占位符:

      var [a,b,,c] = [1]
      var c;var b;var a;!function(){var _1= [1];a=_1[0];b=_1[1];c=_1[3]}()

      也可以是对象:

      var {a} = {"a":1}
      var a;!function(){var _0= {"a":1};a=_0["a"]}()

      注意变量名和键名要一致。

      对象可以用:号更改引用:

      var {a,b:c} = {"a":1,"b":2}
      var a;!function(){var _0= {"a":1,"b":2};a=_0["a"];c=_0["b"]}()

      它们甚至可以互相嵌套递归:

      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]}()

      解构还允许在未定义的情况下默认赋值:

      var [a=1] = []
      var a;!function(){var _0= [];a=_0[0];if(_0.indexOf(a)!=0)a=1}()

      表达式赋值也可以:

      ({a=1} = {})
      (!function(){var _0= {};a=_0["a"];if(!_0.hasOwnProperty('a'))a=1;return _0}())

      数组解构最后允许rest运算符:

      var [a, ...b] = [1, 2, 3]
      var b;var a;!function(){var _0= [1, 2, 3];a=_0[0];b=_0.slice(1)}()

About

Javascript Downcast (es6 to es5)

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • JavaScript 98.2%
  • HTML 1.7%
  • Makefile 0.1%