主题
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种结果
- typeof检测的结果首先是一个字符串 ,字符串中包含了对应的数据类型(例如:“number”、“string”、“boolean”、“undefined“、”object”、“function”、“symbol” 、“bigint”)
- 特殊的检测结果:NaN、Infinity
javascript
typeof NAN === 'string'
typeof Infinity === 'string'
typeof null === 'object'
为何 typeof null === 'object'
?
- 这是计算机的bug:所有的值在计算中都已二进制编码存储,浏览器把前三位是000的当做对象,而null的二进制前三位就是000,所以被识别为对象,但是它不是对象,他是空对象指针,是基本类型值
- typeof 普通对象/数组对象/正则对象... 结果都是"object" , 这样就无法基于typeof区分是普通对象还是数组对象等了
二. 数据类型之间的转换
1. 隐式转换
[]+{} === "[object Object]"; {}+[] === 0
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 会假定以下情况:
- 如果输入的 string 以 0x 或 0X(一个 0,后面是小写或大写的 X)开头,那么 radix 被假定为 16,字符串的其余部分被当做十六进制数去解析。
- 如果输入的 string 以 "0"(0)开头,radix 被假定为 8(八进制)或 10(十进制)。具体选择哪一个 radix 取决于实现。ECMAScript 5 澄清了应该使用 10 (十进制),但不是所有的浏览器都支持。因此,在使用 parseInt 时,一定要指定一个 radix。
- 如果输入的 string 以任何其他值开头,radix 是 10 (十进制)。
parsetFloat
- 如果 parseFloat 在解析过程中遇到了除正号(+)、负号(- U+002D HYPHEN-MINUS)、数字(0-9)、小数点(.)、或者科学记数法中的指数(e 或 E)以外的字符,则它会忽略该字符以及之后的所有字符,返回当前已经解析到的浮点数。
- 第二个小数点的出现也会使解析停止(在这之前的字符都会被解析)。
- 参数首位和末位的空白符会被忽略。
- 如果参数字符串的第一个字符不能被解析成为数字,则 parseFloat 返回 NaN。
- parseFloat 也可以解析并返回 Infinity。
- parseFloat 解析 BigInt 为 Numbers, 丢失精度。因为末位 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.all
在IE10
及之前浏览器中都是false
, 现在都是true
可以理解为除了以上的值为假值外,剩下的都为真值。注意:
document.all
,是一个伪数组对象, 包含了整个页面的所有元素,由DOM提供给JS程序使用,在IE10(包含)
以前,他转Boolean
都是是true
,IE11
开始就为false
了,其他浏览器也是false
。document.all
不是标准语法,已被废弃,一般有用的可能就是if(document.all){/* it's IE */}
;- 负数也属于真值:
-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
附录: 类型转换表
三. 数值误差
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的区别?
undefined
和null
类型都只有一个值,就是他们自己本身undefined
表示从未赋值,null
指赋过值,但目前没有值;typeof null === 'object',typeof undefined === 'undefined'
JSON
序列化的时候,会过滤掉值为undefined
的属性,但是不会过滤值为null
的属性null
转数字是0
,undefined
转数字是NaN
五. JSON
1. JSON.stringify
JSON.stringify(value[, replacer [, space]])
接受3个参数
value
: 要序列化的值replacer
(可选):如果该参数是一个函数,则在序列化过程中,被序列化的值的每个属性都会经过该函数的转换和处理;如果该参数是一个数组,则只有包含在这个数组中的属性名才会被序列化到最终的 JSON 字符串中;如果该参数为 null 或者未提供,则对象所有的属性都会被序列化。space
(可选):指定缩进用的空白字符串,用于美化输出(pretty-print);如果参数是个数字,它代表有多少的空格;上限为 10。该值若小于 1,则意味着没有空格;如果该参数为字符串(当字符串长度超过 10 个字母,取其前 10 个字母),该字符串将被作为空格;如果该参数没有提供(或者为 null),将没有空格。
- 所有安全的JSON值,都可以使用
JSON.stringify()
字符串化,安全的JSON值是指能够呈现为有效的JSON格式的值。
不安全的JSON值:undefined、function、symbol和包含循环引用(对象之间相互引用,形成一个无限循环)的对象都不符合JSON的结构标准
- 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条规则
- 规则 1:
NaN
和其他任何类型比较永远返回false
(包括和他自己)。 - 规则 2:
Boolean
和其他任何类型比较,Boolean
首先被转换为Number
类型。 - 规则 3:
String
和Number
比较,先将String
转换为Number
类型。 - 规则 4:
null == undefined
比较结果是true
,除此之外,null
、undefined
和其他任何结果的比较值都为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 时,返回其右侧操作数,否则返回左侧操作数。?.
叫做可选链,当左侧的操作数为null
或undefined
时短路,避免报错,可以代替常用的.
;
??
Node14.0.0开始支持, chrome80开始支持,火狐72开始支持?.
Node14.0.0开始支持,chrome80开始支持,火狐74开始支持
语法:
- 值1 ?? 值2
- 值1 || 值2
- 取值
obj.val?.prop
obj.val?.[expr]
函数调用obj.func?.(args)
注意:可选链不能用于赋值,比如 判断方式不同: 使用 ??
时,只有当值1为null
或undefined
时才返回值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
不会报错,但是在块级作用域中会报错;
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