10

foreword

Please tell me the data type in JavaScript ?

In the front-end interview, it is estimated that everyone has been asked this way.

Answer: The data types in Javascript include primitive types and reference types. nullundefinedbooleanstringsymbolbigInt , number . The reference type refers to Object .

Yes, I answered the same way, but this is usually the first question, and many, many questions can be raised from this question, such as

  • What is the difference between Null and Undefined ? What should be paid attention to in the front-end empty judgment?
  • typeof null Why object ?
  • Why ES6 to propose Symbol ?
  • BigInt What problem was solved?
  • Why 0.1 + 0.2 !== 0.3? how do you fix this?
  • How to tell if a value is an array?
  • ...

weakly typed language

Because JavaScript is a weakly typed language or a dynamic language. This means that you do not need to declare the type of the variable in advance, the type will be automatically determined during the running of the program, which means that you can use the same variable to store values of different types

 var foo = 42;  // foo is a Number now
foo = "bar";  // foo is a String now
foo = true;   // foo is a Boolean now

While this feature brings us convenience, it also brings us a lot of type errors. Just imagine, if JS is said to be a strongly typed language, then there is no way to convert between types, and there will be a barrier or a layer of protection. Will it be easier to maintain? ——This may be the reason why TypeScript was born.

Mastering the data type of JavaScript is the most basic knowledge point of a front-end

null or undefined

definition

undefined represents an undefined variable. null value represents a null object pointer.

追本溯源: 一开始的时候, JavaScript Brendan Eich其实nullnull Java as in, is treated as an object. But because JavaScript there are two data types: primitive and reference. Brendan Eich I think the value that means "nothing" is best not an object.

Therefore, the design of Javascript is that null is an object representing "nothing", which is 0 when converted to a value; undefined is a primitive value representing "nothing", which is NaN when converted to a value.

 Number(null)
// 0

5 + null
// 5

Number(undefined)
// NaN

5 + undefined
// NaN

The difference and application of Null and Undefined

null means "no object", i.e. there should be no value there. , the typical usage is as follows

  1. As an argument to a function, it means that the argument to the function is not an object.
  2. as the end of the object prototype chain.
 Object.getPrototypeOf(Object.prototype)
// null

undefined means "missing value", i.e. there should be a value here, but it hasn't been defined . Typical usage is:

  1. When the variable is declared, but not assigned a value, it is equal to undefined .
  2. When calling the function, the parameter that should be provided is not provided, the parameter is equal to undefined .
  3. The object has no assigned property, the value of this property is undefined .
  4. When the function has no return value, the default return undefined .
 var i;
i // undefined

function f(x){console.log(x)}
f() // undefined

var  o = new Object();
o.p // undefined

var x = f();
x // undefined

What should I pay attention to when judging empty?

javaScript Five kinds of null and false values, respectively undefined, null, false, "", 0, NAN

This can sometimes easily lead to some problems, such as

 let a = 0;
console.log(a || '/'); // 本意是只要 a 为 null 或者 Undefined 的时候,输出 '/',但实际上只要是我们以上的五种之一就输出 '/'

Of course we can write

 let a = 0;
if (a === null || a === undefined) {
  console.log('/');
} else {
  console.log(a);
}

It's not always very elegant, so the ES specification proposes the null coalescing operator (??)

The null coalescing operator (??) is a logical operator. When the left operand is null or undefined, it returns its right operand, otherwise it returns the left operand.

The above example can be written as:

 let a = 0;
console.log(a??'/'); // 0

typeof null - JS mistakes

 typeof null // "object"

The value in JavaScript is represented by a label representing the type and the actual data value. The first version JavaScript used 32 bits to store the value, and the type was identified by the lower 1 or 3 bits of the value, and the type tag of the object was 000. as follows

  • 1: Integer (int)
  • 000: Reference type (object)
  • 010: Double-precision floating-point type (double)
  • 100: string (string)
  • 110: Boolean (boolean)

But there are two special values:

  • undefined, use the integer −2^30 (negative 2 to the 30th power, not in the range of integers)
  • null, machine code null pointer (C/C++ macro definition), the lower three digits are also 000

