重读 ES6 — 数组、对象的扩展

上一篇《读 ES6 — 字符串、数值、正则的扩展》 将新增 API 做了一些梳理,隔离了一些复杂而低频的知识点,从有限的篇幅分析,可以窥探到 JavaScript 内部代码组织更趋于合理、而本身 API 则变得更加易用和强大。

本篇继续沿着上篇的分析方向,来整理下 ES6 基本类型中的数组对象 新增特性。(函数稍特别,将单独来梳理)

因为它们在 JavaScript 语言中的地位无比重要,无论是 ES5 还是 ES6 中都应该优先掌握它们。本篇着重梳理 ES6 相对于 ES5 的增量知识,但是有些增量使用起来仍然很费劲,用到一个知识点恐怕先得弄清一大片才行——好比对朋友说了一个谎(大家应该都有这样干过吧......),就得用好几个谎才能圆回来。

比如当你开始用起 Array.from() 你可能会遇到 数组的空位的概念,然后猛然一惊还有这事,那岂不是我曾经有段程序用错了埋了 bug? 比如函数的尾调用优化,是什么鬼呀?还比如对象 Object 搞了一大堆 getOwnPropertyDescriptor()setPrototypeOf()getPrototypeOf() 这些看着 prototype 就头疼的方法......

确实,这些东西有点繁琐,不过没关系,如之前的理念我们暂且先把它们隔离起来,关在铁笼子里,等咱先打完小怪,在来收拾它们。(怎么感觉和一款叫 “轩辕剑...” 的游戏剧情有点相似)。另外,就是很多 API 可能初期有点抗拒,只要用起来了就觉得太 TM 顺手丝滑了。

数组的扩展

一如既往,啰嗦完毕开始进入正题。数组新增的特性有:

  • 新增方法(包括在命名空间 Array 上和数组实例上)
  • spread 运算符
  • 数组空位的概念

新增方法

数组实例的新增方法:

在数组内自我覆盖: copyWithin()
见好就收: find() 和 findIndex()
是否包含了某项: includes()
自我填充: fill()
花式遍历: entries()、keys() 和 values()

以及命名空间 Array 上的方法:

数组产自近亲: Array.from()
化零为整: Array.of()

新增方法都很简单,和上一篇总结的特征规律是相似的。比如封装高频的写法:

// ES5 数组是否存在某项
var isExist = [1, NaN, 3, 6, 8].indexOf(6) !== -1;
// => 这样写总感觉不够直观
// => 经常还要查 `indexOf` 的 `O` 是大写还是小写有木有!
// => 因为 `typeof` 的是小写!

// 莫名其妙 `NaN` 的存在性判断不出来
[1, NaN, 3, 6, 8].indexOf(NaN);
// => -1

// ES6 数组是否存在某项就没有上述的问题
let isExist = [1, NaN, 3, 6, 8].includes(NaN)

另外比较重要的特征就是:

  • 几乎都是纯函数
  • 更偏向声明式编程
  • API 的行为统一明确

首先,为什么说几乎都是 “纯函数”(相关知识若有需要,可以阅读本人的另一篇专题文章《走向 JavaScript 函数式编程》)? 新增的方法都满足相同输入恒有相同输出,但是还有一点点 “副作用”,就是改变了外部变量即当前数组本身。但总的来说,比起 ES5,已向 “确定性” API 迈出了一大步。

于是,将稍有 “副作用” 的方法改成 “纯函数”,是非常容易的:

// 带副作用的 fill 方法会改变数组 arr
arr.fill(val, start, end);

// 很容易封装成纯函数,并且实现一定的柯里化
function fill(arr) {
    let inner_arr = arr.slice(0);
    return function (val, start, end) {
        return inner_arr.fill(val, start, end);
    }
}

其次,偏向于声明式编程。ES6 看起来是要围剿 for循环,让它们少出头露面的办法就是提供新的 API来隐藏它们。

比如,在一次 “寻找 NaN” 的活动中,这样的调用是不是更 “声明式”,更丝滑顺畅?

//在数组中寻找 NaN
let index = [1, 2, 3, NaN, 6, 8].findIndex(Number.isNaN);
// => 3

let item = [1, 2, 3, NaN, 6, 8].find(Number.isNaN);
// => NaN

至于统一而确定的行为,在上例中 includes 以及 Array.from() 都有体现。对于一个重量级的语言,尤其是欲意 “征服全宇宙” 的 JavaScript 来说,定当以极为挑剔的眼光审视它,而 ES6 已经做了很多。

spread 运算符

