• 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