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

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

javascript的数据类型可以分为两类:原始类型(基础数据类型)对象类型(引用数据类型)
原始类型包括:数字字符串布尔值、以及特殊的undefinednull
除了以上的数据类型,其他就都是对象类型了
具有代表性的对象类型有:对象(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的数字表示范围是有限制的(能否表示的限制能否满足精度到个位的限制以及能否作为数组索引的限制
具体的情况如下图:
Number数轴
(图片来自网络,侵删)

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

下溢的情况为:
当运算的结果无限接近于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

建议使用这个方法!

最后,最近一段时间我的博客会保持长时间更新,针对文章有什么问题,大家可以在下方留言,感谢!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值