Skip to content

JS基础知识巩固

一. js中的数据类型

1. js数据类型有哪些,区别是什么?

  • 基本数据类型: String  Number  Boolean undefined null symbol BigInt
  • 引用数据类型: Object (普通对象object, 数组对象Array, 正则对象RegExp, 日期对象Date,函数类型Function...)
  • 简单类型是栈存储
  • 引用类型是堆存储, 然后栈中存储其在堆中的内存地址

函数function是可调用对象,它有一个内部属性[[call]],该属性使其可以被调用。函数不仅是对象还可以拥有属性, 比如length, 函数的length指的是形参列表的长度。

2. js检测数据类型的方法

  • typeof value 检测数据类型的运算符,
    • 返回字符串
    • 检测很快,检测原始值除了null还是很准确的
    • 检测一个未被声明的变量,不会报错,返回 undefined字符串 'undefined'
  • example instanceof class 检测某一数据类型是否属于这个类,
    • 返回布尔值
    • 本意:检测实例是否属于某个类
  • [example].constructor === class 检测实例和类关系的,从而检测数据类型
  • Object.prototype.toString.call(value) 检测数据类型 本意:获取构造函数
    • 返回字符串
    • 检测准确,返回值例如:'[object Number]'、'[object String]'、'[object Boolean]'、'[object Null]'、'[object Undefined]'、'[object BigInt]'、'[object Symbol]'、'[object Object]'、'[object Array]'、'[object Function]'、'[object Date]'、'[object RegExp]'、'[object Error]'、'[object Map]'、'[object Set]'....
  • Array.isArray(value) 检测value是否为数组
    • 返回布尔值
  • isNaN(value) 检测一个值value是否是一个数字
    • 返回boolan
    • Number.isNaN() 与window.isNaN()有区别
javascript
Number.isNaN('123') // false
Number.isNaN(123) // false
Number.isNaN("foo") // false 判断不是数字的数字
Number.isNaN(NaN) // true  只有NaN才为true 等价于 NaN !== NaN

isNaN("123") // false
isNaN(123) // false
isNaN("foo") true 判断不是一个数值
isNaN(NaN) // true

其他检测方法: 检测一个值时候,是先把这个值转化为number,再进行isNaN 检测

3. typeof 的细节点-->有8种结果

  1. typeof检测的结果首先是一个字符串 ,字符串中包含了对应的数据类型(例如:“number”、“string”、“boolean”、“undefined“、”object”、“function”、“symbol” 、“bigint”)
  2. 特殊的检测结果:NaN、Infinity
javascript
typeof  NAN === 'string' 
typeof  Infinity === 'string'
typeof null === 'object'

为何 typeof null === 'object'?

  • 这是计算机的bug:所有的值在计算中都已二进制编码存储,浏览器把前三位是000的当做对象,而null的二进制前三位就是000,所以被识别为对象,但是它不是对象,他是空对象指针,是基本类型值
  1. typeof 普通对象/数组对象/正则对象... 结果都是"object" , 这样就无法基于typeof区分是普通对象还是数组对象等了

二. 数据类型之间的转换

1. 隐式转换

  1. []+{} === "[object Object]"; {}+[] === 0
  2. isNaN(_value_) 检测一个值时候,是先把这个值转化为number,再进行isNaN 检测

2. 转数字

1 Number(val) 隐式转换就是这样执行的

javascript
Number(false) // 0
Number(true) // 1

Number(null) // 0

Number(undefined) // NaN

Nunber('') // 0
Number('234') // 234
Number('abc') // NaN
Number(NaN) // NaN
Number('123f') // NaN
Number('1.2E35') // 1.2e+35(数值)
Number('2e+35') // 2e+35(数值)

Number(Symbol(123)) // Uncaught TypeError: Cannot convert a Symbol value to a number

Number(234n) // 234
Number(BigInt(1234)) // 1234

Number([]) // 0
Number([2]) // 2
Number([1,2]) // NaN
Nunmber({}) // NaN

