I happened to see an article about the self-check list of a [qualified] front end engineer in Tiktok’s front end security team, but the article actually looks more like a withdrawal form. After reading, I found that I could only write the front end, and many questions did not ask why. At the same time, this article also contains some front-end advanced points, just as their own needs, so I follow this list to comb through the front-end content, for their own shortcomings, but also to see how long I can insist, in order to have the power to insist for a longer time, I hope you don’t mean the hands of praise and comments.

About data types

Dynamic typing: JS is a weakly typed or dynamic language, meaning that variable types do not need to be declared in advance

The data type

Primitive/primitive: Types can be obtained using the Typeof operator

  1. Undefined: The default value of a variable that has not been assigned
  2. Boolean:trueandfalse
  3. Number
  4. String
  5. BigInt
  6. Symbol
  7. null

Number

Range: IEEE 754 based double precision 64-bit binary format values -(2^53-1) to 2^53-1

Security range check: number. MAX_VALUE and number. MIN_VALUE; Number.issafeinteger (), number.max_SAFE_INTEGER, and number.min_safe_INTEGER

Includes: integer, floating point, +Infinity, -infinity, NaN

BigInt

A basic numeric type that can represent integers with arbitrary precision. You can work with operands or convert to Boolean, but you can’t work with numbers

Symbol

Created through Symbol([description]), the description parameter can only be used for debugging.

Symbol can be used to create anonymous object attributes, creating private members of a class.

Properties created with Symbol are not enumerable and therefore do not appear in loop structures such as for in; Because create attribute is anonymous, so also can’t through the Object. The getOwnPropertyNames (). But can be by creating the original Symbol of access, or use the Object. The getOwnPropertySymbols () to get to the Symbol attribute array.

Null, and Undefined

  • Both Null and Undefined have only one value, Null and Undefined, respectively
  • A null value means an empty object pointer, sotypeof null === 'object'
  • Undefined is derived from null, sonull == undefined // trueBut only if ==, === is false
  • Null should be used to populate unassigned object type variables, not to display unassigned original value type variables as undefined

Complex type/reference type

Object

In computer science, an object is a region of memory that can be referenced by an identifier. —MDN

ECMAScript defines two types of object properties: data properties, which are our custom properties, and accessor properties, which are GET and set.

About the storage

Opinions on variable storage data are contrary, and there is no authoritative data to be supplemented

Raw and reference values (value types and reference types)

Primitive value: The simplest data, the primitive type mentioned above. Raw values we operate directly on the actual data stored in variables, and are therefore accessed by value (hence the name value type).

Reference value: An object composed of multiple values. Reference values are objects that are stored in memory, but JS does not have the ability to access memory locations directly, so only references to object data (memory addresses) are stored in variables, so access by reference.

Copy operation

The difference in how the original value and reference value variables are stored also determines the performance of the variable copy.

When the original value is copied from a variable to another variable, a copy of the value is made without interference. From the little Red Book:

Since a reference value variable is the memory address of the stored object, the new variable gets only a copy of the memory address during the copy operation. This causes the two variables to refer to the same object, so they interact during modification, and that’s where it comes inDeep copy and shallow copy. Again using the little Red Book illustration:

About the raw value wrapper type

There are three primitive value wrapper types, the built-in objects that correspond to primitive types: Boolean, Number, and String.

After you define a string, you can use.length to get the length of the string,.includes() to determine if the string contains certain characters, and so on.

But primitive types are not objects, and there should be no properties or methods, yet we don’t get any errors when we use them, because JS does a lot behind the scenes for us.

When we read a primitive type, JS will do the following for us

// Define a string
let str = 'I am string';
// This is how we write STR
let res = str.includes('str');

// This is what the actual JS does for us.
let str = new String("I am string");
let res = str.includes('str');
str = null;
Copy the code

So it can be seen that when we read the property or method of the original value, JS will first create an instance of the corresponding type through the built-in object, then access the property or method we need on the instance, and destroy the created instance after the call.

Determine the data type

// Basic type
let num = 1
let str = 'javascript'
let bool = true
let bignum = 1n
let unde = undefined
let nul = null
let sym = Symbol('javascript')
// Reference type
let obj = { lang: 'javascript'.version: 'ES6' }
let func = function() { console.log('javascript')}let arr = ['1'.'2'.3]
Copy the code

