移除注释的完善思路:真的可以用正则实现?

2018-07-12 admin

导语

网上有很多自称能实现移除JS注释的正则表达式,实际上存在种种缺陷。这使人多少有些愕然,也不禁疑惑到:真的可以用正则实现吗?而本篇文章以使用正则移除JS注释为目标,通过实践,由浅及深,遇到问题解决问题,一步步看看到底能否用正则实现!

移除注释的完善思路:真的可以用正则实现?

本篇涉及了ES6的字符串正则扩展,详细请点击查看。

1 单行注释

单行注释要么占据一整行,要么处于某一行的最后。 正常情况下不难,直接通过正则匹配,再用replace方法移除便可。

let codes = `
  let name = "Wmaker"; // This is name.
  if (name) {
    // Print name.
    console.log("His name is:", name);
  }
`;

console.log( codes.replace(/\/\/.*$/mg, '') );

// 打印出:
// let name = "Wmaker"; 
// if (name) {
//   
//   console.log("His name is:", name);
// }

上面是成功的删除了注释,不过对于独占一整行的注释清理的不够彻底,会留下空白行。实际上,行尾注释前面的空白也被保留了下来。所以目标稍稍提高,清除这些空白。操作起来也并不难,思路大致这样:删除整行,实际上是删除本行末尾的换行符或上一行末尾的换行符。而换行符本身也属于空白符。所以只需操作正则,匹配到注释以及注释前面所有的空白符即可,一箭双雕。

let codes = `
  let name = "Wmaker"; // This is name.
  if (name) {
    // Print name.
    console.log("His name is:", name);
  }
`;

console.log( codes.replace(/\s*\/\/.*$/mg, '') );

// 打印出:
// let name = "Wmaker";
// if (name) {
//   console.log("His name is:", name);
// }

