New methods of Object object Object in core JS: assign, is, setPrototypeOf- Explanation of use

Object.is()

ES5 compares whether two values are equal. There are only two operators: the equality operator (= =) and the strict equality operator (= =). Both of them have disadvantages. The former will automatically convert the data type, while the NaN of the latter is not equal to itself, and + 0 is equal to - 0. JavaScript lacks an operation. In all environments, as long as the two values are the same, they should be equal.

ES6 proposes the "same value equality" algorithm to solve this problem. Object.is is a new way to deploy this algorithm. It is used to compare whether two values are strictly equal, which is basically consistent with the behavior of the strict comparison operator (= = =).

Object.is('foo', 'foo')
// true
Object.is({}, {})
// false

There are only two differences: one is that + 0 is not equal to - 0, and the other is that NaN is equal to itself.

+0 === -0 //true
NaN === NaN // false

Object.is(+0, -0) // false
Object.is(NaN, NaN) // true

ES5 can deploy object through the following code is.

Object.defineProperty(Object, 'is', {
  value: function(x, y) {
    if (x === y) {
      // For cases where + 0 is not equal to - 0
      return x !== 0 || 1 / x === 1 / y;
    }
    // For NaN
    return x !== x && y !== y;
  },
  configurable: true,
  enumerable: false,
  writable: true
});

Object.assign()

Basic Usage

Object. The assign method is used to merge objects and copy all enumerable attributes of the source object to the target object.

const target = { a: 1 };

const source1 = { b: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

Object. The first parameter of the assign method is the target object, and the following parameters are the source object.

Note that if the target object has an attribute with the same name as the source object, or multiple source objects have an attribute with the same name, the subsequent attribute will overwrite the previous attribute.

const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

If there is only one parameter, object Assign will return this parameter directly.

const obj = {a: 1};
Object.assign(obj) === obj // true

If the parameter is not an object, it will be converted to an object and then returned.

typeof Object.assign(2) // "object"

Since undefined and null cannot be converted into objects, an error will be reported if they are used as parameters.

Object.assign(undefined) // report errors
Object.assign(null) // report errors

If the non object parameter appears in the location of the source object (i.e. non first parameter), the processing rules are different. First, these parameters will be converted to objects. If they cannot be converted to objects, they will be skipped. This means that if undefined and null are not in the first parameter, no error will be reported.

let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true

Other types of values (i.e. numeric, string and Boolean values) are not in the first parameter and will not report an error. However, except that the string will be copied into the target object in the form of array, other values will not have an effect.

const v1 = 'abc';
const v2 = true;
const v3 = 10;

const obj = Object.assign({}, v1, v2, v3);
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

In the above code, v1, v2 and v3 are string, Boolean value and numerical value respectively. As a result, only the string is integrated into the target object (in the form of character array), and the numerical value and Boolean value will be ignored. This is because only string wrapped objects produce enumerable properties.

Object(true) // {[[PrimitiveValue]]: true}
Object(10)  //  {[[PrimitiveValue]]: 10}
Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}

In the above code, Boolean value, numeric value and string are respectively converted into the corresponding wrapper object. You can see that their original values are on the internal attribute [[PrimitiveValue]] of the wrapper object. This attribute will not be used by object Copy of assign. Only the wrapper object of string will produce enumerable real attributes, and those attributes will be copied.

Object. The attributes copied by assign are limited. Only the self attributes of the source object (not the inherited attributes) and the enumerable attributes (enumerable: false) are copied.

Object.assign({b: 'c'},
  Object.defineProperty({}, 'invisible', {
    enumerable: false,
    value: 'hello'
  })
)
// { b: 'c' }

In the above code, object The object to be copied by assign has only one non enumerable attribute invisible, which is not copied in.

The attribute named Symbol value will also be used by object Assign copy.

Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }

Attention

(1) Shallow copy

Object. The assign method implements a shallow copy rather than a deep copy. In other words, if the value of an attribute of the source object is an object, the target object copies the reference of the object.

const obj1 = {a: {b: 1}};
const obj2 = Object.assign({}, obj1);