对象 -> Number遵循规则Symbol.toPrimitive/valueOf/toString/Number

  • 先看对象是否有valueof()方法, 有则执行并返回基本类型值 --> 再执行Number()
  • 没有valueof方法则使用toString()处理一下 --> 接着执行Number()
  • 若没有toString方法直接报类型错误
javascript
// eg:1
var a = {
  valueOf: function() {
    return '42'
  }
}
Number(a) // 42

// eg:2
var b = {
 toString(){
    return '42'
  }
}
Number(b) // 42

// eg:3
var c = [1,2,3]
c.toString = function(){
  return '42'
}
Number(c) // 42

注意:

  • 0开头的八进制数也是按照十进制处理, 例如:0x7a - 0 === 122, (八进制0x7a转换成为十进制就是122)

2 parsetInt/parserFloat

parsetInt

parsetInt 是把一个字符串转换为数字(如果值不是字符串,也要先转换为字符串), 原理: 从字符串最左侧开始查找,把所有的有效数字字符转换为数字,一旦遇到一个非有效数字字符,则停止向后查找(不管后面还有数字都不找了)

javascript
parseInt("42px"); // 42
parseInt("w42px"); // NaN
parseInt("42"); // 42

parsetInt()可以接收第二个参数,表示要将第一个参数看做是几进制的数值对待,取值[2-36]之间,不传不一定默认10 !!!,所以在使用 parseInt 时,一定要指定一个 radix。

javascript
parseInt('123', 5) // 将'123'看作 5 进制数,返回十进制数 38 => 1*5^2 + 2*5^1 + 3*5^0 = 38

如果 radix 是 undefined、0 或未指定的,JavaScript 会假定以下情况:

  1. 如果输入的 string 以 0x 或 0X(一个 0,后面是小写或大写的 X)开头,那么 radix 被假定为 16,字符串的其余部分被当做十六进制数去解析。
  2. 如果输入的 string 以 "0"(0)开头,radix 被假定为 8(八进制)或 10(十进制)。具体选择哪一个 radix 取决于实现。ECMAScript 5 澄清了应该使用 10 (十进制),但不是所有的浏览器都支持。因此,在使用 parseInt 时,一定要指定一个 radix
  3. 如果输入的 string 以任何其他值开头,radix 是 10 (十进制)。

parsetFloat

  • 如果 parseFloat 在解析过程中遇到了除正号(+)、负号(- U+002D HYPHEN-MINUS)、数字(0-9)、小数点(.)、或者科学记数法中的指数(e 或 E)以外的字符,则它会忽略该字符以及之后的所有字符,返回当前已经解析到的浮点数。
  • 第二个小数点的出现也会使解析停止(在这之前的字符都会被解析)。
  • 参数首位和末位的空白符会被忽略。
  • 如果参数字符串的第一个字符不能被解析成为数字,则 parseFloat 返回 NaN。
  • parseFloat 也可以解析并返回 Infinity
  • parseFloat 解析 BigIntNumbers, 丢失精度。因为末位 n 字符被丢弃。
  • parseFloat 也可以转换一个已经定义了 toString 或者 valueOf 方法的对象,它返回的值和在调用该方法的结果上调用 parseFloat 值相同。

3. 转字符串

javascript
基本类型
null --> "null"
undefined  --> "undefined" 
true --> "true"
123 --> "123"

//极大或者极小的数值
var a = 1.07*1000*1000*1000*1000*1000*1000*1000; // 7个1000一共21位数字
a.toString() // 1.07e21

Symbol(123).toString() // "Symbol(123)"
BigInt(100).toString() // "100"
100n.toString() // "100"
  • 对于普通对象转字符串,除非自行定义,否则toString返回内部属性[[Class]]的值, 如 "[object Object]"
  • 若对象拥有自己的toString方法,在字符创化时候,就会调用该方法并使用其返回值。
  • JSON序列化并不是严格意义上的强制类型转换,细节可见下面的JSON章节

4. 转布尔

