Strong and weak references

Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

When we define a variable equal to an object, we can think of the variable as having a reference to the object

cosnt obj = { name: 'Klaus' }
Copy the code

At this point we can assume that obj has a reference to a literal ({name: ‘Klaus’})

By default, this reference is a strong reference.

That is, when GC checks, it recognizes a strong reference and considers that the literal is not a garbage object

However, the reference of the value created by using WeakSet/WeakMap is actually a weak reference

That is, although we can still use this reference to store, retrieve, and access the corresponding literal

But the GC does not ‘recognize’ weak references when doing garbage collection.

If an object literal does not have any strong references, even if the object literal has weak references, the GC will still treat the object literal as garbage

set

Before ES6, we stored data in two main structures: arrays and objects

Two additional data structures have been added to ES6 :Set, Map, and their other forms, WeakSet, WeakMap

Set, map, WeakSet, WeakMap these new data types are in essence a special object type data

A Set is a new data structure that can be used to hold data, similar to an array

But the biggest difference between a Set and an array is that the elements in an array are repeatable, whereas the elements in a Set are not

So the biggest function of set is that it can deduplicate array elements

// Create a set using the set constructor (there is no literal creation of a set)
let set = new Set(a)// When a set is added, an iterable can be passed in
set = new Set([1.2.3.4.5])
Copy the code
let set = new Set(a)// Add data -- returns the Set object itself
set.add(1)
set.add(2)
set.add(1)

console.log(set) // => Set(2) {1, 2}

// Literals create objects, address references are different
set.add({})
set.add({})

console.log(set) // => Set(2) {1, 2, {}, {}}
Copy the code
let set = new Set([1.2.3.4.5])

// Delete data -- Returns Boolean
set.delete(1)

// Check whether the element exists -- returns Boolean
console.log(set.has(2)) // => true

// Clear all elements in set -- no return value
set.clear()
Copy the code
let set = new Set([1.2.3.4.5])

// set traversal
// 1. forEach
set.forEach(item= > console.log(item))

// 2. for ... of
// There is no key in a set, so you can't use for... The in method traverses the set
for (const item of set) {
  console.log(item)
}
Copy the code
// Array decrement
const arr = [1.2.3.4.5.1.3.5]

// How to remove weight in ES5
const newArr = []

arr.map(item= > {
  if (newArr.indexOf(item) === -1) {
    newArr.push(item)
  }
})

console.log(newArr)

const arr = [1.2.3.4.5.1.3.5]

// ES6 uses set
console.log(Array.from(new Set(arr)))
console.log([...new Set(arr)])
Copy the code

weakset

Weakset is another data structure similar to Set

Difference between WeakSet and set:

  1. The value in WeakSet can only be object type and cannot store basic data type
  2. A WeakSet reference to an element is a weak reference, and if there are no other references to an object, the GC will reclaim the object
/ / create
const ws = new WeakSet(a)const obj = {}

// Add an element -- returns the WeakSet object itself
ws.add(obj)

// Check whether the object exists -- returns Boolean
console.log(ws.has(obj)) // => true

// Remove the element -- returns Boolean
ws.delete(obj)
Copy the code

Note: WeakSet cannot traverse

WeakSet is just a weak reference to an object, and we can’t be sure if an element will be destroyed during access

That means that the return result of accessing the same Weakset for many times may be inconsistent, and there may be problems when dealing with the element value

So Weakset does not allow the use of forEach, for… Of and other methods for traversal operation

// WeakSet cannot perform any form of traversal operation
// So there is no size attribute or forEach method on WeakSet
WeakSet {
      
       } is the result of printing WeakSet output directly at any time, because it cannot iterate over values
      
console.log(ws) // => WeakSet { <items unknown> }
Copy the code

Application scenarios

Restrict a method on an object to being called only from an instance of the object

const ws = new WeakSet(a)class Person {
  constructor() {
    ws.add(this)}running() {
    if(! ws.has(this)) {
      throw new Error('The running method can only be called by instances of Person')}console.log('running')}}const per = new Person()
per.running() // => running
per.running.call({ name: 'Klaus' }) // error
Copy the code

map

Maps, like objects, can be used to store mappings

But unlike objects, when objects store key-value pairs, their keys can only be strings or Symbol values

If it is of any other type, it is converted to a string type and used as the key of the object

There are no restrictions on the map key. You can use any data type as the map key

const key = { usernmame: 'Klaus' }

const obj = {
  [key] : 'Kluas'
}

// {usernmame: 'Klaus'} will automatically call toString to convert to [object object]
console.log(obj) // => { '[object Object]': 'Kluas' }
Copy the code
// Basic usage
let map = new Map(a)// when created using the map constructor
// The Map constructor supports passing entries as arguments in formats similar to [[key, value], [key, value], [key, value],....]
// Key can be any type of data
map = new Map([[{name: 'Klaus'}, 'aaa'], ['key'.'value'], [1.3]])

console.log(map) // => Map(3) { { name: 'Klaus' } => 'aaa', 'key' => 'value', 1 => 3 }
Copy the code
const map = new Map([[{name: 'Klaus'}, 'aaa'], [1.3]])

// Common methods
// Set properties - Returns the entire Map object
map.set('key'.'value')

// Get the length of the map
console.log(map.size) / / = > 3