typeof

typeof num  // 'number'
typeof str  // 'string'
typeof bool // 'boolean'
typeof bignum   // 'bigint'
typeof unde // 'undefined'
typeof nul  // 'object'
typeof sym // 'symbol'

typeof obj // 'object'
typeof func // 'function'
typeof arr  // 'object'
Copy the code

Summary: Basic types and functions other than Null can be determined.

instanceof

Check whether the constructor’s Prototype property appears on the prototype chain of an instance object.

A instanceof B is to determine whether the prototype of B’s constructor is on the prototype chain of A. The prototype property of Object is on the prototype chain of the instance Object o, so O is of type Object.

num instanceof Number // false
str instanceof String // false
bool instanceof Boolean // false
bignum instanceof BigInt // false
// Because Undefined and Null have no built-in objects, instanceof cannot be used
sym instanceof Symbol // false

obj instanceof Object // true
func instanceof Function // true
arr instanceof Array // true
Copy the code

Summary: Instanceof cannot be used to detect primitive types declared directly, but it can if declared using the new constructor. It can detect reference types, but be careful to leave instanceof Object judgment last, since Function and Array instanceof Object are also true.

constructor

Can return the constructor of an instance object.

num.constructor === Number // true
str.constructor === String // true
bool.constructor === Boolean // true
bignum.constructor === BigInt // true
// Null and Undefined have no constructor attribute
sym.constructor === Symbol // true
obj.constructor === Object // true
func.constructor === Function // true
arr.constructor === Array // true
Copy the code

Conclusion: It is possible to judge other types than Null and Undefined, but since the direction of constructor can be changed, the result is not guaranteed to be accurate.

Object.prototype.toString

When toString() is not overridden in a custom object, return “[Object type]”, where type is the type of the object.

To every Object through the Object. The prototype. The toString () to test, need to Function. The prototype. The call () or the Function. The prototype, the apply () in the form of a call, Pass the object to be checked as the first argument, called thisArg.

Object.prototype.toString.call(num) // '[object Number]'
Object.prototype.toString.call(str) // '[object String]'
Object.prototype.toString.call(bool) // '[object Boolean]'
Object.prototype.toString.call(bignum) // '[object BigInt]'
Object.prototype.toString.call(unde) // '[object Undefined]'
Object.prototype.toString.call(nul) // '[object Null]'
Object.prototype.toString.call(sym) // '[object Symbol]'

Object.prototype.toString.call(obj) // '[object Object]'
Object.prototype.toString.call(func) // '[object Function]'
Object.prototype.toString.call(arr) // '[object Array]'
Copy the code

Conclusion: Seemingly the most perfect judgment method, common types can be accurately judged

Type conversion

Explicit type conversion

Manual initiated type conversions, mainly toString, toNumber, toBoolean.

  1. toString
Value types After the transformation
String The original cost
Number ‘1’, ‘NaN’, ‘Infinity’
Boolean ‘true’, ‘false’
Undefined ‘undefined’
Null ‘null’
Symbol VM1086:1 Uncaught TypeError: Cannot convert a Symbol value to a string
BigInt Discard the String after n
Object ‘[Object type] ‘
  1. toNumber
Value types After the transformation
Number The original cost
String 1. Numeric string: Converts to the corresponding number

2. Empty string: Converts to 0

3.
Boolean true: 1 ; false: 0
Undefined NaN
Null 0
Symbol Uncaught TypeError: Cannot convert a Symbol value to a number
BigInt Uncaught TypeError: Cannot convert a BigInt value to a number
Object NaN
  1. toBoolean
Value types After the transformation
Boolean The original cost
String The empty string is false and the rest is true
Number 0 and NaN are false, and the rest are true
Undefined false
Null false
Symbol true
BigInt true
Object true

Implicit type conversion

Implicit conversion is when you try to manipulate a “wrong” data type and JS automatically converts it to the “right” data type. This usually occurs when an operation is performed by an operator.

Implicit conversions to primitive variables result in the same result as manual conversions above. For object variables, let’s look at the ECMAScript specification:

ToPrimitive