以下的值转成Boolean都为false

  • undefined
  • null
  • false
  • +0-0
  • NaN
  • ""
  • document.allIE10及之前浏览器中都是false, 现在都是true

可以理解为除了以上的值为假值外,剩下的都为真值。注意:

  1. document.all,是一个伪数组对象, 包含了整个页面的所有元素,由DOM提供给JS程序使用,在IE10(包含)以前,他转Boolean都是是true, IE11开始就为false了,其他浏览器也是falsedocument.all不是标准语法,已被废弃,一般有用的可能就是if(document.all){/* it's IE */};
  2. 负数也属于真值:-1 --> true

5. ~运算符

~x大致等同于-(x+1)

~一般和indexOf联合起来用,很方便:当indexOf(...)返回-1~将值转换为假值0,其他情况一律转换为真值 例如:

javascript
var a = "hello world";

~a.indexOf("lo"); // -4  --> 为真

if(~a.indexOf("lo")) {  // true
  /* 找到匹配 */
}

~a.indexOf("ol"); // 0  --> 为false
if(~a.indexOf("ol")) { // false
  /* 没有找到匹配 */
}

注意:由-(x+1)推断~-1的结果应该是-0,然而结果是0,因为它是字位操作,而非数学运算;

6. []+{}{}+[]的结果

js
[] + { }; // "[object Object]"
{ } + []; // 0

为什么会出现上面的情况? 答:[] + {}; 中, {} 出现在 +运算符表达式中,被当做一个空对象来处理,[]会被转成"", 字符串和对象相加得到字符串,所以{}转为字符串就是"[object Object]"{}+[];中,{}被单做一个独立的空代码块,不执行任何操作,代码块结尾不需要分号,因此语法上没问题,而+[]显示转换最终就是0

附录: 类型转换表

image.png

三. 数值误差

1. js中, 0.1+0.2为什么不等于0.3?

因为在 0.1+0.2 的计算过程中发生了两次精度丢失。 第一次是在 0.1 和 0.2 转成双精度二进制浮点数时,由于二进制浮点数的小数位只能存储52位,导致小数点后第53位的数要进行为1则进1为0则舍去的操作,从而造成一次精度丢失。 第二次在 0.1 和 0.2 转成二进制浮点数后,二进制浮点数相加的过程中,小数位相加导致小数位多出了一位,又要让第53位的数进行为1则进1为0则舍去的操作,又造成一次精度丢失。最终导致 0.1+0.2 不等于0.3 。 结论:浮点数再用二进制表示时候,二进制位数限制导致发生截取丢失精度。

2. 如何解决浮点数计算精度丢失的问题?

方法1:第三方库 。例如Math.js 方案2:将数字转为字符串, 然后去掉小数点,转为整数后计算,再加上小数点

3. 如何比较两个浮点数是否相等?

javascript
let a = 0.1+0.2;

a === 0.3 // false


/*
 ES6开始, Number里面内置了一个称为“机器精度”的值(2^-52),通过Number.EPSILON来访问
 在指定的误差范围内,若两个数值之间的差值的绝对值小于这个“机器误差”,则视为两值相等
*/
function numberCloseEnoughToEqual(a, b){
  return Math.abs(a - b) < Number.EPSILON;
}

numberCloseEnoughToEqual((0.1+0.2), 0.3) // true
numberCloseEnoughToEqual(0.2, 0.200000000000000000000142578) // true (因此需要指定的精度)

4. js中的几个数值

javascript
// 最大浮点数 大约是 1.798e+308(是一个相当大的数字)
Number.MAX_VALUE

// 最小浮点数 大约是 5e-324, (他不是负数,只是无限趋近于0)
Number.MIN_VALUE

// 最大安全数字 2^53 - 1 (能安全呈现的最大整数)
Number.MAX_SAFE_INTEGER

// 最小安全数字 -2^53 -1(能安全呈现的最小整数)
Number.MIN_SAFE_INTEGER

5. 整数检测

1)检测一个数值是否为整数

  • ES6新增API