spread 运算符(扩展运算符)是三个点(...)。这个新增项非常能让人接受,而且它已经蔓延到了除数字本身外的 函数参数(类似数组)、对象字符串等等类型上。

以一个简单的例子,看看用 ... 书写带来的良好的阅读体验:

// 合并数组,通过 concat() 连接起来
var compose = first_arr.concat(second_arr).concat(third_arr);

//毫无杂质的 ...
let compose = [...first_arr, ...second_arr, ...third_arr];

数组空位

最后数组的空位概念,本文不打算去说明。知识点本身比较简单,但是牵扯到太多的验证,比较难梳理,感觉像是挥不走的苍蝇。所以,最好的办法是在编程实践中再去 “拍” 它。

对象的扩展

其实对象的扩展并不复杂,归结起来差不多以下内容:

  • 为了更好的赋值运算
    • 属性的简写
    • 扩展运算符
  • Object 命名空间的新增方法
    • 常用方法
    • 对象的 prototype 方法
    • 属性的描述方法

为了更好的赋值运算

属性的简写、扩展运算符以及与此紧密相关的解构赋值,可以说为旧的 JavaScript 开创了一批新的赋值运算方式,让 赋值运算 摆脱了一板一眼的 =号运算的方式。

var lang = {name: 'ECMA2015', shortName: 'ES6'};

//ES5 写法比较冗余
var name = lang.name;
var shortName = lang.shortName;

//ES6 写法更加简明
let {name, shortName} = lang;

对一个 ES6 模块的导入 (import) 和导出 (export) ,也能充分凸显这种 赋值运算 的便利性。

// a.js
const version = '1.0.0';
let fn1 = () => {};
let fn2 = () => {};
export { version, fn1, fn2 };  //注:为了节约空间,就写成一行了

//b.js
import { version, fn1 } from './a';

// 如果不能解构赋值
import a from './a';
let version = a.verion;
let fn1 = a.fn1;

属性的简写

上述 赋值运算,和一个符号有关,那就是 ES6 的大括号{},与 ES5 不同的是,ES6{}不仅能开辟一块作用域,又能进行 模式匹配运算,有如自带魔法一般。

先来看看一个有意思的例子:将任意一个变量变成对象。

// ES5 需要获取形参名
function var2obj(x) {
    var obj = {};
    var str_fn = var2obk.toString();
    var key = str_fn.split(')')[0].split('(')[1];
    obj[key] = x;
    return obj;
}
var x = 'unkown';
var myObj = var2obj(x);
// => {x: 'unkown'};

更为完整的情形,请参考 这里 。但在 ES6{} 眼里,完全是另一番景象。甚至可以通过这个方式,轻易的获取到所有形参名。(此处不去延伸)

function var2obj (x) {
    return {x};
}
var x = 'unkown';
var myObj = var2obj(x);
// => {x: 'unkown'};

let y = 'yes';
var anotherObj = var2obj(y);
// => {y: 'yes'};

大括号 {} 自带运算魔法。解析时,能将 {x} 一分为二,自动展开为 {x: x}。反之,将对象的键值合成一个变量项,写在 {} 中,就是属性的简写。

// 简写形式
let {name, age} = {name: 'jeremy', age: 18};

// 展开形式
let {name: name, age: age} = {name: 'jeremy', age: 18};

简写对象的解构赋值,可以看做是先转化成上述的 “展开形式” ,然后再开始匹配赋值的。

因此,解构赋值 = 号左边的任何键名,都必须来自 = 号右边对象中的某个元素,否则将无法识别(undefined)。换句话说,= 号右边对象能够访问到的属性,都是可以被解构和赋值给 = 号左边的,包括它原型链上的属性。

function Corder (name) {
    this.name = name;
}
Corder.prototype.age = 18;
let {name, age} = new Corder('jeremy');
// => age 18

扩展运算符

对象也有扩展运算符 (...),和数组的是类似的,简单理解就是剥离了一层大括号 {},将对象键值对直接暴露出来。

扩展运算并不难,但是有一个和解构赋值不同的特征,是它不会将原型链上的属性、方法暴露出来。准确的说,扩展运算符是取出对象自身(不包括其原型链上)的所有可遍历属性。

let {...aCoder} = new Corder('jeremy');
console.log(aCoder);
// => {name: ''jeremy'}

let aCoder = Object.assign({}, new Corder('jeremy'));
// => {name: ''jeremy'}

可见,...Object.assign 都没有将原型链上的 age 取出来。