null代表的是空指针(低000 ),因此,null的类型标签是000typeof null also returns "object" as a result.

This is a design error of JavaScript , but it cannot be modified. After all, if you modify it, it will affect the existing code.

Number——0.1+0.2 !== 0.3

Phenomenon

In JavaScript there will be a phenomenon similar to the following

 0.1 + 0.2 
0.30000000000000004

reason

In the process of operating on floating point numbers, we need to convert decimal to binary. The rules for converting decimal to binary are as follows:

Multiply the number after the decimal point by 2, take the integer part of the result (either 1 or 0), then multiply the fractional part by 2, and then take the integer part of the result... and so on until the fractional part is 0 or a bit The number is enough and it is OK. Then arrange the integer parts taken in order

According to the above rules, the final representation of 0.1 is as follows:

 0.000110011001100110011(0011无限循环)……

So, the loss of precision is not a language problem, but an inherent flaw in the storage of floating-point numbers.

JavaScript 64位双精度Number类型值, IEEE754 ,0.1 的二进制数只保留52 significant digits, i.e.

 1.100110011001100110011001100110011001100110011001101 * 2^(-4)

Similarly, the binary number of 0.2 is

 1.100110011001100110011001100110011001100110011001101 * 2^(-3)

In this way, precision has been lost in the conversion between bases. The operation is as follows

 0.00011001100110011001100110011001100110011001100110011010
+0.00110011001100110011001100110011001100110011001100110100
------------------------------------------------------------
=0.01001100110011001100110011001100110011001100110011001110

So it leads to 0.1 + 0.2 !== 0.3 in the final calculation result

How to solve

  • Convert numbers to integers
 function add(num1, num2) {
 const num1Digits = (num1.toString().split('.')[1] || '').length;
 const num2Digits = (num2.toString().split('.')[1] || '').length;
 const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));
 return (num1 * baseNum + num2 * baseNum) / baseNum;
}
  • class library
    NPM上有许多支持JavaScript Node.js的数学库, math.jsdecimal.js , D.js etc
  • ES6
    ES6 Added a tiny constant on the Number object Number.EPSILON
 Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// "0.00000000000000022204"

The purpose of introducing such a small amount is to set an error range for floating-point calculations. If the error can be less than Number.EPSILON , we can consider the result to be reliable.

 function withinErrorMargin (left, right) {
    return Math.abs(left - right) < Number.EPSILON
}
withinErrorMargin(0.1+0.2, 0.3)

Future Solutions - TC39 Decimal proposal

Proposal currently in Stage 1 . The later mentioned BigInt is extended to the positive number boundary of JS , which exceeds the 2^53 safe integer problem. Decimal is to solve the decimal problem of JS -2^53. This proposal introduces a new primitive type in JS: decimal (suffix m), which declares that the number is a decimal operation.

 let zero_point_three = 0.1m + 0.2m;
assert(zero_point_three === 0.3m);
// 提案中的例子
function calculateBill(items, tax) {
  let total = 0m;
  for (let {price, count} of items) {
    total += price * BigDecimal(count);
  }
  return BigDecimal.round(total * (1m + tax), {maximumFractionDigits: 2, round: "up"});
}

let items = [{price: 1.25m, count: 5}, {price: 5m, count: 1}];
let tax = .0735m;
console.log(calculateBill(items, tax));

Extension - storage of floating point numbers in memory

So what does the final floating point number look like in memory? EEE754 gives a definition for floating point representation

(-1)^S M 2^E

The meaning of each symbol is as follows: S, is the sign bit, which determines whether it is positive or negative. When it is 0, it is a positive number, and when it is 1, it is a negative number. M, refers to the effective number of digits, greater than 1 and less than 2. E, is the exponent bit.

Javascript is a 64-bit double-precision floating-point number, the highest 1 bit is the sign bit S, the next 11 bits are the exponent E, and the remaining 52 bits are the significand M.

Use this visualizer to see the binary representation of floating point numbers in memory)

BigInt - Breaking the Biggest Limits

JavaScript The Number type is a double-precision IEEE 754 64-bit floating point type.
The maximum value in JavaScript is 2^53 .

