preface

Object is the most complex concept of JavaScript language, as long as the object is understood thoroughly, JavaScript is able to get through both sides.

object

An object differs from other primitive types in that it is a compound value: it aggregates together many values (primitive values or other objects) that can be accessed by name.

Thus, an object can also be thought of as an unordered collection of properties, each of which is a name-value pair. The attribute name is a string (or symbol), so we can think of an object as a mapping from string to value.

var o = { a: 1 }; o.b = 2; console.log(o.a, o.b); / / 1. 2Copy the code

Objects in JavaScript are unique in that they are highly dynamic because JavaScript gives users the ability to add state and behavior to objects at run time.

Two classes of properties for JavaScript objects

Properties are not simply names and values for JavaScript. JavaScript uses a set of attributes to describe properties.

Data attributes

  • Value: indicates the value of the attribute.
  • Writable: Determines whether a property can be assigned a value.
  • Enumerable: Determines if for in can enumerate this property.
  • Different: Determines whether the property can be deleted or changed.

Accessor (getter/setter) properties

  • Getter: Function or undefined, called when fetching a property value.
  • Setter: function or undefined, called when setting property values.
  • Enumerable: Determines if for in can enumerate this property.
  • Different: Determines whether the property can be deleted or changed
var o = { a: 1 }; Object.defineProperty(o, "b", {value: 2, writable: false, enumerable: false, configurable: true}); / / a and b are attribute data, but the characteristic value changes the Object. The getOwnPropertyDescriptor (o, "a"); // {value: 1, writable: true, enumerable: true, configurable: true} Object.getOwnPropertyDescriptor(o,"b"); // {value: 2, writable: false, enumerable: false, configurable: true} o.b = 3; console.log(o.b); / / 2Copy the code

We used Object.defineProperty to define the property, which changes the writable and Enumerable properties of the property.

An object can also be thought of as an index structure of attributes (an index structure is a common type of data structure, we can think of it as a dictionary that can quickly find values using keys).

Related knowledge

  1. Used before Vue3.0Object.definePropertyDefines attributes, which are the “core” of Vue’s implementation of two-way data binding.
  2. Does JavaScript’s use of a set of attributes to describe properties remind you of a classic problem? What’s the difference between an attribute and a property? Here is a simple understanding:
    • Attribute is the attribute of an HTML element tag<div id="id1" class="class1" title="title1" a='a1'></div>Id, class, title, etc
    • Object property property refers to the property of the element node, which can be manipulated with JavaScript after domization.

The difference between object and object orientation

  • An object is a data structure:var o = {a:1};
  • Object orientation describes a form of code organization structure, a method of modeling problem domain in the real world in software

JavaScript itself is object-oriented, but it does it in a different way than the mainstream genre. JavaScript is an object-oriented system based on prototypes.

Most other languages describe object orientation based on classes.

The big question in your mind at this point is what is prototype-based and what is class-based, and how they are implemented.

Understanding object Orientation

Object Oriented Programming (OOP) is a kind of computer Programming architecture. OOP achieves the three main goals of software engineering: reusability, flexibility and extensibility.

Its main features are

  • inheritance
  • encapsulation
  • polymorphism

Don’t think of objects as having these properties, but rather object-oriented programming as having these properties. That is, both class-implemented object orientation and prototype-implemented object orientation should have these features.

The point of this article is not to explain exactly what object-oriented programming is, but to focus on prototype systems in JavaScript.

Implement object orientation through classes

The most successful genre is the use of “classes” to describe objects, giving rise to popular programming languages such as C++ and Java. This school is called class-based programming languages.

In such languages, there is always a class and an object is instantiated from that class. Classes may form inheritance, combination and other relations between classes. Classes in turn are often integrated with the language’s type system to form some compile-time capabilities.

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }

  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

const point = new Point(10,20);
Copy the code

Implement object orientation through prototypes

Prototype – based object-oriented systems create new objects by “copying”.

