Các loại bản ghi & Tuple
Tính không thay đổi đang dần tìm đường đến Javascript. Sau khi chấp nhận tính bất biến của API tạm thời, các loại bất biến khác, cụ thể là Bản ghi & Bộ dữ liệu, cũng sẽ đến với Javascript.
Giới thiệu
Hãy bắt đầu với bản ghi. Loại bản ghi là một cấu trúc giống như đối tượng bất biến sâu sắc. Để tạo một bản ghi, hãy sử dụng một đối tượng bằng chữ và đặt trước nó một ký hiệu băm (#).
// a classic object
const user = { name: "S. Holmes", age: 27 }
const address = { street: "221B Baker Street", city: 'London'}
// a record
const user = #{ name: "S. Holmes", age: 27 }
const address = #{ street: "221B Baker Street", city: 'London'}
// array
const enemies = ["James Moriarty", "Irene Adler"]
const friends = ["G. Lestrade", "John Hamish Watson"]
// tuple
const enemies = #[ "James Moriarty", "Irene Adler"]
const friends = #["G. Lestrade", "John Hamish Watson"]
#[10, 25] can be viewed as #{0: 10, 1: 25 }
hợp chất nguyên thủy
Các loại Bản ghi & Bộ dữ liệu là các nguyên hàm hỗn hợp với các giá trị không thay đổi . Đủ công bằng nhưng chính xác thì định nghĩa vụng về này có nghĩa là gì?
Hãy bắt đầu với thuật ngữ nguyên thủy . Cho đến nay, Javascript chỉ chứa bảy loại nguyên thủy :
- sợi dây
- con số
- cố chấp
- boolean
- không xác định
- biểu tượng
- vô giá trị
// primitive but not compound
const str = "Primitive value"
const number = 1
const bool = true
const n = null
// the compound value:
// consists of two primitive values
const object = { name: 'Primitive value', number: 33 }
// consists of two primitive values & one function
const object = { name: 'Primitive value', number: 33, fn: () => {} }
// record with a nested record
const record = #{ name: 'Primitive value', number: 33,
nestedRecord : #{ number: 50} }
// tuple with a nested tuple and record
const tuple = #[ 'Primitive value', 33, #{ number: 50}, #[1, true, null] ]
// FORBIDDEN usage:
// record with a nested object
// it throws an error as it’s not a primitive value anymore
const record = #{ name: 'Primitive value', number: 33,
nestedObject: { number: 50} }
Record & Tuple không phải là một khái niệm mới. Hầu hết các ngôn ngữ chức năng đều có các loại này. Ngoài ra, nhiều ngôn ngữ chính thống như C#, Python và Java gần đây cũng đã áp dụng chúng. Ngay cả trong Javascript, đã có công việc trước đây được thực hiện trên các cấu trúc dữ liệu bất biến được cung cấp bởi thư viện Immutable.JS và những thứ khác.
Hãy so sánh các bản ghi của immutable.JS với cú pháp của bản ghi gốc mới sắp ra mắt:
//Immutable.JS
const record1 = Record({ a: 1, b: 2 })
const record2 = record1.set('b', 50)
const b = record2.get('b')
const recordA = Record({ a: 1, b: 2 })
const recordB = Record({ a: 1, b: 2 })
recordA.equals(recordB) // true
recordA == (recordB) // false !!!
// Native records
const recordNative = #{ a: 1, b: 2 }
const recordNative2 = #{ ...recordNative, b: 50 }
const b = recordNative2.b
const recordA = #{ a: 1, b: 2 }
const recordB = #{ a: 1, b: 2 }
recordA == (recordB) // true !!!
Xin đừng hiểu sai ý tôi, tôi không chỉ trích cách làm việc của Immutable.JS. Không có gì. Immutable.JS đã và đang làm rất tốt trong việc phổ biến khái niệm về các loại bất biến trong Javascript. Tuy nhiên, tôi nghĩ đã đến lúc mong đợi có các loại bất biến gốc trong toolbelt của chúng ta .
Các tính năng chính của Record & Tuple
Bây giờ hãy xem xét các tính năng chính của các loại mới.
bình đẳng
Bình đẳng luôn là một vấn đề khó khăn. Sử dụng các đối tượng cổ điển, bạn chỉ phải dựa vào đẳng thức tham chiếu:
// Identity equality
const user = { name: 'Dr. Watson'}
const userRef = user
userRef == user // => true
const user = { name: 'Dr. Watson'}
const user2 = { name: 'Dr. Watson'}
user2 == user // => false
// Value equality
const user = #{ name: 'Dr. Watson'}
const user2 = #{ name: 'Dr. Watson'}
user2 == user // => true
const user1 = #{ name: 'S. Holmes', age: 27 }
const user2 = #{ age: 27, name: 'S. Holmes'}
user1 == user2 // => true
Tình hình liên quan đến bộ dữ liệu là một chút khác nhau. Mã này hoạt động như mong đợi:
const friends1 = #['G. Lestrade', 'John Hamish Watson']
const friends2 = #['G. Lestrade', 'John Hamish Watson']
friends1 == friends2 // => true
const friends1 = #['G. Lestrade', 'John Hamish Watson']
const friends2 = #['John Hamish Watson', 'G. Lestrade']
friends1 == friends2 // => false
tính bất biến
Các cấu trúc bất biến có nhiều lợi ích hơn so với các đối tác có thể thay đổi của chúng. Trong bối cảnh Javascript, các cấu trúc bất biến giúp chúng tôi ngăn chặn sửa đổi vô tình và làm cho cơ sở mã của chúng tôi dễ dàng suy luận hơn.
Một ví dụ về tính bất biến:
const primitive = 'string is an immutable primitive'
const primitive2 = primitive + '— change it'
primitive == primitive2 // => false
const obj1 = { id: 'A', value: 1}
const obj2 = obj1
// the following code changes the value of obj2.value
// but it also change obj1.value
obj2.value = 2
obj1 === obj2 // => still true
obj1.value === obj2.value // => true
// But with immutability, you can't change the record like a normal object.
const record1 = #{ id: 'A', value: 1}
record1.value = 2 // throws error
// You need to be more explicit to get changed value
const record2 = #{ ... record1, value: 2}
Làm việc với Bản ghi & Tuple
Cho đến giờ, tôi đã chỉ cho bạn cú pháp để tạo các bộ hoặc bản ghi theo nghĩa đen. Tuy nhiên, cú pháp mới hỗ trợ các trường hợp sử dụng nâng cao hơn.
Tạo một phiên bản đã sửa đổi bằng cách sử dụng trải rộng
Bạn có thể sửa đổi các phiên bản của mình bằng cách sử dụng toán tử trải rộng quen thuộc “…”. Nó tương tự như phương thức with cho các đối tượng Temporal.
Ví dụ về toán tử trải rộng:
const user = #{ name: 'S. Holmes', age: 27 }
// with immutable types, you are restricted to changing value directly:
user.age = 28 // throws error
// With spread operator, you can copy a record
// and modify it at the same time:
const userOneYearOlder = #{ ...user, age: 28}
// => #{ name: 'S. Holmes', get: 28 }
// Of course, it also means:
user == userOneYearOlder // => false
// Tuple
const friends1 = #['G. Lestrade', 'Dr. Watson']
const friends2 = #[...friends1, 'Mrs Hudson']
// => #['G. Lestrade', 'Dr. Watson', 'Mrs Hudson']
// You can modify an item at a particular position with the method 'with'
const friends3 = friends1.with(0, 'Mrs Hudson')
// => #['Mrs Hudson', 'Dr. Watson']
const friends1 = #['Dr. Watson', 'G. Lestrade']
const friends2 = friends1.toReversed()
// => ['G. Lestrade', 'Dr. Watson']
const friends3 = #['G. Lestrade', 'Dr. Watson']
const friends4 = friends3.toSorted()
// friends4 == #['Dr. Watson', 'G. Lestrade']
Chuyển đổi mảng thành bộ dữ liệu và đối tượng thành bản ghi
Thật dễ dàng để chuyển đổi các thể hiện giữa các loại bất biến và các đối tác của chúng:
const record = #{ name: 'S. Holmes', age: 27 }
// Note that any iterable of entries will work
const record2 = Record.fromEntries([['age', 27], #['name', 'S. Holmes']])
const tuple = Tuple(...[1, 2, 3])
// Note that an iterable will work as well
const tuple2 = Tuple.from([1, 2, 3])
record == #{ name: 'S. Holmes', age: 27 } // => true
tuple == #[1, 2, 3] // => true
// JS Compiler throws an error when using a non-primitive value
// TypeError: cannot use an object as a value in a record
Record({ a: {} })
// TypeError: cannot use an object as a value in a record
Tuple.from([{}, {} , {}])
const obj = { ...record }
const arr = [ ...tuple ]
Chúng ta có nên áp dụng Record & Tuple không?
Tôi sẽ nói có, không do dự. Tôi đã tóm tắt các lập luận của mình dưới đây.
quen thuộc
Tôi thích ý tưởng được đề cập trong cuốn sách Kill with it Fire của Marriane Bellotti:
Việc áp dụng công nghệ mới được thúc đẩy bởi sự quen thuộc hơn là ưu việt.
Khái niệm của Marriane rất phù hợp ở đây. Các tác giả của đề xuất Tuple & Record đã cố gắng bắt chước ngữ nghĩa của đối tượng và mảng để giúp chúng tôi áp dụng các kiểu nguyên thủy mới này. Kết quả là bản ghi hoạt động tương tự như các đối tượng hoặc mảng. Vì vậy, việc áp dụng nên nhanh chóng.
Lập mô hình miền kinh doanh của chúng tôi
Tính bất biến và so sánh tỏa sáng khi lập mô hình miền. Các nhà phát triển ứng dụng kinh doanh thường sử dụng nhiều lớp miền đại diện cho Người dùng, Hóa đơn, Báo cáo và các đối tượng trong thế giới thực khác. Trong những trường hợp này, hồ sơ có thể đứng vững.
Nhưng trước khi những người quá khích về OOP bắt đầu thực hiện các cú ném bóng của họ, hãy để tôi làm rõ: Tôi biết rằng việc sử dụng các bản ghi cho các lớp miền sẽ dẫn đến một mô hình miền thiếu máu - mô hình chống OOP. Tuy nhiên, cũng có thể xác định các hành vi cho các khái niệm miền trong ngôn ngữ lập trình bằng cách sử dụng các hàm bậc cao hơn hoặc các kỹ thuật chức năng khác. Vui lòng đọc thêm về ý tưởng này trong “ Mô hình miền chức năng có luôn thiếu máu không ” hoặc “ Mô hình miền thiếu máu không phải là mẫu chống đối, đó là một thiết kế RẮN. ”
Cuối cùng, tôi đã thử cả mô hình miền cổ điển và thiếu máu, và mô hình trước đây phù hợp với tôi hơn, ngay cả trong ngôn ngữ OOP nghiêm ngặt.
Còn Tuple thì sao?
Trong khi các bản ghi phù hợp để lập mô hình các lớp miền của bạn, các bộ dữ liệu có cách sử dụng chuyên biệt hơn. Các bộ cung cấp một cách dễ dàng để trả về hoặc chuyển nhiều giá trị từ hoặc tới một hàm. Các nhà phát triển Javascript, đặc biệt là các nhà phát triển React, đã sử dụng các bộ dữ liệu rất nhiều sau khi nhiệm vụ giải cấu trúc xuất hiện trong Javascript.
Móc useState của React đã minh họa nó một cách hoàn hảo:
// [count, setCount] is a classic tuple-like array
const [count, setCount] = useState(0)
Một trường hợp sử dụng khác cho bộ dữ liệu là mẫu ghi nhớ . Bây giờ, không có bộ dữ liệu riêng, bạn cần sử dụng thủ thuật để tuần tự hóa các đối số của hàm, chẳng hạn như JSON.stringify :
const memoized = new Map()
function compute(...args) {
// a hack
const key = JSON.stringify({...args})
if (memoized.has(key))
{
return memoized.get(key)
}
else {
const value = peformSlowComputation(...args)
memoized[key] = value
return value
}
}
const memoized = new Map()
function compute(... args) {
// a nice and clean key
const key = #[...args]
if (memoized.has(key ))
{
return memoized.get(key)
}
else {
const value = performSlowComputation(...args)
memoized[key] = value
return value
}
}
Tôi cũng phải cảnh báo bạn, hãy sử dụng các bộ dữ liệu một cách cẩn thận. Đôi khi trường hợp sử dụng của bạn có thể trông hoàn toàn ổn khi sử dụng một tuple. Cuối cùng mặc dù, nó có thể mang lại nhiều nhầm lẫn. Ví dụ: tọa độ địa lý:
const [lat, long] = getCoordinates()
const [long, lat] = getCoordinates()
// it works in both cases:
const {lat,long} = getCoordinates()
const {long,lat} = getCoordinates()
Tối ưu hóa
Các cấu trúc bất biến không chỉ cung cấp mã sạch hơn mà còn có các hiệu ứng hiệu suất ẩn. Vì JS là một ngôn ngữ động nên trình biên dịch JS phải đưa ra nhiều phỏng đoán về cách bố trí bộ nhớ của đối tượng. Thực tế là bạn có thể thêm trường của đối tượng mới vào bất kỳ lúc nào khiến mọi thứ trở nên phức tạp. Các ngôn ngữ tĩnh cung cấp khả năng tối ưu hóa tốt hơn vì chúng biết trước hình dạng của các đối tượng. Để tìm hiểu thêm, hãy đọc bài viết sau về tối ưu hóa chức năng trong V8 . Với các loại bất biến, tác giả của trình biên dịch Javascript có thể tối ưu hóa chúng, tương tự như kiểu gõ tĩnh.
Trạng thái
Kể từ tháng 1 năm 2023, Record & Tuple vẫn đang ở giai đoạn 2, có nghĩa là chúng sẽ không xuất hiện sớm trên trình duyệt của chúng tôi. Tuy nhiên, với plugin polyfill và Babel đang hoạt động này , bạn có thể bắt đầu khám phá các trường hợp sử dụng khả thi. Hoặc bạn có thể chơi với những loại mới này trong sân chơi này .
Phần kết luận
Record & Tuple sẽ là một điều quan trọng khác trong Javascript. Phải thừa nhận rằng ngay từ cái nhìn đầu tiên, nó ít hữu ích hơn so với API tạm thời mới hoặc API ưa thích như WebXR . Tuy nhiên, nó mang đến một khái niệm phổ biến khác từ các ngôn ngữ chức năng, điều này chắc chắn sẽ giúp việc viết mã của chúng ta dễ dàng hơn.
Chúng tôi là ACTUM Digital và tác phẩm này được viết bởi Marek , Nhà phát triển giao diện người dùng cấp cao của Bộ phận Apollo.
Nếu bạn cần trợ giúp cho dự án hoặc sáng kiến của mình, chỉ cần gửi thư cho chúng tôi.

![Dù sao thì một danh sách được liên kết là gì? [Phần 1]](https://post.nghiatu.com/assets/images/m/max/724/1*Xokk6XOjWyIGCBujkJsCzQ.jpeg)



































