乐趣区

关于javascript:JavaScript-基本数据类型知识总结

前言

JavaScript (以下简称 JS) 是一种弱类型语言, 申明变量时并不需要指定变量的类型, 变量会在运行时主动确定类型. 这也意味着能够应用同一个变量保留不同类型的数据.

JS 的数据类型分为两大类: 根本类型 对象类型.

其中, 根本类型分为:

  • String: 文本字符串.
  • Number: 整数和浮点数.
  • Boolean: 示意 真 (true) / 伪 (false) 的两个非凡值.
  • Null: 示意空值.
  • Undefined: 示意未定义或者不存在.
  • Symbol: 示意举世无双的值, 能够保障不会与其余属性名产生抵触.
  • BigInt: 可能反对比 Number 类型的范畴更大的整数值类型.

Symbol 和 BigInt 是 ES6 当前新增的.

对象类型 又名 援用类型 / 简单类型, 除了根本类型以外的都是对象类型, 例如: 纯对象, 对象实例, 函数等.

P.S. 本文的内容会比拟着重于根本类型的常识盘点, 对于对象类型的常识盘点会再另开一文进行总结. 那么开始.

String

在 JS 中想要示意一个字符串有三种形式: 单引号', 双引号", 反引号 ` :

'单引号字符串'
"双引号字符串"
` 反引号字符串 `

应用哪种引号定义的字符串内不能插入该引号, 但能够通过应用反斜杠来插入该引号:

// 这样写会报错!
'''"""

// 这样写才是对的.
'"'// -->"""
"'"// -->"'"

// 在字符串中应用了反斜杠前面的值能够示意它原来的意思.
'\'' // --> """"\"" // --> "'"

反引号表示法又叫 模板字符串, 是 ES6 新增的定义字符串的形式. 它次要是为了解决应用 单引号 / 双引号 定义的字符串须要换行 或是 多个变量叠加 带来的繁琐不不便的问题, 是一种增强版的字符串.

在模板字符串的大括号外部能够执行 JS 代码, 甚至能够嵌套模板字符串:

const a = 'boy', b = 'next', c = 'door';

// 单引号的, 不行!
const str1 = 'the' + a + '' + b +' '+ c; // -->"the boy next door"

// 反引号的, 行! 咱们模板字符串真是太厉害了!
const str2 = `the ${a} ${b} ${c}`; // --> "the boy next door"
const str3 = `the ${a} ${b} ${`${str2}`}`; // --> "the boy next the boy next door"

在模板字符串内插入对象时并不会被转换成 JSON 字符串, 而是会被转换成原始类型的字符串, 并且还能够在模板字符串内调用函数:

const a = {boy: 'next door'};
const boy = () => 'next door';

`${a}` // --> "[object Object]"
`${boy()}` // --> "next door"

// 你能够把它当作:
''+ a // -->"[object Object]"'' + boy() // --> "next door"

此外, 模板字符串还能紧跟在一个函数名前面, 这个函数会被调用用来解决这个模板字符串, 这被称为 “ 标签模板 ” (tagged template) 性能:

console.log`boy next door`;

// 等同于:
console.log(['boy next door']) // --> ["boy next door"]

有个驰名的 CSS in JS 的库 styled-components 就是用的这种形式.

要如何了解呢? 来做个试验:

const a = 'boy', b = 'next', c = () => 'door';

function test(...args) {console.log(args);
}

test`the ${a} ${b} ${c()}`
// --> [["the", ""," ",""], "boy", "next", "door"]

能够看到, 接管到的第一个参数是一个数组, 其余参数是大括号内的代码的执行后的后果. 也就是说, 这个函数实际上是以上面的模式调用:

test(["the", ""," ",""], a, b, c())

阐明第一个数组参数的成员是模板字符串中那些没有变量替换的局部, 其余参数都是模板字符串各个变量被替换后的值. 那么来改写一下 test 函数, 将各个参数依照原来的地位拼合回去, 让它把本来的字符串输入进去:

function test(s, ...args) {
  let result = '';
  let i = 0;
    
  while (i < s.length) {result += s[i];
    if (i < args.length) {result += args[i];
    }
    i++;
  }
    
  return result;
}

test`the ${a} ${b} ${c()}`; // --> "the boy next door"

Number / BigInt

这两个类型放一起说, 因为它们都是属于示意数字的类型.

先说对于 Number 类型, 在面试里可能常会被问到以下问题:

  1. NaN 是什么类型?

    NaN 是属于 number 类型的:

    typeof NaN; // --> "number"

    而判断一个数是不是 NaN 的办法能够用 isNaN() 来进行判断:

    const num = +'a'; // --> NaN
    
    // 不能应用这种形式来判断.
    num === NaN; // --> false; 这也阐明了两个 NaN 是不相等的.
    
    // 应该用这种形式来判断.
    isNaN(num); // --> true

    平时应用的 isNaN 是在 window 上的办法, 其实它的本意是判断参数应用一元加号运算符是否会转换成 Number 类型:

    isNaN('string'); // --> true
    
    // 等同于:
    isNaN(+'string'); // --> true
    
    // 传入一个 BigInt.
    isNaN(123n); // 会报错 TypeError: Cannot convert a BigInt value to a number

    ES6 后新增了一个 Number.isNaN,用于判断传入的参数是否严格的等于 NaN:

    Number.isNaN('string'); // --> false
    
    Number.isNaN(NaN); // --> true
  2. 精度问题

    0.1 + 0.2 !== 0.3; // --> false
    9999999999999999 === 10000000000000001 // --> true

    这个问题其实很常见了, 起因解释起来太长了, 只简略的说是 浮点数转化过程中呈现的失落 就能够了.

    以下解决方案都能够解决这个问题:

    • 能够通过将浮点数转换成整数后进行运算, 再对后果进行解决:

      (0.1 * 10 + 0.2 * 10) / 10; // --> 0.3

      然而这种形式不适用于超过 Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER 范畴的浮点数运算, 因而还是举荐应用上面的形式.

    • 应用第三方封装类库, 如 mathjs 等. 这些库不仅解决了浮点数的运算精度问题, 还反对了大数运算, 并且修复了 toFixed 后果不精确的问题.

BigInt 是为了反对示意在 Number.MAX_SAFE_INTEGERNumber.MIN_SAFE_INTEGER 的范畴之外的整数值, 在对大整数执行数学运算时, 能以任意精度示意整数的能力尤为重要.

创立 BigInt 只须要在整数开端追加 n 即可, 或者应用 BigInt 的构造函数:

const a = 10n;
typeof a; // --> "bigint"

// 应用 BigInt 的构造函数
const b = BigInt(10);
typeof b; // --> "bigint"

除一元加号 + 运算符外, 所有算术运算符都可用于 BigInt:

10n + 10n; // --> 20n
10n - 9n; // --> 1n
10n * 2n; // --> 20n
20n / 2n; // --> 10n

const a = 10n;
++a; // --> 11n
--a; // --> 9n

// 当然, 不能混合惯例数字进行运算, 否则会报错.
10n + 1; // --> TypeError: Cannot mix BigInt and other types, use explicit conversions

// 不能应用严格相等运算符将 BigInt 与惯例数字进行比拟, 因为它们的类型不同:
10n === 10; // --> false

// 应用宽松相等运算符是能够的:
10n == 10; // --> true

这也是为什么下面 isNaN 办法不反对传入 BigInt 的起因了, 因为它不能应用一元加号运算符.

个别业务向编程是很少会用到 BigInt 类型的, 只有在须要解决大精度整数的时候才会须要用上, 所以你也不必放心面试的时候会问到这个, 大概率是不会的. ????

Boolean

这个数据类型有且只有两个值: truefalse. 这个类型其实没啥好说的, 就跑下题说说 隐式类型转换 的问题吧:

应用宽松相等运算符 == 会试图比拟他们的值, 如果比拟单方的类型不统一, 会进行隐式转换.

String, Number, Boolean 三种类型之间的比拟会优先转换成 Number 后再来进行比拟:

// String 和 Number 进行比拟时, 会先将 String 转成 Number 后再进行比拟.
'123' == 123;
// 相当于: 123 == 123 
// --> true

// String 和 Boolean 进行比拟时, 会先将 String 和 Boolean 都转成 Number 后再进行比拟.
'123' == true;
// 相当于: 123 == 1 
// --> false

// Number 和 Boolean 进行比拟时, 会先将 Boolean 转成 Number 后再进行比拟.
123 == false;
// 相当于: 123 == 0 
// --> false

而对象类型和 String, Number, Boolean 三种类型之间进行比拟的状况, 会先调用对象类型的 valueOf 办法, 再应用 toString 办法后再进行比拟:

[] == ''; 
// 相当于: [].valueOf().toString() == ''// -->'' == ''
// --> true

[] == 0; 
// 相当于: [].valueOf().toString() == 0 
// --> "" == 0 
// --> 0 == 0 
// --> true

// 所以:
({}) != '';
// 相当于: ({}).valueOf().toString() != ''// -->"[object Object]"!=''
// --> true

最初对象类型和对象类型之间进行比拟的状况, 没有隐式转换规则, 比拟的是援用地址, 所以:

// 两个不同地址的对象类型永远不可能相等:
{} != {}; // --> true
[] != []; // --> true

// 只有援用地址一样才会相等:
var a = {};
var b = a;
var c = [];
a.d = c;
var d = a.d;

a == b; // --> true
c == d; // --> true

好, 到这里我想你应该曾经根本明确比拟的规定了, 那么来看看这个:

var a = [];

a == !!a; // --> false

啊, 这 … 那么多为什么? 咱们都晓得, !运算符会把一个值转换成 Boolean 类型而后取反.

因为 [] == false, 所以依据规定等式会转换成 false == !!false 才对.

其实这是个误区, 尽管 [] == false 没有错, 但这是个隐式转换规则, 并不代表它转换成 Boolean 类型就是 false.

Boolean([]); // --> true

// 所以你明确了吧, 等式实际上相当于:
[] == !!Boolean([])
// --> [] == !!true
// --> false == true
// --> false

BTW, 转换成 Boolean 类型为 false 的值只有: 空字符串, 0, null,undefined 这几种.

要留神, 隐式转换 类型转换 之间并无任何交加关系.

Null / Undefined

这两个类型放一起说, 因为它们这两个类型很特地, 首先这两种类型的值都只有一个, 就是它们本人.

在面试里有时会问这样的一个问题: null 和 undefined 有什么区别 ?

很多人都是从语义上解释它们的区别: null 是刻意设置的空值, 而 undefined 是申明了变量但没有赋值.

尽管 null 和 undefined 转换成 Boolean 类型都是 false, 然而如同下面所说, 隐式转换和类型转换并无任何交加关系:

null == false; // --> false
undefined == false; // --> false

并且这两种类型之间只有它们两个本人可能相互进行隐式转换:

null == undefined; // --> true
undefined == null; // --> true

此外, 当你对 null 应用 typeof 时, 会失去 object 的后果, 这也是常问的数据类型面试题之一.

这其实是 JS 的一个 bug. 其起因是因为不同的对象在 JS 底层都示意为二进制, 如果二进制前三位都为 0 的话就会判断为 object 类型, null 的二进制示意是全 0, 天然前三位也是 0, 所以执行 typeof 时会返回 object.

Symbol

对于这个类型, 我感觉阮一峰老师对 Symbol 的介绍就说的很好, 所以在这里我就不介绍了.

平时写我的项目可能不太用的上, 然而在一些开源的库和框架里会比拟常见. 例如在 React 中自定义组件的类型就是一个非凡的 Symbol 值, 只有判断类型等于这个值就会被当做是自定义组件解决.

其起因是因为这个类型的唯一性, 通过 Symbol 返回的值都是在 JS 底层计算好的惟一值:

'a' === 'a'; // --> true
Symbol('a') === Symbol('a'); // --> false

如果还不了解, 能够这么解释: 打个比方, Symbol 值就像一个上了锁的箱子, 每次用 Symbol 生成的都是不同的箱子.
那如果你申明了 Symbol 值而不必变量去援用它, 就相当于把箱子上了锁而后把钥匙扔进大海里, 再也打不开了.

在定义 Symbol 的时候, 传入的值会被当做这个 Symbol 值的形容 (description), 能够用 Symbol.prototype.description (当然它是无奈批改的) 进行拜访:

// 这个变量 a 就相当于钥匙.
const a = Symbol('tom');

a.description; // --> "tom"

a.description = 'jerry';

a.description; // --> "tom"

常见用法有把 Symbol 作为对象的键值, 保障外部属性唯一性:

const a = Symbol('a');

const obj = {[a]: 'hello there',
    // 如果你这样定义, 你就找不到它了.
    [Symbol('b')]: 'you will never find me'
}

obj[a]; // --> "hello there"
obj[Symbol('b')]; // --> undefined

实际上 JS 外部有很多属性都是通过 Symbol 值来定义的, 遍历对象的时候, 该属性不会呈现在 for...in, for...of 循环中, 也不会被 Object.keys(), Object.getOwnPropertyNames(), JSON.stringify() 返回.

ES6 还提供了 11 个 内置的 Symbol 值, 指向语言外部应用的办法. 对于这些内容, 能够查看阮一峰老师的介绍).

因为他真的讲的比我好. TAT

最初

谢谢你能保持浏览到这里. 本文到这里就完结了, 心愿以上内容能对你有所帮忙.

退出移动版