前端-手写sizeOf函数

305 阅读4分钟

基础存储单元

位(bit):二进制数中的一位,可以是0或者1,是计算机中数据的最小单位,简称b。

字节(Byte):计算机中数据的基本单位,每8位组成一个字节,简称B。各种信息在计算机中存储、处理至少需要一个字节。例如,一个ASCII码用一个字节表示,一个汉字用两个字节表示。

内存占用

在这里,我们只讨论四种基本类型,String,Number,Boolean,Object,Array

String:2个字节

我们都知道,计算机内部,所有的信息最终都是一个二进制值。每个二进制位有0和1的状态,所以八个二进制就可以组合出256种状态,这就称为一个字节。

上世纪60年代,美国制定了一套字符编码,将英文字符和二进制位之间一一对应,被称为ASCII码,一直沿用至今。

但这用来表示中文字符,一个字节远远不够,所以中国就制定了GB2312编码用来表示中文字符。其他国家也因为同样的原因制定了自己的编码规则。

世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此非常容易因编码冲突导致乱码的出现。

为了解决乱码的问题,Unicode编码应运而生。它将世界上所有的符号都纳入其中,每一个符号都给予一个独一无二的编码,采用2个字节表示一个字哦福,最多可以表示65535个字符。

JS采用的也是Unicode编码,所以JS中String类型占用2个字节。

Number:8个字节

JS中的Number类型采用IEEE754标准中的双精度浮点数来表示一个数字,不区分整数和浮点数。

在IEEE754中,双精度浮点数采用64位存储,即8个字节表示一个浮点数。其存储结构如下图所示:

number-64-bit.png

Boolean:4个字节

Boolean实际上只有一个字节,但在不同平台由于对齐的缘故,会导致分配的内存至少占用4到8个字节,并且以最低有效位和3位为零的方式对齐。

A boolean is actually 1 byte. But alignment may cause 4 bytes to be used on a 32-bit platform or 8 bytes on a 64-bit platform. This old trick comes from the observation that allocated memory takes up at least 4 or 8 bytes, and are aligned in the way that the least significant bit or three will be zero.

详情查看(需要梯子)

实现

思路

考察计算机基础,js内存考察 递归 边界处理

1.了解各个类型所占用字节数 2.根据各个类型分开判断,

  • String需要字符串长度*2;
  • Number直接return 8;
  • Boolean直接return 4;
  • Array需要考虑对象数组,数字数组,字符串数组等,使用reduce累加即可;
  • Object需要考虑null,null的话直接return 0;非null,因为key和value都是占用内存空间的,所以对key和value都进行累加。(注意:两个key有可能引用一个value,所以需要Set来存储这唯一value)。

代码

    // 用来存储已经被引用的变量
    const seen = new Set();
    function sizeOfObject(obj) {
        if (obj === null) return 0;
        let bytes = 0;
        //对象里的key也占用内存空间
        const keys = Object.keys(obj);
        //遍历获取,累加key和value所占用的字节
        for (let i = 0; i < keys.length; i++) {
            const key = keys[i];
            bytes += calculator(key);
            
            if (Object.prototype.toString.call(obj[key]) === 'Object') {
                //如果已经存在,就直接跳出这次循环
                if (seen.has(obj[key])) continue;
                seen.add(obj[key]);
            }

            bytes += calculator(obj[key]);
        }
        return bytes;
    }
    function calculator(params) {
        const paramsType = Object.prototype.toString.call(params).slice(8, -1);

        switch (paramsType) {
            case 'String':
                return params.length * 2;
            case 'Number':
                return 8;
            case 'Boolean':
                return 4;
            case 'Object':
                return sizeOfObject(params);
            case 'Array':
                // 需要考虑对象数组,数字数组,字符串数组等情况
                //[1,2,3,4,5]
                //[{x;1}, {x;2}]
                return params.map(calculator).reduce((prev, curr) => prev + curr, 0);
            default:
                //默认返回0
                return 0;
        }
    }
    
    // 测试
    const testObj = {
        a: 111,
        b: 'cccx',
        2222: false,
        xxxx: true,
    };
    const str = 'str';
    const num = 123;
    const boo = true;
    console.log(`calclator(testData)`, calculator(testObj)); //44
    console.log(`calclator(str)`, calculator(str)); //6
    console.log(`calclator(num)`, calculator(num)); //8
    console.log(`calclator(boo)`, calculator(boo)); //4

为什么Object.prototype.toString.call(params)可以判断类型?

这是调用了Object原型链的toString方法,所以对于Object是可以判断出来的。而能够判断基础类型,是因为它对值进行了包装。

「那么,什么是包装对象:」

所谓“包装对象”,指的是与数值、字符串、布尔值分别相对应的Number、String、Boolean三个原生对象。这三个原生对象可以把原始类型的值变成(包装成)对象。 详情参考

参考