obj1.a.b = 2;
obj2.a.b // 2

In the above code, the value of the a attribute of the source object obj1 is an object, object The copy of assign gets the reference of this object. Any change of this object will be reflected on the target object.

(2) Replacement of attributes with the same name

For this kind of nested object, once an attribute with the same name is encountered, object Assign is handled by replacing, not adding.

const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }

In the above code, the a attribute of the target object is completely replaced by the a attribute of the source object without the result of {A: {B: 'hello', D: 'e'}}. This is usually not what developers want and requires special care.

Some function libraries provide object The customized version of assign (such as Lodash's. defaultsDeep method) can be merged with a deep copy.

(3) Array processing

Object.assign can be used to handle arrays, but will treat arrays as objects.

Object.assign([1, 2, 3], [4, 5])
// [4, 5, 3]

In the above code, object Assign treats the array as an object with property names 0, 1 and 2, so property 4 of the source array overrides property 1 of the target array.

(4) Processing of value taking function

Object.assign can only copy values. If the value to be copied is a value function, it will be evaluated and then copied.

const source = {
  get foo() { return 1 }
};
const target = {};

Object.assign(target, source)
// { foo: 1 }

In the above code, the foo attribute of the source object is a value function, object Assign will not copy the value function. It will only copy the value after getting the value.

Common use

Object. The assign method has many uses.

(1) Adding properties to an object

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

The above method passes object Assign method to add the x attribute and y attribute to the object instance of the Point class.

(2) Adding methods to objects

Object.assign(SomeClass.prototype, {
  someMethod(arg1, arg2) {
    ···
  },
  anotherMethod() {
    ···
  }
});

// Equivalent to the following
SomeClass.prototype.someMethod = function (arg1, arg2) {
  ···
};
SomeClass.prototype.anotherMethod = function () {
  ···
};

The above code uses the concise representation of object attributes, directly put the two functions in braces, and then use the assign method to add them to someclass Prototype.

(3) Clone object

function clone(origin) {
  return Object.assign({}, origin);
}

The above code copies the original object to an empty object to obtain the clone of the original object.

However, cloning in this way can only clone the value of the original object itself, not the inherited value. If you want to maintain the inheritance chain, you can use the following code.

function clone(origin) {
  let originProto = Object.getPrototypeOf(origin);
  return Object.assign(Object.create(originProto), origin);
}

(4) Merge multiple objects

Merge multiple objects into an object.

const merge =
  (target, ...sources) => Object.assign(target, ...sources);

If you want to return a new object after merging, you can rewrite the above function to merge an empty object.

const merge =
  (...sources) => Object.assign({}, ...sources);

(5) Specify default values for attributes

const DEFAULTS = {
  logLevel: 0,
  outputFormat: 'html'
};

function processContent(options) {
  options = Object.assign({}, DEFAULTS, options);
  console.log(options);
  // ...
}

In the above code, the DEFAULTS object is the default value and the options object is a user supplied parameter. Object. The assign method combines DEFAULTS and options into a new object. If they have the same name attribute, the attribute value of options will overwrite the attribute value of DEFAULTS.

Note that due to the shallow copy problem, the values of all attributes of the DEFAULTS object and options object should be simple types and should not point to another object. Otherwise, this property of the DEFAULTS object is likely to have no effect.

const DEFAULTS = {
  url: {
    host: 'example.com',
    port: 7070
  },
};

processContent({ url: {port: 8000} })
// {
//   url: {port: 8000}
// }

The original intention of the above code is to add URL Port changed to 8000, URL Host remains unchanged. The actual result is options URL overrides defaults URL, so URL Host does not exist.

Object.getOwnPropertyDescriptors()

Object of ES5 The getownpropertydescriptor () method will return the descriptor of an object property. ES2017 introduces object Getownpropertydescriptors () method returns the description object of all its own properties (non inherited properties) of the specified object.

const obj = {
  foo: 123,
  get bar() { return 'abc' }
};

Object.getOwnPropertyDescriptors(obj)
// { foo:
//    { value: 123,
//      writable: true,
//      enumerable: true,
//      configurable: true },
//   bar:
//    { get: [Function: get bar],
//      set: undefined,
//      enumerable: true,
//      configurable: true } }

In the above code, object The getownpropertydescriptors () method returns an object. The property names of all original objects are the property names of the object, and the corresponding property values are the description objects of the property.

The implementation of this method is very easy.

function getOwnPropertyDescriptors(obj) {
  const result = {};
  for (let key of Reflect.ownKeys(obj)) {
    result[key] = Object.getOwnPropertyDescriptor(obj, key);
  }
  return result;
}

The purpose of this method is to solve the problem of object The problem that assign() cannot copy the get attribute and set attribute correctly.

const source = {
  set foo(value) {
    console.log(value);
  }
};

const target1 = {};
Object.assign(target1, source);

Object.getOwnPropertyDescriptor(target1, 'foo')
// { value: undefined,
//   writable: true,
//   enumerable: true,
//   configurable: true }

In the above code, the value of the foo attribute of the source object is an assignment function, object The assign method copies this attribute to the target1 object, and the value of this attribute becomes undefined. This is because object The assign method always copies the value of an attribute, not the assignment method or value taking method behind it.

At this time, object The getownpropertydescriptors () method works with object With the defineproperties () method, you can achieve the correct copy.

const source = {
  set foo(value) {
    console.log(value);
  }
};

const target2 = {};
Object.defineProperties(target2, Object.getOwnPropertyDescriptors(source));
Object.getOwnPropertyDescriptor(target2, 'foo')
// { get: undefined,
//   set: [Function: set foo],
//   enumerable: true,
//   configurable: true }

In the above code, the logic of merging two objects can be written as a function.

const shallowMerge = (target, source) => Object.defineProperties(
  target,
  Object.getOwnPropertyDescriptors(source)
);

Object. Another use of the getownpropertydescriptors () method is to cooperate with object Create () method to clone the object attributes to a new object. This is a shallow copy.

const clone = Object.create(Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj));

