JS即将发布数组的4个新特性,学会了拿去吹牛

更新日期: 2022-01-13阅读: 760标签: 特性

新特性总览:

  • at(): 数组支持索引查询
  • Array Group: 数组元素分类;
  • Array find from last: 数组逆向查询
  • Resizable and growable Array Buffer: Array Buffer支持大小伸缩

在开始之前,简单描述下事情的来龙去脉。

这些新特性被TC39组织纳入了 stage3 和 stage4 状态,TC39简单说就是一个推进EMCAScript(俗称JS)语言进步的一个官方组织。一些优秀提案,比如Promise,箭头函数等等都是出自该组织。360集团作为TC39成员之一,正参与该项EMCAScript语言推进活动,所以在这里给各位分享一下最新的语言特性;最后解释一下stage3状态;每个提案被投放到EMCAScript语言的正式环境之前,都要经历4个阶段,stage1至stage4。到了stage3阶段基本被社团的成员认可,接下来就是各家浏览器厂商,Chrome,Firefox,Safari等等的研发支持。stage4阶段就是已经投放到各大浏览器,可以让我们使用。通常我们在配置babel文件时,就会接触到这个stage概念;

{ "presets": ["es2015", "react", "stage-3"] }

提案一 at()

数组支持索引查询

过去我们在使用[]语法时,会以为数组和字符串支持按照索引去查询元素。比如:

const arr = [1,2,3,4,5];

console.log(arr[4])  // 5

这样的使用确实会让我们产生误导,以为arr[4]是查询数组第四个元素,但其实试一下arr[-1]就发现取到的是undefined。在EMCAScript里,[]语法最初被设计是适用于所有对象,比如arr[1],实际上只是引用了键为 “1” 的对象的属性,这是任何对象都可以拥有的。所以arr[-1]这样的代码书写看似有效,但它返回对象的 “-1” 属性的值.

const arr = [1,2,3,4,5];

console.log(arr[-1])  // undefined

at()提案就是解决数组、字符串、TypedArray不能直接通过索引查询。该提案目前进入satge4,被各大浏览器实现。可以复制下面的代码直接在浏览器尝试。

const arr = [1,2,3,4,5];

console.log(arr.at(-2))  // 4

评价: 过去代码中arr[arr.length-1]的使用,会让初学者或者其他语言的开发者感觉古怪,arr.at(index)则显然更加语义话,是个不错的提案。

提案二 Array Group

用于数组元素分类

给数组Array的原型上添加了两个方法,groupBy和groupByToMap。内容比较简单,直接看使用案例就可以看懂,这里不再赘述。

  • groupBy

使用案例:

将数组中的元素,按照数字 ‘40’ 来进行分类

let array = [23, 56, 78, 42, 11, 49]
array.groupBy((item,index) => {
    return item > 40 ? '比40大' : "比40小"
})
// {'比40大': [56, 78, 42, 49] , '比40小': [23,11]}

groupBy方法返回了一个新的匿名对象,其中对象的键key为groupBy的回调函数的返回值。

如果没有groupBy提案,我们过去是这么实现:

Array.prototype.groupBy = function(callback) {
    const object = {};
    for(let i =0; i < this.length; i++) {
        let key = callback(this[i],i,this);
        if(object[key]) {
            object[key].push(this[i])
        } else {
            object[key] = [this[i]]
        }
    }
    return object;
}
  • groupByToMapgroupByToMap方法返回了一个常规的Map,其中Map的键key为groupBy的回调函数的返回值。

使用案例:

const array = [1, 2, 3, 4, 5];
const odd  = { odd: true };
const even = { even: true };
array.groupByToMap((num, index, array) => {
  return num % 2 === 0 ? even: odd;
});

// =>  Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }

如果没有groupByToMap提案,我们过去是这么实现:

Array.prototype.groupByToMap = function(callback) {
    const mapObject = new Map();
    for(let i =0; i < this.length; i++) {
        let key = callback(this[i],i,this);
        if(mapObject.get(key)) {
            mapObject.get(key).push(this[i])
        } else {
            mapObject.set(key,[this[i]])
        }
    }
    return mapObject;
}

评价:实际开发中,类似这样的数组 [ 1 , 2 , 3 , 4 , 5 ],如果要分奇数偶数的话,需要重新声明一个对象来存储。

