Object related methods

JavaScript provides many related methods on the Object object to handle related operations of object-oriented programming. This chapter introduces these methods.

Object.getPrototypeOf()

The Object.getPrototypeOf method returns the prototype of the parameter object. This is the standard way to obtain prototype objects.

var F = function () {};
var f = new F();
Object.getPrototypeOf(f) === F.prototype; // true

In the above code, the prototype of the instance object f is F.prototype.

The following are the prototypes of several special objects.

// The prototype of the empty object is Object.prototype
Object.getPrototypeOf({}) === Object.prototype; // true

// The prototype of Object.prototype is null
Object.getPrototypeOf(Object.prototype) === null; // true

// The prototype of the function is Function.prototype
function f() {}
Object.getPrototypeOf(f) === Function.prototype; // true

Object.setPrototypeOf()

The Object.setPrototypeOf method sets the prototype for the parameter object and returns the parameter object. It accepts two parameters, the first is an existing object, and the second is a prototype object.

var a = {};
var b = { x: 1 };
Object.setPrototypeOf(a, b);

Object.getPrototypeOf(a) === b; // true
ax; // 1

In the above code, the Object.setPrototypeOf method sets the prototype of the object a to the object b, so a can share the properties of b.

The new command can be simulated using the Object.setPrototypeOf method.

var F = function () {
  this.foo = "bar";
};

var f = new F();
// Equivalent to
var f = Object.setPrototypeOf({}, F.prototype);
F.call(f);

In the above code, the new command creates a new instance object, in fact, it can be divided into two steps. The first step is to set the prototype of an empty object to the prototype property of the constructor (the above example is F.prototype); the second step is to bind the this inside the constructor to this empty object, and then execute The constructor makes the methods and properties defined on this (the above example is this.foo) are transferred to this empty object.

Object.create()

The common way to generate instance objects is to use the new command to make the constructor return an instance. But in many cases, only one instance object can be obtained. It may not be generated by the constructor at all. Can one instance object be generated from another instance object?

JavaScript provides the Object.create() method to meet this need. This method accepts an object as a parameter, and then uses it as a prototype to return an instance object. This instance completely inherits the properties of the prototype object.

// Prototype object
var A = {
  print: function () {
    console.log("hello");
  },
};

// instance object
var B = Object.create(A);

Object.getPrototypeOf(B) === A; // true
B.print(); // hello
B.print === A.print; // true

In the above code, the Object.create() method uses the A object as the prototype to generate the B object. B inherits all the attributes and methods of A.

In fact, the Object.create() method can be replaced with the following code.

if (typeof Object.create !== "function") {
  Object.create = function (obj) {
    function F() {}
    F.prototype = obj;
    return new F();
  };
}

The above code shows that the essence of the Object.create() method is to create an empty constructor F, then let the F.prototype property point to the parameter object obj, and finally return an instance of F, thus Implement the instance to inherit the attributes of obj.

The new objects generated in the following three ways are equivalent.

var obj1 = Object.create({});
var obj2 = Object.create(Object.prototype);
var obj3 = new Object();

If you want to generate an object that does not inherit any properties (for example, there is no toString() and valueOf() methods), you can set the parameter of Object.create() to null.

var obj = Object.create(null);

obj.valueOf();
// TypeError: Object [object Object] has no method'valueOf'

In the above code, the prototype of the object obj is null, it does not have some properties defined on the Object.prototype object, such as the valueOf() method.

When using the Object.create() method, the object prototype must be provided, that is, the parameter cannot be empty or not an object, otherwise an error will be reported.

Object.create();
// TypeError: Object prototype may only be an Object or null
Object.create(123);
// TypeError: Object prototype may only be an Object or null

The new object generated by the Object.create() method dynamically inherits the prototype. Any method added or modified on the prototype will be immediately reflected on the new object.

var obj1 = { p: 1 };
var obj2 = Object.create(obj1);

obj1.p = 2;
obj2.p; // 2