// perhaps

const shallowClone = (obj) => Object.create(
  Object.getPrototypeOf(obj),
  Object.getOwnPropertyDescriptors(obj)
);

The above code will clone the object obj.

In addition, object The getownpropertydescriptors () method enables one object to inherit from another. In the past, inheriting another object was often written as follows.

const obj = {
  __proto__: prot,
  foo: 123,
};

ES6 regulations__ proto__ Only browsers need to be deployed, and other environments do not need to be deployed. If removed__ proto__, The above code should be changed to the following.

const obj = Object.create(prot);
obj.foo = 123;

// perhaps

const obj = Object.assign(
  Object.create(prot),
  {
    foo: 123,
  }
);

With object Getownpropertydescriptors (), we have another way to write it.

const obj = Object.create(
  prot,
  Object.getOwnPropertyDescriptors({
    foo: 123,
  })
);

Object.getOwnPropertyDescriptors() can also be used to implement the Mixin pattern.

let mix = (object) => ({
  with: (...mixins) => mixins.reduce(
    (c, mixin) => Object.create(
      c, Object.getOwnPropertyDescriptors(mixin)
    ), object)
});

// multiple mixins example
let a = {a: 'a'};
let b = {b: 'b'};
let c = {c: 'c'};
let d = mix(c).with(a, b);

d.c // "c"
d.b // "b"
d.a // "a"

The above code returns a new object d, which represents the operation that objects a and b are mixed into object c.

For completeness, object After getownpropertydescriptors () enters the standard, a new reflection. Net will be added in the future Getownpropertydescriptors() method.

__ proto__ Property, object setPrototypeOf(),Object.getPrototypeOf()

The object inheritance of JavaScript language is realized through prototype chain. ES6 provides more operation methods of prototype objects.

__ proto__ attribute

__ proto__ Property (two underscores before and after each), which is used to read or set the prototype object of the current object. Currently, all browsers (including IE11) have deployed this property.

// Writing method of es5
const obj = {
  method: function() { ... }
};
obj.__proto__ = someOtherObj;