const obj = { odd: [1, 3, 5], even: [2, 4] }

而groupBy提案返回的是一个null-prototype对象,{ odd: [1, 3, 5], even: [2, 4] },不需要再声明一个具名对象。groupByToMap返回的是一个常规Map对象,总体来说比较实用。

提案三 Array find from last

从数组的最后一个到第一个查找元素的方法

使用案例:

按照以前的js写法,要想倒叙查询数组元素。

先进行一次反转reverse,反转一次数组,再使用find进行查询。

const array = [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }];

[...array].reverse().find(n => n.value % 2 === 1); 
// { value: 3 }

或者在原型上挂一个方法来实现:

Array.prototype.findLast = function(callback) {
    const len = this.length;
    for(let i = len - 1; i > 0; i--) {
        if(callback(this[i],i,this)) {
            return this[i]
        }
    }
    return -1
}

Array find from last 提案方案:

通过提案提供的方法findLast,支持了直接逆向查询数组。

array.findLast(n => n.value % 2 === 1); // { value: 3 }

评价:从性能看,减少了一次数组反转,确实有看的见的优化。从语义上来说,有正向查询就应该有反向查询,符合人们的思维习惯,总体来说比较实用。

提案四 Resizable and growable Array Buffer

扩展ArrayBuffer构造函数,采用额外的最大长度,以允许就地增长和缩小缓冲区。

在介绍提案之前,需要提前了解下什么是ArrayBuffer,它是解决什么问题的,怎么用,在哪里用。这样会方便理解该提案。以下介绍ArrayBuffer的内容我已经过滤了很多细枝末节的理论,希望能做到之前没有这方面基础的同学也可以一次性读懂ArrayBuffer。

Array Buffer这个概念,大家在日常开发中几乎不会直接接触这个概念,但和我们关系却很密切。

简单理解Array Buffer,可以说它让EMCAScript操作内存空间成为可能。它的诞生是因为一个项目,WebGL,这个项目里需要浏览器与显卡进行频繁的通信,而以往的文本传输,需要把文本'hello wrold',翻译01010111...类似这样的机器语言,非常消耗性能,而直接以二进制的形式通信就非常有必要了,Array Buffer也就此诞生;

const buffer = new ArrayBuffer(32);

这样创建了一段32字节的内存空间,buffer。有一些约定好的特性,比如生成的ArrayBuffer,不能被直接修改,需要通过 TypedArray视图和DataView视图修改,TypedArray就是一系列更改内存的构造函数统称,比如Uint8Array,Uint16Array,Uint32Array,DataView是更灵活的操作ArrayBuffer的视图。大概是因为最开始设计是为了解决图像绘制的问题,所以就起名叫各种视图,记住就ok了。

以下是TypedArray视图支持的一些数据类型

| 数据类型 | 字节长度 | 含义 | 对应的 C 语言类型 | | ------- | ---- | ----------------- | -------------- | | Int8 | 1 | 8 位带符号整数 | signed char | | Int16 | 2 | 16 位带符号整数 | short | | Int32 | 4 | 32 位带符号整数 | int |

//生成12字节的内存空间
const buffer = new ArrayBuffer(12);
//用Int32Array视图读取这个内存空间
const x1 = new Int32Array(buffer);
//直接修改
x1[0] = 1;//1
//用Int8Array视图读取这个内存空间
const x2 = new Uint8Array(buffer);
x2[0]  = 2;//2
//x1的值发生变化
x1[0] // 2

上面代码对同一段内存,分别建立两种视图:32 位带符号整数(Int32Array构造函数)和 8 位不带符号整数(Uint8Array构造函数)。由于两个视图对应的是同一段内存,一个视图修改底层内存,会影响到另一个视图。

ArrayBuffer 的一些理解

Array Buffer是一个二进制数组,也可以说是一个构造函数,但本质上是一个由一堆 0 1 组成的缓存空间,他在内存中主要发挥数据缓冲的作用。简单理解就是计算机要运行,就是cpu要处理 0 1 组成的二进制数据,但有时二进制数据来的又快又多,cpu还在处理其他任务,那这些数据就得原地等待,如果cpu处理任务很快,但是数据传输的慢了,就得让cpu空闲等待数据,造成资源浪费,ArrayBuffer就是充当一个缓冲内存空间。让这个内存空间随时数据存在,不会让数据和cpu等待。

