琐碎点汇总
在 Node 环境中使用 ESM
两种方式:
- 把模块文件的后缀改成
.mjs; - 给最近的上级 package.json 文件添加名为
type的字段,并将字段值改成module。
ESM 中不支持 CommonJS 模块提供的某些引用
这包括:
- require
- exports
- module.exports
- __filename
- __dirname
要使用 require 函数,可以这样做:
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
// 就可以使用 require 函数引用 CommonJS 模块了
import.meta.url可以获得当前模块的文件路径(文件网址字符串)。
要访问到 __filename 和 __pathname,可以这么做:
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
// 将文件网址转成路径地址
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// 或者 const __dirname = resolve();
需要注意的是,ESM 的全局作用域中,
this是未定义的(undefined),而 CommonJS 中 this 则是指向exports的引用。
ESM 符号绑定
有两个文件,代码如下:
// b.mjs
export let count = 1;
export const increase = () => ++ count;
// a.mjs
import { count, increase } from './b.mjs';
console.log(count); // 1
increase();
console.log(count); // ?
运行后我们发现下面的 count 值变成了 2。ES Module 中导入的变量和导出的变量它们公用同一处内存空间,这与 JS 变量中的值类型和引用类型不同,被称为符号绑定(相当于 C/C++ 中的引用传递)。
在使用 import 进行导入时,这些绑定值只能被导入模块所读取(可读不可改),因此在 a.mjs 中修改 count 会报错。
编码顺序和字典顺序
比如有一些中文名字,我们希望按照拼音升序的方式进行排序,怎么正确的处理?
如果使用数组中自带的 sort API,你会发现排序并不准确,原因是 sort 默认排序是将元素转换为字符串,然后按照它们的 UTF-16 码元值升序排序。
假如要对 ['张三', '王五'] 数组按照拼音升序排列,预期应该是 ['王五', '张三'],但使用 sort 排序后发现不符合预期,原因是‘王’的码元值比‘张’的要大,所以排在了前面。
'张'.charCodeAt(); // 24352
'王'.charCodeAt(); // 29579
不过浏览器中为我们提供了另一个 API —— localeCompare,表示参考字符串在排序顺序中是在给定字符串之前、之后还是与之相同。如果引用字符串(referenceStr)存在于比较字符串(compareString)之前则为负数;如果引用字符串存在于比较字符串之后则为正数;相等的时候返回 0。比如:
'王'.localeCompare('张'); // -1,‘王’ 在 ‘张’ 之前
'w'.localeCompare('h'); // 1,‘w’ 在 ‘h’ 之后
'100'.localeCompare('20'); // -1,1 在 2 之前
因此字典排序就可以写成:
// < 0,a 在 b 前,[a, b](升序排列)
['张三', '王五'].sort((a, b) => a.localeCompare(b));
零宽字符
在开发中你是否遇到过这种问题,明明看着一模一样的字符串,结果对比的时候发现是 false,排查了半天也没有头绪。比如下面代码:
const str_1 = '零宽字符';
const str_2 = '零宽字符';
console.log(str_1 === str_2); // false 😭
console.log(str_1.length === str_2.length); // false 🤔
这是因为某个变量里存在零宽字符,比如 '零宽\u200d字符',当在 浏览器控制台输入时,得到的还是 “零宽字符” 字符串,\u200d 就是一个零宽字符(使用 vim 编辑器打开可以显示出来)。
在真实的场景下,用户可能复制了带有零宽字符的文本粘贴到了某个富文本或者表单中,导致我们代码中比较字符串时出现问题。
零宽字符在界面上没有任何宽度,也被叫做“幽灵字符”,下面的 Unicode 编码的字符都是零宽字符:
U+200B: 零宽度空格符,用于较长单词的换行分割;U+FEFF: 零宽度非断空格符,用于阻止特定位置的换行分割;U+200D: 零宽度连字符,用于阿拉伯与印度语系等文字中,使不会发生连字的字符间产生连字效应;U+200C: 零宽度段字符,用于阿拉伯文、德文、印度语系等文字中,阻止会发生连子的字符间的连字效果;U+200E: 左至右符,用于在混合文字方向的多种语音文本中(例如:混合左至右书写的英文与右至左书写的希伯来语),规定排版文字书写方向为左支右;U+200F: 右至左符,用于在混合文字方向的多种语言文本中,规定排版文字书写方向为右至左;
零宽字符可以用于数字水印,比如版权问题,防止别人复制粘贴,就可以在文本中插入零宽字符。
闭包漏洞
如何在不改变上面代码的情况下,修改 obj 对象?
const o = (function () {
const obj = {
a: 1,
b: 2,
};
return {
get: function(k) {
return obj[k];
}
};
})();
可能你会想到调用 valueOf(Object 原型上的方法)获取到原始的 obj 对象:
o.get('valueOf')();
但遗憾的是执行上面代码会报错,原因是 valueOf 方法内部使用了 this,但是上面的调用方式会丢失 this。
不过还是可以利用 Object.prototype 解决这个问题的,代码如下:
// 在 Object 原型对象上注册一个属性,getter 返回 this
Object.defineProperty(Object.prototype, 'abc', {
get: function() {
return this;
}
});
const obj = o.get('abc');
obj.a = '1111'; // 更改对象的属性值
console.log(o.get('a')); // '1111'
如何避免这种漏洞?
