【基础系列】javascript数据类型(原始类型)

开辟了一个关于javascript的基础系列,更加深刻、细致的了解这门语言。今天分享的是js的数据类型。

javascript的数据类型能够分为两类:原始类型(基础数据类型)对象类型(引用数据类型)
原始类型包括:数字字符串布尔值、以及特殊的undefinednull
除了以上的数据类型,其余就都是对象类型了
具备表明性的对象类型有:对象(object)数组(array)函数(function)javascript

本次咱们着重介绍原始数据类型java

两个小注意点:
1.js语言是弱类型语言(并非没有数据类型)
2.在js语言中所声明的变量是没有数据类型的,所以能够被赋予任何类型的值es6

原始类型(基础数据类型)

数字

和其余变成语言不一样,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的数字表示范围是有限制的(可否表示的限制可否知足精度到个位的限制以及可否做为数组索引的限制
具体的状况以下图:
Number数轴
(图片来自网络,侵删)网络

所以javascript在进行数学运算时,会出现溢出下溢两种状况,
溢出的状况为:
当运算结果超出了js语言所能表示的上线(即图中1.8e308正无穷的区域),结果会返回Infinity(表示无穷大)
一样的,当计算的负数的值超过了能表示的负数范围(即图中-1.8e308负无穷的区域),结果会返回-Infinity(表示负无穷大)dom

下溢的状况为:
当运算的结果无限接近于0,并比js能表示的最小值还小的状况(即图中05e-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/21/81/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

所有的转义字符以下:

clipboard.png

测试输出结果以下:

clipboard.png

可是,在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中布尔值有两个truefalse

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语言内部的实现上不必定建立或销毁这个对象,可是整个过程在执行层面看起来是这样的,咱们也能够这么进行理解)

原始类型和引用类型的变与不变关系

想要深刻理解原始类型和引用类型的变与不变,相等比较等问题的时候,咱们须要借助的思想来理解,咱们能够这么思考:

clipboard.png
(图片来自网络,侵删)

这张图阐述了原始类型和引用类型的关系:
原始类型保存在栈内存中,原始类型(包括字符串、数字、布尔型、undefined)是保存在栈内存中,是不能够修改的(咱们所看到的修改,其实都是删除后从新赋值),当复制一个原始类型的时候,其实就是在内存中复制这么值。其中,undefined表明的就是未被赋值的一个栈内存的区域。

引用类型保存在堆内存中,可是在栈内存中存了一个引用类型的地址,栈内存中的地址有一个指针指向堆内存的引用类型。这个引用类型是能够进行修改的,好比咱们能够向数组中push一个新值。若是咱们只是简单的复制一个引用类型(浅拷贝),那么其实复制的是这个在栈内存中的地址,复制后的值发生修改,那么以前被复制的值也一样会被修改,所以在复制引用类型的时候,最好要进行深拷贝。其中,null很特殊,表示的是在栈内存中,有一个指针指向堆内存中的引用类型,一旦这个指针掉了,就是null。

类型转换

jacascript中的类型转换很是常见,也是javascript语言中很是重要的一点。
首先咱们来看一下类型转化表:

clipboard.png

任意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)

其次就是咱们知道的一些全局函数:toFixedtoExponentialtoPrecisionparseIntparseFloat

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

除了undefinednull,其余类型的变量均能使用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

建议使用这个方法!

最后,最近一段时间个人博客会保持长时间更新,针对文章有什么问题,你们能够在下方留言,感谢!
相关文章
相关标签/搜索