Everything is an object

Ecma-262 defines an object as an unordered collection of attributes

When we define an object, we often use object literals to define it. In this way, the definition is simply given the key value relationship, and the operation and control of the attribute cannot be carried out at a deeper level, such as controlling whether the attribute can be deleted, whether the attribute can be used in the for… By default, all of these are ok, but what if you don’t want a property to have these permissions? JavaScript provides refined methods for manipulating Object properties, namely Object.defineProperty() and Object.defineProperties()

// Object literals
let obj = {
  name: 'jack'
}
Copy the code

First, the object from shallow to deep

1. Classification of attributes

Object properties are divided into two types: data properties and accessor properties

Properties of data are also specific, defined by property descriptors

1.1 Data Attributes

Data attribute provides four different data attribute descriptors, which are different, Enumerable, Writable and Value

let obj = {
  name: 'jack'.age: 18
}

Object.defineProperty(obj, 'address', {
  value: 'Bei Jing'.configurable: false.enumerable: true.writable: true
})

The default value is true, and the default value is false */ if the property is specified by any additional property, the 64x works without any additional information
// Because the signals are different: false, they are not configurable and invalid
Object.defineProperty(obj, 'adress', {
  value: 'tt'.configurable: true
})
// Cannot delete the address attribute
delete obj.address
console.log(obj.address) //BeiJjing

/** * 2. Enumerable * specifies whether a property can be returned in for-in or object.keys ()
console.log(obj) //{ name: 'jack', age: 18, address: 'Bei Jing' }
console.log(Object.keys(obj)) //[ 'name', 'age', 'address' ]

/** * 3. Writable * indicates whether the value of a property can be changed
obj.address = 'Tian Jin'
console.log(obj.address) //Tian Jin
/** * 4.value * The value of the property, which is returned when the property is read and modified when the property is modified * Default: undefined */
Object.defineProperty(obj, 'friend', {})
console.log(obj.friend) //undefined
Copy the code

Object literals are defined in such a way that they actually define data properties. The value is the value stored in the value descriptor. The other three descriptors are all true

1.2 Accessor Properties

The accessor property supports four different property descriptors: Cis, Enumerable, GET, and set

/** * Store attribute descriptors: * ① hide a private attribute that does not want to be directly used and assigned * ② If we want to intercept the process of accessing and setting a property, we can also use store attribute descriptors to achieve */
var obj = {
  name: 'li'.age: 18.// Private attributes (community defaults to private attributes beginning with _). There are no private attributes in Typescript
  _address: 'Kunming'
}

Object.defineProperty(obj, 'address', {
  configurable: true.enumerable: true.get() { // when the property is obtained
    foo()
    return this._address
  },
  set(v) { // when the property is set
    bar()
    this._address = v
  }
})
console.log(obj.address)
obj.address = 'Qujing'
console.log(obj.address)

function foo() {
  console.log('Got a value once')}function bar() {
  console.log('Changed the value once')}Copy the code

In addition, each object actually has an implicit stereotype object[[prototype]]There is also a display prototype object if it is a functionprototypeThere will be a section on these two properties later, but the prototype chain is a core part of JavaScript

2. Define multiple attributes simultaneously

The use of the Object. DefineProperties

Object.defineProperties(obj, {
  name: {
    configurable: true.enumerable: true.writable: true.value: 'li',},age: {
    configurable: false.enumerable: false.get() {
      return this._age
    },
    set(v) {
      this._age = v
    }
  }
})
Copy the code

In practice, I feel that attributes are rarely defined in this way, and may be used when writing some tool libraries

3. Restrictions on attributes

// Test object
let obj = {
  name: 'jack'.age: 18
}
Copy the code
3.1 Disabling Extension

👉 object.preventExtensions () is used to prevent objects from extending new properties

👉 object.isextensible () is used to determine whether an Object isExtensible

Object.preventExtensions(obj)
obj.address = 'Beijing'
console.log(obj.address) //undefined
console.log(Object.isExtensible(obj)) //false
Copy the code
3.2 the seal

