当我第一次深入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 等图书馆以正确的不变代码。