There are two ways to realize the “copy operation” of prototype system:

  • One is to make the new object hold a reference to the prototype without actually copying it. (Shallow copy)
  • The other is to actually copy the object so that the two objects are no longer related. (Deep copy)

JavaScript obviously chooses the first option (shallow copy).

var copyObj = Object.create(Object.prototype);
Copy the code
  • Object. propToType is a prototype Object
  • Create copies an Object based on the prototype Object with Object.create

The copyObj Object is then associated with the Object.propToType Object

Prototype. ToSay = function(){console.log(" my new skill "); } copyObj.toSay(); // My new skillCopy the code

We add a method to the prototype object, and its replicas can have that method right away.

Aside from JavaScript’s complex syntactic facilities for emulating Java classes (new, Function Object, Function prototype attributes, etc.), the prototype system is fairly simple, and I can summarize it in two ways:

  • If all objects have private fields [[prototype]], that is the prototype of the object.
  • Reads a property, and if the object itself does not have one, continues to access the object’s prototype until it is empty or found.

This model hasn’t changed much in various historical versions of ES, but since ES6, JavaScript has provided a series of built-in functions for more direct access to manipulation prototypes.

The three methods are as follows:

  • Create Creates a new Object based on the specified prototype. The prototype can be null.
  • Object.getprototypeof Gets the prototype of an Object;
  • Object.setPrototypeOf Sets the prototype of an Object.

With these three approaches, we can abandon class thinking altogether and use prototypes for abstraction and reuse. I use the following code to show an example of abstracting a cat and a tiger with a prototype.

var cat = { say(){ console.log("meow~"); }, jump(){ console.log("jump"); } } var tiger = Object.create(cat, { say:{ writable:true, configurable:true, enumerable:true, value:function(){ console.log("roar!" ); } } }) var anotherCat = Object.create(cat); anotherCat.say(); var anotherTiger = Object.create(tiger); anotherTiger.say();Copy the code

This code creates a “cat” Object and makes some modifications to the cat to create a tiger. After that, we can use object.create to create other cat and tiger objects. We can control the behavior of all cats and tigers from the “original cat Object” and “original tiger Object”.

Summary:

  • The method of creating a new object by copying the specified object is called prototype based object creation
  • Instantiating an object through a class and inheriting combinations between classes is called class-based object creation

JavaScript “Emulates Class-based Object Orientation”

I’m sure you’re wondering that’s not how we usually write it. Instead, function Cat(){}; const cat = new Cat(); This is the way to write code.

This is “simulating class-based object orientation” and the key is the new operator

The new operator, which takes a constructor and a set of call parameters, actually does several things:

  1. Create a new object based on the constructor’s Prototype property.
  2. Pass this and the call arguments to the constructor, and execute;
  3. If the constructor returns an object, otherwise the object created in the first step is returned.

Actions such as new attempt to make function objects syntactically similar to classes.

function Cat(){ this.p1 = 1; this.p2 = function(){ console.log(this.p1); } } var o1 = new c1; o1.p2(); // 1 function c2(){ } c2.prototype.p1 = 1; c2.prototype.p2 = function(){ console.log(this.p1); } var o2 = new c2; o2.p2(); / / 1Copy the code

Two questions arise at this point, right?

  1. Cat. Prototype. Constructor What are these respectively? What is the connection between them?
  2. Does ES6 Class really implement a Class, or is it also emulating? Which one should I use?

Complicated relationships

function Foo(){};
Foo.prototype.say = function(){
    console.log("say");
}
var f1 = new Foo;
console.log(f1.constructor === Foo); //true
Copy the code

