变量类型
数据类型
最新的 ECMAScript
标准定义了 7
种数据类型
对象的底层数据结构
Symbol类型在实际开发中的应用,手动实现一个简单的Symbol
变量在内存中的具体存储形式
提示
栈内存:原始类型
以键值对的方式储存在栈内存中
堆内存:复杂类型
会在堆内存中开辟一块空间,储存这个对象的值,并同时在栈内存中储存变量和指向对象的指针
栈内存 | 堆内存 |
---|---|
存储基础数据类型 | 存储引用数据类型 |
按值访问 | 按引用访问 |
存储的值大小固定 | 存储的值大小不定,可动态调整 |
由系统自动分配内存空间 | 由程序员通过代码进行分配 |
主要用来执行程序 | 主要用来存放对象 |
空间小,运行效率高 | 空间大,但是运行效率相对较低 |
先进后出,后进先出 | 无序存储,可根据引用直接获取 |
基本类型对应的内置对象,装箱拆箱操作
基本类型对应的内置对象
Boolean
Number
String
true === new Boolean(true) // false
1874 === new Number(1874) // false
'summer' === new String('summer') // false
true === new Boolean(true).valueOf() // true
注意
引用类型和包装类型的主要区别就是对象的生存期,使用new
操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中,而自基本类型则只存在于一行代码的执行瞬间,然后立即被销毁,这意味着我们不能在运行时为基本类型添加属性和方法。
const name = 'summer'
name.age = 24
=> name.age // undefined
装箱拆箱操作
- 装箱转换:把基本类型转换为对应的包装类型
- 拆箱操作:把引用类型转换为基本类型
既然原始类型不能扩展属性和方法,那么我们是如何使用原始类型调用方法的呢?
每当我们操作一个基础类型时,后台就会自动创建一个包装类型的对象,从而让我们能够调用一些方法和属性,例如下面的代码:
var name = "ConardLi";
var name2 = name.substring(2);
复制代码实际上发生了以下几个过程:
- 创建一个
String
的包装类型实例 - 在实例上调用
substring
方法 - 销毁实例
也就是说,我们使用基本类型调用方法,就会自动进行装箱和拆箱操作,相同的,我们使用Number
和Boolean
类型时,也会发生这个过程。
从引用类型到基本类型的转换,也就是拆箱的过程中,会遵循ECMAScript
规范规定的toPrimitive
原则,一般会调用引用类型的valueOf
和toString
方法,你也可以直接重写toPeimitive
方法。一般转换成不同类型的值遵循的原则不同,例如:
- 引用类型转换为
Number
类型,先调用valueOf
,再调用toString
- 引用类型转换为
String
类型,先调用toString
,再调用valueOf
若valueOf
和toString
都不存在,或者没有返回基本类型,则抛出TypeError
异常。
const obj = {
valueOf: () => { console.log('valueOf'); return 123 },
toString: () => { console.log('toString'); return 'ConardLi' }
};
console.log(obj - 1) // valueOf 122
console.log(`${obj}ConardLi`) // toString ConardLiConardLi
const obj2 = {
[Symbol.toPrimitive]: () => { console.log('toPrimitive'); return 123 },
};
console.log(obj2 - 1) // valueOf 122
const obj3 = {
valueOf: () => { console.log('valueOf'); return {} },
toString: () => { console.log('toString'); return {} },
};
console.log(obj3 - 1)
// valueOf
// toString
// TypeError
除了程序中的自动拆箱和自动装箱,我们还可以手动进行拆箱和装箱操作。我们可以直接调用包装类型的valueOf或toString,实现拆箱操作:
const num =new Number('123')
console.log( typeof num.valueOf() ) // number
console.log( typeof num.toString() ) // string
理解值类型和引用类型
- 值类型等于原始类型: 以键值对的方式储存在栈内存中
- 复杂类型等于引用类型:会在堆内存中开辟一块空间,储存这个对象的值,并同时在栈内存中储存变量和指向对象的指针
null 和 undefined 的区别
null
null
表示"没有对象"
,即该处不应该有值。典型用法是:
- 作为函数的参数,表示该函数的参数不是对象。
- 作为对象原型链的终点。
Object.getPrototypeOf(Object.prototype)
// null
undefined
undefined
表示"缺少值"
,就是此处应该有一个值,但是还没有定义。典型用法是:
- 变量被声明了,但没有赋值时,就等于
undefined
。 - 调用函数时,应该提供的参数没有提供,该参数等于
undefined
。 - 对象没有赋值的属性,该属性的值为
undefined
。 - 函数没有返回值时,默认返回
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
判断 JavaScript 数据类型
判断JS数据类型的四种方法
typeOf
instanceof
constructor
toString
['Boolean', 'Number', 'String', 'Null', 'Undefined', 'Symbol', 'Object', 'Function', 'Array', 'Error', 'RegExp', 'Math', 'JSON', 'global']
typeOf
typeof
操作符返回一个字符串,表示未经计算的操作数的类型。
调用 | 结果 |
---|---|
typeof true | boolean |
typeof 1874 | number |
typeof 'summer' | string |
typeof null | |
typeof undefined | undefined |
typeof Symbol() | symbol |
typeof {} | object |
typeof function() {} | function |
typeof [] | |
typeof new Error() | |
typeof new RegExp() | |
typeof Math | |
typeof JSON | |
typeof window |
instanceof
instanceof
运算符用于测试构造函数的prototype
属性是否出现在对象的原型链中的任何位置。
- 所有引用类型都具有对象特性,即可以自由扩展属性
- 所有引用类型都具有一个
__proto__
(隐式原型)属性,是一个普通对象- 所有的函数都具有
prototype
(显式原型)属性,也是一个普通对象- 所有引用类型
__proto__
值指向它构造函数的prototype
- 当试图得到一个对象的属性时,如果变量本身没有这个属性,则会去他的
__proto__
中去找
[] instanceof Array
实际上是判断Foo.prototype
是否在[]
的原型链上。
[] instanceof Array // true
new Date() instanceof Date // true
new RegExp() instanceof RegExp // true
注意
使用instanceof
来检测数据类型,不会很准确,这不是它设计的初衷:
[] instanceof Object // true
function(){} instanceof Object // true
使用instanceof
也不能检测基本数据类型,所以instanceof
并不是一个很好的选择。
constructor
toString
每一个引用类型都有toString方法,默认情况下,toString()方法被每个Object对象继承。如果此方法在自定义对象中未被覆盖,toString() 返回 "[object type]",其中type是对象的类型。
提示
注意,上面提到了如果此方法在自定义对象中未被覆盖,toString才会达到预想的效果,事实上,大部分引用类型比如Array、Date、RegExp等都重写了toString方法。
我们可以直接调用Object原型上未被覆盖的toString()方法,使用call来改变this指向来达到我们想要的效果。
调用 | 结果 |
---|---|
Object.prototype.toString.call(true) | [object Boolean] |
Object.prototype.toString.call(1874) | [object Number] |
Object.prototype.toString.call('summer') | [object String] |
Object.prototype.toString.call(null) | [object Null] |
Object.prototype.toString.call(undefined) | [object Undefined] |
Object.prototype.toString.call(Symbol()) | [object Symbol] |
Object.prototype.toString.call({}) | [object Object] |
Object.prototype.toString.call(function() {}) | [object Function] |
Object.prototype.toString.call([]) | [object Array] |
Object.prototype.toString.call(new Error()) | [object Error] |
Object.prototype.toString.call(new RegExp()) | [object RegExp] |
Object.prototype.toString.call(Math) | [object Math] |
Object.prototype.toString.call(JSON) | [object JSON] |
Object.prototype.toString.call(window) | [object Window] |
利用闭包
and toString
封装
不推荐将这个函数用来检测可能会产生包装类型的基本数据类型上,因为 call 会将第一个参数进行装箱操作
// es6
const typeValidate = type => target => `[object ${type}]` === Object.prototype.toString.call(target)
// es5
function typeValidate(type) {
return function (target) {
return "[object " + type + "]" === Object.prototype.toString.call(target)
}
}
const isArray = typeValidate('Array')
const isString = typeValidate('String')
isString(new String('summer')) // true
isArray([]) // true
当你想判断一个基本类型的数据时,你可以用
typeof
去判断,它很简单,而且可靠;当你想判断一个对象属于哪个子类型时,你可以使用instanceof
运算符或constructor
属性,但是你需要有个预期的类型,不然就要针对每一种类型写不一样的if...else...
语句,还有一点需要注意的就是constructor
属性可以被修改,所以并不可靠;如果你不嫌代码量多,要求准确且全面,那你可以用Object.prototype.toString.call()
进行判断。
可能发生隐式类型转换的场景以及转换原则,应如何避免或巧妙应用
==
非严格相等 相等操作符比较两个值是否相等,在比较前将两个被比较的值转换为相同类型。在转换后(等式的一边或两边都可能被转换),最终的比较方式等同于全等操作符
===
的比较方式。 相等操作符满足交换律。
判等
x | y | == | === | Object.is |
---|---|---|---|---|
undefined | undefined | true | true | true |
null | null | true | true | true |
true | true | true | true | true |
false | false | true | true | true |
"foo" | "foo" | true | true | true |
0 | 0 | true | true | true |
+0 | -0 | true | true | false |
0 | false | true | false | false |
"" | false | true | false | false |
"" | 0 | true | false | false |
"0" | 0 | true | false | false |
"17" | 17 | true | false | false |
[1,2] | "1,2" | true | false | false |
new String("foo") | "foo" | true | false | false |
null | undefined | true | false | false |
null | false | false | false | false |
undefined | false | false | false | false |
{ foo: "bar" } | { foo: "bar" } | false | false | false |
new String("foo") | new String("foo") | false | false | false |
0 | null | false | false | false |
0 | NaN | false | false | false |
"foo" | NaN | false | false | false |
NaN | NaN | false | false | true |
Truthy (真值)
在
JavaScript
中,Truthy
(真值)指的是在 布尔值 上下文中转换后的值为真的值。所有值都是真值,除非它们被定义为falsy
(即除了false
,0
,""
,null
,undefined
和NaN
外)。 JavaScript 中的真值示例如下(将被转换为 true,if 后的代码段将被执行):
if (true)
if ({})
if ([])
if (42)
if ("foo")
if (new Date())
if (-42)
if (3.14)
if (-3.14)
if (Infinity)
if (-Infinity)
出现小数精度丢失的原因,JavaScript 可以存储的最大数字,最大安全数字,JavaScript 处理大数字的方法,避免精度丢失的方法
出现小数精度丢失的原因
最大数字,最大安全数字
Number.MAX_VALUE
Number.MAX_SAFE_INTEGER
处理大数字的方法,避免精度丢失的方法
node-bignum
node-bigint
number-precision
bigInt
类型在es10
中被提出,现在Chrome中已经可以使用
参考
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Data_structures
- https://juejin.im/entry/589c29a9b123db16a3c18adf
- http://www.ruanyifeng.com/blog/2014/03/undefined-vs-null.html
- https://juejin.im/post/5cec1bcff265da1b8f1aa08f#heading-7
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Equality_comparisons_and_sameness