对了,这里提到了 取出对象自身(不包括其原型链上)的所有可遍历属性,不禁脑袋中又蹦出一个问题:到底哪些运算或方法,只获取到对象自身的可遍历的属性?又有哪些是可以获取到对象原型链上的属性呢?

面对 ES6 不胜枚举的新增特性,信息量的暴增,往往让人烦躁不堪。情绪性的鄙夷油然而生:看吧,就为了解决些小问题,却弄出这么一大堆东西来,有意思吗!

Object 的新增方法

信息量暴增,确有其事。不过本文意在梳理,解决的问题就是从这些繁杂的信息中,提取容易的、有利的为自己所用,其他晦涩麻烦的暂且一并锁在铁笼子里。

从总体上看,ES6 的新增的特性并非没有瑕疵(笔者实际编程中也曾遇到过,以后的篇幅有机会再提),而且很多粒度很小,辨识起来的确很麻烦。但单单从刚才的提问来说,遍历自身属性还是原型链属性,ES6 并没有给我们制造麻烦。

第一类在 Object 命名空间上新增方法:

  • Object.assign(target[, source1, source2, ...])
  • Object.keys(obj)
  • Object.values(obj)
  • Object.entries(obj)
  • Object.is(a, b)
  • Object.getOwnPropertyDescriptors(obj)

除了 Object.is(a, b) 之外,它们都是处理属性自身的可遍历的属性的方法。Own 是专属自有属性的特定命名,所以带 OwnAPI 只遍历到自有属性就很好识别了,包括 ES5obj.hasOwnProperty() 就是这个规则。此外,前文的 ... 扩展运算符是也归类于此。

第二类在 Object 命名空间上新增方法:

  • Object.setPrototypeOf(obj, proto)
  • Object.getPrototypeOf(obj)

这两个方法是直接对原型链对象的 setget,虽不用于遍历,但属于和原型链直接打交道的方法。真正能遍历到原型链的还是 ES5 已有的 for in 循环。

到此,问题就非常明确了,仍然只有极少数的 API 可以遍历或者直接操作原型链,其他绝大多数新增 API,都只明确的限定在对象自有属性的遍历上。

属性的描述方法

对象的每个属性都有一个描述对象(Descriptor)。属性的描述对象在 ES5就具备了,但当初很少接触到这个概念。具体上,它包含以下项目:

{
    value: 'attr', //属性值
    writable: true, // 可写
    enumerable: true, //可枚举可遍历
    configurable: true //可配置
}

描述对象之于属性,好比于原子细分成用若干个质子、中子来描述。描述对象用来对外界开放处置属性的权限,这在设计健壮的 API 是非常有用的。

为此而新增的方法有:

  • Object.getOwnPropertyDescriptors(obj)
  • Object.getOwnPropertyDescriptor(obj, key)

上文曾提过,该方法只会遍历到对象自身属性。至于其他方面,延伸暂无必要。

ES6 之于遍历

很多时候,提到获取数组、对象中的元素时候,总会说来个 for 循环,来遍历一下。数组、对象大部分的消费方式就是通过遍历。类似的,字符串也有遍历,甚至 Generator + yield 也是对状态的遍历。看过或用过这么多 ES6API,不知您是否注意到,作为如此共性的 遍历 这事儿,ES6 其实开放了一个底层概念:Iterator 遍历器。

读过 underscore.js 源码的同学,不难发现它在 遍历 问题上也是采用了统一的 口径 ——即用数组的 for i++ 方式,对象的遍历 for in 最终也是落脚在数组遍历上。

这两个问题摆在一起,能为我们设计 API 提供怎样的启示呢?信息量略大,容我想好再说,哈哈~

最后, Iterator 遍历器这个偏底层的概念,本文还是老套路,把它先关在笼子里,以后再来拜会。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 1.属性的简洁表示法 允许直接写入变量和函数 上面代码表明,ES6 允许在对象之中,直接写变量。这时,属性名为变量...
    雨飞飞雨阅读 1,130评论 0 3
  • 三,字符串扩展 3.1 Unicode表示法 ES6 做出了改进,只要将码点放入大括号,就能正确解读该字符。有了这...
    eastbaby阅读 1,514评论 0 8
  • 冬季,北方的城市到处都有雾霾,出行带来了麻烦,但穿过厚厚的雾霾,能有一束光亮打来,就是一个温暖的信号,让前行更加有...
    叶知秋哥阅读 99评论 0 0
  • 好久没有这么心痛了,这心痛的原因七年来始终没有改变。 当今天再次看到你因电脑屏幕上的东西而来不及收拾的悲伤心情时,...
    你是此生最美的风景love阅读 178评论 0 0