最近脑子里有冒出“多看点书”的想法,但我个人不是很喜欢翻阅纸质书籍,另一方面也是因为我能抽出来看书的时间比较琐碎,所以就干脆用app看电子书了(如果有比较完整的阅读时间,还是建议看纸质书籍,排版看起来更舒服点)。考虑到平时工作遇到的大部分问题还是javascript强相关的,于是我选择从《Javascript权威指南第6版》开始。

数据类型有哪些?

javascript的数据类型分为两大类,一类是原始类型(primitive type),一类是对象类型(object type)。

原始类型

原始类型又称为基本类型,分为Number, String, Boolean, Undefined, Null几类。比较特殊的是,undefinedUndefined类型中的唯一一个值;同样地,nullNull类型中的唯一一个值。

除此之外,ES6引入了一个比较特殊的原始类型Symbol,用于表示一个独一无二的值,具体使用方法可以看阮一峰老师的ECMAScript6入门,或者直接翻阅MDN,我平时看MDN比较多,感觉比较权威,API也很完善。

为什么说Symbol是原始类型,而不是对象类型呢?因为我们知道,大部分程序员都是没有对象的,那么要想找到女朋友,最快的办法就是new一个。

const options = {
'性格': '好',
'颜值': '高',
'对我': '好'
}
const gf = new GirlFriend(options) // new一个女朋友

好了,不皮了,回到正题,意思就是,Symbol是没有构造函数constructor的,不能通过new Symbol()获得实例。

但是获取symbol类型的值是通过调用Symbol函数得到的。

const symbol1 = Symbol('Tusi')

Symbol值是唯一的,所以下面的等式是不成立的。

Symbol(1) === Symbol(1) // false

对象类型

对象类型也叫引用类型,简单地理解呢,对象就是键值对key:value的集合。常见的对象类型有Object, Array, Function, Date, RegExp等。

除了这些,Javascript还有蛮蛮多的全局对象,具体见JavaScript 标准内置对象。但是全局对象并不意味着它就是一种对象类型,就比如JSON是一个全局对象,但是它不是一种类型,这一点要搞清楚。

前面说了,对象可以new出来,所以对象类型都有构造函数,Object类型对应的构造函数是Object()Array类型对应的构造函数是Array(),不再赘述。

var obj = new Object() // 不过我们一般也不会这么写一个普通对象
var arr1 = new Array(1) // 创建一个length是1的空数组
var arr2 = new Array(1, 2) // 创建数组[1, 2]

栈内存和堆内存

栈内存的优势是,存取速度比堆内存要快,充分考虑这一点,其实是可以优化代码性能的。

栈内存

原始类型是按值访问的,其值存储在栈内存中,所占内存大小是已知的或是有范围的;

对基本类型变量的重新赋值,其本质上是进行压栈操作,写入新的值,并让变量指向一块栈顶元素(大概意思是这样,但是v8等引擎有没有做这方面的优化,就要细致去看了)

var a = 1; // 压栈,1成为栈顶元素,其值赋给变量a
a = 2; // 压栈,2成为栈顶元素,并赋值给变量a(内存地址变了)

堆内存

而对象类型是按引用访问的,通过指针访问对象。

指针是一个地址值,类似于基本类型,存储于栈内存中,是变量访问对象的中间媒介。

而对象本身存储在堆内存中,其占用内存大小是可变的,未知的。

举例如下:

var b = { name: 'Tusi' }

运行这行代码,会在堆内存中开辟一段内存空间,存储对象{name: 'Tusi'},同时声明一个指针,其值为上述对象的内存地址,指针赋值给引用变量b,意味着b引用了上述对象。

对象可以新增或删除属性,所以说对象类型占用的内存大小一般是未知的。

b.age = 18; // 对象新增了age属性

那么,按引用访问是什么意思呢?

我的理解是:对引用变量进行对象操作,其本质上改变的是引用变量所指向的堆内存地址中的对象本身。

这就意味着,如果有两个或两个以上的引用变量指向同一个对象,那么对其中一个引用变量的对象操作,会影响指向该对象的其他引用变量。

