javascript 大数据处理方法

随着前端的飞速发展,在浏览器端完成复杂的计算,支配并处理大量数据已经家常便饭。那么,如何在最小化内存消耗的前提下,高效优雅地完成复杂场景的处理,愈来愈考验开发者功力,也直接决定了程序的性能。javascript

本文展示了一个彻底在控制台就能模拟体验的实例,经过一步步优化,实现了生产并操控多个1000000(百万级别)对象的场景。css

导读:这篇文章涉及到 javascript 中 数组各类操做、原型原型链、ES六、classes 继承、设计模式、控制台分析 等内容。前端

要求阅读者具备 js 面向对象扎实的基础知识。若是你是初级前端开发者,很容易被较为复杂的逻辑绕的云里雾里,“从入门到放弃”,不过建议先收藏。若是你是“老司机”,本文提供的解决思路但愿对你有所启发,抛砖引玉。java

 

场景和初级感知

具体来讲,咱们须要一个构造函数,或者说相似 factory 模式,实例化1000000个以上对象实例。jquery

先来感知一下具体实现:设计模式

Step1

打开你的浏览器控制台,仔细观察并复制粘贴如下代码,触发执行。数组

a = new Array(1e6).fill(0); 

咱们建立了一个长度为1000000的数组,数组的每一项元素都为0。浏览器

Step2

在数组 a 的基础上,再生产一个长度为1000000的数组 b,数组的每一项元素都是一个普通 javascript object,拥有 id 属性,而且其 id 属性值为其在元素中的 index 值;微信

b = a.map((val, ix) => ({id: ix})) 

Step3

接下来,在 b 的基础上,再生产一个长度为1000000的数组 c ,相似于 b,同时咱们增长一些其它属性,使得数组元素对象更加复杂一些:app

c = a.map((val, ix) => ({id: ix, shape: 'square', size: 10.5, color: 'green'})) 

语义上,咱们能够更直观的理解:c 就是包含了1000000个元素的数组,每一项都是一个绿色的、size 为10.5的小方块。

若是你按照指示作了下来,控制台上会有如下内容:

 

 

 

 

深层探究

你也许会想,这么大的数据量,内存占用会是什么样的状况呢?

好,我来带你看看,点击控制台 Profiles,选择 Take Shapshot。在Window->Window 目录下,根据内存进行筛选,你会获得:

 

很明显,咱们看到:

  • a数组:8MB;
  • b数组:40MB;
  • c数组:64MB

也许在实际场景中,除了1000000个绿色的、size为10.5的小方块,咱们还须要不少不一样颜色,不一样 size 的形状。以前,这样“变态”的需求常见于游戏应用中。可是如今,复杂项目中相似场景,也许距离你并不遥远。

ES6 Classes处理需求

简单“热身”以后,咱们了解了实际需求。接下来,咱们考察一下 ES6 Classes 处理这个问题的状况。请从新刷新浏览器 tab,复制执行如下代码。