The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType. The abstract operation ToPrimitive converts its input argument to a non-Object type. If an object is capable of converting to more than one primitive type, it may use the optional hint PreferredType to favour that type. Conversion occurs according to the following algorithm:

The implicit operation ToPrimitive takes an input parameter and an optional PreferredType parameter. ToPrimitive converts the input argument it receives to a non-object type. If an object can be converted to multiple primitive types, it can use the type given by the hint parameter PreferredType. The conversion is performed according to the following algorithm:

1. Assert: input is an ECMAScript language value.
Assert: The input parameter is an ECMAScript value
2. If Type(input) is Object, then
// 2. If input is Object
    a. If PreferredType is not present, let hint be "default".
    // a. If the PreferredType parameter does not exist, hint "default"
    b. Else if PreferredType is hint String.let hint be "string".
    // if PreferredType is "String", "String"
    c. Else,
    // c. If neither is true
        1. Assert: PreferredType is hint Number.
        PreferredType indicates the hint Number
        2. Let hint be "number".
        // 2. hint 为 "number"
    d. Let exoticToPrim be ? GetMethod(input, @@toPrimitive).
    // d. Assign @@toprimitive to exoticToPrim
    e. If exoticToPrim is not undefined, then
    E. If exoticToPrim is not undefined
        1. Let result be ? Call(exoticToPrim, input, « hint »).
        1. Assign the result of Call(exoticToPrim, input, « hint ») to result;
        2. If Type(result) is not Object.return result.
        // 2. If result is not an object type, return result
        3. Throw a TypeError exception.
        // 3. Otherwise, an error is thrown
    f. If hint is "default", set hint to "number".
    F. If hint is default, reset it to number
    g. Return ? OrdinaryToPrimitive(input, hint).
    G. Return the execution result of OrdinaryToPrimitive(input, hint)
3. Return input.
// 3. If input is not an object type, return the original value
Copy the code

When ToPrimitive is called with no hint, then it generally behaves as if the hint were Number. However, objects may over-ride this behaviour by defining a @@toPrimitive method. Of the objects defined in this specification only Date objects (see 20.4.4.45) and Symbol objects (see 19.4.3.5) over-ride the default ToPrimitive behaviour. Date objects treat no hint as if the hint were String.

When ToPrimitive is called with no hint value, hint is usually treated as Number. But some object types override this behavior with the @@toprimitive method. Only Date and Symbol currently override the default behavior in the ECMAScript specification. Date defaults to String when there is no hint value.

OrdinaryToPrimitive

In the ToPrimitive method, if the input parameter has no special @@toprimitive method, the result of the OrdinaryToPrimitive(input, hint) is returned. Now let’s see what this method does:

1. Assert: Type(O) is Object.
// 1. Enter O as the object type
2. Assert: Type(hint) is String and its value is either "string" or "number".
Hint is a string, and its value is "string" or "number".
3. If hint is "string", then
// if hint is "string"A. Let methodNames be «"toString"."valueOf" ».
    A. Set methodNames to « "toString", "valueOf" »
4. Else,
/ / 4. OtherwiseA. Let methodNames be «"valueOf"."toString" ».
    // a. methodNames is set to « "valueOf", "toString" »
5. For each name in methodNames in List order, do
// 5. The value name in methodNames is iterated sequentially
    a. Let method be ? Get(O, name).
    // a. Set method to O[name]
    b. If IsCallable(method) is true, then
    // b. If method can be called
        1. Let result be ? Call(method, O).
        // 1. Assign the result of method to result
        2. If Type(result) is not Object.return result.
        // 2. If result is not an object, return result
6. Throw a TypeError exception.
// Otherwise, an error is thrown
Copy the code

The rules for implicit type conversions can be summarized as follows:

  1. Check whether the value passed in is an object. If not, return the original value.
  2. If it is an object, check whether the PreferredType parameter exists, and set the hint to ‘default’ if it does not, string to string if it is a string, and ‘number’ if it is any other value.
  3. Check if there is one on the input parameter@@toPrimitiveIf the result is not an object, thenReturns the result; Otherwise a type error is thrown.
  4. Set hint to number.
  5. If hint is string, call it firsttoString, after the callvalueOf.
  6. Otherwise, call firstvalueOf, after the calltoString.
  7. If the result of a method call is not an object, the result of the call is returned
  8. Otherwise throw an error

