Interview Says: Talk About Data Types in JavaScript

foreword

Please JavaScript tell me about the data type in ?

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

Answer: JavascriptThe data types in include primitive types and reference types. The primitive types include null, undefined, boolean, string, symbol, bigInt, 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

  • NullUndefinedWhat is the difference between and ? What should be paid attention to in the front-end empty judgment?
  • typeof nullWhy is it object?
  • ES6Why bring it up Symbol?
  • BigIntWhat's the problem?
  • 0.1 + 0.2 !== 0.3?Why and how do you solve this problem?
  • How to tell if a value is an array?
  • ...

weakly typed language

Because it JavaScriptis 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 JSit is a strongly typed language, then there is no conversion between various types, and there is a layer of barrier or protection. Will it be easier to maintain? - This may be the reason for its TypeScriptbirth .

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

null or undefined

definition

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

追本溯源: 一开始的时候, JavaScript 设计者 Brendan Eich 其实只是定义了 nullnull 像在 Java 里一样,被当成一个对象。但是因为 JavaScript 中有两种数据类型:原始数据类型和引用数据类型。 Brendan Eich 觉得表示"无"的值最好不是对象。

所以 Javascript 的设计是 null是一个表示"无"的对象,转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN。

Number(null)
// 0


5 + null
// 5




Number(undefined)
// NaN




5 + undefined
// NaN复制代码

5 + undefined // NaN

Null 和 Undefined 的区别和应用

null表示"没有对象",即该处不应该有值。,典型的用法如下

  1. 作为函数的参数,表示该函数的参数不是对象。
  2. 作为对象原型链的终点。
Object.getPrototypeOf(Object.prototype)
// null复制代码

undefined表示"缺少值",就是此处应该有一个值,但是还没有定义。典型用法是:

  1. 变量被声明了,但没有赋值时,就等于 undefined
  2. 调用函数时,应该提供的参数没有提供,该参数等于undefined
  3. 对象没有赋值的属性,该属性的值为 undefined
  4. 函数没有返回值时,默认返回 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复制代码

var x = f(); x // undefined

判空应该注意什么?

javaScript 五种空值和假值,分别为 undefined,null,false,"",0,NAN

这有时候很容易导致一些问题,比如

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

当然我们可以写成

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

始终不是很优雅,所以 ES规范 提出了空值合并操作符(??)

空值合并操作符(??)是一个逻辑操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。

上面的例子可以写成:

let a = 0;
console.log(a??'/'); // 0复制代码

typeof null——JS 犯的错

typeof null // "object"复制代码

JavaScript 中的值是由一个表示类型的标签和实际数据值表示的。第一版的 JavaScript 是用 32 位比特来存储值的,且是通过值的低 1 位或 3 位来识别类型的,对象的类型标签是 000。如下

  • 1:整型(int)
  • 000:引用类型(object)
  • 010:双精度浮点型(double)
  • 100:字符串(string)
  • 110:布尔型(boolean)

但有两个特殊值:

  • undefined,用整数−2^30(负2的30次方,不在整型的范围内)
  • null,机器码空指针(C/C++ 宏定义),低三位也是000

由于 null 代表的是空指针(低三位也是 000 ),因此,null 的类型标签是 000typeof null 也因此返回 "object"。

这个算是 JavaScript 设计的一个错误,但是也没法修改,毕竟修改的话,会影响目前现有的代码

Number——0.1+0.2 !== 0.3

现象

JavaScript 会存在类似如下的现象

0.1 + 0.2
0.30000000000000004复制代码

原因

我们在对浮点数进行运算的过程中,需要将十进制转换成二进制。十进制小数转为二进制的规则如下:

对小数点以后的数乘以2,取结果的整数部分(不是1就是0),然后再用小数部分再乘以2,再取结果的整数部分……以此类推,直到小数部分为0或者位数已经够了就OK了。然后把取的整数部分按先后次序排列

根据上面的规则,最后 0.1 的表示如下:

0.000110011001100110011(0011无限循环)……复制代码

所以说,精度丢失并不是语言的问题,而是浮点数存储本身固有的缺陷。

JavaScript 是以 64 位双精度浮点数存储所有 Number 类型值,按照 IEEE754 规范,0.1 的二进制数只保留 52 位有效数字,即

1.100110011001100110011001100110011001100110011001101 * 2^(-4)复制代码

同理,0.2的二进制数为

1.100110011001100110011001100110011001100110011001101 * 2^(-3)复制代码

这样在进制之间的转换中精度已经损失。运算的时候如下

0.00011001100110011001100110011001100110011001100110011010
+0.00110011001100110011001100110011001100110011001100110100


=0.01001100110011001100110011001100110011001100110011001110复制代码

=0.01001100110011001100110011001100110011001100110011001110

所以导致了最后的计算结果中 0.1 + 0.2 !== 0.3

如何解决

  • 将数字转成整数
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;
}复制代码
  • 类库
    NPM 上有许多支持 JavaScriptNode.js 的数学库,比如 math.jsdecimal.js,D.js 等等
  • ES6
    ES6Number 对象上新增了一个极小的常量——Number.EPSILON
