JS中的非可变性

非可变性是函数式编程的一个核心规则,对于面向对象编程也有很多用处。本文为参考sitepoint(参考链接1)中的文章后所记录的一些主要内容。

参考链接1:http://www.sitepoint.com/immutability-javascript/ 参考链接2:immutable-js 参考链接3:Immutable_object

什么是非可变性?(Immutability)

如果用非可变性来形容一个对象,对么这个对象的特点是:这个对象在创建之后不会被修改。JS中很多值是非可变的,例如:

var statement = "I am an immutable value";
var otherStr = statement.slice(8, 17);

在执行完以上代码后,statement的值并不会改变。实际上JS中所有字符串方法都不会改变原字符串,而是返回新的字符串。因为字符串是非可变的--不能被修改,只能创建新的字符串。在JS中不只有字符串是非可变,普通的数值也是非可变的。2 + 3并不会改变2的值。

JS中存在中着大量的可变性

在JS中,字符串和数值被设计为非可变的,但是很多情况下并非如此。例如下面:

var arr = [];
var v2 = arr.push(2);

以上代码会使得arrv2都变为[2]push操作改变了原数组的内容。要想使数组满足非可变性,需要使用自定义的类似的数据结构:

var arr = new ImmutableArray([1, 2, 3, 4]);
var v2 = arr.push(5);

arr.toArray(); // [1, 2, 3, 4]
v2.toArray();  // [1, 2, 3, 4, 5]

var person = new ImmutableMap({name: "Chris", age: 32});
var olderPerson = person.set("age", 33);

person.toObject(); // {name: "Chris", age: 32}
olderPerson.toObject(); // {name: "Chris", age: 33}

以上模拟的非可变数组和非可变Map的操作并不会改变原数据结构中的内容,而是返回新的对象。

JS中非可变性的意义

在应用开发过程中,经常需要管理和跟踪一些状态(在很多UI框架中很常见),这个过程较困难且容易出错。使用非可变性数据结构进行开发,可以使应用中的数据流以不一样的形式来实现和管理。 在使用普通对象(可变性对象)进行开发时,当需要跟踪管理某些数据的变更,需要用到Object.observe之类的方法来监控某个对象,并指定相应的回调函数。这种方法有两弊端:

  1. 为了实现监控某些对象的某些改变成进行某些操作,需要维持较大的内存空间来记录这些信息。过度地使用会使得程序的性能下降。
  2. 让程序的不同部分之间由同步变为异步,更容易发生错误,且排查起来更加困难。 如果使用非可变性对象来存储应用数据,为了监控某个对象的属性是否发生改变,不需要使用“订阅者--发布者”模式,而直接使用上一步得到的新对象与原对象作比较:
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 2);
assert(map1 === map2); // no change
var map3 = map1.set('b', 50);
assert(map1 !== map3); // change

使用非可变性对象可以将异步的“订阅者--发布者”模式变成同步的顺序逻辑,即在可能产生新数据的操作之后进行判断并处理。非可变性对象的另一个好处是克隆对象比较方便。因为非可变性对象在创建之后不会被修改,所以可以直接使用等号赋值将一个对象的引用赋给另一个对象:

var map1 = Immutable.Map({a:1, b:2, c:3});
var clone = map1;

这种赋值引用的方式可以极大地节约内存。说到节约内存,非可变性对象很容易让人怀疑:“像这样有一点修改就创建一个完全的新对象,是不是会很浪费空间?”。如果在创建新对象的时候是完全开辟新的内存空间来存储原对象的所有属性,那么确实很浪费空间。但是在实现非可变性数据结构时可以采用“共享数据结构”(structural sharing)的方法,不同对象的相同值的属性可以共享,只额外保存新的属性值,和一些用于共享的引用信息,这样就可以解决内存开销过大的问题。虽然还是会有一额外的内存开销,但是相比于非可变性数据结构在其它方面带来的开发和性能方面的好处来说可以忽略。下面介绍的immutable-js也是用到了共享数据结构的方法。

immutable-js简介

immutable-js是facebook开发的JS非可变性数据结构集合。里面包含的非可变性数据结构包括List,Stack,Map,OrderedMap,Set,OrderedSetRecord。这些数据结构参考了ES6中新增的一些数据结构,并有所增强。上一节提到了“共享数据结构”,在immutable-js中使用的是hash maps triesvecor tries这两种Clojure和Scala使用的方法来实现,使数据复制与缓存的开销大大降低。immutable-js源码使用ES6标准开发,通过编译程序编译为ES3代码。