When it comes to operators, which type compromises into the other depends on the specification of the particular operator. For example, when you add a number to a string, the number is converted to a string. But by subtracting, the string is converted to a number. You can check out the ECMAScript specification for yourself, and if there’s a lot of interest, schedule one.

About JS accuracy

JS follows the IEEE 754 standard and uses double precision (64-bit) to store numbers. The 64 bits include 1 sign bit, 11 exponent bits, and 52 decimal bits.

How does a computer store a number? Taking 32.25 as an example, let’s try the rule conversion:

  1. First convert 32.25 to binary and get100000.01
  2. The second order system is then expressed by scientific notation, yielding 1.0000001*2^5^
  3. Sign bit: Positive number is 0, negative number is 1, so sign bit is0
  4. Exponent bit: Represents the coded value of the exponent field, equal to the actual value of the exponent (i.e., 5 here) plus some fixed value (the standard fixed value is 2^ E -1^-1, e is the length of the exponent bit, in JS is 11, so the fixed value in JS is always 1023). This calculation shows that the exponential bit of 32.25 is 102810And convert the result to binary10000000100
  5. Decimal place: 0000001. The 52 bits need to be filled with 0

So eventually the storage form of 32.25 is a: 0, 10000000100, 0000001000000000000000000000000000000000000000000000

The problem of lost accuracy

When we know how numbers are stored inside a computer, it is not difficult to understand why the problem of loss of accuracy occurs. We use the classic 0.1 + 0.2! Take == 0.3 as an example, first convert 0.1 and 0.2 into binary respectively to obtain:

0.0001, 1001, 1001, 1001… (1001 infinite loop) 0.0011 0011 0011 0011… (0011 Infinite loop)

However, since the decimal part of a double-precision floating-point number has only 52 bits, the rest of the number must be rounded according to the decimal rounding rule. In binary, 0 is rounded to 1, so when converted to floating point, we can get:

0, 01111111011, 1001100110011001100110011001100110011001100110011010

0, 01111111100, 1001100110011001100110011001100110011001100110011010

Because rounding is involved in converting to floating point numbers, you can already see a loss of precision, which is what the computer actually stores.

In addition, the computer first converts the stored data to a binary decimal and then adds it, so we convert the floating point representation to binary and get:

0.00011001100110011001100110011001100110011001100110011010

0.0011001100110011001100110011001100110011001100110011010

And then you add up the binary forms

0.01001100110011001100110011001100110011001100110011001110

Converting the result to decimal yields 0.30000000000000004, which explains why 0.1 + 0.2! = = 0.3.

The Big Slim crisis

Because a double-precision floating-point digit has a maximum of 52 digits, it is bound to represent a finite number of digits. It is easy to imagine that when all 52 digits are 1 and there are no decimal points, the number should be the largest.

Since the integer bits are always 1 in scientific notation for binary numbers, this is omitted in floating-point numbers, which means that the largest number in JS is actually 53 bits. When they are all 1, you get 9007199254740991.

Number.MAX_SAFE_INTEGER === 9007199254740991 // true
Copy the code

When numbers exceed the maximum safe number, there is a possibility of rounding, so precision loss occurs.

9999999999999999= = =10000000000000001 // true 10000000000000000
Copy the code

Converting two numbers to a double-precision floating-point representation shows that they are the same.

Solution (elemental reactions, of course)

  1. For the loss of precision of decimals, the method of enlarging and then reducing the result can be adopted. As for the magnification factor, it can be dynamically determined according to the actual decimal number of data.

  2. For large integers, there is a BigInt property currently in Stage4, which can also be manipulated by converting it to a string.

  3. Of course, whether it’s a decimal or a large integer, you can borrow the power of wind farms (third-party libraries), such as MathJS, etc.

The last

JS variables and types, more important is also above these.

As for how variables are stored in the end, the consistent view that basic types have stack memory and reference types have heap memory has been questioned after querying the data. However, there is no more authoritative information and ECMAScript standard has not been implemented yet, so we temporarily shelve it and make up for it later when we find it.

Can see here are big guy, I hope you big guy reward out of hand praise ~