JS面相对象小案例:自定义安全数组

在JS中,数组不像其他语言(java、python)中那样安全,它具有动态性和弱类型性,切越界访问没有具体的报错,而是返回空,为提升数组的安全性,我们可以自行定义一个安全数组。

一、增加报错

与其他语言一样,增加IndexError,继承内置的Error对象。示例如下:

javascript 复制代码
class IndexError extends Error {
	constructor(message) {
		super(message);
		this.name = "索引越界";
	}
}

这样,我们就可以通过throw语句,抛出new IndexError()异常。

二、定义安全数组类SafeArray

这里,可以使用ES6语法来定义,结构比较简单,也容易理解,示例如下:

javascript 复制代码
class SafeArray {
	
    #_array;

    constructor(...initialArray) {
        // 约定的私有属性
        this.#_array = [...initialArray];
    }
}

注意:上面代码中的 # 表示定义一个私有属性或方法(也就是说,只能在内部访问,不能在类的外部进行访问。),并不是所有的编译器都支持。因为它是ECMAScript 2022新增的语法。

三、添加你想要的getter和setter

1、返回长度

javascript 复制代码
    // 获取数组的长度
    get length() {
        return this.#_array.length;
    }

这样,我们调用new SafeArray().length,就能得到安全数组的长度

2、可以添加sum属性

javascript 复制代码
    // 求和
    get sum() {
    	return this.#_array.reduce((s, elt) => s+=elt, 0);
    }

这样,调用.sum,就能计算数组中各元素相加的和,壮大了内置数组Array的功能。照这个思路,还可以添加更多的聚合函数,如求平均、最值等等。

四、编写安全数组的方法

确定好结构,与必要的属性之后,我们需要为安全数组提供一些必要的方法,如安全的获取元素,安全的添加元素,安全的查找元素等等。示例如下:

javascript 复制代码
    #_isValidIndex(index) {
        return Number.isInteger(index) && index >= 0 && index < this.#_array.length;
    }

    // 安全地获取索引处的值,如果索引无效则返回undefined
    getItem(index) {
        if (this.#_isValidIndex(index)) {
            return this.#_array[index];
        }
        throw new IndexError("数组索引超出范围");
    }

    // 安全地设置索引处的值,如果索引无效则不进行操作
    setItem(index, value) {
        if (this.#_isValidIndex(index)) {
            this.#_array[index] = value;
        } else {
            throw new IndexError("数组索引超出范围");
        }
    }
    
    // 获取指定元素的索引
    indexOf(value, start) {
    	return this.#_array.indexOf(value, start); // 不存在返回 -1
    }
    
    // 判断某个元素是否包含在数组中(适用于判断对象数组或较为复杂的数组中的元素,但存在性能影响)
    contains(value) {
    	let arr = this.#_array;
    	for (let i = 0; i < arr.length; i++) {
    		if (JSON.stringify(arr[i]) === JSON.stringify(value)) return true;
    	}
    	return false;
    }
    
    // 简单的判断某个元素是否包含在数组中
    includes(value) {
    	return this.#_array.includes(value);
    }
    
    // 切片
    slice(start, end) {
    	return this.#_array.slice(start, end);
    }

上述代码中,如果数组索引超出范围,就会抛出索引越界的错误,这是内置数组做不到的。

五、完整代码

javascript 复制代码
class IndexError extends Error {
	constructor(message) {
		super(message);
		this.name = "索引越界";
	}
}

class SafeArray {
	
    constructor(...initialArray) {
        // 约定的私有属性
        this.#_array = [...initialArray];
    }
	
    // 获取数组的长度
    get length() {
        return this.#_array.length;
    }
    
    // 求和
    get sum() {
    	return this.#_array.reduce((s, elt) => s+=elt, 0);
    }
    
    // 求平均
    get average() {
    	if(this.length === 0) throw new Error("数组为空,无法计算");
    	return this.sum / this.length;
    }
    
