JavaScript扩展运算符...的实现原理

... 作用

扩展运算符(spread)是三个点(...),用于取出参数对象中的所有可遍历属性,浅拷贝到当前对象之中。

常见用法

1.浅拷贝数组

javascript 复制代码
const a1 = ['test1', 'test2'];
const a2 = [...a1];
 
a2[0] = 'test2';
a2 // ['test2', 'test2']

2.合并数据

javascript 复制代码
const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
 
// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]
 
// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

3. 解构赋值

javascript 复制代码
const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]
 
const [first, ...rest] = [];
first // undefined
rest  // []
 
const [first, ...rest] = ["foo"];
first  // "foo"
rest   // []

4.字符串/类数组转为真正的数组

因为任何定义了遍历器(Iterator)接口的对象,都可以用扩展运算符转为真正的数组。

javascript 复制代码
[...'test']
// [ "t", "e", "s", "t"]
 
[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

基本实现原理

如果不用 ...,如何实现一样的功能?由上面的用法,可以知道。扩展运算符主要就是浅拷贝可遍历对象属性,那么我们可以用es5的写法实现如下:

javascript 复制代码
// 简单版实现
function _spread() {
    for (var ar = [], i = 0; i < arguments.length; i++){
        ar = ar.concat(arguments[i]);
    }
    return ar;
};

用上面的例子测试一下,结果如下:

javascript 复制代码
const a1 = ['test1', 'test2'];
const a2 = _spread(a1);
 
a2[0] = 'test2';
a2 // ['test2', 'test2']

可以看出上面的例子,没有考虑到属性的可遍历性判断,那么需要进一步优化。

严谨实现

这里分几种情况来考虑就好:

  1. 判断是否为数组,数组一定可迭代,则直接复制数组后返回结果即可。
  2. 判断是否为实现了遍历器(Iterator)接口的对象,若实现了则转为数组。
  3. 如果没有实现遍历器(Iterator)接口的对象,则判断是否为普通字符串/Map/Set等。
  4. 均不满足以上条件的话,则抛错。
    所以,最后实现为:
javascript 复制代码
function _toConsumableArray(arr) {
  return (
    _arrayWithoutHoles(arr) || //  判断是否为数组
    _iterableToArray(arr) || //  判断是否为实现了遍历器(Iterator)接口的对象
    _unsupportedIterableToArray(arr) || // 判断是否为普调字符串/Map/Set等
    _nonIterableSpread() // 则抛错
  );
}
 
function _nonIterableSpread() {
  throw new TypeError(
    "Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
  );
}
 
function _unsupportedIterableToArray(o, minLen) {
  if (!o) return;
  if (typeof o === "string") return _arrayLikeToArray(o, minLen);
  var n = Object.prototype.toString.call(o).slice(8, -1);
  if (n === "Object" && o.constructor) n = o.constructor.name;
  if (n === "Map" || n === "Set") return Array.from(o);
  if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
    return _arrayLikeToArray(o, minLen);
}
 
function _iterableToArray(iter) {
  if (
    (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null) ||
    iter["@@iterator"] != null
  )
    return Array.from(iter);
}
 
function _arrayWithoutHoles(arr) {
  if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}
 
function _arrayLikeToArray(arr, len) {
  if (len == null || len > arr.length) len = arr.length;
  for (var i = 0, arr2 = new Array(len); i < len; i++) {
    arr2[i] = arr[i];
  }
  return arr2;
}
相关推荐
hummhumm2 分钟前
第 12 章 - Go语言 方法
java·开发语言·javascript·后端·python·sql·golang
hummhumm2 分钟前
第 8 章 - Go语言 数组与切片
java·开发语言·javascript·python·sql·golang·database
何曾参静谧2 分钟前
「QT」文件类 之 QDir 目录类
开发语言·qt
何曾参静谧4 分钟前
「QT」文件类 之 QTemporaryDir 临时目录类
开发语言·qt
杜杜的man7 分钟前
【go从零单排】Directories、Temporary Files and Directories目录和临时目录、临时文件
开发语言·后端·golang
qq_308957477 分钟前
Gin 框架入门(GO)-1
开发语言·golang·gin
杨荧1 小时前
【JAVA毕业设计】基于Vue和SpringBoot的宠物咖啡馆平台
java·开发语言·jvm·vue.js·spring boot·spring cloud·开源
zhanghaisong_20151 小时前
Caused by: org.attoparser.ParseException:
前端·javascript·html·thymeleaf
monkey_meng1 小时前
【Rust中的项目管理】
开发语言·rust·源代码管理
喜欢打篮球的普通人1 小时前
rust高级特征
开发语言·后端·rust