我们一直在处理阵列。其中一些很简单,例如一系列字符串或数字,有些更复杂,例如一系列对象。
阵列也可以根据其使用而被认为是复杂的。如果从数组中访问某些元素比直接访问需要更多的工作,那么我认为它很复杂。
这是一个示例:
const todos = [
{ id: 1, title: 'First Todo', completed: false },
{ id: 2, title: 'Second Todo', completed: true },
{ id: 3, title: 'Third Todo', completed: false }
]
如果我想访问带有2
的ID的TODO,我需要写这篇文章:
todos.find(todo => todo.id === 2)
这不是直接访问。我必须根据某些属性进行一些检查。
如果这就是将使用此数组的目的,那么我就不必担心改进它。但是在现实世界项目中,我们使用这些阵列(例如删除,更新或检查它包含特定值)来做更多的事情。
在我向您展示可以对这些阵列进行哪些改进之前,让我向您展示为什么首先需要这些改进。
阵列操作示例
如果我正在构建一个todo应用程序,那么我需要编写与Todos数组相似的代码。
// Get a todo by id
const todo = todos.find(todo => todo.id === todoId)
if (!todo) {
// this todo does not exist
// maybe throw an error about that
}
// Get todos based on their completed state
const completedTodos = todos.filter(todo => todo.completed)
const notCompletedTodos = todos.filter(todo => !todo.completed)
// Check if a todo with some id exists
const todoExists = todos.some(todo => todo.id === todoId)
// Delete a todo using its id
const todoExists = todos.some(todo => todo.id === todoId)
if (todoExists) {
todos = todos.filter(todo => todo.id !== todoId)
}
// Alternative way
const todoIndex = todos.findIndex(todo => todo.id === todoId)
if (todoIndex !== -1) {
todos.splice(todoIndex, 1)
}
// Add a new todo to the todo list
const newTodo = { id: 4, title: 'Fourth Todo', completed: false }
todos.push(newTodo)
这些只是您要为这样一个数组编写的操作的几个示例。
那样写它们似乎可以。但是,当您在整个代码库中重复所有这些操作时,就会出现问题,并且在大多数情况下,您将拥有更多的操作,并且它们会更加复杂。
当您决定更新操作应如何工作时,将会出现更多问题;在这种情况下,您需要更新所有使用该数组的位置,而不是在一个地方更新它,并且在多个位置更新同一内容时更有可能有错误。
另外,使用这种方法,没有明确的方法来测试此数组上的操作。
另一个大问题是可变性。在此示例中,数组是可变的,这意味着应用程序的任何部分都可以任意更改它,这是许多潜在错误的来源。
现在,您知道为什么处理阵列的操作是不好的。解决方案是什么?答案是:封装您的数组。
数组封装
本文的标题是“为您的复杂阵列提供API”,这与说封装您的数组的说法相同。
要封装您的数组,您需要通过将其隐藏并暴露于API进行操作来防止直接更改它。
封装它的最佳方法是用一个对象包裹它并将所有相关操作添加到该对象。
class TodoCollection {
#collection = []
constructor(todos) {
this.#collection = todos
}
get allTodos() {
return structuredClone(this.#collection)
}
get completedTodos() {
return this.#collection.filter(todo => todo.completed)
}
getTodoById(todoId) {
return this.#collection.find(todo => todo.id === todoId)
}
addTodo(todo) {
this.#collection.push(todo)
}
contains(todoId) {
return todos.some(todo => todo.id === todoId)
}
removeTodo(todoId) {
if (!this.contains(todoId)) return
this.#collection = this.#collection.filter(todo => todo.id !== todoId)
}
}
现在,任何时候您都想使用Todos数组,您都会用这样的对象包装它:
const todos = new TodoCollection(todosArray)
// Get a todo
const todo = todos.getTodoById(2)
// Remove a todo
todos.removeTodo(2)
使用这种方法,我有一个明确处理数组的API。
不仅如此,而且现在不可能无意中修改数组。修改它的唯一方法是通过其addTodo
和removeTodo
方法。您无法使用todos.allTodos.push(newTodo)
修改它的原因是因为todos.allTodos
返回了数组的克隆。因此,更改它不会更改原始数组。
现在更新代码很容易。例如,如果您希望您的数组在不存在todo的情况下引发错误,则只需要更新getTodoById
方法的代码。
另一个巨大的好处是现在很容易测试您现在有TodoCollection
类要测试。
如果您喜欢这篇文章,请查看我所有有关Coddip的其他文章,这是可以提高JavaScript代码质量的地方。