ArrayBuffer 遇到的瓶颈

现有的ArrayBuffer存在一个问题,当你正在的ArrayBuffer空间不够需要增加空间时,需要调用ArrayBuffer原型上的一个方法,ArrayBuffer.prototype.slice(),使用如下

const buffer = new ArrayBuffer(8);
const newBuffer = buffer.slice(0, 3);

上面代码拷贝buffer对象的前 3 个字节(从 0 开始,到第 3 个字节前面结束),生成一个新的ArrayBuffer对象。

slice方法其实包含两步,第一步是先分配一段新内存,第二步是将原来那个ArrayBuffer对象拷贝过去。

这样的方式一次使用尚且可以,但在一些绘图场景下,频繁地创建新内存,再复制一份过去,会大量损耗性能。

改造 ArrayBuffer

以上就介绍完了ArrayBuffer,和它目前面临的瓶颈。下面讲一下本次进入stage3的提案。

本提案拓展了ArrayBuffer的构造函数,增加最大和最小伸缩长度的配置---maximumByteLength。 重写了transfer方法,可以用来直接转移内存空间,不必再复制一份内存再挪动。

改造之后的ArrayBuffer构造函数

class ArrayBuffer {
    //options为配置项 
    //maximumByteLength 配置内存伸缩长度
    constructor(byteLength,[ options ]);

    //挪动内存
    transfer(newByteLength);

    //更改内存长度
    resize(newByteLength);

    //分离出一个不可调整大小的ArrayBuffer
    slice(start, end);

    //判断是否可以伸缩
    resizable();

    //获取配置的最大内存长度
    maximumByteLength();

    //获取当前内存长度
    byteLength();
}

使用案例:

声明一个1024字节的内存,并把最大内存长度设置为1024的平方

//增加配置项,maximumByteLength,最大内存长度允许到1024的平方
let rab = new ArrayBuffer(1024, { maximumByteLength: 1024 ** 2 });

//当前内存长度 1024字节
assert(rab.byteLength === 1024);

//最大长度 1024平方字节
assert(rab.maximumByteLength === 1024 ** 2);

//可以就地更改长度
assert(rab.resizable);

//更改内存长度
rab.resize(rab.byteLength * 2);

//内存长度发生变化
assert(rab.byteLength === 1024 * 2);

transfer是对内存进行挪动

let ab = rab.transfer(1024);

上面是挪动从0开始的1024字节

assert(rab.byteLength === 0);
assert(rab.maximumByteLength === 0);

上面是原来的内存(rab)被分离,并清除内存。

// 内存被转移到ab
assert(!ab.resizable);
assert(ab.byteLength === 1024);

内存成功被转移到ab

SharedArrayBuffer

SharedArrayBuffer 顾名思义就是可以共享的ArrayBuffer

ArrayBuffer提案里,将SharedArrayBuffer也进行了改动,让他支持可以增长内存。

先简单介绍下什么是SharedArrayBuffer。

在EMCAScript里有一些计算任务通常会让在worker线程里,每个worker线程都是互相隔离的,他们之间的通信需要通过postMessage来完成。

除了可以进行字符串数外,也可以进行二进制数据通信,但需要将二进制数据复制一份再用过postMessage传递给另一个线程,数据量很大时效率会变低,所以在 es2017 时有人提出了可以共享的SharedArrayBuffer,js的主线程和worker线程可以共享这块内存空间,同时进行数据读写,避免了复制二进制数据带来的性能损耗。

实际场景里,在主线程创建了一个SharedArrayBuffer,并把内存地址通过postMessage发给worker线程

// 主线程
// 新建 1KB 共享内存
const sharedBuffer = new SharedArrayBuffer(1024);
// 主线程将共享内存的地址发送出去
w.postMessage(sharedBuffer);

worker线程接受来自主线程的共享地址 ---- SharedArrayBuffer,不必再复制同一份二进制地址进行传递。通过共享地址提高了性能。

// Worker 线程
onmessage = function (ev) {
  // 主线程共享的数据,就是 1KB 的共享内存
  const sharedBuffer = ev.data;
  // 在共享内存上建立视图,方便读写
  const sharedArray = new Int32Array(sharedBuffer);
  // ...
};