// Writing method of es6
var obj = Object.create(someOtherObj);
obj.method = function() { ... };

This attribute is not written in the body of ES6, but in the appendix because__ proto__ The double underline before and after indicates that it is essentially an internal attribute rather than a formal external API. It was added to ES6 only because of the wide support of the browser. The standard clearly stipulates that only browsers must deploy this attribute, and other operating environments do not necessarily need to deploy it, and it is best to think that this attribute does not exist in new code. Therefore, do not use this attribute from the perspective of semantics or compatibility, but use the following object Setprototypeof() (write operation), object Getprototypeof() (read operation), object Create() instead.

In terms of implementation__ proto__ Called object prototype.__ proto__, The specific implementation is as follows.

Object.defineProperty(Object.prototype, '__proto__', {
  get() {
    let _thisObj = Object(this);
    return Object.getPrototypeOf(_thisObj);
  },
  set(proto) {
    if (this === undefined || this === null) {
      throw new TypeError();
    }
    if (!isObject(this)) {
      return undefined;
    }
    if (!isObject(proto)) {
      return undefined;
    }
    let status = Reflect.setPrototypeOf(this, proto);
    if (!status) {
      throw new TypeError();
    }
  },
});

function isObject(value) {
  return Object(value) === value;
}

If an object itself is deployed__ proto__ Property whose value is the prototype of the object.

Object.getPrototypeOf({ __proto__: null })
// null

Object.setPrototypeOf()

Object. Function and of setprototypeof method__ proto__ Similarly, the prototype object used to set an object returns the parameter object itself. It is the method of setting prototype objects officially recommended by ES6.

// format
Object.setPrototypeOf(object, prototype)

// usage
const o = Object.setPrototypeOf({}, null);

This method is equivalent to the following function.

function setPrototypeOf(obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

Here is an example.

let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);

proto.y = 20;
proto.z = 40;

obj.x // 10
obj.y // 20
obj.z // 40

The above code sets the proto object as the prototype of the obj object, so the properties of the proto object can be read from the obj object.

If the first parameter is not an object, it is automatically converted to an object. However, since the first parameter is returned, this operation will not have any effect.

Object.setPrototypeOf(1, {}) === 1 // true
Object.setPrototypeOf('foo', {}) === 'foo' // true
Object.setPrototypeOf(true, {}) === true // true

Since undefined and null cannot be converted to objects, an error will be reported if the first parameter is undefined or null.

Object.setPrototypeOf(undefined, {})
// TypeError: Object.setPrototypeOf called on null or undefined

Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefined

Object.getPrototypeOf()

This method is similar to object The setprototypeof method is used to read the prototype object of an object.

Object.getPrototypeOf(obj);

Here is an example.

function Rectangle() {
  // ...
}

const rec = new Rectangle();

Object.getPrototypeOf(rec) === Rectangle.prototype
// true

Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false

If the parameter is not an object, it is automatically converted to an object.

// Equivalent to object getPrototypeOf(Number(1))
Object.getPrototypeOf(1)
// Number {[[PrimitiveValue]]: 0}

// Equivalent to object getPrototypeOf(String('foo'))
Object.getPrototypeOf('foo')
// String {length: 0, [[PrimitiveValue]]: ""}

// Equivalent to object getPrototypeOf(Boolean(true))
Object.getPrototypeOf(true)
// Boolean {[[PrimitiveValue]]: false}

Object.getPrototypeOf(1) === Number.prototype // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true

If the parameters are undefined or null, they cannot be converted to objects, so an error will be reported.

Object.getPrototypeOf(null)
// TypeError: Cannot convert undefined or null to object

Object.getPrototypeOf(undefined)
// TypeError: Cannot convert undefined or null to object

Object.keys(),Object.values(),Object.entries()

Object.keys()

ES5 introduces object The keys method returns an array whose members are the key names of all the enumerable properties of the parameter object itself (excluding inheritance).

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]

ES2017 introduce With object Keys supporting object Values and object Entries, as a supplementary means of traversing an object, for Of recycling.

let {keys, values, entries} = Object;
let obj = { a: 1, b: 2, c: 3 };

