数据类型
系列开篇
面试题
- js中的基本类型可以列举下吗
- 基本类型和引用类型区别
- 强制类型转换的一些手写问题
- 数据类型判断方法
- 什么时候自动转换为string类型
- Object.is和===的区别
- ...... 这个方面可以问很多问题,注重对细节的把握,编程是个细活
javascript是弱类型语言
JavaScript 是一种弱类型或者说动态语言。
这意味着你不用提前声明变量的类型,在程序运行过程中,类型会被自动确定。这也意味着你可以使用同一个变量保存不同类型的数据。
那么声明变量的时候并没有预先确定的类型。也就是说变量当前的类型由其值所决定,计算时能发生隐式类型转换,或者叫强制类型转换。
最新的 ECMAScript 标准定义了 8 种数据类型:
7 种 原始类型 | 基本数据类型 | 值类型 |
- Boolean
- Null
- Undefined
- Number
- BigInt
- String
- Symbol
和 引用数据类型 | 对象类型
- Object
基本数据类型详细查看
Boolean
这个没啥好说
Number
NaN
是一种特殊的Number类型 下面列举什么时候返回NaN:
- 无穷大除以无穷大
- 给任意负数做开方运算
- 算数运算符与不是数字或无法转换为数字的操作数一起使用
- 字符串解析成数字
例子
Infinity / Infinity; // 无穷大除以无穷大
Math.sqrt( - 1); // 给任意负数做开方运算
// 算数运算符与不是数字或无法转换为数字的操作数一起使用
// 注意没有 '+' 运算符
'a' - 1;
'a' * 1;
'a' / 1;
// 字符串解析成数字
parseInt('a');
parseFloat('a');
Number('a');
'abc' - 1
undefined + 1
//一元运算符(注意点)
+ 'abc' // NaN
- 'abc' // NaN
undefined++
String
toString()
toString() 可以将数据都转为字符串,但是null
和undefined
不可以转换。例如
console.log(null.toString())
//报错 TypeError: Cannot read property 'toString' of null
console.log(undefined.toString())
//报错 TypeError: Cannot read property 'toString' of undefined
toString()第一个参数,代表进制
- 二进制:.toString(2);
- 八进制:.toString(8);
- 十进制:.toString(10);
- 十六进制:.toString(16);
let a = 10
a.toString(2) //1010
String()
String()可以将null和undefined转换为字符串,但是没法转进制字符串
console.log(String(null));
// null
console.log(String(undefined));
// undefined
string类型转换开发过程中可能出错的点
let obj = {
width: '100'
};
obj.width + 20 // “10020"
预期输出结果120 实际输出结果10020
Null
虽然 typeof null 会输出 object 但null不是对象
Undefined
这个也没啥好说 '未定义'
Symbol
Symbol实例是唯一且不可改变的。也就是说,Symbol 生成一个全局唯一的值可以保证不会与其他属性名产生冲突。
Symbol 作为属性名,遍历对象的时候,该属性不会出现在for...in、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。
但是,它也不是私有属性,有一个Object.getOwnPropertySymbols()
方法,可以获取指定对象的所有 Symbol 属性名。该方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
Symbol("foo") !== Symbol("foo")
const foo = Symbol()
const bar = Symbol()
typeof foo === "symbol"
typeof bar === "symbol"
let obj = {}
obj[foo] = "foo"
obj[bar] = "bar"
JSON.stringify(obj) // {}
Object.keys(obj) // []
Object.getOwnPropertyNames(obj) // []
Object.getOwnPropertySymbols(obj) // [ foo, bar ]
详细内容请查看 MDN
另外有个妙用 利用Symbol消除魔术字符串
Bigint
BigInt
是一种新的数据类型,用于当整数值大于Number数据类型支持的范围时。这种数据类型允许我们安全地对大整数执行算术操作,表示高分辨率的时间戳,使用大整数id,等等,而不需要使用库。
为什么需要BigInt?
在JS中,所有的数字都以双精度64位浮点格式表示,那这会带来什么问题呢? 这导致JS中的Number无法精确表示非常大的整数,它会将非常大的整数四舍五入,确切地说,JS中的Number类型只能安全地表示
-9007199254740991(-(2^53-1)) 和 9007199254740991((2^53-1))
,
任何超出此范围的整数值都可能失去精度。
console.log(99999999999999999); //=>1000000000000000000
// 同时也会有一定的安全性问题:
9007199254740992 === 9007199254740993; // 居然是true!
如何创建并使用BigInt?
要创建BigInt,只需要在数字 末尾追加n
即可。
console.log( 9007199254740995n ); // → 9007199254740995n
console.log( 9007199254740995 ); // → 9007199254740996
另一种创建BigInt的方法是用BigInt()构造函数
BigInt("9007199254740995"); // → 9007199254740995n
简单使用如下:
10n + 20n; // → 30n
10n - 20n; // → -10n
+10n; // → TypeError: Cannot convert a BigInt value to a number
-10n; // → -10n
10n * 20n; // → 200n
20n / 10n; // → 2n
23n % 10n; // → 3n
10n ** 3n; // → 1000n
const x = 10n;
++x; // → 11n
--x; // → 9n
console.log(typeof x); //"bigint"
值得警惕的点
- BigInt不支持一元加号运算符, 这可能是某些程序可能依赖于 + 始终生成 Number 的不变量,或者抛出异常。
- 因为隐式类型转换可能丢失信息,所以不允许在bigint和 Number 之间进行混合操作。当混合使用大整数和浮点数时,结果值可能无法由BigInt或Number精确表示。
10 + 10n; // TypeError
- 不能将BigInt传递给Web api和内置的 JS 函数,这些函数需要一个 Number 类型的数字。尝试这样做会报TypeError错误。
Math.max(2n, 4n, 6n); // → TypeError
- 当 Boolean 类型与 BigInt 类型相遇时,BigInt的处理方式与Number类似,换句话说,只要不是0n,BigInt就被视为truthy的值。
if(0n){
//条件判断为false
}
if(3n){
//条件为true
}
- 元素都为BigInt的数组可以进行sort
- BigInt可以正常地进行位运算,如|、&、<<、>>和^
引用数据类型|对象类型 列举
- 普通对象-Object
- 数组对象-Array
- 日期对象-Date
- 正则对象-RegExp
- 数学函数-Math
- 函数对象-Function
- Set/Map/WeakSet/WeakMap
基本类型和引用类型区别
不可变与可变
对象类型也叫引用类型,array
和function
是对象的子类型。对象在逻辑上是属性的无序集合,是存放各种值的容器。对象值存储的是引用地址,所以和基本类型值不可变
的特性不同,对象值是可变的
,我们可为为引用类型添加属性和方法,也可以删除其属性和方法
存放位置不同
基本类型值
=>栈内存
引用类型
=>同时在栈内存和堆内存
如
let name = 'hello'
let age = 25;
[栈区]
name | hello
age | 25
栈区包括了 变量的标识符和变量的值
javascript和其他语言不同,其不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间,那我们操作对象的引用,所以引用类型的值是按引用访问的。
准确地说,引用类型的存储需要内存的栈内存和堆内存共同完成,栈区内
存保存变量标识符
和指向堆内存中该对象的指针
,也可以说是该对象在堆内存的地址
。
栈区 堆区
person1 | 堆内存地址1(指针) ---> obj1
强制类型转换 | 隐式类型转换规则
对象 — 原始值转换
当对象相加 obj1 + obj2,相减 obj1 - obj2,或者使用 alert(obj) 打印时会发生什么? 在这种情况下,对象会被自动转换为原始值,然后执行操作。
ToPrimitive对原始类型不发生转换处理
,只针对引用类型(object)的
,其目的是将引用类型(object)转换为非对象类型,也就是原始类型。
ToPrimitive 运算符接受一个值,和一个可选的期望类型作参数。对象到原始值的转换,是由许多期望以原始值作为值的内建函数和运算符自动调用的。
转换后的结果原始类型是由期望类型决定的,期望类型其实就是我们传递的type。直接看下面比较清楚。ToPrimitive方法大概长这么个样子具体如下。
/**
* @obj 需要转换的对象
* @type 期望转换为的原始数据类型,可选
*/
ToPrimitive(obj,type)
- type为string
- 先调用obj的toString方法,如果为原始值,则return,否则第2步
- 调用obj的valueOf方法,如果为原始值,则return,否则第3步
- 抛出TypeError 异常
- type为number
- 调用obj的valueOf方法,如果为原始值,则返回,否则下第2步
- 调用obj的toString方法,如果为原始值,则return,否则第3步
- 抛出TypeError 异常
- type参数为空
- 该对象为Date,则type被设置为String
- 否则,type被设置为Number
toString (Object.prototype.toString())
toString 方法返回一个表示该对象的字符串。
'1'.toString()
为什么可以调用? 其实在这个语句运行的过程中做了这样几件事情:
let s = new Object('1');
s.toString();
s = null;
- 第一步: 创建Object类实例。注意为什么不是String ? 由于Symbol和BigInt的出现,对它们调用new都会报错,目前ES6规范也不建议用new来创建基本类型的包装类。
- 第二步: 调用实例方法。
- 第三步: 执行完方法立即销毁这个实例。
整个过程体现了基本包装类型的性质,而基本包装类型恰恰属于基本数据类型,包括Boolean
, Number
和String
。
valueOf (Object.prototype.valueOf())
valueOf 方法返回指定对象的原始值。
JavaScript 调用 valueOf() 方法用来把对象转换成原始类型的值(数值、字符串和布尔值)。但是我们很少需要自己调用此函数,valueOf 方法一般都会被 JavaScript 自动调用。
let str = new String('123');
console.log(str.valueOf());
// 123 字符串
var num = new Number(123);
console.log(num.valueOf());
// 123 数字类型
let date = newDate();
console.log(date.valueOf());
//1526990889729
let bool = newBoolean('123');
console.log(bool.valueOf());
//true
let obj = newObject({
valueOf: () => {
return 1
}
})
console.log(obj.valueOf());
// 1
Number
对象这里要先转换为原始值,调用ToPrimitive转换,type指定为number
了,继续回到ToPrimitive进行转换(看ToPrimitive)。
- null 转换为
0
- undefined 转换为
NaN
- true 转换为
1
,false 转换为0
- 字符串转换时遵循数字常量规则,
转换失败返回 NaN
String
对象这里要先转换为原始值,调用ToPrimitive转换,type就指定为string
了,继续回到ToPrimitive进行转换(看ToPrimitive)。
- null 转换为
'null'
- undefined 转换为
'undefined'
- true 转换为
'true'
,false 转换为'false'
- 数字转换遵循通用规则,极大极小的数字使用指数形式
String(null) // 'null'
String(undefined) // 'undefined'
String(true) // 'true'
String(1) // '1'
String(-1) // '-1'
String(0) // '0'
String(-0) // '0'
String(Math.pow(1000,10)) // '1e+30'
String(Infinity) // 'Infinity'
String(-Infinity) // '-Infinity'
String({}) // '[object Object]'
String([1,[2,3]]) // '1,2,3'
String(['koala',1]) //koala,1
Boolean
除了下述 6 个值转换结果为 false
,其他全部为 true
- undefined
- null
- -0
- 0或+0
- NaN
- ""(空字符串)
假值以外的值都是真值。其中包括所有对象(包括空对象)的转换结果都是true
甚至连false对应的布尔对象new Boolean(false)
也是true
Boolean(undefined) // false
Boolean(null) // false
Boolean(0) // false
Boolean(NaN) // false
Boolean('') // false
Boolean({}) // true
Boolean([]) // true
Boolean(newBoolean(false)) // true
强制类型转换不同场景应用
什么时候自动转换为string
类型
- 在没有对象的前提下 字符串的自动转换,主要发生在字符串的
加法运算
时。当一个值为字符串,另一个值为非字符串,则后者转为字符串。
'2' + 1 // '21'
'2' + true // "2true"
'2' + false // "2false"
'2' + undefined // "2undefined"
'2' + null // "2null"
- 当有对象且与对象进行加法运算
// toString 的对象
let obj2 = {
toString: function() {
return 'a'
}
}
console.log('2' + obj2) //输出结果 2a
//常规对象
let obj1 = {
a: 1,
b: 2
}
console.log('2' + obj1); //输出结果 2[object Object]
- 几种特殊对象
'2' + {} // "2[object Object]"
'2' + [] // "2"
'2’ + function() {} // "2function (){}"
'2' + ['k', 1] // "2k,1"
'2'+obj2
的详细解析步骤
- 左边为string,
ToPrimitive
原始值转换后不发生变化
- 右边转化时同样按照ToPrimitive进行原始值转换,由于指定的type是number,进行ToPrimitive转化调用
obj2.valueOf()
,得到的不是原始值, 是对象
,进行第三步 - 调用
toString()
return 'a'
是原始值 - 符号两边存在string,而且是
+
号运算符则都采用String
规则转换为string类型进行拼接 - 输出结果2a
'2'+obj1
的详细解析步骤
- 左边为string,
ToPrimitive
转换为原始值后不发生变化
- 右边转化时同样按照
ToPrimitive
进行原始值转换,由于指定的type是number,进行ToPrimitive转化调用obj1.valueOf()
,得到{ a: 1, b: 2}
- 调用toString() return
"[object Object]"
- 符号两边存在string,而且是+号运算符则都采用String规则转换为string类型进行拼接
- 输出结果
"2[object Object]"
注意:不管是对象还不是对象,都有一个转换为原始值的过程,也就是ToPrimitive
转换,只不过原始类型转换后不发生变化,对象类型才会发生具体转换。
什么时候自动转换为Number类型
有加法运算符
,但是无String类型
的时候,都会优先转换为Number类型
true + 0 // 1
true + true // 2
true + false // 1
- 除了加法运算符,
其他运算符都会把运算自动转成数值
'5' - '2' // 3
'5' * '2' // 10
true - 1 // 0
false - 1 // -1
'1' - 1 // 0
'5' * [] // 0
false / '5' // 0
'abc' - 1 // NaN
null + 1 // 1
undefined + 1 // NaN
//一元运算符(注意点)
+ 'abc' // NaN
- 'abc' // NaN
+ true // 1
- false // 0
null
转为数值时为0
,而undefined
转为数值时为NaN
判断等号
也放在Number
里面特殊说明==
抽象相等比较与+
运算符不同,不再是String优先,而是Number优先。- 如果x,y均为number,直接比较没什么可解释的了
- 如果存在对象,
ToPrimitive()
type为number进行转换,再进行后面比较
例1
let obj1 = {
valueOf: function() {
return '1'
}
}
1 == obj1 // true
例2
[] == ![] // true
例3
3 == true // false
'0' == false // true
例4
'0' == 0 //true
1 == obj1
解释
- obj1 转为原始值,调用
obj1.valueOf()
返回原始值'1'
'1'toNumber
得到 1 然后比较 1 == 1 得 true
[] == ![]
解释
[]
作为对象ToPrimitive
得到''
![]
作为boolean转换
得到0
'' == 0
- 转换为
0==0
得到true
3 == true
/'0' == false
解释
- 存在boolean,按照
ToNumber
将boolean
转换为1或者0
,再进行后面比较 3 == 1
得false
/0 == 0
得true
- 如果x为
string
,y为number
,x转成number进行比较
'0' toNumber()
得到0
0 == 0
true
什么时候进行布尔转换
- 布尔比较时
if(obj)
,while(obj)
等判断时或者 三元运算符只能够包含布尔值
if (!undefined && !null && !0 && !NaN && !'') {
console.log('true');
}
// true
- 下面两种情况也会转成布尔类型
expression ? true: false
!! expression
数据类型判断方法
typeof
typeof 操作符返回一个字符串,表示未经计算的操作数的类型。
-
null 的判定有误差,得到的结果如果使用
typeof null
得到的结果是object
-
操作符对对象类型及其子类型,例如函数(可调用对象)、数组(有序索引对象)等进行判定,则除了函数都会得到
object
的结果。
typeof 'nevermore' // 'string'
typeof true // 'boolean'
typeof 10 // 'number'
typeof Symbol() // 'symbol'
typeof null // 'object' 无法判定是否为 null
typeof undefined // 'undefined'
typeof {} // 'object'
typeof [] // 'object'
typeof (() = >{}) // 'function'
instanceof
instanceof 运算符用于检测构造函数的 prototype
属性是否出现在某个实例对象的原型链上
。
[] instanceof Array // true
({}) instanceof Object // true
(() = >{}) instanceof Function // true
// instanceof 也不是万能的
let arr = []
let obj = {}
arr instanceof Array // true
arr instanceof Object // true
obj instanceof Object // true
在这个例子中,arr
数组相当于 new Array()
出的一个实例,所以 arr.proto === Array.prototype
,又因为 Array 属于 Object 子类型,即 Array.prototype.proto === Object.prototype
,所以 Object 构造函数在 arr 的原型链【关联概念】上
。所以 instanceof 仍然无法优雅的判断一个值到底属于数组还是普通对象。
手动实现一下 instanceof 核心: 原型链的向上查找。
function myInstanceof(left, right) {
//基本数据类型直接返回false
if(typeof left !== 'object' || left === null) return false;
//getProtypeOf 是 Object对象自带的一个方法,能够拿到参数的 [原型对象]
let proto = Object.getPrototypeOf(left);
while(true) {
//查找到尽头,还没找到 返回false
if(proto == null) {
return false;
}
//找到相同的原型对象
if(proto == right.prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
}
Object.prototype.toString() [最好]
Object.prototype.toString.call({}) // '[object Object]'
Object.prototype.toString.call([]) // '[object Array]'
Object.prototype.toString.call(() => {}) // '[object Function]'
Object.prototype.toString.call('nevermore') // '[object String]'
Object.prototype.toString.call(1) // '[object Number]'
Object.prototype.toString.call(true) // '[object Boolean]'
Object.prototype.toString.call(Symbol()) // '[object Symbol]'
Object.prototype.toString.call(null) // '[object Null]'
Object.prototype.toString.call(undefined) // '[object Undefined]'
Object.prototype.toString.call(newDate()) // '[object Date]'
Object.prototype.toString.call(Math) // '[object Math]'
Object.prototype.toString.call(newSet()) // '[object Set]'
Object.prototype.toString.call(newWeakSet()) // '[object WeakSet]'
Object.prototype.toString.call(newMap()) // '[object Map]'
Object.prototype.toString.call(newWeakMap()) // '[object WeakMap]'
我们可以发现该方法在传入任何类型的值都能返回对应准确的对象类型
该方法本质就是依托Object.prototype.toString() 方法得到对象内部属性 [[Class]]
传入原始类型却能够判定出结果是因为对值进行了包装
null
和 undefined
能够输出结果是内部实现有做处理
比较运算符
Object.is和===的区别
Object.is
它用来比较两个值是否严格相等,与严格比较运算符(===
)的行为基本一致
不同之处只有两个:
- +0不等于-0,
- NaN等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
== 和 ===有什么区别
===
叫做严格相等,是指:左右两边不仅值要相等,类型也要相等
例如'1'=== 1
的结果是false
,因为一边是string,另一边是number。
==
不像 ===
那样严格,对于一般情况,只要值相等,就返回true,但 ==
还涉及一些类型转换,它的转换规则如下:
- 两边的类型是否相同,相同的话就比较值的大小,例如
1==2
,返回 false - 判断的是否是
null
和undefined
,是的话就返回 true - 判断的类型是否是
String
和Number
,是的话,把String类型转换成Number,再进行比较 - 判断其中一方是否是
Boolean
,是的话就把Boolean转换成Number,再进行比较 - 如果其中一方为
Object
,且另一方为String、Number或者Symbol
,会将Object转换成字符串,再进行比较
console.log({a: 1} == true); // false
console.log({a: 1} == "[object Object]"); // true
如何让if(a == 1 && a == 2)条件成立
let a = {
value: 0,
valueOf: function() {
this.value++;
return this.value;
}
};
console.log(a == 1 && a == 2); //true
这是奇技淫巧,没什么用,看下理解就行。
其他
js 浮点计算精度丢失问题
0.1 + 0.2 = 0.30000000000000004
0.7 + 0.1 = 0.7999999999999999
0.2 + 0.4 = 0.6000000000000001
1.5 - 1.2 = 0.30000000000000004
0.3 - 0.2 = 0.09999999999999998
19.9 * 100 = 1989.9999999999998
0.8 * 3 = 2.4000000000000004
35.41 * 100 = 3540.9999999999995
0.3 / 0.1 = 2.9999999999999996
0.69 / 10 = 0.06899999999999999
原理参考这篇 精度丢失问题-看这篇文章就够了
解决方案 推荐用下面这个库
github.com/nefe/number…
['1', '2', '3'].map(parseInt)
主要考察 JS的映射与解析 和 parseInt这个函数的进制参数。
map接收一个 callback Function 本例就是 parseInt => 转换来看就是
['1', '2', '3'].map((item, index) => {
return parseInt(item, index)
})
那么 index 就是进制
了
parseInt('1', 0) // 1
parseInt('2', 1) // NaN
parseInt('3', 2) // NaN, 3不是二进制
输出 1, NaN, NaN
参考
- developer.mozilla.org/zh-CN/docs/…
- mp.weixin.qq.com/s/Wv5K6M4lT…
- juejin.im/post/5dac5d…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!