var b = { name: 'Tusi' }; // 创建对象,变量b指向该对象
var c = b; // 声明变量c,指向与b一致
b.age = 18; // 通过变量b修改对象
// 产生副作用,c受到影响
console.log(c); // {name: "Tusi", age: 18}

考虑到对象操作的副作用,我们会在业务代码中经常使用深拷贝来规避这个问题。

数据类型的判断

判断数据类型是非常重要的基础设施之一,那么如何判断数据类型呢?请接着往下看。

typeof

javascript本身提供了typeof运算符,可以辅助我们判断数据类型。

typeof操作符返回一个字符串,表示未经计算的操作数的类型。

typeof的运算结果如下,引用自MDN typeof

数据类型 运算结果
Undefined "undefined"
Null "object"
Boolean "boolean"
Number "number"
String "string"
Symbol "symbol"
Function "function"
其他对象 "object"
宿主对象(由JS环境提供,如Nodejs有global,浏览器有window) 取决于具体实现

可以看到,typeof能帮我们判断出大部分的数据类型,但是要注意的是:

  1. typeof null的结果也是"object"
  2. 对象的种类很多,typeof得到的结果无法判断出数组,普通对象,其他特殊对象

那么如何准确地知道一个变量的数据类型呢?

结合instanceof

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

利用instanceof,我们可以判断一个对象是不是某个构造函数的实例。那么结合typeof,我们可以封装一个基本的判断数据类型的函数。

基本思想是:首先看typeof是不是返回"object",如果不是,说明是普通数据类型,那么直接返回typeof运算结果即可;如果是,则需要先把null这个坑货摘出来,然后依次判断其他对象类型。

function getType(val) {
const type = typeof val;
if (type === 'object') {
if (val === null) {
// null不是对象,所以不能用instanceof判断
return 'null'
} else if (val instanceof Array) {
return 'array'
} else if (val instanceof Date) {
return 'date'
} else if (// 其他对象的instanceof判断) {
return 'xxx'
} else if (val instanceof Object) {
// 所有对象都是Object的实例,所以放最后
return 'object'
}
} else {
return type
}
}
// 测试下
getType(Symbol(1)) // "symbol"
getType(null) // "null"
getType(new Date()) // "date"
getType([1, 2, 3]) // "array"
getType({}) // "object"

但是,要把常用的对象类型都列举出来也是有点麻烦的,所以也不算一个优雅的方法。

终极神器toString

有没有终极解决方案?当然是有的。但是,不是标题中的toString,而是Object.prototype.toString。用上它,不仅上面的数据类型都能被判断出来,而且也可以判断ES6引入的一些新的对象类型,比如Map, Set等。

// 利用了Object.prototype.toString和正则表达式的捕获组
function getType(val) {
return Object.prototype.toString.call(val).replace(/\[object\s(\w+)\]/, '$1').toLowerCase();
} getType(new Map()) // "map"
getType(new Set()) // "set"
getType(new Promise((resolve, reject) => {})) // "promise"

为什么普通的调用toString不能判断数据类型,而Object.prototype.toString可以呢?

因为Object是基类,而各个派生类,如DateArray等在继承Object的时候,一般都重写(overwrite)了toString方法,用以表达自身业务,从而失去了判断类型的能力。

装箱和拆箱

首先解释一下什么是装箱和拆箱,把原始类型转换为对应的对象类型的操作称为装箱,反之是拆箱。

装箱

我们知道,只有对象才可以拥有属性和方法,但是我们在使用一些基本类型数据的时候,却可以直接调用它们的一些属性或方法,这是怎么回事呢?

var a = 1;
a.toFixed(2); // "1.00" var b = 'I love study';
b.length; // 12
b.substring(2, 6); // "love"

其实在读取一些基本类型数据的属性或方法时,javascript会创建临时对象(也称为“包装对象”),通过这个临时对象来读取属性或方法。以上代码等价于:

var a = 1;
var aObj = new Number(a);
aObj.toFixed(2); // "1.00" var b = 'I love study';
var bObj1 = new String(b);
bObj1.length; // 12
var bObj2 = new String(b);
bObj2.substring(2, 6); // "love"

临时对象是只读的,可以理解为它们在发生读操作后就销毁了,所以不能给它们定义新的属性,也不能修改它们现有的属性。

var c = '123';
c.name = 'jack'; // 给临时对象加新属性是无效的
c.name; // undefined
c.length; // 3
c.length = 2; // 修改临时对象的属性值,是无效的
c.length; // 3

我们也可以显示地进行装箱操作,即通过String(), Number(), Boolean()构造函数来显示地创建包装对象。

var b = 'I love study';
var bObj = new String(b);

拆箱

对象的拆箱操作是通过valueOftoString完成的,且看下文。

类型的转换

javascript在某些场景会自动执行类型转换操作,而我们也会根据业务的需要进行数据类型的转换。类型的转换规则如下:

对象到原始值的转换

toString

toString()是默认的对象到字符串的转换方法。

var a = {};
a.toString(); // "[object Object]"

但是很多类都自定义了toString()方法,举例如下:

  • Array:将数组元素用逗号拼接成字符串作为返回值。
var a = [1, 2, 3];
a.toString(); // 1,2,3
  • Function:返回一个字符串,字符串的内容是函数源代码。
  • Date:返回一个日期时间字符串。
var a = new Date();
a.toString(); // "Sun May 10 2020 11:19:29 GMT+0800 (中国标准时间)"
  • RegExp:返回表示正则表达式直接量的字符串。
var a = /\d+/;
a.toString(); // "/\d+/"

valueOf

valueOf()会默认地返回对象本身,包括Object, Array, Function, RegExp

日期类Date重写了valueOf()方法,返回一个1970年1月1日以来的毫秒数。

var a = new Date();
a.toString(); // 1589095600419

对象 --> 布尔值

从上表可见,对象(包括数组和函数)转换为布尔值都是true

对象 --> 字符串

对象转字符串的基本规则如下:

  • 如果对象具有toString()方法,则调用这个方法。如果它返回字符串,则作为转换的结果;如果它返回其他原始值,则将原始值转为字符串,作为转换的结果。
  • 如果对象没有toString()方法,或toString()不返回原始值(不返回原始值这种情况好像没见过,一般是自定义类的toString()方法吧),那么javascript会调用valueOf()方法。如果存在valueOf()方法并且valueOf()方法返回一个原始值,javascript将这个值转换为字符串(如果这个原始值本身不是字符串),作为转换的结果。
  • 否则,javascript无法从toString()valueOf()获得一个原始值,会抛出异常。

对象 --> 数字

与对象转字符串的规则类似,只不过是优先调用valueOf()

  • 如果对象具有valueOf()方法,且valueOf()返回一个原始值,则javascript将这个原始值转换为数字(如果原始值本身不是数字),作为转换结果。
  • 否则,如果对象有toString()方法且返回一个原始值,javascript将这个原始值转换为数字,作为转换结果。
  • 否则,javascript将抛出一个类型错误异常。

显示转换

使用String(), Number(), Boolean()函数强制转换类型。

var a = 1;
var b = String(a); // "1"
var c = Boolean(a); // true

隐式转换

在不同的使用场景中,javascript会根据实际情况进行类型的隐式转换。举几个例子说明下。

加法运算符+

我们比较熟悉的运算符有算术运算符+, -, *, /,其中比较特殊的是+。因为加法运算符+可以用于数字加法,也可以用于字符串连接,所以加法运算符的两个操作数可能是类型不一致的。

当两个操作数类型不一致时,加法运算符+会有如下的运算规则。

  • 如果其中一个运算符是对象,则会遵循对象到原始值的转换规则,对于非日期对象来说,对象到原始值的转换基本上是对象到数字的转换,所以首先调用valueOf(),然而大部分对象的valueOf()返回的值都是对象本身,不是一个原始值,所以最后也是调用toString()去获得原始值。对于日期对象来说,会使用对象到字符串的转换,所以首先调用toString()