    // 最大值
    get max() {
    	return Math.max(...this.#_array);
    }
    
    // 最小值
    get min() {
    	return Math.min(...this.#_array);
    }
    
    // 返回数组的维度(复杂度较高)
    get dimension() {
    	let r = 0, max = 0;
	    let stack = [{ array: this.#_array, depth: 0 }];
	    while (stack.length > 0) {
	        let { array, depth } = stack.pop();
	        if (Array.isArray(array)) {
	            r = depth + 1;
	            // 将当前数组的所有元素推入栈中,并增加深度
	            for (let item of array) {
	                stack.push({ array: item, depth: r });
	            }
	            // 更新最大维度
	            max = Math.max(max, r);
	        }
	    }
	    return max;
    }

    // 安全地获取索引处的值,如果索引无效则返回undefined
    getItem(index) {
        if (this.#_isValidIndex(index)) {
            return this.#_array[index];
        }
        throw new IndexError("数组索引超出范围");
    }

    // 安全地设置索引处的值,如果索引无效则不进行操作
    setItem(index, value) {
        if (this.#_isValidIndex(index)) {
            this.#_array[index] = value;
        } else {
            throw new IndexError("数组索引超出范围");
        }
    }
    
    // 获取指定元素的索引
    indexOf(value, start) {
    	return this.#_array.indexOf(value, start); // 不存在返回 -1
    }
    
    // 判断某个元素是否包含在数组中(适用于判断对象数组或较为复杂的数组中的元素,但存在性能影响)
    contains(value) {
    	let arr = this.#_array;
    	for (let i = 0; i < arr.length; i++) {
    		if (JSON.stringify(arr[i]) === JSON.stringify(value)) return true;
    	}
    	return false;
    }
    
    // 简单的判断某个元素是否包含在数组中
    includes(value) {
    	return this.#_array.includes(value);
    }
    
    // 切片
    slice(start, end) {
    	return this.#_array.slice(start, end);
    }
    
    // 添加到数组的开头
    unshift(value) {
    	this.#_array.unshift(value);
    }

    // 添加元素到数组末尾
    push(value) {
        this.#_array.push(value);
    }

    // 移除数组末尾的元素
    pop() {
        return this.#_array.pop();
    }
    
    // 移除数组开头的元素
    shift() {
    	return this.#_array.shift();
    }
    
    // join
    join(delimiter) {
    	return this.#_array.join(delimiter);
    }
    
    // concat
    concat(...other) {
    	return this.#_array.concat(...other);
    }

    // 在指定索引处插入元素,如果索引无效则插入到末尾
    insert(index, value) {
        if (this.#_isValidIndex(index)) {
            this.#_array.splice(index, 0, value);
        } else {
            this.#_array.push(value);
        }
    }

    // 移除指定索引的元素,如果索引无效则不进行操作
    remove(index) {
        if (this.#_isValidIndex(index)) {
            return this.#_array.splice(index, 1)[0];
        } else {
            throw new IndexError("数组索引超出范围");
        }
    }
    
    // 返回数组的字符串表示
    toString() {
        return this.#_array.toString();
    }

	// 排序
	sort(callback) {
		if(callback === undefined) callback = function(){return undefined};
		this.#_notFuncError(callback, "callback");
		return this.#_array.sort(callback);
	}
	
	// reduce
	reduce(callback, init) {
		if(callback === undefined) callback = function(){};
		this.#_notFuncError(callback, "callback");
		return this.#_array.reduce(callback, init);
	}
	
	// forEach
	forEach(callback) {
		if(callback === undefined) callback = function(){};
		this.#_notFuncError(callback, "callback");
		this.#_array.forEach(callback);
	}
	
	// Map
	map(callback) {
		if(callback === undefined) callback = function(){};
		this.#_notFuncError(callback, "callback");
		return this.#_array.map(callback);
	}
	
	// filter
	filter(conditionFunction) {
		if(conditionFunction === undefined) conditionFunction = function(){return true};
		this.#_notFuncError(conditionFunction, "conditionFunction");
		return this.#_array.filter(conditionFunction);
	}
	
	// find
	find(callback) {
		if(callback === undefined) callback = function(){};
		this.#_notFuncError(callback, "callback");
		return this.#_array.find(callback);
	}
	
	// findIndex
	findIndex(callback) {
		if(callback === undefined) callback = function(){};
		this.#_notFuncError(callback, "callback");
		return this.#_array.findIndex(callback);
	}
	
	// every
	every(conditionFunction, context) {
    	if(conditionFunction === undefined) conditionFunction = function(){return false};
		this.#_notFuncError(conditionFunction, "conditionFunction");
    	return this.#_array.every(conditionFunction, context);
    }
    
    // some
    some(conditionFunction, context) {
    	if(conditionFunction === undefined) conditionFunction = function(){return false};
		this.#_notFuncError(conditionFunction, "conditionFunction");
		return this.#_array.some(conditionFunction, context);
    }
	
	// 检查是不是数组
	static isArray(arr) {
		return Array.isArray(arr);
	}
	
	// 检查是不是安全数组
	static isSafeArray(arr) {
		return arr instanceof SafeArray;
	}
	
    // 检查索引是否有效
    #_isValidIndex(index) {
        return Number.isInteger(index) && index >= 0 && index < this.#_array.length;
    }
    
    // 不是函数的固定报错
    #_notFuncError(fn, c) {
    	if(typeof fn !== "function") throw new TypeError("参数" + c + "不是函数");
    }
    
    // 私有属性
    #_array;
    
}

上述是一个完整的SafeArray类是一个功能丰富且安全的数组实现,它通过封装和私有化内部状态,提供了对数组操作的更高层次的控制和安全性。尽管在某些方面可能存在性能开销,但它为需要严格数据完整性和安全性的场景提供了有用的工具。

相关推荐
无限大.23 分钟前
实现前端当中的页面过渡动画
前端
纠结哥_Shrek39 分钟前
独立成分分析 (ICA):用于信号分离或降维
人工智能·python·算法
图扑软件1 小时前
双子塔楼宇自控可视化管理
前端·javascript·人工智能·智慧城市·数字孪生·可视化·楼宇自控
m0_528723811 小时前
react注意事项
前端·react.js·前端框架
纪伊路上盛名在2 小时前
ML基础-Jupyter notebook中的魔法命令
linux·服务器·人工智能·python·jupyter
黑不拉几的小白兔2 小时前
Python爬虫学习第三弹 —— Xpath 页面解析 & 实现无广百·度
爬虫·python·学习
frontendMomo2 小时前
从0到1搭建react组件库-Grid篇
前端·react.js
kakaZhui2 小时前
【llm对话系统】大模型源码分析之 LLaMA 模型的 Masked Attention
人工智能·python·chatgpt·aigc·llama
孤客网络科技工作室2 小时前
如何使用 Python 进行文件读写操作?
python
米码收割机2 小时前
【python】python基于机器学习与数据分析的二手手机特性关联与分类预测(源码+数据集)【独一无二】
python·机器学习·数据分析