In the above code, modifying the object prototype obj1 will affect the instance object obj2.

In addition to the prototype of the object, the Object.create() method can also accept a second parameter. This parameter is an attribute description object, and the object attribute it describes will be added to the instance object as its own attribute.

var obj = Object.create(
  {},
  {
    p1: {
      value: 123,
      enumerable: true,
      configurable: true,
      writable: true,
    },
    p2: {
      value: "abc",
      enumerable: true,
      configurable: true,
      writable: true,
    },
  }
);

// Equivalent to
var obj = Object.create({});
obj.p1 = 123;
obj.p2 = "abc";

The object generated by the Object.create() method inherits the constructor of its prototype object.

function A() {}
var a = new A();
var b = Object.create(a);

b.constructor === A; // true
b instanceof A; // true

In the above code, the prototype of the b object is the a object, so it inherits the constructor A of the a object.

Object.prototype.isPrototypeOf()

The isPrototypeOf method of the instance object is used to determine whether the object is the prototype of the parameter object.

var o1 = {};
var o2 = Object.create(o1);
var o3 = Object.create(o2);

o2.isPrototypeOf(o3); // true
o1.isPrototypeOf(o3); // true

In the above code, o1 and o2 are both prototypes of o3. This means that as long as the instance object is on the prototype chain of the parameter object, the isPrototypeOf method returns true.

Object.prototype.isPrototypeOf({}); // true
Object.prototype.isPrototypeOf([]); // true
Object.prototype.isPrototypeOf(/xyz/); // true
Object.prototype.isPrototypeOf(Object.create(null)); // false

In the above code, because Object.prototype is at the top of the prototype chain, it returns true for various instances, except for objects that directly inherit from null.

Object.prototype.__proto__

The __proto__ property of the instance object (two underscores before and after) returns the prototype of the object. This attribute can be read and written.

var obj = {};
var p = {};

obj.__proto__ = p;
Object.getPrototypeOf(obj) === p; // true

The above code uses the __proto__ property to set the p object as the prototype of the obj object.

According to the language standard, the __proto__ property only needs to be deployed by the browser, and other environments may not have this property. The two underscores before and after it indicate that it is essentially an internal attribute and should not be exposed to users. Therefore, this property should be used as little as possible, and instead use Object.getPrototypeOf() and Object.setPrototypeOf() to read and write prototype objects.

The prototype chain can be expressed intuitively with __proto__.

var A = {
  name: "Zhang San",
};
var B = {
  name: "Li Si",
};

var proto = {
  print: function () {
    console.log(this.name);
  },
};

A.__proto__ = proto;
B.__proto__ = proto;

A.print(); // Zhang San
B.print(); // Li Si

A.print === B.print; // true
A.print === proto.print; // true
B.print === proto.print; // true

In the above code, the prototypes of the A object and the B object are both proto objects, and they both share the print method of the proto object. In other words, the print methods of A and B are both calling the print method of the proto object.

Comparison of methods for obtaining prototype objects

As mentioned earlier, the __proto__ property points to the prototype object of the current object, that is, the prototype property of the constructor.

var obj = new Object();

obj.__proto__ === Object.prototype;
// true
obj.__proto__ === obj.constructor.prototype;
// true

The above code first creates a new object obj, and its __proto__ property points to the prototype property of the constructor (Object or obj.constructor).

Therefore, there are three ways to obtain the prototype object of the instance object obj.

-obj.__proto__ -obj.constructor.prototype -Object.getPrototypeOf(obj)

Of the above three methods, the first two are not very reliable. The __proto__ property only needs to be deployed in the browser, and it is not necessary to deploy it in other environments. And obj.constructor.prototype may become invalid when the prototype object is manually changed.

var P = function () {};
var p = new P();

var C = function () {};
C.prototype = p;
var c = new C();

c.constructor.prototype === p; // false

In the above code, the prototype object of the constructor C is changed to p, but the c.constructor.prototype of the instance object does not point to p. Therefore, when changing the prototype object, the constructor property is generally set at the same time.