👉 object.seal () disables the configuration of the property (without deleting the property), calls Object.preventExtensions() and sets the existing property to false

👉 object.issealed () determines whether an Object isSealed

Object.seal(obj)
delete obj.name
console.log(obj.name) //jack
console.log(Object.isSealed(obj)) //true
Copy the code
3.3 freeze

👉 object.freeze () is used to disallow property modification, which actually calls Object.seal() and sets the writable of the existing property to false

👉 object.isfrozen () is used to check whether an Object isFrozen

Object.freeze(obj)
obj.name = 'jj'
console.log(obj.name) //jack
console.log(Object.isFrozen(obj)) //true
Copy the code

4. Obtain the property descriptor of the object

  • Object.getOwnPropertyDescriptor()

    The accessor property contains different, Enumerable, GET, and set properties, and the data property contains different, Enumerable, Writable, and value properties.

  • Object.getOwnPropertyDescriptors()

    Will actually call Object on each has its own properties. The getOwnPropertyDescriptor () and return them in a new Object

console.log(Object.getOwnPropertyDescriptor(obj, 'age')) // Get the attribute descriptor for the age attribute
console.log(Object.getOwnPropertyDescriptors(obj)) // Get all attribute descriptors
Copy the code

5. Obtain the properties of the object

// All statements 5, 6, and 7 use this object to test
const obj = {
  name: 'jack'.age: 19.foo(){},Symbol('symAttr')]: 'Symbol Attribute'
}
Object.defineProperty(obj, 'info', {
  value: 'hello'.enumerable: false
})
Object.defineProperty(obj, Symbol('s2'), {
  value: 'Symbol 2'.enumerable: false
})

// Attributes on the prototype chain
obj.__proto__.protoAttr1 = 'proto Attr1'
obj.__proto__[Symbol('protoSym')] = 'proto Attribute'
Object.defineProperty(obj.__proto__, 'protoAttr2', {
  value: 'proto Attr2'.enumerable: false
})
Copy the code
5.1 Object.keys()

Returns an array containing the names of all the given object’s own enumerable properties. Properties on the stereotype chain are not retrieved

console.log(Object.keys(obj))  //[ 'name', 'age', 'foo' ]
Copy the code
5.2 Object.getOwnPropertyNames()

Slightly different from object.keys (), non-enumerable attributes are also available

console.log(Object.getOwnPropertyNames(obj)) //[ 'name', 'age', 'foo', 'info' ]
Copy the code
5.3 Object.getOwnPropertySymbols()

Returns an array containing all symbolic properties of the specified object itself, including non-enumerable properties.

console.log(Object.getOwnPropertySymbols(obj))  //[ Symbol(s1), Symbol(s2) ]
Copy the code
5.4 for... in

Use for… When in traverses an object, it uses a prototype chain lookup, meaning that any enumerable property that can be accessed through the prototype chain is traversed

const arr = []
for (const item in obj) {
  arr.push(item)
}
console.log(arr) //[ 'name', 'age', 'foo', 'protoAttr1' ]
Copy the code
5.5 in

When you use the IN operator to check whether an attribute exists in an object, you also look for the entire stereotype chain, whether enumerable or not

console.log('protoAttr2' in obj) //true
Copy the code

6. Obtain the object value

6.1 Object.values()

Returns an array of the given object’s own enumerable property values, excluding symbolic properties

console.log(Object.values(obj)) // [ 'jack', 19, [Function: foo] ]
Copy the code
6.2 for... of

Iterators and generators will be reviewed separately later, using String as an example of the built-in wrapped object type

const str = 'abcde'
const res = []
for (const item of str) {
  res.push(item)
}
console.log(res) //[ 'a', 'b', 'c', 'd', 'e' ]
Copy the code

In fact, the first step is to create a string of primitive value type, when for… The of operation implicitly wraps STR as an object, takes the iterator STR [symbol.iterator](), and calls its next() method to iterate over it.

