内容简介:The “everything is an object” maxim clearly describes just how importantThink about the ways you usually interact with objects. And I don’t mean some complex, dedicated ones like functions or arrays (which are still objects after all), but simple structure
The “everything is an object” maxim clearly describes just how important objects are in JavaScript. These structures form the basis of the entire language! With that said, I think it’s easy to assume that the JS Object API doesn’t get the attention it deserves.
Think about the ways you usually interact with objects. And I don’t mean some complex, dedicated ones like functions or arrays (which are still objects after all), but simple structures that you use to organize your data. Certainly, you use dot
or bracket notation
to access the object's properties, and maybe even Object.assign()
or the spread operator
if you write more modern code. But what else do you use? I bet it's not that much.
Because of the fundamental nature of objects, they and their syntax are meant to be simple. But you might find yourself in a situation where you require some more advanced functionality that you don't know, but the Object API can provide. So, in this blog post, I’d like to walk you through some of these lesser-known functionalities, that might actually be really useful!
Object manipulation
Aside from everything I've just mentioned, Object API provides you with additional methods to interact with your objects. The two I'd like to tell you about here are Object.defineProperty()
and Object.defineProperties()
.
Object.defineProperty()
The most obvious way to set an object's property is by doing it right when declaring the object or later on with the dot or bracket notation. Thus, having the Object.defineProperty()
method might feel a bit repetitive and unnecessary. And in most cases it is, but it also provides some functionalities that you can't get anywhere else!
Object.defineProperty()
not only defines/sets/overrides the property's value but its whole descriptor
- something like metadata of the property. Take a look:
let obj = {}; let firstProperty = 10; Object.defineProperty(obj, "firstProperty", { configurable: true, enumerable: true, get: () => { console.log(`Retrieving the property "firstProperty".`); return firstProperty; }, set: newValue => { console.log(`Setting the property "firstProperty" to ${newValue}.`); firstProperty = newValue; } }); Object.defineProperty(obj, "secondProperty", { configurable: false, enumerable: false, writable: false, value: "value" }); obj.firstProperty; // Retrieving the property "firstProperty". 10 obj.secondProperty; // "value" obj.firstProperty = 20; // Setting the property "firstProperty" to 20. obj.secondProperty = "secondValue"; obj.firstProperty; // Retrieving the property "firstProperty". 20 obj.secondProperty; // "value"
Above I use Object.defineProperty()
to set two properties on the object obj
. The first argument that the method takes is the object
that the property will be set on. It'll be returned later on from the Object.defineProperty()
call. After that comes the second argument, which is the property's name
and the last one, which is the property's descriptor
.
I used two properties on purpose - to showcase the two flavors of descriptors - the data
and the accessor
ones. They share two properties - configurable
and enumerable
. The first one defines whether the property's descriptor type can be changed (e.g. by calling Object.defineProperty()
the second time) or the property deleted (with the delete
keyword). while the second one controls whether the property appears in the for... in
loop or when used with some methods we'll discuss later on. Both properties default to false
, which sets apart the most basic Object.defineProperty()
call from the usual syntax.
Now, data descriptors allow you to set two other properties - value
and writable
. While the meaning of the first one is obvious, the second one refers to the possibility of changing (aka writing to) the property. Mind you that it's not the same as the configurable
property, but like the one mentioned, defaults to false
.
The second kind of descriptors - accessor descriptors, also provide you with two additional properties, but this time they're called get
and set
. These should have a form of individual functions that are called accordingly when the property is retrieved and set. They're the same setters
and getters
that you might have seen before, with the difference being that they're defined after the object is created. Just to remind you:
let firstProperty = 10; let obj = { get firstProperty() { console.log(`Retrieving the property "firstProperty".`); return firstProperty; }, set firstProperty(newValue) { console.log(`Setting the property "firstProperty" to ${newValue}.`); firstProperty = newValue; } };
Properties that have setters and/or getters cannot have values of their own. Instead, they either calculate them from the other properties or use different variables.
Object.defineProperties()
So, if you want to use Object.defineProperty()
to define multiple properties, you'll be better off using Object.defineProperties()
instead. Here's how it looks:
let obj = {}; let firstProperty = 10; Object.defineProperties(obj, { firstProperty: { configurable: true, enumerable: true, get: () => { console.log(`Retrieving the property "firstProperty".`); return firstProperty; }, set: newValue => { console.log(`Setting the property "firstProperty" to ${newValue}.`); firstProperty = newValue; } }, secondProperty: { configurable: false, enumerable: false, writable: false, value: "value" } });
Basically, you just swap out the string argument for an object with property name - descriptor key-value structure, that's easier to use, read and manage when multiple properties are involved.
Immutability
The introduction of the const
keyword in ES6 as a new way of declaring "variables" spread a little controversy as to what exactly is constant
. As it turns out - it's not the value (as usual), but the variable
itself. So, if you e.g. assign an object to such a variable, you won't be able to change the variable's value, but you'll be able to freely change the properties of the assigned object.
const obj = {}; obj.property = 1; obj.property; // 1 obj = {}; // ERROR
This might be OK for most, but not so for the ones striving for immutability
. const
doesn't guarantee your variable's value to stay the same unless it's a primitive
(i.e. number, string or boolean). And that's where the Object API comes into play, with a set of methods that allow you to define the mutation rules of not only a single property (like with Object.defineProperty()
) but the entire objects!
Object.preventExtensions()
Starting with the most "loose" of the methods, Object.preventExtensions()
simply prevents any new properties from being added to an object (aka extending
it). Once you call it with your object as an argument, no new property will be allowed to be defined (even with the use of Object.defineProperty()
).
const obj = Object.preventExtensions({ firstProperty: 10, secondProperty: 20 }); obj.firstProperty = 100; delete obj.secondProperty; // true obj.thirdProperty = 30; // nothing or ERROR obj.firstProperty; // 100 obj.secondProperty; // undefined obj.thirdProperty; // undefined
Object.preventExtensions()
, as well as all the other "locking" methods of Object API, return the passed object, making for a nice, immutability-friendly syntax you see above.
Again, after calling the method, you can do pretty much everything but define new properties. This includes deleting and changing the already present property values and descriptors. An attempt to set new property will either be left silent or throw an error (e.g. when you're in strict mode).
You can check whether the object can be extended with the Object.isExtensible()
method.
const firstObject = { property: 10 }; const secondObject = Object.preventExtensions({ property: 20 }); Object.isExtensible(firstObject); // true Object.isExtensible(secondObject); // false
Object.seal()
If you want to go a bit further than Object.preventExtensions()
, you can use Object.seal()
to not only disallow any new properties to be set, but also make all exiting properties non-configurable
. Remember the configurable
property from the Object.defineProperty()
? Object.seal()
is like combining Object.preventExtensions()
with Object.defineProperties()
where you override all existing properties to be non-configurable. Your properties are still writable
(unless you've previously set them not to), so you can easily change their values. However, you cannot delete a property or change the descriptor type (from data to accessor or vice versa).
const obj = Object.seal({ firstProperty: 10, secondProperty: 20 }); obj.firstProperty = 100; delete obj.secondProperty; // false obj.thirdProperty = 30; // nothing or ERROR obj.firstProperty; // 100 obj.secondProperty; // 20 obj.thirdProperty; // undefined
If you want to check whether the given object has already been sealed, you can use the Object.isSealed()
method. Also useful might be the previously-discussed Object.isExtensible()
method, which, when the object is sealed, will return false
.
const obj = Object.seal({ property: 20 }); Object.isSealed(obj); // true Object.isExtensible(obj); // false
Object.freeze()
Lastly, if you want to take the immutability of your objects to another level, Object.freeze()
is at your disposal. As the name indicates, it not only makes your object non-extensible
and non-configurable
but also completely unchangeable
. You can only access your previously-defined properties and that's it! Any attempt to change anything won't work and will either be left silent or throw an error.
const obj = Object.freeze({ firstProperty: 10, secondProperty: 20 }); obj.firstProperty = 100; // nothing or ERROR delete obj.secondProperty; // false obj.thirdProperty = 30; // nothing or ERROR obj.firstProperty; // 10 obj.secondProperty; // 20 obj.thirdProperty; // undefined
To check if an object is "frozen", you'll have to use the Object.isFrozen()
method, but keep in mind that both Object.isExtensible()
and Object.isSealed()
still apply.
const obj = Object.freeze({ property: 20 }); Object.isFrozen(obj); Object.isSealed(obj); // true Object.isExtensible(obj); // false
Now, just to remind you that as "everything is an object", the same "locking" methods can be applied to all the other objects that are present in JS. Examples of such include custom classes , functions , and most importantly - arrays . This is especially great when you're going for full-blown immutability and functional programming in pure JS.
const arr = Object.freeze([1, 2, 3]); arr.push(4); // ERROR arr.pop(); // ERROR arr[0] = 0; // nothing or ERROR
Iteration
As we're on the topic of arrays, let's talk about iteration . Looping through arrays is normal, but what about objects? There's certainly less freedom in that department.
There's a for...in
loop that lets you iterate through enumerable
properties (remember the descriptors we've talked about before) of an object and read their key names.
const obj = { firstProperty: 10, secondProperty: 20 }; for (const key in obj) { const value = obj[key]; }
However, this method is pretty limiting. You only get access to the property keys and you have to use that to access the value if you need that. That's one additional (and possibly unnecessary) Line Of Code (LOC) to be added to your codebase - one that could have been avoided.
Basically, you have a lot less flexibility with simple objects than with arrays and their API. So, how about converting objects to arrays and looping through that instead? Well, that's exactly what some of the Object API methods allow you to do!
Object.keys()
Let's start with the simplest of methods - Object.keys()
. Like the name implies it returns all the keys of the passed object in a form of an array of strings
. When your data is organized in such a way, you can use e.g. the .forEach()
method from Array API to loop through all of the retrieved property keys.
const obj = { firstProperty: 10, secondProperty: 20 }; const keys = Object.keys(obj); // ["firstProperty", "secondProperty"] keys.forEach(key => { const value = obj[key]; });
Still, Object.keys()
isn't that of a compelling option. It pretty much gives you the same result as the for...in
loop at the loss of some performance. However, if you consider this syntax better or cleaner you shouldn't care about such small performance benefits.
Object.keys()
also stands out from the rest of related Object API methods, with better support for older browsers. Most notably it supports up (or rather down) to IE 9
, while the next two methods don't support this particular browser at all! Still, if the support of old browsers matters to you and you don't want to use any polyfills, you'll be better served by the for...in
loop, which supports even IE 6
!
Object.values()
As for the "need only the values" case we've discussed before, Object.values()
will serve this purpose just fine. Instead of keys, it returns an array
of object's property values
.
const obj = { firstProperty: 10, secondProperty: 20 }; const values = Object.values(obj); // [10, 20] values.forEach(value => { // do something with value });
Object.entries()
Finally, Object.entries()
is a method that gives you access both to the object's keys as well as its values. It returns them in the form of an array of key-value pairs
(arrays).
const obj = { firstProperty: 10, secondProperty: 20 }; const entries = Object.entries(obj); // [["firstProperty", 10], ["secondProperty", 20]] entries.forEach(([key, value]) => { // do something with the key and the value });
Object.entries()
feels especially good when used with the destructuring syntax
like in the example above.
Object.fromEntries()
While Object.fromEntries()
isn't a method meant for iterating through objects, it does basically the opposite of what the Object.entries()
method does. Namely, it converts an array of key-value pairs ( Object.entries()
output) to an object
. Just a fun fact!
const obj = { firstProperty: 10, secondProperty: 20 }; const entries = Object.entries(obj); // [["firstProperty", 10], ["secondProperty", 20]] const objCopy = Object.fromEntries(entries);
Maps are better?
In comparison to the for...in
loop, any of these methods don't take into consideration properties from the object's prototype
. To achieve the same (usually desired) effect with the for...in
loop, you'll have to use the .hasOwnProperty()
method to check whether the property is the object's own.
You should also remember that both for...in
loop and Object API methods ignore the non-enumerable
properties (like I've said before), and the ones that use Symbols
as their keys.
In reality, though, all that is kind-of "low-level" (as far as the JS goes) stuff, and you're unlikely to have to deal with any of such issues in real-world projects. What's more important, however, is the fact that any of the ways of object iterations we've just covered don't guarantee the order of iterated keys, values, entries or whatever. It usually follows the order in which the properties were defined, but it's not a good practice to follow such an assumption.
If you're going for something that's like an array and object combined, you might be interested in Maps . These are structures that organize data in a key-value fashion and allow for iteration while maintaining the correct order of the key-value pairs. They also have decent cross-browser support and other unique properties known from both arrays and objects. I've covered them already in one ofmy previous posts, so go check it out if you're interested!
Final words
That’s it! Hope you enjoyed the article and learned something new. The amount of possibilities JS API can offer is truly impressive! Here, we've barely scratched the surface! From the Object API itself, we've missed some more complex, prototype-related methods. I think they're not as useful as the ones listed (especially when writing modern JS) but I encourage you to explore them on your own to strengthen your JS knowledge!
So, if you like the article, consider sharing it with others and following me on Twitter , Facebook or through my weekly newsletter for more up-to-date content. You can also check out my YouTube channel and drop a like or a sub there. As always, thanks for reading and have a great day!
以上所述就是小编给大家介绍的《Secrets of the JavaScript Object API》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Laravel框架关键技术解析
陈昊、陈远征、陶业荣 / 电子工业出版社 / 2016-7 / 79.00元
《Laravel框架关键技术解析》以Laravel 5.1版本为基础,从框架技术角度介绍Laravel构建的原理,从源代码层次介绍Laravel功能的应用。通过本书的学习,读者能够了解Laravel框架实现的方方面面,完成基于该框架的定制化应用程序开发。 《Laravel框架关键技术解析》第1章到第4章主要介绍了与Laravel框架学习相关的基础部分,读者可以深入了解该框架的设计思想,学习环......一起来看看 《Laravel框架关键技术解析》 这本书的介绍吧!