分类
Javascript

如何读懂编译后的JS代码

JS代码为啥要编译?主要是采用ES6及以上语法编写的代码兼容不了老浏览器老环境。Bable可以将其转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。Babel通过插件让你现在就能使用新的语法,无需等待浏览器的支持。

在我们安装了@babel/cli、@babel/core、@babel/preset-env和后,就可以使用Babel来转码了。

// Babel 输入: ES2015 箭头函数
[1, 2, 3].map(n => n + 1);

上面代码执行npx babel src/main.js -o bundle.js后转换为

"use strict";

// Babel 输入: ES2015 箭头函数
[1, 2, 3].map(function (n) {
  return n + 1;
});

当然,这种简单语法,通过Bable官网的在线工具也能做到。Bable编译后的JS代码不难呀,您这样想就错了。实际项目可能很多文件很多模块,有可能还用Webpack等工具压缩混淆过,比这复杂多了。

比如下面一个比较常见的NodeJS工程。

// Rectangle
class Rectangle {
  constructor(w, h) {
    this.w = w;
    this.h = h;
  }

  calcArea() {
    return this.w * this.h;
  }

  calcCircumference() {
    return 2 * this.w + this.h;
  }
}
module.exports = Rectangle;
// main.js
const express = require('express');
const Rectangle = require('./Rectangle.js');

const app = express();
const port = 3000;
app.get('/', (req, res) => {
  res.send('Hello World!');
})
app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
})

console.log(new Rectangle(10, 20).calcArea());

webpack借助babel-loader实现了babel的代码转化功能,还可以实行代码合并压缩混淆。nodejs代码可以直接用node命令来跑,即使是生产环境部署也可以这么做。但有时为了保护代码,生产环境下使用webpack编译的“安全”代码。遇到这种代码,多数开发人员可能是就是看:天书“了”。如下面美化过的代码:

(() => {
  var e = {
    503: e => {
      function r(e, r) {
        for (var t = 0; t < r.length; t++) {
          var n = r[t];
          n.enumerable = n.enumerable || !1, n.configurable = !0, "value" in n && (n.writable = !0), Object.defineProperty(e, n.key, n)
        }
      }

      var t = function () {
        function e(r, t) {
          !function (e, r) {
            if (!(e instanceof r)) throw new TypeError("Cannot call a class as a function")
          }(this, e), this.w = r, this.h = t
        }

        var t, n;
        return t = e, (n = [{
          key: "calcArea", value: function () {
            return this.w * this.h
          }
        }, {
          key: "calcCircumference", value: function () {
            return 2 * this.w + this.h
          }
        }]) && r(t.prototype, n), Object.defineProperty(t, "prototype", {writable: !1}), e
      }();
      e.exports = t
    }, 860: e => {
      "use strict";
      e.exports = require("express")
    }
  }, r = {};

  function t(n) {
    var o = r[n];
    if (void 0 !== o) return o.exports;
    var i = r[n] = {exports: {}};
    return e[n](i, i.exports, t), i.exports
  }

  var n, o, i, a = {};
  n = t(860), o = t(503), (i = n()).get("/", (function (e, r) {
    r.send("Hello World!")
  })), i.listen(3e3, (function () {
    console.log("Example app listening on port ".concat(3e3))
  })), console.log(new o(10, 20).calcArea());
  var c = exports;
  for (var s in a) c[s] = a[s];
  a.__esModule && Object.defineProperty(c, "__esModule", {value: !0})
})();

503、860、e,r一堆乱七八糟的变量,看不懂呀。别着急,慢慢分析。建议从后想前看,也许您也注意到了。第44行的860对应第30~33行是一个自执行函数,该函数返回对express模块的引用,并通过e.exports暴露。503对应第30~33行也是一个自执行函数e,该函数返回对Rectangle类引用,并通过e.exports暴露。e包含应用使用的所有模块,是webpack对modules实现。e.exports实际上就是module.exports。函数t是webpack对require实现。Rectangle类内部的r函数会检测类构造方法调用,t函数相当类构造方法的es5实现。

还是云里雾里的。将Webpack的optimization配置如下:

optimization: {
    moduleIds: "named",
    minimize: false,
    minimizer: [new TerserPlugin({
      terserOptions: {
        format: {
          beautify: true,
          comments: true
        }
      }
    })]
  }

其中,moduleIds: “named”, 可以生成对调试更友好的可读的 id。moduleIds默认值为deterministic,使用最小3 位数字来表示moduleIds。

再次使用Webpack编译。

/******/ (() => { // webpackBootstrap
/******/ 	var __webpack_modules__ = ({

/***/ "./src/Rectangle.js":
/***/ ((module) => {

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }

var Rectangle = /*#__PURE__*/function () {
  function Rectangle(w, h) {
    _classCallCheck(this, Rectangle);

    this.w = w;
    this.h = h;
  }

  _createClass(Rectangle, [{
    key: "calcArea",
    value: function calcArea() {
      return this.w * this.h;
    }
  }, {
    key: "calcCircumference",
    value: function calcCircumference() {
      return 2 * this.w + this.h;
    }
  }]);

  return Rectangle;
}();

module.exports = Rectangle;

/***/ }),

/***/ "express":
/***/ ((module) => {

"use strict";
module.exports = require("express");

/***/ })

/******/ 	});
/************************************************************************/
/******/ 	// The module cache
/******/ 	var __webpack_module_cache__ = {};
/******/ 	
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/ 		// Check if module is in cache
/******/ 		var cachedModule = __webpack_module_cache__[moduleId];
/******/ 		if (cachedModule !== undefined) {
/******/ 			return cachedModule.exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = __webpack_module_cache__[moduleId] = {
/******/ 			// no module.id needed
/******/ 			// no module.loaded needed
/******/ 			exports: {}
/******/ 		};
/******/ 	
/******/ 		// Execute the module function
/******/ 		__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/ 	
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/ 	
/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
var express = __webpack_require__("express");

var Rectangle = __webpack_require__("./src/Rectangle.js");

var app = express();
var port = 3000;
app.get('/', function (req, res) {
  res.send('Hello World!');
});
app.listen(port, function () {
  console.log("Example app listening on port ".concat(port));
});
console.log(new Rectangle(10, 20).calcArea());
})();

var __webpack_export_target__ = exports;
for(var i in __webpack_exports__) __webpack_export_target__[i] = __webpack_exports__[i];
if(__webpack_exports__.__esModule) Object.defineProperty(__webpack_export_target__, "__esModule", { value: true });
/******/ })()
;

这段编译过的代码没有压缩混淆,moduleIds也使用了模块名,对比源码,应该能懂了吧?

纯JS项目还好,如果使用了React、Vue等框架,还得读懂模板转换处理逻辑,路漫漫呀,下次分解。

评论已关闭。