开辟了一个关于javascript的基础系列,更加深入、细致的了解这门语言。今天分享的是js的数据类型。
javascript的数据类型可以分为两类:原始类型(基础数据类型)
和对象类型(引用数据类型)
原始类型包括:数字
、字符串
、布尔值
、以及特殊的undefined
和null
除了以上的数据类型,其他就都是对象类型了
具有代表性的对象类型有:对象(object)
、数组(array)
、函数(function)
本次我们着重介绍原始数据类型
两个小注意点:
1.js语言是弱类型语言(并不是没有数据类型)
2.在js语言中所声明的变量是没有数据类型的,因此可以被赋予任何类型的值
原始类型(基础数据类型)
数字
和其他变成语言不同,js不区分正整数值和浮点数值,js中所有数字都是用浮点数值表示的。
数字的算术运算符方法有+
,-
,*
,/
,%
(加,减,乘,除,余)。
除此之外,js还支持更复杂的算术运算,这些复杂运算通过作为Math对象的属性定义和常量来实现:
// 2的53次幂
Math.pow(2, 53)
// 0.6的四舍五入值
Math.round(0.6)
// 向上取整
Math.ceil(0.6)
// 向下取整
Math.floor(0.6)
// 取绝对值
Math.abs(-5)
// 求出x,y,z的最大值
Math.max(x, y, z)
// 求出x,y,z的最小值
Math.min(x, y, z)
// 生成一下大于等于0小于1的随机数
Math.random()
// 圆周率
Math.PI
// e自然对数的底数
Math.E
// 3的开平方根
Math.sqrt(3)
// 3的开立方根
Math.pow(3, 1/3)
// 三角函数
Math.sin(0)
// 求10的自然对数
Math.log(10)
// 以10为底数的100的对数
Math.log(100)/Math.LN10
// 以2为底数的512的对数
Math.log(512)/Math.LN2
// e的3次方幂
Math.exp(3)
js的数字表示范围是有限制的(能否表示的限制、能否满足精度到个位的限制以及能否作为数组索引的限制)
具体的情况如下图:
(图片来自网络,侵删)
因此javascript在进行数学运算时,会出现溢出和下溢两种情况,
溢出的情况为:
当运算结果超出了js语言所能表示的上线(即图中1.8e308
—正无穷
的区域),结果会返回Infinity
(表示无穷大)
同样的,当计算的负数的值超过了能表示的负数范围(即图中-1.8e308
—负无穷
的区域),结果会返回-Infinity
(表示负无穷大)
下溢的情况为:
当运算的结果无限接近于0,并比js能表示的最小值还小的情况(即图中0
—5e-324
的区域)。这样结果会返回0
。
同样的,当一个负数发生下溢(即图中0
—-5e-324
的区域),这时结果会返回一个-0
。
上文,我们介绍数字中预定义的全局变量Infinity
,此外还有一个预定义的全局变量NaN
(表示非数字,not-a-number,当运算的结果并不是一个数字值的时候,会返回NaN
)
在js中,NaN有特殊的一点,就是它和任何值都不相等(包括自身),因此想要判断一个值是否为NaN
,可以使用x != x
判断
var x = 1 - 'a'
x != x //true
除此之外,我们还可以调用全局预定好的函数isNaN
// 当传入的参数只要不是一个数字,就返回true
isNaN(5 - 'a') // true
isNaN('1') // true
isNaN('a') // true
isNaN({a: 2}) // true
isNaN(1) //false
isNaN(Infinity) //false
另外,全局还有一个预定好的函数isFinite
// 当传入的参数只要不是NaN, Infinity, -Infinity就返回true
isFinite(5 - 'a') // false
isFinite('1') // false
isFinite('a') // false
isFinite({a: 2}) // false
isFinite(1) // true
isFinite(Infinity) //false
数学中实数有无限多个,而在javascript语言中能通过浮点数的形式只能表现其中的有限个,因此在js中使用实数的时候,我们往往都是使用的一个近似值。
javscript所采用的浮点数表示发,是一种二进制表示法,因此我们可以精确的表示1/2
、1/8
、1/1024
。但是在数学中,我们常用的都是十进制分数1/10
。所以js中并不能精确的表示像0.1
这样简单的数字。
var x = 0.3-0.2
var y = 0.2-0.1
x == y // false
因此要避免在js中用浮点数进行计算(尽量使用整数)
文本
javascript中的字符串采用的是UTF-16编码的Unicode字符集
,字符串的长度是其含有16位值的个数,如下:
var a = 'z'
var b = '?' // 注意,这个字不是“吉祥”的吉
a.length // => 1: a包含的一个16位值 \u007A
b.length // => 2: b包含两个16位值 \uD842\uDFB7
在js语言中,字符串是由单引号或双引号括起来的字符序列,定义的由单引号定界的字符串中可以包含双引号,同样,定义的由双引号定界的字符串中也可以包含单引号。
字符串可以拆分为数行,每行必须以\
结束,如果希望在字符串中再起一行可以使用转义字符\n
全部的转义字符如下:
测试输出结果如下:
但是,在ES6中,新增了模板字符串,模板字符串是用反勾号`
将字符括起
在模板字符串中换行就简单很多:
`
hello
world
`
// 等价于
'hello\nworld'
除此之外,模板字符串还支持元素注入
var str = 'world'
`hello ${world}`
// 等价于
'hello ' + str
除了字符串的length属性之外,字符串还有很多可以调用的方法
var str = 'Hello, World'
str.charAt(0) // H, 返回第一个位置的字符
str.charAt(s.length - 1) // t, 返回最后一个位置的字符
str.substring(1,4) // ell, 返回位置2-4的字符
str.slice(1,4) // ell, 同上
str.slice(-3) // rld, 返回最后三个字符
str.indexOf('l') // 2, 返回首次出现l的位置
str.lastIndexOf('l') // 10,返回最后一次出现l的位置
str.split(", ") // ['Hello', 'World'], 分割为数组
str.replace('H', 'h') // 'hello, World', 将h替换为H
str.toUpperCase() // 'HELLLO, WORLD', 将字符串所有字母变为大写
str.toLowerCase() // 'hello, world', 将字符串所有字母变为小写
// es6新增方法
let s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
// includes():返回布尔值,表示是否找到了参数字符串。
// startsWith():返回布尔值,表示参数字符串是否在原字符串的头部。
// endsWith():返回布尔值,表示参数字符串是否在原字符串的尾部。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
'na'.repeat(0) // ""
// repeat方法返回一个新字符串,表示将原字符串重复n次
需要注意的是,对于字符串的任何方法都会返回一个新的字符串,而不会在原字符串上修改。
布尔值
javascript中布尔值有两个true
和false
null&undefined
null和undefined都表示”值的空缺“,但事从背后更深远的角度考虑,他们的还是有差别的。
对null进行typeof
检测,返回值是object
对undefined进行typeof
检测,返回值是undefined
undefined表示,对这个值还未定义,还没有进行初始化。比如,当我们声明一个变量,但是却未赋值,此时会返回undefined,当我们获取一个对象未定义的属性,此时会返回undefined,当我们调用一个函数,却未传参,参数会返回undefined。
null表示,没有对象,此处没有值,此处不应该有值。比如,原型链的重点就是null。
后面会从栈
和堆
的角度进行另一番解释。
我们可以这么理解,undefined是系统级的、出乎意料的、类似错误的空缺。而null是程序级的、正常的、在意料之中的值的空缺。在某些场景下,比如想赋值给一个变量,想表示变量为空,或作为参数传入一个函数,这是,最佳的选择是null。
包装对象
了解包装对象之前,我们首先思考这么一个问题。
var a = 'test'
a.length //4
我们知道上面代码中的a
是一个字符串,字符串不是一个对象,不能进行.
关键字的操作。但是,为什么我们可以得到a.length
呢?
因为只要存在包装对象的概念,在上述代码执行的过程中,js会将字符串通过new String
的方式生成一个包装对象,这个对象继承了String
的方法,因为可以通过.
的方式访问到。一旦属性的引用结束,这个包装对象就会被销毁(其实在js语言内部的实现上不一定创建或销毁这个对象,但是整个过程在执行层面看起来是这样的,我们也可以这么进行理解)
原始类型和引用类型的变与不变关系
想要深入理解原始类型和引用类型的变与不变,相等比较等问题的时候,我们需要借助栈
和堆
的思想来理解,我们可以这么思考:
(图片来自网络,侵删)
这张图阐述了原始类型和引用类型的关系:
原始类型保存在栈内存中,原始类型(包括字符串、数字、布尔型、undefined)是保存在栈内存中,是不可以修改的(我们所看到的修改,其实都是删除后重新赋值),当复制一个原始类型的时候,其实就是在内存中复制这么值。其中,undefined代表的就是未被赋值的一个栈内存的区域。
引用类型保存在堆内存中,但是在栈内存中存了一个引用类型的地址,栈内存中的地址有一个指针指向堆内存的引用类型。这个引用类型是可以进行修改的,比如我们可以向数组中push一个新值。如果我们只是简单的复制一个引用类型(浅拷贝),那么其实复制的是这个在栈内存中的地址,复制后的值发生修改,那么之前被复制的值也同样会被修改,因此在复制引用类型的时候,最好要进行深拷贝。其中,null很特殊,表示的是在栈内存中,有一个指针指向堆内存中的引用类型,一旦这个指针掉了,就是null。
类型转换
jacascript中的类型转换非常常见,也是javascript语言中非常重要的一点。
首先我们来看一下类型转化表:
任意JavaScript的值都可以转换为布尔值,只有undefine、null、0、NaN、""会被转换为false,其他所有值都会被转换成true。
当字符串转化为数字数字时,那些数字表示的字符串可以转化为数字,也允许在开始和结尾处有空格,但是其他含有非空非数字字符都不会完成到数字的转化,他们会转化为NaN。
原始值到对象的转换也非常简单,原始值通过调用构造函数,转化为包装对象。
null和undefined除外,他们太特殊了,他们不会到对象进行正常的转化。
其他类型的原始值会按照上表的方式进行转换。
下面我们介绍一下,由对象转化为原始值的过程:
JavaScript中对象到字符串的转换经过如下步骤:
1.如果对象具有toString(),则调用这个方法,如果该方法返回一个原始值,则最后转换成字符串。
2.如果对象没有toString()方法,或者这个方法并不返回一个原始值,那么JavaScript会调用valueOf()方法,如果返回的是原始值,最后就转换为字符串。
如果JavaScript无法从toString()和valueOf()中获得一个原始值,就会抛出类型错误的异常。
对象到数字的转换过程中:
JavaScript优先调用valueof()方法,再调用toString()。
我们可以知道,利用!
,+
,==
进行隐式类型转换
在这里,我们有必要了解==
的类型转换机制,如下:
1.如果两个操作数的类型相同,则和上文所述的严格相等的比较规则一样。如果严格相等,那么比较结果为相等。如果它们不严格相等,则比较结果为不相等。
2.如果两个操作数类型不同,“==”相等操作符也可能会认为它们相等。检测相等将会遵守如下规则和类型转换:
- 如果一个值是null,另一个是undefined,则它们相等。
- 如果一个值是数字,另一个是字符串,先将字符串转换为数字,然后使用转换后的值行比较。
- 如果其中一个值是true,则将其转换为1再进行比较。如果其中一个值是false,则将其转换为0再进行比较。
- 如果一个值是对象,另一个值是数字或字符串,则使用上面讲到的规则先将对象(先调用valueof()方法,再调用toString())转化为原始值,再进行下一步的比较。
说完了,隐式的类型转换,我们再看一下js语言提供的显式类型转换:
首先就是最简单的Boolean()
,Number()
,String()
和Object()
,此外我们知道的toString()
方法和String()
返回的结果是一样的。
Number('3') // => 3
String({}) // => '[object Object]'
String([]) // => ''
Boolean([]) // => true
Boolean('0') // => true
Boolean(0) // => false
Object(3) // => new Number(3)
其次就是我们知道的一些全局函数:toFixed
,toExponential
,toPrecision
,parseInt
,parseFloat
var n = 123.45
n.toFixed(0) // => '123'
n.toFixed(2) // => '123.45'
// 根据指定小数点后的位数,返回字符串
n.toExponential(1) // => '1.2e+5'
// 将数字进行科学计数法,传入参数为小数点后数字个数,返回一个字符串
n.toPrecision(4) // => '123.4'
// 传入参数为保留数字的个数,返回一个字符串
类型检测
首先介绍一下typeof
typeof
运算符返回的不是该变量的类型,而是该变量持有值的类型。在js中直接访问一个未声明的变量,会抛出异常,但是在typeof a
中,不会抛出异常,并且返回undefined
。这样就能通过判断是否存在该变量而安全使用该变量。typeof
运算符适合于检测原始类型和函数。
typeof undefined === 'undefined'
typeof true === 'boolean'
typeof 42 === 'number'
typeof 'str' === 'string'
typeof Symbol() === 'symbol'
typeof null === 'object'
typeof function () {} === 'function'
其次介绍一下instanceof
{a: 1} instanceof Object
:右操作符是一个函数构造器,其原理是判断左边对象的原型链上是否有右边构造器的prototype
属性。不同window或iframe间的对象不能使用instanceof。
[1,2] instanceof Array // => true
[1,2] instanceof Object // => true
'3' instanceof String // => false
new String('3') instanceof String // => true
new String('3') instanceof Object // => true
因此我们看出instanceof
的问题,他对于原始数据类型根本无法检测,对引用数据类型也不能很清楚的判定类别。而且,一旦修改了原型链环节上的prototype
,检测就无法使用。
然后我们再来看一下constructor
我们首先明确一下这个概念:
Object.prototype.constructor === Object // => true
String.prototype.constructor === String // => true
构造函数的prototype
中的constructor属性指向的是这个构造函数本身,因此我们可以利用这个特点。
'1'.constructor === String // => true
(1).constructor === Number // => true
[1,2,3].constructor === Array // => true
除了undefined
和null
,其他类型的变量均能使用constructor
判断出类型。
但是constructor
可以被靠前的原型链覆盖。
var a = [1,2,3]
a.constructor = Object
a.constructor === Array // => false
所以这个也不是很靠谱
最后我们来看一下Object.prototype.toString.call
这个方法百试百灵,是目前公认的最靠谱检测数据类型的方法
var toString = Object.prototype.toString;
console.log(toString.call(new Date) === '[object Date]') //true
console.log(toString.call(new String) ==='[object String]') //true
console.log(toString.call(new Function) ==='[object Function]') //true
console.log(toString.call(Type) ==='[object Function]') //true
console.log(toString.call('str') ==='[object String]') //true
console.log(toString.call(Math) === '[object Math]') //true
console.log(toString.call(true) ==='[object Boolean]') //true
console.log(toString.call(/^[a-zA-Z]{5,20}$/) ==='[object RegExp]') //true
console.log(toString.call({name:'wenzi', age:25}) ==='[object Object]') //true
console.log(toString.call([1, 2, 3, 4]) ==='[object Array]') //true
console.log(toString.call(undefined) === '[object Undefined]') //true
console.log(toString.call(null) === '[object Null]') //true
建议使用这个方法!
最后,最近一段时间我的博客会保持长时间更新,针对文章有什么问题,大家可以在下方留言,感谢!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。