javascript
Number.isInteger(123) // true
Number.isInteger(123.1) // false
  • 若要兼容ES6之前的版本,可以这样
javascript
if(!Number.isInteger) {
 number.isInteger = function(num) {
    return typeof num === 'number' && num % 1 === 0;
  }
}

2)检测一个值是否是安全的整数

  • ES6新增API
javascript
Number.isSafeInteger(Number.MAX_SAFE_INTEGER) // true
Number.isSafeInteger(Math.pow(2,53)) // false
  • 若要兼容ES6之前的版本,可以这样
javascript
if(!Number.isSafeInteger) {
  Number.isSafeInteger = function(num) {
    return typeof num === 'number' && num % 1 === 0 && Math.abs(num)<=Number.MAX_SAFE_INTEGER;
  }
}

四. undefined 与 Null

1. undefined 与 null的区别?

  • undefinednull类型都只有一个值,就是他们自己本身
  • undefined表示从未赋值, null指赋过值,但目前没有值;
  • typeof null === 'object',typeof undefined === 'undefined'
  • JSON序列化的时候,会过滤掉值为undefined的属性,但是不会过滤值为null的属性
  • null转数字是0undefined转数字是NaN

五. JSON

1. JSON.stringify

  1. JSON.stringify(value[, replacer [, space]]) 接受3个参数
  • value: 要序列化的值
  • replacer(可选):如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。
  • space(可选):指定缩进用的空白字符串,用于美化输出(pretty-print);如果参数是个数字,它代表有多少的空格;上限为 10。该值若小于 1,则意味着没有空格;如果该参数为字符串(当字符串长度超过 10 个字母,取其前 10 个字母),该字符串将被作为空格;如果该参数没有提供(或者为 null),将没有空格。
  1. 所有安全的JSON值,都可以使用JSON.stringify()字符串化,安全的JSON值是指能够呈现为有效的JSON格式的值。

不安全的JSON值:undefined、function、symbol和包含循环引用(对象之间相互引用,形成一个无限循环)的对象都不符合JSON的结构标准

  1. JSON.stringify()在对象中遇到undefined、function、symbol时都会将其忽略,在数组中则会返回null,NaN会转换为null,以保证数组单元的位置不变。
javascript
  JSON.stringify(undefined) // undefined
  JSOn.stringify(function () {}) // undefined

  JSON.stringify([1, undefined, function () {}, Symbol('hello'), NaN, '2']) // "[1, null, null, null, null, '2']"
  JSON.stringify({ a: 2, b: function () {}, c: null, d: undefined, e: NaN, f: Symbol('f') }) // "{ "a": 2, "c": null, "e": null }"

对包含循环引用的对象执行JSON.stringify()会出错

2. toJSON

如果对象中定义了toJSON()方法,JSON字符串化时会首先调用该方法,然后用它的返回值来进行序列化

如果要对含有非法JSON值的对象做字符串化,或者对象中的某些值无法被序列化时,就需要定义toJSON()方法来返回一个安全的JSON值

javascript
var o = {}
var a = {
  b: 42,
  c: o,
  d: function () {}
}
// 在a中创建一个循环引用
o.e = a 
// 循环引用在这里会产生错误
// JSON.stringify(a) 

// 自定义JSON序列化
a.toJSON = function () {
  // 序列化仅包含b
  return { b: this.b }
}
JSON.stringify(a) // "{"b":42}"

六. 两值比较

1. =====有什么区别?

“==”允许在相等比较中进行强制类型转换,而“===”不允许。 原理: “==”和“===”都会检查操作数的类型。区别在于操作数类型不同时候,“==”会对操作数其中之一或两者做隐式强制类型转换,转换为相同的类型后,再进行比较,但是“===”却不会,而是直接比较。 因此我们在使用的时候需要考虑有没有做强制类型转换的必要,有就用“==”,没有必要就用“===”。

