转换的原因:
emoji用到的字符是4字节的utf-16(utf-16有2字节和4字节两种编码),而后端数据库是采用的utf-8,最多允许3字节的字符。插入数据库变成乱码的???,接口做了校验的还会报错。
utf-16 从U+0000至U+D7FF以及从U+E000至U+FFFF的码位

第一个Unicode平面(码位从U+0000至U+FFFF)包含了最常用的字符。该平面被称为基本多语言平面,缩写为BMP(Basic Multilingual Plane,BMP)。UTF-16与UCS-2编码这个范围内的码位为16位元长的单个码元,数值等价于对应的码位。BMP中的这些码位是仅有的可以在UCS-2中表示的码位。

从U+10000到U+10FFFF的码位

辅助平面(Supplementary Planes)中的码位,在UTF-16中被编码为一对16位元长的码元(即32位,4字节),称作代理对(Surrogate Pair),具体方法是:
UTF-16解码
lead \ trail DC00 DC01 … DFFF
D800 10000 10001 … 103FF
D801 10400 10401 … 107FF
⋮ ⋮ ⋮ ⋱ ⋮
DBFF 10FC00 10FC01 … 10FFFF
码位减去 0x10000,得到的值的范围为20位元长的 0…0xFFFFF。
高位的10位元的值(值的范围为 0…0x3FF)被加上 0xD800 得到第一个码元或称作高位代理(high surrogate),值的范围是 0xD800…0xDBFF。由于高位代理比低位代理的值要小,所以为了避免混淆使用,Unicode标准现在称高位代理为前导代理(lead surrogates)。
低位的10位元的值(值的范围也是 0…0x3FF)被加上 0xDC00 得到第二个码元或称作低位代理(low surrogate),现在值的范围是 0xDC00…0xDFFF。由于低位代理比高位代理的值要大,所以为了避免混淆使用,Unicode标准现在称低位代理为后尾代理(trail surrogates)。

假设要将U+64321(16进制)转成UTF-16编码。因为它超过U+FFFF,所以他必须编译成32位(4个byte)的格式,如下所示:

V = 0x64321
Vx = V - 0x10000
= 0x54321
= 0101 0100 0011 0010 0001

Vh = 01 0101 0000 // Vx的高位部份的10 bits
Vl = 11 0010 0001 // Vx的低位部份的10 bits
w1 = 0xD800 //結果的前16位元初始值
w2 = 0xDC00 //結果的後16位元初始值

w1 = w1 | Vh
= 1101 1000 0000 0000
 |       01 0101 0000
= 1101 1001 0101 0000
= 0xD950

w2 = w2 | Vl
= 1101 1100 0000 0000
 |       11 0010 0001
= 1101 1111 0010 0001
= 0xDF21

所以这个字U+64321最后正确的UTF-16编码应该是:
0xD950 0xDF21
而在小尾序中最后的编码应该是:
0x50D9 0x21DF

var emoji={
	// 表情转码
    utf16toEntities(str) {
      const patt = /[\ud800-\udbff][\udc00-\udfff]/g; // 检测utf16字符正则,检测是否超过U+FFFF
      str = str.replace(patt, (char) => {
        let H;
        let L;
        let code;
        let s;

        if (char.length === 2) {
          H = char.charCodeAt(0); // 取出高位
          L = char.charCodeAt(1); // 取出低位
          code = (H - 0xD800) * 0x400 + 0x10000 + L - 0xDC00; // 转换算法
          s = `&#${code};`;
        } else {
          s = char;
        }

        return s;
      });

      return str;
    },
    // 表情解码
    entitiestoUtf16(strObj) {
      const patt = /&#\d+;/g;
      const arr = strObj.match(patt) || [];

      let H;
      let L;
      let code;

      for (let i = 0; i < arr.length; i += 1) {
        code = arr[i];
        code = code.replace('&#', '').replace(';', '');
        // 高位   0x400=2^10=1024
        H = Math.floor((code - 0x10000) / 0x400) + 0xD800;
        // 低位
        L = ((code - 0x10000) % 0x400) + 0xDC00;
        code = `&#${code};`;
        const s = String.fromCharCode(H, L);
        strObj = strObj.replace(code, s);
      }
      return strObj;
    }
}
let s="👇👉👈🙌"
const strIn=emoji.utf16toEntities(s)
console.log(strIn) //👇👉👈🙌
const strout=emoji.entitiestoUtf16(strIn)
console.log(strout)//"👇👉👈🙌"

fromCharCode() 可接受一个指定的 Unicode 值,然后返回一个字符串。
.fromCharCode(numX,numX,…,numX)