如果在字符串中出现完整的URL地址,上面的正则会直接匹配而将其删除。网上大多会将URL的格式特征(http://xxx):双下划线前面有冒号,作为解决途径加以利用。但这只是治标不治本的做法,毕竟//以任何形式出现在字符串中是它的自由,我们无从干涉。

这样问题就转变成:如何使正则匹配存在于引号外的双下划线? 想匹配被引号包围,带有双下划线的代码块比较简单:/".*\/\/.*"/mg。难点在于如何实现这个否定,即当正则匹配到双下划线后,再判断其是否在引号里面?绞尽脑汁,也上网查了很多,都没有像样的结果。静心平气,洗把脸刷刷牙再冲个头冷静之后,觉得单纯使用正则的路已经走不通了,得跳出这个圈。

就在近乎精尽人亡的最后关头,在那淫秽污浊的房间上方突然光芒万丈。我急忙护住了充满血丝的眼睛,静待其适应后定睛一看。只见那里显现出了一段文字(Chinese):孩儿啊,先将带有//被引号包围的字符串替换掉,去掉注释后再还原,不就行了吗?

let codes = `
  let name = "Wmaker"; // This is name.
  if (name) {
    // Print name.
    console.log("His name is:", name);
    console.log("Unusual situation, characters of // in quotation marks.");
  }
`;

// 之前的方式。
console.log( codes.replace(/\s*\/\/.*$/mg, '') );
// 打印出:
// let name = "Wmaker";
// if (name) {
//   console.log("His name is:", name);
//   console.log("Unusual situation, characters of
// }

// 现在的方式。
console.log( removeComments(codes) );
// 打印出:
// let name = "Wmaker";
// if (name) {
//   console.log("His name is:", name);
//   console.log("Unusual situation, characters of // in quotation marks.");
// }

function removeComments(codes) {
  let {replacedCodes, matchedObj} = replaceQuotationMarksWithForwardSlash(codes);

  replacedCodes = replacedCodes.replace(/\s*\/\/.*$/mg, '');
  Object.keys(matchedObj).forEach(k => {
    replacedCodes = replacedCodes.replace(k, matchedObj[k]);
  });

  return replacedCodes;

  function replaceQuotationMarksWithForwardSlash(codes) {
    let matchedObj = {};
    let replacedCodes = '';

    let regQuotation = /".*\/\/.*"/mg;
    let uniqueStr = 'QUOTATIONMARKS' + Math.floor(Math.random()*10000);

    let index = 0;
    replacedCodes = codes.replace(regQuotation, function(match) {
      let s = uniqueStr + (index++);
      matchedObj[s] = match;
      return s;
    });

    return { replacedCodes, matchedObj };
  }
}

是的,目标达成了,老天眷顾啊! 另外,有一个需要优化的地方:定义字符串的方式有三种 ’ " ` ,目前我们只匹配了双引号。

为了避免正则的记忆功能,都使用了正则字面量进行测试。

--- 之前
console.log( /".*\/\/.*"/mg.test(`'Unu//sual'`) ); // false
console.log( /".*\/\/.*"/mg.test(`"Unu//sual"`) ); // true
console.log( /".*\/\/.*"/mg.test(`\`Unu//sual\``) ); // false

--- 之后
console.log( /('|"|`).*\/\/.*\1/mg.test(`'Unu//sual'`) ); // true
console.log( /('|"|`).*\/\/.*\1/mg.test(`"Unu//sual"`) ); // true
console.log( /('|"|`).*\/\/.*\1/mg.test(`\`Unu//sual\``) ); // true

啊!问题到此结束了! 真的结束了吗?不!我看了看时间:02:17,然后将眼镜摘下,扯了张纸巾,拭去了几颗泪水。

以下是接连解决的两个问题:贪婪模式和转义字符。

--- STEP 1,由于正则的贪婪模式导致。
let codes = `
  let str = 'abc//abc'; // abc'
`;
console.log( codes.match(/('|"|`).*\/\/.*\1/mg) ); // ["'abc//abc'; // abc'"]

-- 解决

let codes = `
  let str = 'abc//abc'; // abc'
`;
console.log( codes.match(/('|"|`).*?\/\/.*?\1/mg) ); // ["'abc//abc'"]

--- STEP 2,由定义字符串时其中的转义字符导致。
let codes = `
  let str = 'http://x\\'x.com'; // 'acs
`;
console.log( codes.match(/('|"|`).*?\/\/.*?\1/mg) ); // ["'http://x\'", "'; // '"]

-- 解决

let reg = /(?<!\\)('|"|`).*?\/\/.*?(?<!\\)\1/mg;
let codes = `
  let str = 'http://x\\'x.com'; // 'acs
`;
console.log( codes.match(reg) ); // ["'http://x\'x.com'"]

事情到这里,虽然劳累,但多少有些成就感,毕竟成功了。

可是,可是,可是在测试时,竟然无意间发现一个无法逾越的障碍。就好比费劲千辛万苦花费无尽的财力物力之后,某某尤物终于愿意一同去情人旅馆时,却发现家家爆满,没有空余的房间。在强装欢笑,玩命的哄骗着她,一家接连一家的寻找直到终于定到房间后,却发现自己已然挺不起来了!

正则会将任意位置的引号作为查找的起始位置,它不在乎引号是成双的道理。下面是一个示例。

let reg = /(?<!\\)('|"|`).*?\/\/.*?(?<!\\)\1/mg;
let codes = `
  let str = "abc"; // "
`;
console.log( codes.match(reg) ); // [""abc"; // ""]

不过,问题好歹在补过觉之后的 06:37 时得以解决。 思路是这样的:虽然不能正确实现匹配带有//被引号包围的代码块(可能有方法,但能力有限),但是简化成匹配单纯被引号包围的代码块,是简单而且能正确做到的,虽然耗费的内存多了一些。另外,两引号间也可能包含换行符,所以为其增加s模式:.代表全部字符。下面是去除单行注释的最终代码。

let codes = `
  let name = "Wmaker"; // This is name.
  let str = 'http://x\\'x.com' + " / / " + '/"/"/'; // '; // " "
  if (name) {
    // Print name.
    console.log("His name is:", name);
    console.log("Unusual situation, characters of // in quotation marks.");
  }
`;

console.log(removeComments(codes));
// 打印出:
// let name = "Wmaker";
// let str = 'http://x\'x.com' + " / / " + '/"/"/';
// if (name) {
//   console.log("His name is:", name);
//   console.log("Unusual situation, characters of // in quotation marks.");
// }

function removeComments(codes) {
  let {replacedCodes, matchedObj} = replaceQuotationMarksWithForwardSlash(codes);

  replacedCodes = replacedCodes.replace(/\s*\/\/.*$/mg, '');
  Object.keys(matchedObj).forEach(k => {
    replacedCodes = replacedCodes.replace(k, matchedObj[k]);
  });

  return replacedCodes;

  function replaceQuotationMarksWithForwardSlash(codes) {
    let matchedObj = {};
    let replacedCodes = '';

    let regQuotation = /(?<!\\)('|"|`).*?(?<!\\)\1/smg;
    let uniqueStr = 'QUOTATIONMARKS' + Math.floor(Math.random()*10000);

    let index = 0;
    replacedCodes = codes.replace(regQuotation, function(match) {
      let s = uniqueStr + (index++);
      matchedObj[s] = match;
      return s;
    });

    return { replacedCodes, matchedObj };
  }
}

最后补充一点,单双引号虽然也可以多行显示,但其解析后实际是单行的。

let codes = "' \
  Wmaker \
'";
codes.match( /(?<!\\)('|"|`).*?(?<!\\)\1/smg ); // ["'   Wmaker '"]

2 多行注释

啊!难点已经解决,现在就可以悠哉悠哉的往前推进了。 多行注释与单行思路相同,只需在删除注释时多加一个匹配模式。中和两者的最终代码如下。

let codes = `
  let name = "Wmaker"; // This is name.
  let str = 'http://x\\'x.com' + " / / " + '/"/"/'; // '; // " "
  let str = 'http://x\\'x./*a*/com' + " / / " + '/"/"/'; // '; // "/*sad*/ "
  if (name) {
    // Print name.
    /* Print name. */
    console.log("His name is:", name);
    console.log("Unusual situation, characters of // in quotation marks.");
    /*
     * Others test.
     */
    console.log("Unusual situation, characters of /* abc */ in quotation marks.");
  }
`;

console.log(removeComments(codes));
// 打印出:
// let name = "Wmaker";
// let str = 'http://x\'x.com' + " / / " + '/"/"/';
// let str = 'http://x\'x./*a*/com' + " / / " + '/"/"/';
// if (name) {
//   console.log("His name is:", name);
//   console.log("Unusual situation, characters of // in quotation marks.");
//   console.log("Unusual situation, characters of /* abc */ in quotation marks.");
// }

function removeComments(codes) {
  let {replacedCodes, matchedObj} = replaceQuotationMarksWithForwardSlash(codes);

  replacedCodes = replacedCodes.replace(/(\s*\/\/.*$)|(\s*\/\*[\s\S]*?\*\/)/mg, '');
  Object.keys(matchedObj).forEach(k => {
    replacedCodes = replacedCodes.replace(k, matchedObj[k]);
  });

  return replacedCodes;

  function replaceQuotationMarksWithForwardSlash(codes) {
    let matchedObj = {};
    let replacedCodes = '';

    let regQuotation = /(?<!\\)('|"|`).*?(?<!\\)\1/smg;
    let uniqueStr = 'QUOTATIONMARKS' + Math.floor(Math.random()*10000);

    let index = 0;
    replacedCodes = codes.replace(regQuotation, function(match) {
      let s = uniqueStr + (index++);
      matchedObj[s] = match;
      return s;
    });

    return { replacedCodes, matchedObj };
  }
}

3 总结

从以上可以得出结论,单纯使用正则表达式是不能达到目标的,需要配合其它操作才行。但现在得出的结果真的能覆盖全部的情况?会不会有其它的隐藏问题,比如多字节字符的问题。虽然作为一个码农,该有的自信不会少,但慢慢的也明白了自己的局限性。从网上其它的资料看,在正确的解析中去除注释,可能会更为稳妥。但谁知道了?

这里是工作于前端页面的代码及相应示例,下载链接

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Remove Comments</title>
</head>

<body>
  <p>输入:</p>
  <textarea id="input" cols="100" rows="12"></textarea>

  <br /><br />
  <button onclick="transform()">转换</button>

  <p>输出:</p>
  <textarea id="output" cols="100" rows="12"></textarea>

  <script>
    let input = document.querySelector('#input');
    let output = document.querySelector('#output');

    setDefaultValue();

    function transform() {
      output.value = removeComments(input.value);
    }

    function removeComments(codes) {
      let {replacedCodes, matchedObj} = replaceQuotationMarksWithForwardSlash(codes);

      replacedCodes = replacedCodes.replace(/(\s*\/\/.*$)|(\s*\/\*[\s\S]*?\*\/)/mg, '');
      Object.keys(matchedObj).forEach(k => {
        replacedCodes = replacedCodes.replace(k, matchedObj[k]);
      });

      return replacedCodes;

      function replaceQuotationMarksWithForwardSlash(codes) {
        let matchedObj = {};
        let replacedCodes = '';

        let regQuotation = /(?<!\\)('|"|`).*?(?<!\\)\1/smg;
        let uniqueStr = 'QUOTATIONMARKS' + Math.floor(Math.random()*10000);

        let index = 0;
        replacedCodes = codes.replace(regQuotation, function(match) {
          let s = uniqueStr + (index++);
          matchedObj[s] = match;
          return s;
        });

        return { replacedCodes, matchedObj };
      }
    }

    function setDefaultValue() {
      input.value = `let name = "Wmaker"; // This is name.
let str = 'http://x\\'x.com' + " / / " + '/"/"/'; // '; // " "
let str = 'http://x\\'x./*a*/com' + " / / " + '/"/"/'; // '; // "/*sad*/ "
if (name) {
  // Print name.
  /* Print name. */
  console.log("His name is:", name);
  console.log("Unusual situation, characters of // in quotation marks.");
  /*
   * Others test.
   */
  console.log("Unusual situation, characters of /* abc */ in quotation marks.");
}
`;
    }
  </script>
</body>
</html>

这里是工作于Node端的代码及相应示例,下载链接。运行命令:node 执行文件 待转译文件 转移后文件

图片描述

const fs = require('fs');
const path = require('path');
const process = require('process');

let sourceFile = process.argv[2];
let targetFile = process.argv[3];
if (!sourceFile || !targetFile) {
  throw new Error('Please set source file and target file.');
}

sourceFile = path.resolve(__dirname, sourceFile);
targetFile = path.resolve(__dirname, targetFile);

fs.readFile(sourceFile, 'utf8', (err, data) => {
  if (err) throw err;
  fs.writeFile(targetFile, removeComments(data), 'utf8', (err, data) => {
    if (err) throw err;
    console.log('Remove Comments Done!');
  });
});

function removeComments(codes) {
  let {replacedCodes, matchedObj} = replaceQuotationMarksWithForwardSlash(codes);

  replacedCodes = replacedCodes.replace(/(\s*\/\/.*$)|(\s*\/\*[\s\S]*?\*\/)/mg, '');
  Object.keys(matchedObj).forEach(k => {
    replacedCodes = replacedCodes.replace(k, matchedObj[k]);
  });

  return replacedCodes;

  function replaceQuotationMarksWithForwardSlash(codes) {
    let matchedObj = {};
    let replacedCodes = '';

    let regQuotation = /(?<!\\)('|"|`).*?(?<!\\)\1/smg;
    let uniqueStr = 'QUOTATIONMARKS' + Math.floor(Math.random()*10000);

    let index = 0;
    replacedCodes = codes.replace(regQuotation, function(match) {
      let s = uniqueStr + (index++);
      matchedObj[s] = match;
      return s;
    });

    return { replacedCodes, matchedObj };
  }
}

延伸阅读

ES6精华:字符串扩展。文章链接。 ES6精华:正则表达式扩展。文章链接

原文链接:https://segmentfault.com/a/1190000015611632

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-35816.html

文章标题:移除注释的完善思路:真的可以用正则实现?

相关文章
js性能优化 如何更快速加载你的JavaScript页面
确保代码尽量简洁 不要什么都依赖JavaScript。不要编写重复性的脚本。要把JavaScript当作糖果工具,只是起到美化作用。别给你的网站添加大量的JavaScript代码。只有必要的时候用一下。只有确实能改善用户体验的时候用一下。 ...
2015-11-12
10个强大的纯CSS3动画案例分享
我们的网页外观主要由CSS控制,编写CSS代码可以任意改变我们的网页布局以及网页内容的样式。CSS3的出现,更是可以让网页增添了不少动画元素,让我们的网页变得更加生动有趣,并且更易于交互。本文分享了10个非常炫酷的CSS3动画案例,希望大家...
2015-11-16
Android中Okhttp3实现上传多张图片同时传递参数
之前上传图片都是直接将图片转化为io流传给服务器,没有用框架传图片。 最近做项目,打算换个方法上传图片。 Android发展到现在,Okhttp显得越来越重要,所以,这次我选择用Okhttp上传图片。 Okhttp目前已经更新到Okhttp...
2017-03-17
JavaScript实现PC手机端和嵌入式滑动拼图验证码三种效果
PC和手机端网站滑动拼图验证码效果源码,同时包涵了弹出式Demo,使用ajax形式提交二次验证码所需的验证结果值,嵌入式Demo,使用表单形式提交二次验证所需的验证结果值,移动端手动实现弹出式Demo三种效果 首先要确认前端使用页面,比如...
2017-03-17
Angular2-primeNG文件上传模块FileUpload使用详解
近期在学习使用Angular2做小项目,期间用到很多primeNG的模块。 本系列将结合实战总结angular2-primeNG各个模块的使用经验。 文件上传模块FileUploadModule 首先要在使用该组件的模块内导入文件上传模块 ...
2017-03-09
YouTube正式默认使用HTML5视频播放器
YouTube视频网站现在默认使用HTML5播放器,这意味着更好的性能、 稳定性、 电池寿命和甚至是更好的安全性。现在用户通过Chrome、IE 11、Safari 8和Beta版本的Firefox进行浏览的时候都默认使用HTML5视频播放...
2015-11-12
从2014年的发展来展望JS的未来将会如何
&lt;font face=&quot;寰�杞�闆呴粦, Arial, sans-serif &quot;&gt;2014骞达紝杞�浠惰�屼笟鍙戝睍杩呴€燂紝鍚勭�嶈��瑷€灞傚嚭涓嶇┓锛屼互婊¤冻鐢ㄦ埛涓嶆柇鍙樺寲鐨勯渶姹傘€傝繖浜涜��...
2015-11-12
JavaScript常用特效chm下载
下载地址:JavaScript常用特效chm下载 对了,如果打开空白,在手册上右键属性解除锁定即可。 ...
2015-11-12
12个你未必知道的CSS小知识
虽然CSS并不是一种很复杂的技术,但就算你是一个使用CSS多年的高手,仍然会有很多CSS用法/属性/属性值你从来没使用过,甚至从来没听说过。 1.CSS的color属性并非只能用于文本显示 对于CSS的color属性,相信所有Web开发人员...
2015-11-12
Vue.js组件tab实现选项卡切换
本文实例为大家分享了vue插件tab选项卡的具体代码,供大家参考,具体内容如下 效果图: 代码如下: &lt;!DOCTYPE html&gt; &lt;html lang=&quot;en&quot;&gt; &lt;head&gt; ...
2017-03-13
回到顶部