2. 使用== 比较的5条规则12158067.png

  • 规则 1NaN和其他任何类型比较永远返回false(包括和他自己)。
  • 规则 2Boolean 和其他任何类型比较,Boolean 首先被转换为 Number 类型。
  • 规则 3StringNumber比较,先将String转换为Number类型。
  • 规则 4null == undefined比较结果是true,除此之外,nullundefined和其他任何结果的比较值都为false
  • 规则 5:原始类型和引用类型做比较时,引用类型会依照ToPrimitive规则转换为原始类型。如果还是没法得到一个原始类型,就会抛出 TypeError

ToPrimitive规则,是引用类型向原始类型转变的规则,它遵循先valueOf后toString的模式期望得到一个原始类型。

开发中的建议:不要盲目听命于代码工具,每一处都使用===,更不要对这个问题置若罔闻,应该充分理解 === 与==,正确使用它们

3. ==中一些常规例子

javascript
"0" == null     // false null只==undefined和自己
"0" == undefined  // false undefined只==null和自己
"0" == false    // true  先fasle->0 再"0"->0 
"0" == ""      // false 都是string
"0" == NaN     // false NaN和谁都不相等
"0" == []      // false 数组没有valueOf 直接使用toString=>"" "0"不等于""

0 == null      // false null只==undeined和null
0 == undefined    // false undefined只== null和undefined
0 == NaN      // false NaN和谁都不等
0 == []       // true [].toString() -> "" -> 0
0 == {}       // false {} -> valueOf() -> 不能得到原始类型,所以toString() -> '[object Object]' -> NaN
0 == false     // true false->0

false == null    // false null只== undefined
false == undefined  // false undefined只== null
fasle == NaN    // false NaN和谁都不等
false == []     // true false->0  []->'' -> 0
false == {}     // false false->0 {}->'[object Object]' -> NaN

"" == null     // false null只==undefined
"" == undefined   // false undefined只== null
"" == NaN      // false NaN谁都不等
"" == []      // true []->0 ""->0
"" == {}      // false {}->'[object Object]'

七. 其他

1. ??||?.的区别

??叫做 空置合并操作符,当左侧的操作数为 null 或者 undefined 时,返回其右侧操作数,否则返回左侧操作数。 ?. 叫做可选链,当左侧的操作数为nullundefined时短路,避免报错,可以代替常用的.;

??Node14.0.0开始支持, chrome80开始支持,火狐72开始支持 ?.Node14.0.0开始支持,chrome80开始支持,火狐74开始支持

语法:

  • 值1 ?? 值2
  • 值1 || 值2
  • 取值obj.val?.prop obj.val?.[expr] 函数调用obj.func?.(args)

注意:可选链不能用于赋值,比如 判断方式不同: 使用 ?? 时,只有当值1为nullundefined时才返回值2; 使用 || 时,值1会转换为布尔值判断,为true返回值1,false返回值2

javascript
undefined ?? 2 // 2
null ?? 2    // 2
0 ?? 2     // 0
"" ?? 2     // ""
true ?? 2    // true
false ?? 2   // false

2. 语句块 {}

{...}并不是没有赋值的孤立常量,而是一个普通的代码块,它和for/while循环以及if条件语句中代码块的作用基本相同。在ES6/ES2015中有块级作用域的效果。

3. 什么是暂时性死区?

ES6中有了块级作用域的概念,若是代码块中的变量未申明就使用的情况,会报错,这就是暂时性死区(简称TDZ),最直观的就是let/const的块级作用域; 对未声明的变量使用typeof不会报错,但是在块级作用域中会报错; image.png

4. 全局DOM变量的坑

由于浏览器演进的历史遗留问题,在创建带有id属性的DOM元素时也会创建同名的全局变量,值就为对于DOM元素,就是说HTML页面的内容也会产生全局变量,并且稍不注意就很容易让全局变量检查错误百出。

javascript
// 页面中HTML内容
<div id="app">...</div>

// js中打印
console.log(app) // 输出DOM元素 <div id="app">...</div>

// 覆盖app
app = 123
console.log(app) // 123