C.prototype = p;
C.prototype.constructor = C;

var c = new C();
c.constructor.prototype === p; // true

Therefore, it is recommended to use the third method Object.getPrototypeOf to get the prototype object.

Object.getOwnPropertyNames()

The Object.getOwnPropertyNames method returns an array. The members are the key names of all the properties of the parameter object itself, excluding the inherited property keys.

Object.getOwnPropertyNames(Date);
// ["parse", "arguments", "UTC", "caller", "name", "prototype", "now", "length"]

In the above code, the Object.getOwnPropertyNames method returns all the property names of Date itself.

Among the properties of the object itself, some are enumerable and some are not. The Object.getOwnPropertyNames method returns all key names, regardless of whether they can be traversed. To get only those properties that can be traversed, use the Object.keys method.

Object.keys(Date); // []

The above code shows that all the properties of the Date object cannot be traversed.

Object.prototype.hasOwnProperty()

The hasOwnProperty method of an object instance returns a boolean value, which is used to determine whether a property is defined in the object itself or in the prototype chain.

Date.hasOwnProperty("length"); // true
Date.hasOwnProperty("toString"); // false

The above code shows that Date.length (how many parameters the constructor Date can accept) is a property of Date itself, and Date.toString is an inherited property.

In addition, the hasOwnProperty method is the only method in JavaScript that does not traverse the prototype chain when dealing with object properties.

in operator and for...in loop

The in operator returns a boolean value indicating whether an object has a certain property. It does not distinguish whether the attribute is an attribute of the object itself or an inherited attribute.

"length" in Date; // true
"toString" in Date; // true

The in operator is often used to check whether an attribute exists.

To get all the traversable properties of an object (whether it's own or inherited), you can use the for...in loop.

var o1 = { p1: 123 };

var o2 = Object.create(o1, {
  p2: { value: "abc", enumerable: true },
});

for (p in o2) {
  console.info(p);
}
// p2
// p1

In the above code, the p2 property of the object o2 is its own, and the p1 property is inherited. Both properties will be traversed by for...in loop.

In order to obtain the properties of the object itself in the for...in loop, you can use the hasOwnProperty method to judge.

for (var name in object) {
  if (object.hasOwnProperty(name)) {
    /* loop code */
  }
}

To obtain all the properties of an object (whether it is self or inherited, and whether it is enumerable or not), you can use the following function.

function inheritedPropertyNames(obj) {
  var props = {};
  while (obj) {
    Object.getOwnPropertyNames(obj).forEach(function (p) {
      props[p] = true;
    });
    obj = Object.getPrototypeOf(obj);
  }
  return Object.getOwnPropertyNames(props);
}

The above code sequentially obtains the "self" properties of each level of the prototype object of the obj object, thereby obtaining the "all" properties of the obj object, regardless of whether it can be traversed.

The following is an example that lists all the attributes of the Date object.

inheritedPropertyNames(Date);
// [
// "caller",
// "constructor",
// "toString",
// "UTC",
// ...
//]

Copy of object

If you want to copy an object, you need to do the following two things.

-Ensure that the copied object has the same prototype as the original object. -Ensure that the copied object has the same instance attributes as the original object.

The following is the object copy function implemented based on the above two points.

function copyObject(orig) {
  var copy = Object.create(Object.getPrototypeOf(orig));
  copyOwnPropertiesFrom(copy, orig);
  return copy;
}

function copyOwnPropertiesFrom(target, source) {
  Object.getOwnPropertyNames(source).forEach(function (propKey) {
    var desc = Object.getOwnPropertyDescriptor(source, propKey);
    Object.defineProperty(target, propKey, desc);
  });
  return target;
}

Another simpler way is to use ES2017 to introduce the standard Object.getOwnPropertyDescriptors method.

function copyObject(orig) {
  return Object.create(
    Object.getPrototypeOf(orig),
    Object.getOwnPropertyDescriptors(orig)
  );
}