// Check whether an attribute exists -- returns Boolean
console.log(map.has('key')) // => true

// Remove an attribute -- returns Boolean
map.delete('key')

/ / empty map
map.clear()
Copy the code
const map = new Map([[{name: 'Klaus'}, 'aaa'], ['key'.'value']])

// iterate through method 1
For forEach, the first argument is the value of each item, and the second argument is the key of each item
map.forEach((value, key) = > console.log(key, value))

// iterate through method 2
const map = new Map([[{name: 'Klaus'}, 'aaa'], ['key'.'value']])

// iterate through method 2
// for ... When traversing of, the value of each entry is an entry in the map, i.e. [key, value]
// So we need to get the corresponding key and value through array deconstruction
for (const [key, value] of map) {
  console.log(key, value)
}
Copy the code

weakMap

Another data structure similar to Map type is called WeakMap, which also exists in the form of key-value pairs

Difference between weakMap and Map

  • WeakMap keys can only use objects and do not accept other types as keys
  • WeakMap’s reference to a key is a weak reference, and if no other reference refers to the object, GC can reclaim it
// Basic usage
/ / define
const wm = new WeakMap(a)WeakMap also supports creation using Entries objects as parameters
// const wm = new WeakMap([{name: 'key'}, 'value'])

const obj = {name: 'Klaus'}
// Set value -- returns the entire Map object
wm.set(obj, 'Klaus')

/ / get the value
// When we get a value, we store the reference address as an object
// The value can be obtained only through the reference address of the same object
console.log(wm.get(obj)) // => Klaus
console.log({ name: 'Klaus' }) // => undefined

// Check whether a value exists -- returns Boolean
console.log(wm.has(obj)) // => true

// Delete a value -- returns Boolean
wm.delete(obj)

// Like WeakSet, WeakMap cannot perform traversal operation in any form, and there is no size attribute and forEach method
WeakMap {
      
       } is printed directly, because the value inside cannot be iterated
      
console.log(wm) // => WeakMap { <items unknown> }
Copy the code

Usage scenarios

Realize responsive functions (the responsive form of Vue3 is realized through WeakMap)

The so-called responsivity means that when the corresponding attribute of an object changes, it can be detected and the corresponding response function can be executed to complete the corresponding function

// This is just a simple simulation of responsiveness - pseudocode and basic thinking
const obj1 = {
  name: 'Klaus'.age: 23
}

const obj2 = {
  name: 'Alex'.age: 20
}

// The function that needs to be triggered when obj1's name changes
// These functions can be thought of as dependencies of obj1.name, since their execution depends on changing the value of obj1.name
const obj1NameChangedFn1 = () = > console.log('obj1NameChangedFn1')
const obj1NameChangedFn2 = () = > console.log('obj1NameChangedFn2')

const obj1AgeChangedFn = () = > console.log('obj1AgeChangedFn')

const obj2NameChangedFn1 = () = > console.log('obj2NameChangedFn1')
const obj2NameChangedFn2 = () = > console.log('obj2NameChangedFn2')

// 1. Use WeakMap to associate the value of the object with the corresponding function

// The reason why the outermost layer is defined as WeakMap
// So that when obj1 is not in use, deps keys and values with key obj1 can be collected by GC at the appropriate time
const deps = new WeakMap(a)// The inner map is defined as map instead of WeakMap
// Because the object's property value is a string, WeakMap's key must be an object type
const obj1Map = new Map()

deps.set(obj1, obj1Map)
obj1Map.set('name', [obj1NameChangedFn1, obj1NameChangedFn2])
obj1Map.set('age', [obj1AgeChangedFn])

const obj2Map = new Map()
deps.set(obj2, obj2Map)
obj2Map.set('name', [obj2NameChangedFn1, obj2NameChangedFn2])

// 2. Update the dependency (function) when changing the value.
obj1.name = 'Steven'
// when obj1.name is updated, only the dependent function of obj1.name is updated
// Other dependent functions will not be executed
deps.get(obj1).get('name').forEach(fn= > fn())
Copy the code

ES7

includes

Prior to ES7, if we wanted to determine whether an array contained an element, we would get the result through indexOf and determine whether it was -1

In ES7, you can use includes to determine if an array contains a specified element, returning true if it does, false otherwise

/ / ES7 before
const arr = [1.2.3.4.5.NaN]

// arr.indexOf(searchElement[, fromIndex])
console.log(arr.indexOf(2)! = = -1) // => true
console.log(arr.indexOf(6)! = = -1) // => false

// arr.indexof (NaN) --> Returns -1 because NaN! == NaN horizontal holds
console.log(arr.indexOf(NaN)! = = -1) // => false
Copy the code
const arr = [1.2.3.4.5.NaN]

// arr.includes(valueToFind[, fromIndex])
console.log(arr.includes(1)) // => true
console.log(arr.includes(6)) // => false

The includes method determines that the NaN passed in is equal to the NaN of the element in the array
// This is the biggest difference between the includes distribution and indexOf methods
console.log(arr.includes(NaN)) // => true

// The second argument starts at the index value
console.log(arr.includes(1.2)) // => false
Copy the code

Exponentiation operator

/ / ES7 before
console.log(Math.pow(3.3)) / / = > 27

// After ES7 ** can be considered syntactic sugar for the math.pow method
console.log(3 ** 3) / / = > 27
Copy the code