const iterator = str[Symbol.iterator]()
console.log(iterator.next()) //{ value: 'a', done: false }
console.log(iterator.next()) //{ value: 'b', done: false }
console.log(iterator.next()) //{ value: 'c', done: false }
console.log(iterator.next()) //{ value: 'd', done: false }
console.log(iterator.next()) //{ value: 'e', done: false }
console.log(iterator.next()) //{ value: undefined, done: true }
Copy the code

7. Get the objectentries

Returns a [key, value] array of the given object’s own enumerable properties

console.log(Object.entries(obj))
// [ [ 'name', 'jack' ], [ 'age', 19 ], [ 'foo', [Function: foo] ] ]
Copy the code

Add object.fromentries () also allows you to obtain a new Object through entries, which is the new syntax of ECMAScript2019

let arr = [
  ['name'.'jack'],
  ['age'.19],
  [
    'foo'.function () {
      console.log('foo: '.this.name)
    }
  ]
]
let newObj = Object.fromEntries(arr)
newObj.foo() //foo: jack
console.log(newObj) //{ name: 'jack', age: 19, foo: [Function (anonymous)] }
Copy the code

Application scenario: Convert query in URL to object

const queryStr = 'name=jack&age=18'
const queryParams = new URLSearchParams(queryStr)
console.log(queryParams) //URLSearchParams { 'name' => 'jack', 'age' => '18' }
const paramObj = Object.fromEntries(queryParams)
console.log(paramObj) //{ name: 'jack', age: '18' }
Copy the code

Merge object

ES6 provides object. assign(dest,… Source) method, which takes a target object and one or more source objects as arguments, Then each source objects can be enumerated (Object) propertyIsEnumerable () returns true) and has its own (Object. The hasOwnProperty () returns true) attributes are copied to the target Object, the function returns the target Object. Properties with strings and symbols as keys are copied. For each qualifying attribute, the method uses [[Get]] on the source object to Get the value of the attribute, and then uses [[Set]] on the target object to Set the value of the attribute.

1. Simple replication

let source = {
  name: 'lucy'
}
let dest = {}
let obj2 = Object.assign(dest, source)
console.log(dest) //{name: 'lucy'}
console.log(obj2 === dest) //true
Copy the code

2. Get function and set function

The value obtained from the source object accessor property, such as the fetch function, is assigned to the target object as a static value. In other words, you cannot transfer the get and set functions between the two objects; you simply pass the values obtained by the getter function in the source object to the parameters of the setter function in the target object.

let dest = {
  set name(v) {
    console.log('Execute setter in DEST', v)
  }
}
let s1 = {
  get name() {
    return 'jack'}}console.log(s1.name)
console.log(Object.assign(dest, s1))
//jack
// Execute setter Jack in dest
// { name: [Setter] }

