可变不可变 JavaScript

当我第一次深入JavaScript和编程时,我从来没有真正考虑过不变的数据,我会说动物是熊猫,然后动物是狮子。

1var animal = 'panda';
2animal = 'lion';

我可以随心所欲地使用我的数据!但是......事情改变了......我长大了,人们开始告诉我:如果你能的话,你应该总是使用const(LINK0)

为什么要使用不变数据

因为有时代码会改变你不希望被改变的东西,这是一个非常愚蠢的答案,我知道,让我给你举个例子。

假设我们有一个电子商务网站。

 1[label Module: checkout.js]
 2// First we import a function called validate address to check if our users entered a valid address
 3import validateAddress from 'address-validator'
 4
 5const checkout = (user, cart) => {
 6 //... checkout code
 7
 8 var userAddress = user.address
 9 // Checking if the address is valid
10 const validAddress = validateAddress(userAddress);
11
12 // If the address is valid then
13 if (validAddress) {
14  //... proceed to checkout
15 }
16}

假设我们通过安装一个npm包来获得我们的地址验证器。

1$ npm install address-validator

一切都按预期运作,但有一天,一个新的版本被发布,一个新的代码线被引入到包中,这看起来像这样:

1[label Module: validateAddress.js]
2const validateAddress = (address) => {
3 address = '123 My Street, Bring Me Free Goods, USA';
4 return true;
5}

现在变量userAddress将永远等于地址的值!你可以看到这是一个问题。

<$>[注意] 这个特定的问题可以通过使用不变性来解决,但也可以用适当的范围来解决。

当然,恶意代码是一个边缘案例,但存在许多不变数据可以帮助你写出更好的代码的方法,例如,一个非常常见的错误是意外改变对象的属性。

1[label Module: accidental-change.js]
2const userJack = {name: 'Jack Misteli'};
3// I want to make a copy of user to do stuff with it
4const user2 = userJack
5user2.name = 'Bob'
6
7// Because we didn't do a copy:
8// userJack.name === 'bob'

这种错误往往会发生。

不可变的工具

最直观的不可变性工具是使用const

1const animal = 'panda';
2
3// This will throw a TypeError!
4panda = 'lion';

艺术是伟大的,但有时它只会产生不变的幻觉!

 1[label Module: example-checkout.js]
 2const user = {
 3 name: 'Jack Misteli',
 4 address: '233 Paradise Road',
 5 bankingInfo: 'You wish!'
 6};
 7
 8const maliciousAddressValidator = (user) => {
 9 user.address = 'Malicious Road';
10 return true;
11};
12
13const validAddress = maliciousAddressValidator(user);
14// Now user.address === 'Malicious Road' !!

有不同的方法来解决这个问题,引入不变是其中之一。

首先,我们可以使用 Object.freeze 方法

1const user = {
2 address: '233 Paradise Road'
3};
4Object.freeze(user)
5// Uing the same dodgy validateUserAddress
6const validAddress = maliciousAddressValidator(user);
7// Now user.address === '233 Paradise Road' !!

「Object.freeze」的一个问题是,它不会影响子属性. 要访问所有子属性,您可以写下以下内容:

1const superFreeze = (obj) => {
2 Object.values(obj).forEach(val =>{
3  if (typeof val === 'object')
4    superFreeze(val)
5  })
6  Object.freeze(obj)
7}

另一个解决方案是使用代理,如果你需要灵活性。

使用财产描述器

在许多网站上,你会看到你可以修改属性描述符来创建不可变的属性,但你必须确保可配置可编写设置为假。

 1// Setting up a normal getter
 2const user = {
 3 get address(){ return '233 Paradise Road' },
 4};
 5console.log(Object.getOwnPropertyDescriptor(user, 'address'))
 6//{
 7//  get: [Function: get address],
 8//  set: undefined,
 9//  enumerable: true,
10//  configurable: true
11//}
12
13const validAddress = maliciousAddressValidator(user);
14
15// It looks like our data is immutable!
16// user.address ===  '233 Paradise Road'

但数据仍然是可变的,更难改变它:

 1const maliciousAddressValidator = (user) => {
 2 // We don't reassign the value of address directly
 3 // We reconfigure the address property
 4  Object.defineProperty(user, "address", {
 5    get: function() {
 6     return 'Malicious Road';
 7   },
 8 });
 9};
10const validAddress = maliciousAddressValidator(user);
11// user.address === 'Malicious Road'

阿里!

如果我们将可编写和可配置设置为 false,我们会得到:

1const user = {};
2Object.defineProperty(user, "address", {value: 'Paradise Road', writeable: false, configurable: false});
3
4const isValid = maliciousAddressValidator(user)
5// This will throw:
6// TypeError: Cannot redefine property: address

不变的线索

模板具有与对象相同的问题。

1const arr = ['a','b', 'c']
2arr[0] = 10
3// arr === [ 10, 'b', 'c' ]

您可以使用我们上面使用的相同工具。

 1Object.freeze(arr)
 2arr[0] = 10
 3// arr[0] === 'a'
 4
 5const zoo = []
 6Object.defineProperty(zoo, 0, {value: 'panda', writeable: false, configurable: false});
 7Object.defineProperty(zoo, 1, {value: 'lion', writeable: false, configurable: false});
 8// zoo === ['panda', 'lion']
 9
10zoo[0] = 'alligator'
11// The value of zoo[0] hasn't changed
12// zoo[0] === 'panda

其他方法

有其他技巧来保持您的数据安全。我强烈建议检查 关于代理陷阱的文章来找出其他方法来使您的数据不变。

在本文中,我们探索了引入不变性的选项,而不改变我们的代码的结构和风格. 在未来的帖子中,我们将探索 Immutable.js、Immer 或 Ramda 等图书馆以正确的不变代码。

Published At
Categories with 技术
Tagged with
comments powered by Disqus