Foo. Foo. Prototype. Constructor

  • Function Foo is a constructor (class meaning)
  • Var f1 = new Foo instantiates the object
  • The constructor has a prototype property that points to the prototype object of the instance object. So foo. prototype is the f1 prototype object
  • The stereotype object has a constructor property pointing to the corresponding constructor of the stereotype objectFoo.prototype.constructor === Foo
  • Since an instance object can inherit properties from a stereotype, it also has a constructor property that points to the corresponding stereotype object’s constructorf1.constructor === Foo
  • The instance object has a proto property that points to the prototype object corresponding to the instance objectf1.__proto__ === Foo.prototype(protoThe underscore is used to indicate the concept of a private property.

With images, you should be able to make sense of the complex relationships between JavaScript objects.

What does the new + constructor have to do with ES6’s Class?

In getting started with ECMAScript 6, ES6 classes can be seen as a syntactic candy that does most of what ES5 does. The new class notation simply makes writing object prototypes clearer and more like object-oriented programming syntax.

class Point {
  // ...
}

typeof Point // "function"
Point === Point.prototype.constructor // true
Copy the code
  • The data type of a class is a function, and the class itself points to a constructor.
  • When used, the new command is used directly on the class, exactly as the constructor is used.

The prototype property of the constructor continues on the ES6 “class”. In fact, all methods of a class are defined on the prototype property of the class.

class Point { constructor() { // ... } toString() { // ... } toValue() { // ... }} / / equivalent Point. The prototype = {constructor () {}, the toString () {}, toValue () {},};Copy the code

Calling a method on an instance of a class is actually calling a method on the prototype.

In any scenario, it is recommended to use ES6 syntax to define classes and return function to its original functional semantics.

By now you should have a thorough understanding of JavaScript objects, but there is one important concept that we have not covered in JavaScript object orientation above, and that is object inheritance.

Object inheritance

Commonly speaking, inheritance refers to a new object that is modified slightly on the basis of the original object.

function Super(name){
    this.name = name;
    this.colors = ["red","blue","green"];
}

Super.prototype.sayName = function(){
    return this.name;
};
Copy the code

Looking at this code, if we wanted to inherit it, we would want to inherit its attribute name, colors, and its method sayName

Let’s take a look at the recommended inheritance method in ES5:

function Sub(name,age){ Super.call(this,name); This. age = age; this.age = age; this.age = age; // Add your own attribute} if(! Object.create){// This is a simplified version of object.create pollfill, which does not consider null object.create = function(proto){function F(){}; // temporary constructor f.protoType = proto; // The prototype we want to copy is assigned to the temporary constructor's prototype return new F; }} sub. prototype = object.create (super.prototype); / / this is the Super. The prototype is defined on the properties of the copy down the Sub. The prototype. The constructor = Sub; // Change the constructor property value to point to itselfCopy the code

In fact, there are many ways to inherit JavaScript objects, but the core is to copy the properties and methods of the parent class, and can also inherit the properties of the prototype object. Now, of course, we can simply implement inheritance using ES6’s extends, which hides these technical details more elegantly.

Jquery uses copy inheritance, using copy functions to copy the attributes and methods of the parent to the child

function extend(obj,cloneObj){ if(typeof obj ! = 'object'){ return false; } var cloneObj = cloneObj || {}; for(var i in obj){ if(typeof obj[i] === 'object'){ cloneObj[i] = (obj[i] instanceof Array) ? [] : {}; arguments.callee(obj[i],cloneObj[i]); }else{ cloneObj[i] = obj[i]; } } return cloneObj; } var obj1 = {2, a: 1, b: c: [1, 2, 3]}. var obj2=extend(obj1); console.log(obj1.c); / / [1, 2, 3]. The console log (obj2. C); / / [1, 2, 3] obj2. C.p ush (4); console.log(obj2.c); / / [1, 2, 3, 4] the console. The log (obj1. C); / / [1, 2, 3]Copy the code

This is a deep copy of JavaScript objects shallow copy of the question, because the interview often asked, so I still in-depth study.

Object depth copy

Shallow copy

Creates a new object with an exact copy of the original object property values. If the property is a primitive type, it copies the value of the primitive type, and if the property is a reference type, it copies the memory address, so if one object changes the address, it affects the other object.

const obj = {a:1,b:{c:2}}; const copyObj1 = Object.assign({},obj); const copyObj2 = {... obj}; obj.b.c = 4; // copyObj1,copyObj2 will changeCopy the code

Deep copy

To make a complete copy of an object out of memory, a new area of heap memory is created to hold the new object, and modification of the new object does not affect the original object.

Deep copy deals with a variety of situations, here are some common situations to deal with:

  • Consider layer upon layer nesting of objects (recursive solution)
  • Consider the array
  • Consider object circular references (solved by introducing caching)
  • Consider weak reference problems (solve strong references of cache objects with weakMap)
  • Considered null
  • Consider the function
  • Consider continuously traversable objects such as maps and sets

I’m not going to do this step by step, but you can just look at the full version of the code, with detailed comments.

const mapTag = '[object Map]'; const setTag = '[object Set]'; const arrayTag = '[object Array]'; const objectTag = '[object Object]'; const argsTag = '[object Arguments]'; const boolTag = '[object Boolean]'; const dateTag = '[object Date]'; const numberTag = '[object Number]'; const stringTag = '[object String]'; const symbolTag = '[object Symbol]'; const errorTag = '[object Error]'; const regexpTag = '[object RegExp]'; const funcTag = '[object Function]'; const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag]; ForEach function forEach(array, iteratee) {let index = -1; const length = array.length; while (++index < length) { iteratee(array[index], index); } return array; Function isObject(target) {const type = typeof target; return target ! == null && (type === 'object' || type === 'function'); } / / output specific data type function getType (target) {return Object. The prototype. ToString. Call (target); } // Const target = {} is the syntax of const target = new Object(). Another advantage of this approach is that because we are also using the original object constructor, it retains the data on the object's prototype, which would have been lost if we had just used the normal {}. function getInit(target) { const Ctor = target.constructor; return new Ctor(); } / / clone Symbol type function cloneSymbol (target) {return Object (Symbol. The prototype. The valueOf. Call (target)); Function cloneReg(target) {const reFlags = /\w*$/; const result = new target.constructor(target.source, reFlags.exec(target)); result.lastIndex = target.lastIndex; return result; } function cloneFunction(func) {const bodyReg = /(? <={)(.|\n)+(? =})/m; const paramReg = /(? < = \ () + (? =\)\s+{)/; const funcString = func.toString(); if (func.prototype) { const param = paramReg.exec(funcString); const body = bodyReg.exec(funcString); if (body) { if (param) { const paramArr = param[0].split(','); return new Function(... paramArr, body[0]); } else { return new Function(body[0]); } } else { return null; } } else { return eval(funcString); Function cloneOtherType(target, type) {const Ctor = target. Constructor; switch (type) { case boolTag: case numberTag: case stringTag: case errorTag: case dateTag: return new Ctor(target); case regexpTag: return cloneReg(target); case symbolTag: return cloneSymbol(target); case funcTag: return cloneFunction(target); default: return null; }} function clone(target, map = new WeakMap()) {// Clone primitive if (! isObject(target)) { return target; } // initialize const type = getType(target); let cloneTarget; if (deepTag.includes(type)) { cloneTarget = getInit(target); } else { return cloneOtherType(target, type); } // prevent loop references if (map.get(target)) {return map.get(target); } map.set(target, cloneTarget); // Clone set if (type === setTag) {target.forEach(value => {cloneTarget. Add (clone(value, map)); }); return cloneTarget; } // Clone map if (type === mapTag) {target.forEach((value, key) => {clonetarget. set(key, clone(value, map)); }); return cloneTarget; } // Clone objects and arrays const keys = type === arrayTag? undefined : Object.keys(target); forEach(keys || target, (value, key) => { if (keys) { key = value; } cloneTarget[key] = clone(target[key], map); }); return cloneTarget; }Copy the code

Deep object copying involves a wide range of knowledge, which is why it’s one of the must-ask interview questions.