console.log(Object.getOwnPropertyDescriptor(dest, 'name'))
/ / {
// get: undefined,
// set: [Function: set name],
// enumerable: true,
// configurable: true
// }
Copy the code

As you can see, the getter function in s1 is not copied into dest, and the get for the name attribute in dest is still undefined

3. Multi-source replication

If they have the same property, the property copied later overrides the property copied earlier

let dest = {
  set name(v) {
    console.log(v)
  }
}
let s1 = {
  name: 's1 name'.age: 10
}
let s2 = {
  name: 's2 name'
}
let s3 = {
  name: 's3 name'
}
Object.assign(dest, s1, s2, s3)
// s1 name
// s2 name
// s3 name
// The name attribute in dest is still undefined
Copy the code

4. Non-rollback replication

If an error occurs during assignment, the operation is aborted and exits, throwing an error. Object.assign() does not roll back assignments that have already been done, so it is a method that does its best and may only do a partial copy.

let dest = {
  name: 'lucy'
}
let s1 = {
  name: 'jack'.get age() {
    throw new Error('this is a error')},sex: 'male'
}
try {
  Object.assign(dest, s1)
} catch (e) {}
console.log(dest) //{ name: 'jack' }
Copy the code

The name attribute is still copied into Dest and overwritten

5. Object expansion operator

The expansion operator is also a shallow copy. When you build an object, the last one overwrites the first one for the same properties, and the array is converted to an index-worth object

const obj = {
  name: 'jack'.age: 19
}
const arr = ['a'.'b'.'c']
const newObj = {
  name: 'lucy'. obj, ... arr,1: 1
}
console.log(newObj) 
//{ '0': 'a', '1': 1, '2': 'c', name: 'jack', age: 19 }
Copy the code

For the object copy, the following topic on the object shallow copy, deep copy how to achieve

3. Determination of object equality

=== and object.is ()

  • For 0, -0, +0, === all true, object.is () evaluates -0 and +0 to false

    console.log(0= = = +0) // true
    console.log(0= = = -0) // true
    console.log(-0= = = +0) // true
    console.log(Object.is(-0, +0)) // false
    Copy the code
  • For NaN, === all false, object.is () is judged to be true

console.log(NaN= = =NaN) // false
console.log(Object.is(NaN.NaN)) // true
Copy the code

Add: To check for recursion of more than two values using equality pass

function checkEqual(x, ... rest) { return Object.is(x, rest[0]) && (rest.length < 2 || checkEqual(... rest)) }Copy the code

4. Object enhanced syntax

In fact, some syntactic sugar, simplify the object literal writing method, mainly including ① attribute value shorthand, ② computable attribute, ③ method shorthand

/** * Enhanced object literals */
let name = 'why'
let age = 18
let foo2 = 'foo2'

//old style
let obj = {
  name: name,
  age: age
}

let obj1 = {
  Properties are interpreted as property keys with the same name
  name,
  age,
  
  foo: function () {},// Method shorthand method shorthand
  foo1(){},// Computed Property names
  [name + '__'] :'jack'[Symbol('sym')]:'symbol value'.// The method abbreviation is used in conjunction with the calculation property
  [foo2](){}
}
Copy the code

For calculable properties, need to pay attention to, the expression in brackets in the execution may involve closure of side effects, if any errors are thrown termination of object creation, but won’t be rolled back, the side effects of before throwing an error could also modify other data to cause unnecessary trouble, so be careful when using!

🌰 for example

let num = 10
let addNum = function () {
  try {
    num = num * 10
    throw new Error('this is a error')
    return 'info'
  } catch (e) {}
}

let obj = {
  [addNum()]: 'hello world'
}
console.log(num) / / 100
console.log(obj) //{ undefined: 'hello world' }
Copy the code

Num = 100; obj = obj; obj = obj; ‼ ️

5. Object deconstruction

1. Basic use

You can alias and set default values

const obj = {
  name: 'jack'.age: 19
}
const { name: nickName, age, sex = 'female', other } = obj
console.log(nickName) //jack
console.log(age) / / 19
console.log(sex) //sex
console.log(other) //undefined
Copy the code

2. Nested deconstruction

Property is also an object and can continue to be nested. An error is reported if the outer property is not defined and cannot be nested

const obj = {
  info: {
    name: 'hello'}}const {
  info: { name: nickName },
  //foo: { bar } TypeError: Cannot read properties of undefined (reading 'bar')
} = obj
console.log(nickName) //hello
Copy the code

3. Partial deconstruction

const obj = {
  name: 'jack'.age: 19
}
const { name } = obj
Copy the code

4. The parameter context matches

Destructuring assignments can also be done in function argument lists. Destructuring assignments to arguments does not affect the arguments object, but local variables can be declared in the function signature to be used inside the function body

const obj = {
  name: 'jack'.age: 19
}
function foo(param1, { name, age, sex }, param2) {
  console.log(arguments)
  console.log(name, age, sex)
}
foo('first', obj, 'third')
//[Arguments] {
// '0': 'first',
// '1': { name: 'jack', age: 19 },
// '2': 'third'
// }
// jack 19 undefined
Copy the code

Creation is not easy, ask for attention or like 😄😄🤗