BigInt Arbitrary precision numeric type, which has entered the stage3 specification. BigInt can represent arbitrarily large integers. To create a BigInt , we just need to add an n suffix to any integer literal. For example, write 123 as 123n. This global BigInt(number) can be used to convert a Number to a BigInt, the implication being that BigInt(123) === 123n. Now let me use these two points to solve the problem we mentioned earlier:

Symbol - I am the most beautiful boy

definition

ES6 introduces a new primitive data type Symbol , representing a unique value

 let s = Symbol();

typeof s
// "symbol"

Application scenarios

  • Define a set of constants to ensure that the set of constants are not equal. Eliminate magic strings
  • Objects are guaranteed to have different property names

     let mySymbol = Symbol();
    
    // 第一种写法
    let a = {};
    a[mySymbol] = 'Hello!';
    
    // 第二种写法
    let a = {
    [mySymbol]: 'Hello!'
    };
    
    // 第三种写法
    let a = {};
    Object.defineProperty(a, mySymbol, { value: 'Hello!' });
    
    // 以上写法都得到同样结果
    a[mySymbol] // "Hello!"
  • Vue provide and inject . provide and inject allow an ancestor component to inject a dependency into all its descendants, no matter how deep the component hierarchy is, and it will always take effect when the upstream-downstream relationship is established. But this intrusiveness is also very strong. Using Symbols as key can avoid the interference with the component code, and there will be no problems such as the same naming

Array - a special existence in an object

Please tell me how to judge Array?

Why ask this question?

Because the array is a special existence, it is one of the data structures we usually come into contact with the most. It is a special object, and its index is the key value of the "ordinary object". But it also has some methods that "normal objects" do not have, such as map etc.

typeof Yes javascript provided operator to determine the data type, which returns a string representing the data type of the parameter. But we can't judge whether it is an array by typeof . Because typeof arrays and normal objects and null all return "object"

 const a = null;
const b = {};
const c= [];
console.log(typeof(a)); //Object
console.log(typeof(b)); //Object
console.log(typeof(c)); //Object

How to judge an array

  • Object.prototype.toString.call() .
    Every object that inherits Object has toString method, if toString method is not overridden, it will return [Object type] type is the type of the object
 const a = ['Hello','Howard'];
const b = {0:'Hello',1:'Howard'};
const c = 'Hello Howard';
Object.prototype.toString.call(a);//"[object Array]"
Object.prototype.toString.call(b);//"[object Object]"
Object.prototype.toString.call(c);//"[object String]"
  • Array.isArray()

     const a = [];
    const b = {};
    Array.isArray(a);//true
    Array.isArray(b);//false

    Array.isArray() Yes ES5 Added method, when there is no Array.isArray() , you can use Object.prototype.toString.call()

 if (!Array.isArray) {
  Array.isArray = function(arg) {
    return Object.prototype.toString.call(arg) === '[object Array]';
  };
}
  • instanceof . instanceof operator can be used to determine whether the object pointed to by the prototype attribute of a constructor exists on the prototype chain of another object to be tested. Because the constructor of the array is Array , the following judgment can be made. Note: Because arrays are also objects, so a instanceof Object is also true
 const a = [];
const b = {};
console.log(a instanceof Array);//true
console.log(a instanceof Object);//true,在数组的原型链上也能找到Object构造函数
console.log(b instanceof Array);//false
  • constructor . Instances instantiated through the constructor have a constructor attribute.
 function B() {};
let b = new B();
console.log(b.constructor === B) // true

And the array is instantiated by a function called Array . so you can

 let c = [];
console.log(c.constructor === Array) // true
Note: the constructor is subject to change. Therefore, it is not recommended to judge
 let c = [];
c.constructor = Object;
console.log(c.constructor === Array); // false

in conclusion

According to the above description, the judgment method of personal recommendation has the following priorities

isArray > Object.prototype.toString.call() > instanceof > constructor

Summarize

This article discusses and analyzes some common data type problems in JavaScript . I hope it can be helpful for everyone in interviews or work at ordinary times. In addition, things that may not be mentioned, such as type conversion, have the opportunity to discuss again.

Finally, welcome everyone to pay attention to my official account - front-end grocery store, discuss more technical issues~

refer to


Gopal
366 声望77 粉丝