class Shape { constructor (id, shape = 'square', size = 10.5, color = 'green') { this.x = x; // 坐标x轴 this.y = y; // 坐标y轴 Object.assign(this, {id, shape, size, color}) } } a = new Array(1e6).fill(0); b = a.map((val, ix) => new Shape(ix)); 

咱们使用了ES6 Classes,并扩充了每一个形状的坐标信息。 此时,再来看一下内存占用状况:

 

很明显,此时 b 数组由100000个形状组成,占据内存:80MB,超过了先前数组的内存消耗。也许这并不出乎意料,此时的b数组毕竟又多了两个属性。

 

优化设计:Two-Headed Classes

咱们先来分析一下上面的实现,熟悉原型链、原型概念的同窗也许会明白,以前的方案产生的实例,顺着原型链上溯,具备三层原型属性:

第一层:[id, shape, size, color, x, y]; 这一层属性的 hasOwnproperty 为 true; 属性存在于实例自己。
第二层:[Shape]; 顺着原型链上溯,这一层 instance.proto === Constructor.prototype; ( proto 左右两边 __ 被编辑器吃掉了,请见谅,下同)
第三层:[Object]; 这一层: instance.proto.__proto__ === Object.prototype; 若是在向上追溯,就为 null 了。

这样的状况下,实际业务数据层只有一层,即为第一层。

可是,请仔细思考,若是有大量的不一样颜色,不一样size,不一样形状的状况下。单一数据层,是难以知足咱们需求的。 咱们须要,再添加一层数据层,构成所谓的 Two-Headed Classes!同时,还须要对于默认的属性,实现共享,以节省内存的占用。

什么什么?没听明白,那就请看具体操做吧。

 

如何实现?

咱们可使用 Object.create 方法,这样使得生产获得的实例的 proto 指向 b 数组的元素,而后在最顶层设计一个 id 属性。

也许这样说过于晦涩,那就直接参考代码吧,请注意,这是本篇文章最难以理解的地方,请务必仔细揣摩:

two = Object.create(b[0]); // two.__proto__ === b[0] two.id = 1; 

还记得 b 数组是什么嘛?参考上文,它由

b = a.map((val, ix) => new Shape(ix)); 

获得。

这样子的话,对于每个实例,咱们有以下关系:

第一层:[id]; 这一层实例的 hasOwnproperty 为 true;
第二层:[id, shape, size, color, x, y]; 这一层 instance.proto === Constructor.prototype;
第三层:[Shape];
第四层:[Object]; 这一层的再顶层,就为null了。

咱们将 Shape 的一个实例做为一个新的 object 的原型,并复写了 id 属性,原有的 id 属性将做为默认 id。

固然,上边的代码只是“个案”,咱们进行“生产化”:

proto = new Shape(0); function newTwoHeaded (ix) { const obj = Object.create(proto); obj.id = ix; return obj } c = a.map((val, ix) => newTwoHeaded(ix)); 

这么作多加入了一个数据层,那么有什么“收获”呢?咱们来看一下b和c的内存占用状况吧:

这代表:咱们从80MB的b,优化获得了64MB的c! 缘由固然就在于虽然多加了一层原型结构,可是第二层变成了“共享”。

固然,若是到这里你尚未晕的话,可能要问:那第二层诸如 shape, size, color 这些属性变成共享的以后,存在互相干扰怎么破解呢?

好问题,我先不解答,先给你们看一下最后的 final product:

class ShapeMaker { constructor () { Object.assign(this, ShapeMaker.defaults()) } static defaults () { return { id: null, x: 0, y: 0, shape: 'square', size: 0.5, color: 'red', strokeColor: 'yellow', hidden: false, label: null, labelOffset: [0, 0], labelFont: '10px sans-serif', labelColor: 'black' } } newShape (id, x, y) { const obj = Object.create(this); return Object.assign(obj, {id, x, y}) } setDefault (name, value) { this[name] = value; } getDefault (name) { return this[name] } } 

在实例化的时候,咱们即可以这样使用:

shapeProto = new ShapreMaker(); d = a.map((val, ix) => shapeProto.newShape(ix, ix/10, -ix/10)) 

就像上面所说的,初始化实例时,咱们初始化了 id, x, y 这么三个参数。做为该实例自己的数据层。这个实例的原型上,也有相似的参数,来保证默认值。这些原型上的属性,对于实例数组中的每一个实例,都是共享的。

为了更好的对比,若是设计是这样子:

function fatShape (id, x, y) { const a = new shapeMaker(); return Object.assign(a, {id, x, y}) } e = a.map((val, ix) => fatShape(ix, ix/10, -ix/10)) 

那么全部属性没法共享,而是各自拷贝了一份。在内存的占用上,将是咱们给出方案的三倍之多!

 

阿喀琉斯之踵

阿喀琉斯,是凡人珀琉斯和美貌仙女忒提斯的宝贝儿子。忒提斯为了让儿子炼成“金钟罩”,在他刚出生时就将其倒提着浸进冥河,遗憾的是,乖儿被母亲捏住的脚后跟却不慎露在水外,全身留下了唯一一处“死穴”。后来,阿喀琉斯被帕里斯一箭射中了脚踝而死去。 后人常以“阿喀琉斯之踵”譬喻这样一个道理:即便是再强大的英雄,他也有致命的死穴或软肋。

就像咱们刚才提的到解决方案同样,也有一些“不足”。问题其实在以前我也已经抛出:“第二层诸如:shape, size, color 这些属性变成共享的以后,存在互相干扰怎么破解呢?”

这个问题的答案其实也隐藏在上面的代码中,很简单,就是咱们在实例的自身属性上,进行复写,而避免更改原型上的属性形成污染。

若是你看的云里雾里,没关系,立刻看一下我下面的代码说明:

d.every((item) => item.shape === 'square') // true 

打印为 true,是由于 d 数组中的每一个实例的 shape 属性,都在原型上,且初始值都为'square';

如今咱们调用 setDefault 方法,实现对默认 shape 的改写。

shapeProto.setDefault('shape', 'circle'); d.every((item) => item.shape === 'square'); // false 

由于此时全部实例的 shape 都在原型上,并共享这个原型。更改以后,咱们有:

d.every((item) => item.shape === 'circle'); // true 

可是,我只想把第一个实例的 shape 设置为 triangle,其余的不变,该怎么办呢?只须要在第一个实例上,增长一个 shape 属性,进行重写:

d[0].shape = 'triangle'; d.every((item) => item.shape === 'circle'); // false 

好吧,尝试完毕以后,咱们在变回来。

d[0].shape = 'circle'; 

这时候,天然有:

d.every((item) => item.shape === 'circle'); // true

同时,再折腾一下:

d[0].shape = 'triangle'; d.every((item) => item.shape === 'triangle'); // false 

相信下面的也不难理解了:

shapeProto.setDefault('shape', 'triangle'); d.every((item) => item.shape === 'triangle'); // true 

这种模式其实比单纯使用ES6 Classes要灵活的多,同时也节省了内存。全部的静态属性都是共享的,可是共享的静态属性又都是可变的,可复写的。

 

总结

这篇文章,咱们在开头部分了解到了在大量数据的状况下,内存的占用是如何一步一步变的沉重。同时,咱们提供了一种,在传统的 Classes 之上增长一个数据层的方法,有效地解决了这个问题。解决方案充分利用了 Object.create 等手段。

固然,理解这些内容并不简单,须要读者有比较扎实的 javascript 基础。在您阅读过程中,有任何问题,欢迎与我讨论。

 

---恢复内容结束---

<head>
<link rel="stylesheet" href="http://apps.bdimg.com/libs/animate.css/3.1.0/animate.min.css">
<style type="text/css">
#pay_pic{
overflow: hidden;
width: 200px;
margin: 0 auto;
}
</style>
</head>
<body>
<div id="pay_area" style="padding: 10px;border-radius: 5px;background-color:#EDDCBE;text-align:center;font-size: 15px;color: #272822;cursor:pointer;margin: 10px;">
<div id="pay_pic" align="center" class="">
<img src="http://images.cnblogs.com/cnblogs_com/hngdlxy143/1219034/o_in.png" width="200px">
</div>
<div align="center">微信扫一扫打赏支持</div>
<div align="center">Q群交流<span style="border-bottom:1px solid #B82525;font-weight: bold;">651176910</span></div>
<div align="center">共<span style="font-weight: bold; color: #B82525">69</span>人支持!!!</div>
</div>

<script type="text/javascript" src=" http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="text/javascript">
alert();
currentDiggType = 0;
$("#pay_pic").hover(
function () {
$(this).addClass("animated swing");
},
function () {
$(this).removeClass("animated swing");
}
);
</script>
</body>

 

随着前端的飞速发展,在浏览器端完成复杂的计算,支配并处理大量数据已经家常便饭。那么,如何在最小化内存消耗的前提下,高效优雅地完成复杂场景的处理,愈来愈考验开发者功力,也直接决定了程序的性能。

本文展示了一个彻底在控制台就能模拟体验的实例,经过一步步优化,实现了生产并操控多个1000000(百万级别)对象的场景。

导读:这篇文章涉及到 javascript 中 数组各类操做、原型原型链、ES六、classes 继承、设计模式、控制台分析 等内容。

要求阅读者具备 js 面向对象扎实的基础知识。若是你是初级前端开发者,很容易被较为复杂的逻辑绕的云里雾里,“从入门到放弃”,不过建议先收藏。若是你是“老司机”,本文提供的解决思路但愿对你有所启发,抛砖引玉。

 

场景和初级感知

具体来讲,咱们须要一个构造函数,或者说相似 factory 模式,实例化1000000个以上对象实例。

先来感知一下具体实现:

Step1

打开你的浏览器控制台,仔细观察并复制粘贴如下代码,触发执行。

a = new Array(1e6).fill(0); 

咱们建立了一个长度为1000000的数组,数组的每一项元素都为0。

Step2

在数组 a 的基础上,再生产一个长度为1000000的数组 b,数组的每一项元素都是一个普通 javascript object,拥有 id 属性,而且其 id 属性值为其在元素中的 index 值;

b = a.map((val, ix) => ({id: ix})) 

Step3

接下来,在 b 的基础上,再生产一个长度为1000000的数组 c ,相似于 b,同时咱们增长一些其它属性,使得数组元素对象更加复杂一些:

c = a.map((val, ix) => ({id: ix, shape: 'square', size: 10.5, color: 'green'})) 

语义上,咱们能够更直观的理解:c 就是包含了1000000个元素的数组,每一项都是一个绿色的、size 为10.5的小方块。

若是你按照指示作了下来,控制台上会有如下内容:

 

 

 

 

深层探究

你也许会想,这么大的数据量,内存占用会是什么样的状况呢?

好,我来带你看看,点击控制台 Profiles,选择 Take Shapshot。在Window->Window 目录下,根据内存进行筛选,你会获得:

 

很明显,咱们看到:

