- Let’s explore objects in JavaScript
- Cristi Salcescu
- Translation from: Aliyun Translation Group
- Text link: github.com/dawn-teams/…
- Translator: Lingnuma
- Proofreader: Trees, sleeping clouds
Explore JavaScript objects together
An object is a dynamic collection of attributes, with a hidden attribute (note: __proto__) linked to the stereotype.
A property has a key and a value.
Properties of the key
The key of the property is a unique string.
There are two ways to access properties: dot notation and parenthesis notation. When using dot notation, the key of an attribute must be a valid identifier.
let obj = {
message : "A message"
}
obj.message //"A message"
obj["message"] / /"A message"
Copy the code
Accessing a nonexistent property does not throw an error, but returns undefined.
obj.otherProperty //undefined
Copy the code
When using parenthesis notation, the key of an attribute should not be a valid identifier — it can be any value.
let french = {};
french["thank you very much"] = "merci beaucoup";
french["thank you very much"]; //"merci beaucoup"
Copy the code
When the attribute’s key is a non-string value, it is converted to a string using the toString() method (if available).
let obj = {};
//Number
obj[1] = "Number 1";
obj[1] === obj["1"]; //true
//Object
let number1 = {
toString : function() { return "1"; }
}
obj[number1] === obj["1"]; //true
Copy the code
In the example above, the object number1 is used as a key. It is converted to a string, and the result “1” is used as the key of the property.
The value of the attribute
The value of a property can be any underlying data type, object, or function.
Object as value
Objects can be nested within other objects. Look at this example:
let book = {
title : "The Good Parts",
author : {
firstName : "Douglas",
lastName : "Crockford"
}
}
book.author.firstName; //"Douglas"
Copy the code
In this way, we can create a namespace:
let app = {};
app.authorService = { getAuthors : function() {}}; app.bookService = { getBooks :function() {}};Copy the code
Function as value
When a function is represented as a property value, it is usually referred to as a method. In a method, the this keyword represents the current object.
This will have different values depending on how the function is called. To learn more about this losing context, see what to do when “this” loses context.
dynamic
Objects are dynamic by nature. You can add or delete attributes at will.
let obj = {};
obj.message = "This is a message"; //add new property
obj.otherMessage = "A new message"; //add new property
delete obj.otherMessage; //delete property
Copy the code
Map
We can think of the object as a Map. The Map key is the property of the object.
Accessing a key does not require scanning all properties. The time complexity of access is O (1).
The prototype
The object has a “hidden” attribute __proto__ linked to the protoobject from which the object inherits properties.
For example, an Object created using Object literals has a link to Object.prototype:
var obj = {};
obj.__proto__ === Object.prototype; //true
Copy the code
Prototype chain
The stereotype object has its own stereotype. When a property is accessed and not included in the current object, JavaScript looks down the stereotype chain until it finds the property being accessed, or reaches NULL.
read-only
The stereotype is only used to read values. Object changes only affect the current object and do not affect the prototype of the object. This is true even if the prototype has a property of the same name.
An empty object
As we can see, the empty Object {} is not really empty because it contains a link to Object.prototype. To create a truly empty Object, we can use Object.create(null). It creates an object without any attributes. This is usually used to create a Map.
Raw values and wrapped objects
To the extent that you allow access to properties, JavaScript describes raw values as objects. Of course, primitive values are not objects.
(1.23). ToFixed (1); //"1.2"
"text".toUpperCase(); //"TEXT"
true.toString(); //"true"
Copy the code
To allow access to the properties of the original value, JavaScript creates a wrapper object and then destroys it. The JavaScript engine optimizes the process of creating and destroying wrapper objects.
Numeric, string, and Boolean values all have equivalent wrapper objects. The options are Number, String, and Boolean.
Null and undefined have no corresponding wrapper object and do not provide any methods.
Built-in prototype
‘Numbers’ inherits from’ number. prototype ‘and’ number. prototype ‘inherits from’ Object.prototype ‘.
var no = 1;
no.__proto__ === Number.prototype; //true
no.__proto__.__proto__ === Object.prototype; //true
Copy the code
Strings inherits from String.prototype. Booleans inherited from Booleans. Prototype
Functions are objects that inherit from function.prototype. Functions have methods like bind(), apply(), and call().
All objects, functions, and primitive values (except null and undefined) inherit properties from Object.prototype. They both have the toString() method.
Use Polyfill to extend the built-in objects
JavaScript can easily extend built-in objects with new functionality.
Polyfill is a snippet of code that implements a feature in a browser that does not support it.
utility
For example, this polyfill for Object.assign() is not available, then add a new method to Object.
Writes a similar polyfill for array.from (), and adds a new method to Array if it’s not available.
The prototype
New methods can be added to the prototype.
For example, string.prototype.trim () polyfill allows all strings to use the trim() method.
let text = " A text ";
text.trim(); //"A text"
Copy the code
Array.prototype.find() polyfill lets all arrays use the find() method. Polyfill is the same.
let arr = ["A"."B"."C"."D"."E"];
arr.indexOf("C"); / / 2Copy the code
Single inheritance
Object.create() creates a new Object with a specific prototype Object. It’s used to do unitary inheritance. Consider the following example:
let bookPrototype = {
getFullTitle : function() {return this.title + " by "+ this.author; }}let book = Object.create(bookPrototype);
book.title = "JavaScript: The Good Parts";
book.author = "Douglas Crockford"; book.getFullTitle(); //JavaScript: The Good Parts by Douglas CrockfordCopy the code
Multiple inheritance
Object.assign() copies attributes from one or more objects to the target Object. It’s used for multiple inheritance. Look at the following example:
let authorDataService = { getAuthors : function() {}};let bookDataService = { getBooks : function() {}};let userDataService = { getUsers : function() {}};let dataService = Object.assign({},
authorDataService,
bookDataService,
userDataService
);
dataService.getAuthors();
dataService.getBooks();
dataService.getUsers();
Copy the code
Immutable object
Object.freeze() Freezes an Object. Attributes cannot be added, deleted, or changed. The object becomes immutable.
"use strict";
let book = Object.freeze({
title : "Functional-Light JavaScript",
author : "Kyle Simpson"
});
book.title = "Other title"; //Cannot assign toread only property 'title'
Copy the code
Object.freeze() executes a shallow freeze. To freeze deeply, you need to recursively freeze each property of the object.
copy
Object.assign() is used as a copy Object.
let book = Object.freeze({
title : "JavaScript Allongé",
author : "Reginald Braithwaite"
});
let clone = Object.assign({}, book);
Copy the code
Object.assign() performs shallow copy, not deep copy. It copies the first level properties of the object. Nested objects are shared between the original and replica objects.
Object literals
Object literals provide a simple, elegant way to create objects.
let timer = {
fn : null,
start : function(callback) { this.fn = callback; },
stop : function() {},}Copy the code
However, this syntax has some drawbacks. All attributes are public, methods can be redefined, and the same methods cannot be used in new instances.
timer.fn; //null timer.start =function() { console.log("New implementation"); }
Copy the code
Object.create()
Object.create() and object.freeze () together solve the last two problems.
First, I’ll use all the methods to create a frozen prototype, timerPrototype, and then create objects to inherit it.
let timerPrototype = Object.freeze({
start : function() {},
stop : function() {}});let timer = Object.create(timerPrototype);
timer.__proto__ === timerPrototype; //true
Copy the code
When a stereotype is frozen, the object that inherits it cannot change its properties. Now, the start() and stop() methods cannot be redefined.
"use strict";
timer.start = function() { console.log("New implementation"); } //Cannot assign to read only property 'start' of object
Copy the code
Object.create(timerPrototype) can be used to build more objects using the same prototype.
The constructor
Originally, the JavaScript language proposed constructors as syntactic sugar for these. Look at the following code:
function Timer(callback){
this.fn = callback;
}
Timer.prototype = {
start : function() {},
stop : function() {}}function getTodos() {}
let timer = new Timer(getTodos);
Copy the code
All functions defined with the function keyword can be used as constructors. The constructor is called with the new function. New object set prototype to FunctionConstructor. Prototype.
let timer = new Timer();
timer.__proto__ === Timer.prototype;
Copy the code
Similarly, we need to freeze the stereotype to prevent methods from being redefined.
Timer.prototype = Object.freeze({
start : function() {},
stop : function() {}});Copy the code
New operator
When newTimer() is executed, it has the same effect as newTimer() :
function newTimer() {let newObj = Object.create(Timer.prototype);
let returnObj = Timer.call(newObj, arguments);
if(returnObj) return returnObj;
return newObj;
}
Copy the code
Using timer.prototype as the prototype, a new object is created. The Timer function is then executed and the property fields are set for the new object.
class
ES2015 brings better grammar sugar to all of this. Look at the following example:
class Timer{
constructor(callback){
this.fn = callback;
}
start() {}
stop() {}
}
Object.freeze(Timer.prototype);
Copy the code
Use the class built object to set the prototype to classname.prototype. When creating objects using classes, you must use the new operator.
let timer= new Timer();
timer.__proto__ === Timer.prototype;
Copy the code
Class syntax does not freeze stereotypes, so we need to do this later.
Object.freeze(Timer.prototype);
Copy the code
Prototype-based inheritance
In JavaScript, objects inherit from objects.
Constructors and classes are syntactic sugar for all methods used to create prototype objects. It then creates a new object that inherits from the prototype object and sets the data fields for the new object. Prototype-based inheritance has the benefit of protecting memory. Prototypes are created once and used by all instances.
No packaging
The prototype-based inheritance model has no privacy. All object attributes are public.
Keys () returns an array containing all the property keys. It can be used to iterate over all attributes of an object.
function logProperty(name){
console.log(name); //property name
console.log(obj[name]); //property value
}
Object.keys(obj).forEach(logProperty);
Copy the code
The simulated private mode includes using _ to mark private attributes so that others avoid using them:
class Timer{ constructor(callback){ this._fn = callback; this._timerId = 0; }}Copy the code
The factory pattern
JavaScript provides a new way to create encapsulated objects using the factory pattern.
function TodoStore(callback){
let fn = callback;
function start() {},
function stop() {}
return Object.freeze({
start,
stop
});
}
Copy the code
The FN variable is private. Only the start() and stop() methods are public. The start() and stop() methods cannot be changed by outsiders. This is not used here, so there is no problem with this losing context.
The object literal is still used to return objects, but this time it only contains functions. More importantly, these functions are closures that share the same private state. Object.freeze() is used to freeze public apis.
For a complete implementation of the Timer object, see a practical JavaScript object with encapsulation capabilities.
conclusion
JavaScript treats raw values, objects, and functions like objects.
Objects are dynamic in nature and can be used as maps.
Objects inherit from other objects. Constructors and classes are syntactic sugars for creating objects that inherit from other prototype objects.
Object.create() can be used for single inheritance, and Object.assign() for multiple inheritance.
Factory functions can build encapsulated objects.
For more information about JavaScript features, see:
Discover the power of first class functions
How point-free composition will make you a better functional programmer
Here are a few function decorators you can write from scratch
Why you should give the Closure function another chance
Make your code easier to read with Functional Programming