ArrayBuffer提案对SharedArrayBuffer也进行了拓展,允许SharedArrayBuffer支持增加内存,但与ArrayBuffer不同的是,不支持减少内存,很显然,如果减少内存的话,很有可能影响到正在共享这块内存的其他程序。

下面是SharedArrayBuffer的构造函数。在ArrayBuffer构造函数里的resize,这里命名是grow,也能体现出两者的差异了。

class SharedArrayBuffer {
    //和ArrayBuffer一样配置maximumByteLength
    constructor(byteLength, [ options ]);

    //只能增加内存,而不能缩短内存
    grow(newByteLength);

    //分离出一份不可增长的 SharedArrayBuffer。
    slice(start, end);

    //判断是否可以扩充内存
    growable();

    //获取最大内存长度
    maximumByteLength();

    //获取当前内存长度
    byteLength();
  }

目前 ArrayBuffer 提案遇到一个阻碍,真实场景中,可能存在多个线程需要扩大内存,按照现在的设计是需要先占用内存,再扩大或缩小至目标内存,但真实场景里,可能存在内存不够的情况,那么会造成资源抢夺。所以如何分配内存资源的问题仍需解决。

结束

小伙伴们对这些提案有兴趣的话,可以留言一起探讨。

以上就是本次介绍的四个提案。

如果对你有帮助的话,希望各位朋友点赞转发,你的鼓励是我们继续推广js语言新特性的动力。

作者:王明 —— 360集团导航事业部资深开发工程师
来自:https://zhuanlan.zhihu.com/p/451641148


链接: https://fly63.com/article/detial/11033

CSS新特性contain,控制页面的重绘与重排

contain 属性允许我们指定特定的 DOM 元素和它的子元素,让它们能够独立于整个 DOM 树结构之外。目的是能够让浏览器有能力只对部分元素进行重绘、重排,而不必每次都针对整个页面。

Html5、Css3、ES6的新特性

Html5的新特性语义化标签:有利于SEO,有助于爬虫抓取更多的有效信息,爬虫是依赖于标签来确定上下文和各个关键字的权重。表单新特性,多媒体视频(video)和音频(audio)

ES6新特性--var、let、const

var不存在块级作用域,具有变量提升机制。 let和const存在块级作用域,不存在变量提升。在同一作用域内只能声明一次。const在声明时需要赋值且无法修改,但如果常量是对象,则对象的属性可以修改。

Js即将到来的3个新特性

Optional Chaining(可选链式调用);Nullish coalescing(空值合并);Pipeline operator(管道运算符)通过三个函数对字符串进行处理;

Angular 8的新特性介绍

在今天早些时候Angular团队发布了8.0.0稳定版。其实早在NgConf 2019大会上,演讲者就已经提及了从工具到差分加载的许多内容以及更多令人敬畏的功能。下面是我对8.0.0一些新功能的简单介绍,希望可以帮助大家快速了解新版本

使用 React 要懂的 Js特性

与我使用的其他框架相比,我最喜欢 React 的原因之一就是它对 JavaScript 的暴露程度。没有模板DSL( JSX 编译为合理的 JavaScript),组件 API 只是通过添加 React Hooks 变得更简单,并且该框架为解决的核心 UI 问题提供非常少的抽象概念

ES2019 新特性汇总

最近 ECMAScript2019,最新提案完成:tc39 Finished Proposals,我这里也是按照官方介绍的顺序进行整理,如有疑问,可以查看官方介绍啦~另外之前也整理了 《ES6/ES7/ES8/ES9系列》,可以一起看哈。

Js的用途和特性

JavaScript 最初的目的是为了“赋予网页生命”。这种编程语言我们称之为脚本。它们可以写在 HTML 中,在页面加载的时候会自动执行。脚本作为纯文本存在和执行。它们不需要特殊的准备或编译即可运行。

十个超级实用的 JS 特性

你可能刚上手 JavaScript,或者只是曾经偶尔用过。不管怎样,JavaScript 改变了很多,有些特性非常值得一用。 这篇文章介绍了一些特性,在我看来,一个严肃的 JavaScript 开发者每天都多多少少会用到这些特性

解密HTTP/2与HTTP/3 的新特性

HTTP/2 相比于 HTTP/1.1,可以说是大幅度提高了网页的性能,只需要升级到该协议就可以减少很多之前需要做的性能优化工作,当然兼容问题以及如何优雅降级应该是国内还不普遍使用的原因之一。

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!