  • a数组:8MB;
  • b数组:40MB;
  • c数组:64MB

也许在实际场景中,除了1000000个绿色的、size为10.5的小方块,咱们还须要不少不一样颜色,不一样 size 的形状。以前,这样“变态”的需求常见于游戏应用中。可是如今,复杂项目中相似场景,也许距离你并不遥远。

ES6 Classes处理需求

简单“热身”以后,咱们了解了实际需求。接下来,咱们考察一下 ES6 Classes 处理这个问题的状况。请从新刷新浏览器 tab,复制执行如下代码。

class Shape { constructor (id, shape = 'square', size = 10.5, color = 'green') { this.x = x; // 坐标x轴 this.y = y; // 坐标y轴 Object.assign(this, {id, shape, size, color}) } } a = new Array(1e6).fill(0); b = a.map((val, ix) => new Shape(ix)); 

咱们使用了ES6 Classes,并扩充了每一个形状的坐标信息。 此时,再来看一下内存占用状况:

 

很明显,此时 b 数组由100000个形状组成,占据内存:80MB,超过了先前数组的内存消耗。也许这并不出乎意料,此时的b数组毕竟又多了两个属性。

 

优化设计:Two-Headed Classes

咱们先来分析一下上面的实现,熟悉原型链、原型概念的同窗也许会明白,以前的方案产生的实例,顺着原型链上溯,具备三层原型属性:

第一层:[id, shape, size, color, x, y]; 这一层属性的 hasOwnproperty 为 true; 属性存在于实例自己。
第二层:[Shape]; 顺着原型链上溯,这一层 instance.proto === Constructor.prototype; ( proto 左右两边 __ 被编辑器吃掉了,请见谅,下同)
第三层:[Object]; 这一层: instance.proto.__proto__ === Object.prototype; 若是在向上追溯,就为 null 了。

这样的状况下,实际业务数据层只有一层,即为第一层。

可是,请仔细思考,若是有大量的不一样颜色,不一样size,不一样形状的状况下。单一数据层,是难以知足咱们需求的。 咱们须要,再添加一层数据层,构成所谓的 Two-Headed Classes!同时,还须要对于默认的属性,实现共享,以节省内存的占用。

什么什么?没听明白,那就请看具体操做吧。

 

如何实现?

咱们可使用 Object.create 方法,这样使得生产获得的实例的 proto 指向 b 数组的元素,而后在最顶层设计一个 id 属性。

也许这样说过于晦涩,那就直接参考代码吧,请注意,这是本篇文章最难以理解的地方,请务必仔细揣摩:

two = Object.create(b[0]); // two.__proto__ === b[0] two.id = 1; 

还记得 b 数组是什么嘛?参考上文,它由

b = a.map((val, ix) => new Shape(ix)); 

获得。

这样子的话,对于每个实例,咱们有以下关系:

第一层:[id]; 这一层实例的 hasOwnproperty 为 true;
第二层:[id, shape, size, color, x, y]; 这一层 instance.proto === Constructor.prototype;
第三层:[Shape];
第四层:[Object]; 这一层的再顶层,就为null了。

咱们将 Shape 的一个实例做为一个新的 object 的原型,并复写了 id 属性,原有的 id 属性将做为默认 id。

固然,上边的代码只是“个案”,咱们进行“生产化”:

proto = new Shape(0); function newTwoHeaded (ix) { const obj = Object.create(proto); obj.id = ix; return obj } c = a.map((val, ix) => newTwoHeaded(ix)); 

这么作多加入了一个数据层,那么有什么“收获”呢?咱们来看一下b和c的内存占用状况吧:

这代表:咱们从80MB的b,优化获得了64MB的c! 缘由固然就在于虽然多加了一层原型结构,可是第二层变成了“共享”。

固然,若是到这里你尚未晕的话,可能要问:那第二层诸如 shape, size, color 这些属性变成共享的以后,存在互相干扰怎么破解呢?

好问题,我先不解答,先给你们看一下最后的 final product:

class ShapeMaker { constructor () { Object.assign(this, ShapeMaker.defaults()) } static defaults () { return { id: null, x: 0, y: 0, shape: 'square', size: 0.5, color: 'red', strokeColor: 'yellow', hidden: false, label: null, labelOffset: [0, 0], labelFont: '10px sans-serif', labelColor: 'black' } } newShape (id, x, y) { const obj = Object.create(this); return Object.assign(obj, {id, x, y}) } setDefault (name, value) { this[name] = value; } getDefault (name) { return this[name] } } 

在实例化的时候,咱们即可以这样使用:

shapeProto = new ShapreMaker(); d = a.map((val, ix) => shapeProto.newShape(ix, ix/10, -ix/10)) 

就像上面所说的,初始化实例时,咱们初始化了 id, x, y 这么三个参数。做为该实例自己的数据层。这个实例的原型上,也有相似的参数,来保证默认值。这些原型上的属性,对于实例数组中的每一个实例,都是共享的。

为了更好的对比,若是设计是这样子:

function fatShape (id, x, y) { const a = new shapeMaker(); return Object.assign(a, {id, x, y}) } e = a.map((val, ix) => fatShape(ix, ix/10, -ix/10)) 

那么全部属性没法共享,而是各自拷贝了一份。在内存的占用上,将是咱们给出方案的三倍之多!

 

阿喀琉斯之踵

阿喀琉斯,是凡人珀琉斯和美貌仙女忒提斯的宝贝儿子。忒提斯为了让儿子炼成“金钟罩”,在他刚出生时就将其倒提着浸进冥河,遗憾的是,乖儿被母亲捏住的脚后跟却不慎露在水外,全身留下了唯一一处“死穴”。后来,阿喀琉斯被帕里斯一箭射中了脚踝而死去。 后人常以“阿喀琉斯之踵”譬喻这样一个道理:即便是再强大的英雄,他也有致命的死穴或软肋。

就像咱们刚才提的到解决方案同样,也有一些“不足”。问题其实在以前我也已经抛出:“第二层诸如:shape, size, color 这些属性变成共享的以后,存在互相干扰怎么破解呢?”

这个问题的答案其实也隐藏在上面的代码中,很简单,就是咱们在实例的自身属性上,进行复写,而避免更改原型上的属性形成污染。

若是你看的云里雾里,没关系,立刻看一下我下面的代码说明:

d.every((item) => item.shape === 'square') // true 

打印为 true,是由于 d 数组中的每一个实例的 shape 属性,都在原型上,且初始值都为'square';

如今咱们调用 setDefault 方法,实现对默认 shape 的改写。

shapeProto.setDefault('shape', 'circle'); d.every((item) => item.shape === 'square'); // false 

由于此时全部实例的 shape 都在原型上,并共享这个原型。更改以后,咱们有:

d.every((item) => item.shape === 'circle'); // true 

可是,我只想把第一个实例的 shape 设置为 triangle,其余的不变,该怎么办呢?只须要在第一个实例上,增长一个 shape 属性,进行重写:

d[0].shape = 'triangle'; d.every((item) => item.shape === 'circle'); // false 

好吧,尝试完毕以后,咱们在变回来。

d[0].shape = 'circle'; 

这时候,天然有:

d.every((item) => item.shape === 'circle'); // true

同时,再折腾一下:

d[0].shape = 'triangle'; d.every((item) => item.shape === 'triangle'); // false 

相信下面的也不难理解了:

shapeProto.setDefault('shape', 'triangle'); d.every((item) => item.shape === 'triangle'); // true 

这种模式其实比单纯使用ES6 Classes要灵活的多,同时也节省了内存。全部的静态属性都是共享的,可是共享的静态属性又都是可变的,可复写的。

 

总结

这篇文章,咱们在开头部分了解到了在大量数据的状况下,内存的占用是如何一步一步变的沉重。同时,咱们提供了一种,在传统的 Classes 之上增长一个数据层的方法,有效地解决了这个问题。解决方案充分利用了 Object.create 等手段。

固然,理解这些内容并不简单,须要读者有比较扎实的 javascript 基础。在您阅读过程中,有任何问题,欢迎与我讨论。

相关文章
相关标签/搜索