1 + {}; // "1[object Object]"
1 + new Date(); // "1Sun May 10 2020 22:53:24 GMT+0800 (中国标准时间)"
  • 在进行了对象到原始值的转换后,如果加法运算符+的其中一个操作数是字符串的话,就将另一个操作数也转换为字符串,然后进行字符串连接。
var a = {} + false; // "[object Object]false"

var b = 1 + []; // "1"
  • 否则,两个操作数都将转换为数字(或者NaN),然后进行加法操作。
var a = 1 + true; // 2

var b = 1 + undefined; // NaN

var c = 1 + null; // 1

[] == ![]

还有个很经典的例子,就是[] == ![],其结果是true。一看,是不是觉得有点懵,一个值的求反竟然还等于这个值!其实仔细分析下过程,就能发现其中的奥秘了。

  1. 首先,我们要知道运算符的优先级是这样的,一元运算符!的优先级高于关系运算符==

  1. 所以,右侧的![]首先会执行,而逻辑非运算符!会首先将其操作数转为布尔值,再进行求反。[]转为布尔值是true,所以![]的结果是false。此时的比较变成了[] == false
  2. 根据比较规则,如果==的其中一个值是false,则将其转换为数字0,再与另一个操作数比较。此时的比较变成了[] == 0
  3. 接着,再参考比较规则,如果一个值是对象,另一个值是数字或字符串,则将对象转为原始值,再进行比较。左侧的[]转为原始值是空字符串"",所以此时的比较变成了"" == 0
  4. 最后,如果一个值是数字,另一个是字符串,先将字符串转换为数字,再进行比较。空字符串会转为数字000自然是相等的。

搞懂了这个问题,也可以分析下为什么{} == !{}的结果是false了,这个就比较简单了。

看到这里,你还觉得数据类型是简单的知识点吗?有兴趣深究的朋友可以翻阅下ES5的权威解释

最后

数据类型是javascript中非常重要的一部分,搞清楚数据类型的基本知识点,对于学习javascript的后续知识点多有裨益。

另外,写笔记其实对思考问题很有帮助,就算只是总结很简单的基础知识,也是多有助益。

以上内容是个人笔记和总结,难免有错误或遗漏之处,欢迎留言交流。