var Immutable = require('immutable');
var map1 = Immutable.Map({a:1, b:2, c:3});
var map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50

非可变性数据简单使用示例

Immutability in JavaScript一文中,作者简单地讲解了使用immutable-js中的非可变性数据结构来实现扫雷游戏(Minesweeper)的单元格管理。详细代码在code pen这里。下面是两个主要的函数

  • 初始化数据结构:
function createGame(options) {
  return Immutable.fromJS({
    cols: options.cols,  //列数
    rows: options.rows,  //行数
    tiles: initTiles(options.rows, options.cols, options.mines) //单元格列表
  });
}

/*初始化后的数据结构形如:
{
    cols: 2,
    rows: 2,
    tiles: [{id: 0,isRevealed: false},{id: 1,isRevealed: false},
            {id: 2,isRevealed: false},{id: 3,isRevealed: false}]
}
*/
  • 揭开单元格操作:
function revealTile(game, tile) {
  return game.getIn(['tiles', tile]) ?  //判断揭开的单元格id是否存在
    game.setIn(['tiles', tile, 'isRevealed'], true) :
    game;
}

作者在后续还分析了ES7中的Object.observe()方法并不能很好地解决UI框架中的状态跟踪问题。例子也是使用上面的扫雷:

var tiles = [{id: 0, isRevealed: false}, {id: 1, isRevealed: true}];
Object.observe(tiles, function () { /* ... */ });

tiles[0].id = 2;

在使用原生数组存储单元格信息时,使用Object.observe()不能捕捉到tiles中某个元素的属性被修改。

总结

本文简述了Immutability in JavaScript以及immutable-js文档中关于非可变性之于JS的意义与应用场景。非可变性在JS中实际存在(字符串和数值),在一些函数式编程语言中是一个重要概念(Scala等)。在涉及到状态变更的应用中,使用非可变性数据结构开发的程序在数据流特性上与“订阅者--发布者”有着很大的不同。 JS也是一种函数式编程语言,在ES6中新增的尾调用优化特性使JS更具有“函数式”特性。如果能参考其它函数式语言使用非可变性数据结构来构建数据流,可能会有很好的效果。但是究竟能否对现有observe模式的程序有性能和开发便利方面的改进,还需要进一步调查。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Java Edge

后端开发必备JQuery常用知识点jQuery.each(object, [callback])1 筛选2 属性3 文档处理4 回调函数

向每个匹配的元素内部追加内容。 这个操作与对指定的元素执行appendChild方法,将它们添加到文档中的情况类似。

693
来自专栏Python

python2.7 的中文编码处理,解决UnicodeEncodeError: 'ascii' codec can't encode character 问题

4502
来自专栏IT笔记

聊聊Servlet、Struts1、Struts2以及SpringMvc中的线程安全

很多初学者,甚至是工作1-3年的小伙伴们都可能弄不明白?servlet Struts1 Struts2 springmvc 哪些是单例,哪些是多例,哪些是线程安...

3526
来自专栏Android中高级开发

Android开发之漫漫长途 番外篇——内存泄漏分析与解决

该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,我会尽量按照先易后难的顺序进行编写该系列。该系列引用了《Android开发艺术探索...

652
来自专栏java闲聊

modelMapper入门及使用解析

在mappermodel中,一般情况下保持属性名一致即可以不用任何配置就可直接转换,mappermodel的原理是基于反射原理进行赋值的,或是直接对成员变量赋值...

372
来自专栏java一日一条

Java虚拟机体系结构,你知道吗?

众所周知,Java支持平台无关性、安全性和网络移动性。而Java平台由Java虚拟机和Java核心类所构成,它为纯Java程序提供了统一的编程接口,而不管下层操...

562
来自专栏web前端-

JSON

    一般情况下,我们的json数据都是从服务端获取到的,获取的json数据是以字符串的形式返回的。这个字符串虽然是json格式的,但是不能被直接使用,我们...

784
来自专栏SHERlocked93的前端小站

JS中的垃圾回收与内存泄漏

Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。其原理是:垃圾收...

1103
来自专栏Java帮帮-微信公众号-技术文章全总结

Java面试系列9

✎一、Java有没有goto? java中的保留字,现在没有在java中使用。 ✎二、必须要知道的运行时异常 ArithmeticException 是...

2534
来自专栏mySoul

属性 元素的内容 创建,插入和删除节点 虚拟节点

表示HTML文档元素的HTMLElement对象定义了读/写属性。映射了元素的HTML属性。HTMLElement定义了通用的HTTP属性。以及事件处理程序的属...

703

扫码关注云+社区