Number.EPSILON
// 2.220446049250313e-16
Number.EPSILON.toFixed(20)
// "0.00000000000000022204"复制代码

引入一个这么小的量,目的在于为浮点数计算设置一个误差范围,如果误差能够小于 Number.EPSILON,我们就可以认为结果是可靠的。

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

未来的解决方案——TC39 Decimal proposal

目前处于 Stage 1 的提案。后文提到的 BigInt 扩展的是 JS 的正数边界,超过 2^53 安全整数问题。Decimal 则是解决JS的小数问题-2^53。这个议案在JS中引入新的原生类型:decimal(后缀m),声明这个数字是十进制运算。

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));复制代码

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

拓展——浮点数在内存中的存储

所以最终浮点数在内存中的存储是什么样的呢?EEE754 对于浮点数表示方式给出了一种定义

(-1)^S M 2^E

各符号的意思如下:S,是符号位,决定正负,0时为正数,1时为负数。M,是指有效位数,大于1小于2。E,是指数位。

Javascript 是 64 位的双精度浮点数,最高的 1 位是符号位S,接着的 11 位是指数E,剩下的 52 位为有效数字M。

可借助 这个可视化工具 查看浮点数在内存中的二进制表示)

BigInt——突破最大的限制

JavaScriptNumber 类型为 双精度IEEE 754 64位浮点类型。
在 JavaScript 中最大的值为 2^53

BigInt 任意精度数字类型,已经进入stage3规范。BigInt 可以表示任意大的整数。要创建一个 BigInt ,我们只需要在任意整型的字面量上加上一个 n 后缀即可。例如,把123 写成 123n。这个全局的 BigInt(number) 可以用来将一个 Number 转换为一个 BigInt,言外之意就是说,BigInt(123) === 123n。现在让我来利用这两点来解决前面我们提到问题:

Symbol——我是独一无二最靓的仔

定义

ES6 引入了一种新的原始数据类型 Symbol,表示独一无二的值

let s = Symbol();




typeof s
// "symbol"复制代码

typeof s // "symbol"

应用场景

  • 定义一组常量,保证这组常量都是不相等的。消除魔法字符串
  • 对象中保证不同的属性名

    let mySymbol = Symbol();
    
    
    
    
    // 第一种写法
    let a = {};
    a[mySymbol] = 'Hello!';
    
    
    
    
    // 第二种写法
    let a = {
    [mySymbol]: 'Hello!'
    };
    
    
    
    
    // 第三种写法
    let a = {};
    Object.defineProperty(a, mySymbol, { value: 'Hello!' });
    
    
    
    
    // 以上写法都得到同样结果
    a[mySymbol] // "Hello!"复制代码

    // 以上写法都得到同样结果 a[mySymbol] // "Hello!"

  • Vue 中的 provideinjectprovideinject 可以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。但这个侵入性也是非常强的,使用 Symbols 作为 key 可以避免对减少对组件代码干扰,不会有相同命名等问题

数组——对象中一个特殊的存在

请说下判断 Array 的方法?

为什么会问这个问题?

因为数组是一个特殊的存在,是我们平时接触得最多的数据结构之一,它是一个特殊的对象,它的索引就是“普通对象”的 key 值。但它又拥有一些“普通对象”没有的方法,比如 map

typeofjavascript 原生提供的判断数据类型的运算符,它会返回一个表示参数的数据类型的字符串。但我们不能通过 typeof 判断是否为数组。因为 typeof 数组和普通对象以及 null,都是返回 "object"

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

判断数组的方法

  • Object.prototype.toString.call()
    每一个继承 Object 的对象都有 toString 方法,如果 toString 方法没有重写的话,会返回 [Object type],其中 type 为对象的类型
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()ES5 新增的方法,当不存在 Array.isArray() ,可以用 Object.prototype.toString.call() 实现

if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}复制代码
  • instanceofinstanceof 运算符可以用来判断某个构造函数的 prototype 属性所指向的對象是否存在于另外一个要检测对象的原型链上。因为数组的构造函数是 Array,所以可以通过以下判断。注意:因为数组也是对象,所以 a instanceof Object 也为 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。通过构造函数实例化的实例,拥有一个 constructor 属性。
function B() {};
let b = new B();
console.log(b.constructor === B) // true复制代码

而数组是由一个叫 Array 的函数实例化的。所以可以

let c = [];
console.log(c.constructor === Array) // true复制代码
注意:constructor 是会被改变的。所以不推荐这样判断
let c = [];
c.constructor = Object;
console.log(c.constructor === Array); // false复制代码

结论

根据上面的描述,个人推荐的判断方法有如下的优先级

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

总结

本文针对于 JavaScript 中部分常见的数据类型问题进行了讨论和分析。希望对大家面试或者平时的工作都能有所帮助。另外可能没有提及的比如类型转换等有机会再讨论一下

最后,欢迎大家关注我的公众号——前端杂货铺,技术问题多讨论~

参考

本文来源:面试说:聊聊JavaScript中的数据类型

Guess you like

Origin juejin.im/post/7147723796043022350
Recommended