js数据类型很简单,却也不简单的更多相关文章

  1. vue—你必须知道的 js数据类型 前端学习 CSS 居中 事件委托和this 让js调试更简单—console AMD && CMD 模式识别课程笔记(一) web攻击 web安全之XSS JSONP && CORS css 定位 react小结

    vue—你必须知道的   目录 更多总结 猛戳这里 属性与方法 语法 计算属性 特殊属性 vue 样式绑定 vue事件处理器 表单控件绑定 父子组件通信 过渡效果 vue经验总结 javascript ...

  2. js便签笔记(13)——jsonp其实很简单【ajax跨域请求】

    前两天被问到ajax跨域如何解决,还真被问住了,光知道有个什么jsonp,迷迷糊糊的没有说上来.抱着有问题必须解决的态度,我看了许多资料,原来如此... 为何一直知道jsonp,但一直迷迷糊糊的不明白 ...

  3. js数据类型简单介绍

    JS数据类型 ECMAScript中有5种简单的数据类型:Undefined,Null,Boolean,Number,String.还有一种复杂的数据类型--Object(本质上是由一组无序的名值对组 ...

  4. JavaScript大厦之地基:js数据类型

    一.数据和类型        俗话说物以类聚,人以群分:这里将人和物都按类别进行了区分.我们数据也一样,使用计算机我们能处理数值,也可以处理文本还可以处理图形.音频.视频等各种各样的数据,不同的数据有 ...

  5. 由js apply与call方法想到的js数据类型(原始类型和引用类型)

    原文地址:由js apply与call方法想到的js数据类型(原始类型和引用类型) js的call方法与apply方法的区别在于第二个参数的不同,他们都有2个参数,第一个为对象(即需要用对象a继承b, ...

  6. 用JS做一个简单的电商产品放大镜功能

    使用js制作一个简单的产品放大图 购物网站的产品页经常会放有一个产品展示图区.该图区有一个功能就是产品图的放大功能,移动左侧的焦点区域,可以放大细节部分观看,详情如下图.实现该功能的方法也非常简单. ...

  7. JavaScript学习10 JS数据类型、强制类型转换和对象属性

    JavaScript学习10 JS数据类型.强制类型转换和对象属性 JavaScript数据类型 JavaScript中有五种原始数据类型:Undefined.Null.Boolean.Number以 ...

  8. 分享:计算机图形学期末作业!!利用WebGL的第三方库three.js写一个简单的网页版“我的世界小游戏”

    这几天一直在忙着期末考试,所以一直没有更新我的博客,今天刚把我的期末作业完成了,心情澎湃,所以晚上不管怎么样,我也要写一篇博客纪念一下我上课都没有听,还是通过强大的度娘完成了我的作业的经历.(当然作业 ...

  9. HTTP真的很简单

    原文:HTTP Made Really Easy因为我本身网络基础就很差,所以看到这篇文章一方面是学习网络知识,另一方面为了锻炼我蹩脚的英语水平,文中如有错误,欢迎浏览指正! 前言 在看这篇文章的时候 ...

随机推荐

  1. C#中分布式事务的超时处理问题

    事务是个很精妙的存在,我们在数据层.服务层.业务逻辑层等多处地方都会使用到. 在这里我只说下TransactionScope这个微软推荐使用的隐式事务.它是从Framework 2.0开始引入的一个事 ...

  2. codeforces Equalizing by Division (easy version)

    output standard output The only difference between easy and hard versions is the number of elements ...

  3. Spring Data REST不完全指南(二)

    上一篇文章介绍了Spring Data REST的功能及特征,以及演示了如何在项目中引入Spring Data REST并简单地启动演示了Spring Data REST项目.在本文中,我们将深入了解 ...

  4. CVE-2018-12613 的一些思考

    复现 CVE-2018-12613 的一些思考,关于文件包含路径的问题 漏洞 /index.php 第 55 行 $target_blacklist = array ( 'import.php', ' ...

  5. PHP 将字符串转换为字符集格式UTF8/GB2312/GBK 函数iconv()

     iconv()介绍 iconv函数可以将一种已知的字符集文件转换成另一种已知的字符集文件 iconv('要转化的格式',‘转化后的格式’,‘转化的数据’); 但是转化是经常出错,一般需要在转成的编码 ...

  6. 虚拟化KVM之优化(三)

    KVM的优化 1.1 cpu的优化 inter的cpu的运行级别,(Ring2和Ring1暂时没什么用)Ring3为用户态,Ring0为内核态 Ring3的用户态是没有权限管理硬件的,需要切换到内核态 ...

  7. OpenCV学习(1)——初步接触

    一.介绍OpenCV           OpenCV的全称是Open Source Computer Vision Library,是一个跨平台的计算机视觉库.OpenCV是由英特尔公司发起并参与开 ...

  8. Spring Cloud 系列之 Stream 消息驱动(二)

    本篇文章为系列文章,未读第一集的同学请猛戳这里:Spring Cloud 系列之 Stream 消息驱动(一) 本篇文章讲解 Stream 如何实现消息分组和消息分区. 消息分组 如果有多个消息消费者 ...

  9. 飞机大战-面向对象-pygame

    飞机大战 最近学习了python的面向对象,对面向对象的理解不是很深刻. 面向对象是数据和函数的'打包整理',将相关数据和处理数据的方法集中在一个地方,方便使用和管理. 本着学习的目的,在网上找了这个 ...

  10. vue项目中上拉加载和下拉刷新页面的实现

    功能:上拉加载,下拉刷新 使用方法: 自己创建一个.vue的文件(我自己是创建了一个PullToRefresh.vue的文件),将代码粘贴进去,具体的样式问题自己在该文件中调整. <templa ...