for (let key of keys(obj)) {
  console.log(key); // 'a', 'b', 'c'
}

for (let value of values(obj)) {
  console.log(value); // 1, 2, 3
}

for (let [key, value] of entries(obj)) {
  console.log([key, value]); // ['a', 1], ['b', 2], ['c', 3]
}

Object.values()

Object. The values method returns an array whose members are the key values of all the enumerable properties of the parameter object itself (excluding inheritance).

const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]

The member order of the returned array is consistent with the arrangement rules described in "traversal of attributes" in this chapter.

const obj = { 100: 'a', 2: 'b', 7: 'c' };
Object.values(obj)
// ["b", "c", "a"]

In the above code, the attribute named value is traversed from small to large according to the value, so the return order is b, c and a.

Object.values returns only the traversable properties of the object itself.

const obj = Object.create({}, {p: {value: 42}});
Object.values(obj) // []

In the above code, object If the object attribute (attribute p) added by the second parameter of the Create method is not explicitly declared, it is not traversable by default, because the attribute of p describes the enumerable of the object. By default, it is false and object Values does not return this property. Just change enumerable to true, object Values returns the value of property p.

const obj = Object.create({}, {p:
  {
    value: 42,
    enumerable: true
  }
});
Object.values(obj) // [42]

Object.values filters the attribute named Symbol value.

Object.values({ [Symbol()]: 123, foo: 'abc' });
// ['abc']

If object The parameter of the values method is a string, which will return an array composed of various characters.

Object.values('foo')
// ['f', 'o', 'o']

In the above code, the string will first be converted into an array like object. Each character of the string is an attribute of the object. Therefore, object Values returns the key value of each attribute, which is an array of characters.

If the parameter is not an object, object The object will be converted to values first. Because of the wrapper objects of numeric and Boolean values, non inherited properties are not added to the instance. So, object Values returns an empty array.

Object.values(42) // []
Object.values(true) // []

Object.entries()

Object. The entries () method returns an array whose members are an array of key value pairs of all the enumerable properties of the parameter object itself (excluding inheritance).

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]

Except that the return value is different, the behavior of this method is the same as that of object Values are basically the same.

If the property name of the original object is a Symbol value, the property will be ignored.

Object.entries({ [Symbol()]: 123, foo: 'abc' });
// [ [ 'foo', 'abc' ] ]

In the above code, the original object has two properties, object Entries only outputs attributes with attribute names other than Symbol values. There may be reflection in the future The ownentries() method returns all the properties of the object itself.

Object. The basic purpose of entries is to traverse the properties of objects.

let obj = { one: 1, two: 2 };
for (let [k, v] of Object.entries(obj)) {
  console.log(
    `${JSON.stringify(k)}: ${JSON.stringify(v)}`
  );
}
// "one": 1
// "two": 2

Object. Another use of the entries method is to turn the object into a real Map structure.

const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }

Implement object by yourself The entries method is very simple.

// Version of the Generator function
function* entries(obj) {
  for (let key of Object.keys(obj)) {
    yield [key, obj[key]];
  }
}

// Version of non Generator function
function entries(obj) {
  let arr = [];
  for (let key of Object.keys(obj)) {
    arr.push([key, obj[key]]);
  }
  return arr;
}

Object.fromEntries()

Object. The fromentries () method is object The inverse operation of entries() is used to convert a key value pair array into an object.

Object.fromEntries([
  ['foo', 'bar'],
  ['baz', 42]
])
// { foo: "bar", baz: 42 }

The main purpose of this method is to restore the data structure of key value pairs to objects, so it is particularly suitable to convert the Map structure to objects.

// Example 1
const entries = new Map([
  ['foo', 'bar'],
  ['baz', 42]
]);

Object.fromEntries(entries)
// { foo: "bar", baz: 42 }

// Example 2
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }

One use of this method is to convert the query string into an object in conjunction with the URLSearchParams object.

Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }

 

Keywords: Javascript object

Added by spiyun on Thu, 10 Feb 2022 14:30:04 +0200