Javascript数据类型知多少?

开发 前端
正如你所知道的,数据类型是作为js的入门知识点,在整个js的学习过程中也是尤为重要的。数据类型看起来简单,但是围绕着其衍生的边界数据类型判断问题、深拷贝浅拷贝问题对于新手而言是难以理解的。

[[438470]]

正如你所知道的,数据类型是作为js的入门知识点,在整个js的学习过程中也是尤为重要的。数据类型看起来简单,但是围绕着其衍生的边界数据类型判断问题、深拷贝浅拷贝问题对于新手而言是难以理解的。

一、数据类型

JavaScript 是一种弱类型或者说动态类型,这就意味着你不需要提前声明变量的类型,在程序运行的过程中,类型会被自动确定。这就意味着你可以使用同一个变量保存不同类型的数据.

js内存分为栈内存(stack)和堆内存(heap)

  • 栈内存:是一种特殊的线性表,它具有后进先出的特性,存放基本类型。
  • 堆内存:存放引用类型(在栈内存中存一个基本类型值保存对象在堆内存中的地址,用于引用这个对象)。

数据类型根据存储方式分为两类:

  • 基本数据类型(简单数据类型、原始数据类型):值存储在栈内存中,被引用或拷贝时,会创建一个完全相等的变量。占用空间小、大小固定,通过按值来访问,属于被频繁使用的数据。
  • 引用数据类型(复杂数据类型):地址存储在栈内存中,值存在了堆内存中,多个引用会指向同一个地址。占据空间大、占用内存不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。

根据上面的标准划分数据类型,常见的有:

  • 基本数据类型:String、Number、Boolean、Undefined、Null、Symbol、BigInt
  • 复杂数据类型:Object、Array、Date、Function、RegExp等

未命名文件 (1).png

二、数据类型的检测

通常的数据类型的检测有三种方法:

  • typeof
  • instanceof

2.1 typeof

使用typeof进行基础数据类型(null除外)检测,但是对于引用数据类型,除了function外,其它的均无法进行判断。

  1. typeof "yichuan"; //"string" 
  2. typeof 18; //"number" 
  3. typeof undefined; //undefined 
  4. typeof true; //boolean 
  5. typeof Symbol(); //"symbol" 
  6. typeof null; //"object" 
  7. typeof []; //"object" 
  8. typeof {}; //"object" 
  9. typeof console; //"object" 
  10. typeof console.log; //"function" 

2.2 instanceof

使用instanceof是通过原型链进行查找,可以准确地判断复杂引用数据类型,但是不能准确判断基础数据类型。

  1. let Fun = Function(){}; 
  2. let fun = new Fun(); 
  3. fun instanceof Fun;//true 
  4.  
  5. let str = new String("yichuan"); 
  6. str instanceof String;//true 
  7.  
  8. let str = "yichuan"
  9. str instanceof String;//false 

2.3 Object.prototype.toString.call()

Object.prototype.toString方法返回对象的类型字符串,因此可用来判断一个值的类型。因为实例对象有可能会自定义toString方法,会覆盖Object.prototype.toString,所以在使用时,最好加上call。所有的数据类型都可以使用此方法进行检测,且非常精准。

  1. Object.prototype.toString.call("yichuan");//["object String"
  2. Object.prototype.toString.call(18);//["object Number"
  3. Object.prototype.toString.call(true);//["object Boolean"
  4. Object.prototype.toString.call(null);//["object Null"
  5. Object.prototype.toString.call(new Symbol());//["object Symbol"
  6. Object.prototype.toString.call({});//["object Object"
  7. Object.prototype.toString.call([]);//["object Array"
  8. Object.prototype.toString.call(/123/g);//["object RegExp"
  9. Object.prototype.toString.call(function(){});//["object Function"
  10. Object.prototype.toString.call(new Date());//["object Date"
  11. Object.prototype.toString.call(document);//["object HTMLDocument"
  12. Object.prototype.toString.call(window);//["object Window"

我们可以看到此输出的结果都是["object Xxxx"]首字母大写。

2.4 通用的数据类型判断方法

  1. function getType(obj){ 
  2.  //先判断输入的数据判断返回结果是否为object 
  3.   if(typeof obj !== "object"){ 
  4.    return typeof obj; 
  5.   } 
  6.   // 对于typeof返回object的,再进行具体的判断,使用正则返回结果,切记正则中间有个空格哦 
  7.   return Object.prototype.toString.call(obj).replace(/^\[object (\S+)\]$/,"$1"); 

切记:

  • 使用typeof返回的类型是小写
  • 使用toString返回的类型是大写
  1. getType("yichuna");//"string" 
  2. getType(18);//"number" 
  3. getType(true);//"boolean" 
  4. getType(undefined);//"undefined" 
  5. getType();//"undefined" 
  6. getType(null);//"Null" 
  7. getType({});//"Object" 
  8. getType([]);//"Array" 
  9. getType(function(){});//"Function" 
  10. getType(new Date());//"Date" 
  11. getType(/123/g);//"RegExp" 

三、数据类型转换

3.1 强制类型转换

常见的强制类型转换方法有:

  • Number()
  • String()
  • Boolean()
  • parseInt()
  • parseFloat()
  • toString()

3.2 Number()方法的强制转换规则

  • 布尔值 true和false分别被转换为1和0
  • 数字 返回本身
  • null 返回0
  • undefined 返回NaN
  • 字符串
    • 如果字符串中只包含数字,则将其转换为十进制
    • 如果字符串中只包含有有效的浮点格式,将其转换为浮点数值
    • 如果是空字符串,将其转换为0
    • 如果不是以上格式的字符串,则均返回NaN
    • Symbol 抛出异常

3.3 Boolean()方法的强制转换规则

undefined、null、false、""、0(包括+0、-0)、NaN转换出来都是false,其余类型转换都是true。特别注意:Boolean({})转换为true

3.4 隐式类型转换==

  • 如果类型相同,无需进行类型转换
  • 如果其中一个操作值为null或undefined,那么另一个操作符必须是null或undefined才会返回true,否则均返回false
  • 如果其中一个值是Symbol类型,那么返回false
  • 如果其中一个操作知为Boolean,那么转为number
  • 两个操作值均为string和number类型,那么将字符串转为number
  • 如果一个操作值为object,且另一个为string、number或symbol,就会把object转为原始数据类型判断

小试牛刀:

  1. null == undefined; //true 
  2. null == 0;//false 
  3. "" == null;//false 
  4. "" == 0;//true 会转为number类型再进行判断 
  5. "123" == 123;//true 
  6. 0 == false;//true 
  7. 1 == true;//true 

3.5 隐式类型转换+

"+"号操作符,不仅可以用于数字相加,还可以用于字符串拼接。

  • 如果其中一个是字符串,另外一个是number、undefined、null或boolean,则调用toString()方法进行字符串拼接
  • 如果是纯字符串、数组、正则等,则默认调用对象的转换方法会存在优先级,然后进行拼接
  • 如果字符串和bigInt进行相加,会先将bigInt转为字符串
  • 如果number类型与undefined相加,则得到NaN
  1. 1 + 2;//3 
  2. 1 + "2";//"12" 
  3.  
  4. "1" + undefined;//"1undefined" 
  5. "1" + null;//"1null" 
  6. "1" + true;//"1true" 
  7. "1"  + 1n;//"11" 字符串和bigInt进行相加,会先将bigInt转为字符串 
  8.  
  9. 1 + undefined;//NaN undefined会先转为NaN 
  10. 1 + null;//1 null转为0 
  11. 1 + true;//2 
  12. 1 + 1n;//Error 

3.6 object的转换规则

  • 如果部署了Symbol.toPrimitive方法,优先调用再返回
  • 调用valueOf(),如果转换为基础类型则返回
  • 调用toString(),如果转换为基础数据类型则返回
  • 如果都没有返回基础数据类型,则会报错

四、深拷贝和浅拷贝

在js的编程中经常需要进行数据进行复制,那么什么时候使用深拷贝、什么时候使用浅拷贝呢,是开发过程中需要思考的?如何提升自己手写js的能力,以及对一些边界情况的深入思考能力呢?

有两个重要问题:

  • 拷贝一个很多嵌套的对象要如何实现呢?
  • 深拷贝写成什么程度才能让面试官满意呢?

4.1 浅拷贝的原理和实现

自己创建一个新的对象,来接受要重新复制或引用的对象值。

  • 如果对象属性是基本数据类型,复制的就是基本数据类型的值给新对象;
  • 如果对象属性是引用数据类型,赋值的则是内存中的地址,如果其中一个对象改变了这个内存中的地址,肯定会影响另外一个对象

4.1.1 Object.assign

Object.assign是es6中object的一个方法,该方法可以用于js对象的合并等多个用途,其中一个用途就是可以进行浅拷贝。

  1. Object.assign(target,...sources);//target目标对象,sources待拷贝的对象 

注意:

  • Object.assign不会拷贝对象的继承属性
  • Object.assign不会拷贝对象的不可枚举属性

例如:

  1. let obj = {}; 
  2. let obj1 = { 
  3.  name:"yichuan"
  4.   scores:{ 
  5.    math:100, 
  6.     Chinese:100 
  7.   } 
  8. }; 
  9.  
  10. Object.assign(obj,obj1); 
  11. console.log(obj);//{name:"yichuan",scores:{math:100,Chinese:100}} 

改变目标对象的值:我们可以看到下面改变了目标对象的值,会引起待拷贝对象的值的改变。

  1. let obj = {}; 
  2. let obj1 = { 
  3.  name:"yichuan"
  4.   scores:{ 
  5.    math:100, 
  6.     Chinese:100 
  7.   } 
  8. }; 
  9. Object.assign(obj,obj1); 
  10.  
  11. console.log(obj);//{name:"yichuan",scores:{math:100,Chinese:90}} 
  12. obj.scores.Chinese = 10; 
  13. console.log(obj);//{name:"yichuan",scores:{math:100,Chinese:90}} 
  14. console.log(obj1);//{name:"yichuan",scores:{math:100,Chinese:90}} 

不可拷贝不可枚举属性

  1. let obj1 = { 
  2.  user:{ 
  3.    name:"yichuan"
  4.     age:18 
  5.   }, 
  6.   idCard:Symbol(1) 
  7. }; 
  8.  
  9. Object.defineProperty(obj1,"innumerable",{ 
  10.   value:"不可枚举属性"
  11.   enumerable:false 
  12. }); 
  13.  
  14. let obj2 = {}; 
  15. Object.assign(obj2,obj1); 
  16. obj1.user.name = "onechuan"
  17.  
  18. console.log("obj1",obj1);//{user: {…}, idCard: Symbol(1), innumerable: '不可枚举属性'
  19. console.log("obj2",obj2);//{user: {…}, idCard: Symbol(1)} 我们可以看到并没有innumerable属性 

4.1.2 展开运算符

  1. /* 对象的拷贝 */ 
  2. let obj1 = { 
  3.  user:{ 
  4.    name:"yichuan"
  5.     age:18 
  6.   }, 
  7.   school:"实验小学" 
  8. }; 
  9.  
  10. let obj2 = {...obj1}; 
  11. obj2.school = "五道口男子技校"
  12. console.log(obj1);//{school: "实验小学",user: {name'yichuan', age: 18}} 
  13. obj2.user.age = 19; 
  14. console.log(obj2);//{school: "实验小学",user: {name'yichuan', age: 19}} 
  15.  
  16. /* 数组的拷贝 */ 
  17. let arr = ["red","green","blue"]; 
  18. let newArr = [...arr]; 
  19. console.log(arr);//['red''green''blue'
  20. console.log(newArr);//['red''green''blue'

4.1.3 concat拷贝数组

数组的concat方法其实也是浅拷贝

  1. let arr = ["red","green","blue"]; 
  2. let newArr = arr.concat(); 
  3. newArr[1] = "black"
  4. console.log(arr);//["red","green","blue"]; 
  5. console.log(newArr);//["red","black","blue"]; 

4.1.4 slice拷贝数组

slice方法仅针对数组类型,arr.slice(begin,end);

  1. let arr = ["red","green","blue"]; 
  2. let newArr = arr.slice(); 
  3. newArr[1] = "black"
  4. console.log(arr);//["red","green","blue"]; 
  5. console.log(newArr);//["red","black","blue"]; 

4.1.5 手写浅拷贝

  • 对基本数据类型进行最基本的拷贝
  • 对引用数据类型开辟新的存储,并且拷贝一层对象属性
  1. function shallowClone(target){ 
  2.  //先要判断是否为对象数据类型 
  3.   if(typeof target === "object" && target !== null){ 
  4.     //判断输入的是object类型还是数组类型 
  5.    const cloneTarget = Array.isArray(target) ?[]:{}; 
  6.     //遍历目标对象元素 
  7.     for(let prop in target){ 
  8.      //判断cloneTarget对象上是否有此属性,没有进行拷贝 
  9.       if(!cloneTarget.hasOwnProperty(prop)){ 
  10.        cloneTarget[prop] = target[prop] 
  11.       } 
  12.     } 
  13.     return cloneTarget; 
  14.   } 
  15.   return target; 

4.2 深拷贝的原理和实现

前面我们知道浅拷贝只是创建了一个新的对象,复制了原有对象的基本类型的值。对于复杂引用数据类型,其在堆内存中完全开辟了一块内存地址,并将原有对象完全复制过来存放。

深拷贝就是将一个对象从内存中完整地拷贝出来给目标对象,并在堆内存中开辟新的空间进行存储新对象的值,且新对象的值改变不会影响原对象,也就是实现了二者的隔离。

4.2.1 JSON.stringify()

其实在实际开发过程使用最简单的深拷贝就是使用JSON.stringify()配合JSON.parse()。但其实是有缺陷的,不影响简单使用。注意:

  1. let obj1 = { 
  2.  user:{ 
  3.    name:"yichuan"
  4.     age:18 
  5.   }, 
  6.   school:"实验小学" 
  7. }; 
  8. let obj2 = JSON.parse(JSON.stringify(obj1)); 
  9. console.log(obj1);//{school: "实验小学",user: {name'yichuan', age: 18}} 
  10. console.log(obj2);//{school: "实验小学",user: {name'yichuan', age: 18}} 
  11. obj2.school = "门头沟学员"
  12. obj2.user.age = 19; 
  13. console.log(obj1);//{school: "实验小学",user: {name'yichuan', age: 18}} 
  14. console.log(obj2);//{school: "门头沟学院",user: {name'yichuan', age: 19}} 

4.2.2 简易手写深拷贝

作为简易版手写深拷贝,只能完成基础的拷贝功能,也存在一些缺陷:

  • 不能拷贝不可枚举的属性以及symbol类型
  • 只能针对普通的引用类型的值做递归复制
  • 对象的属性里面成环,即循环引用没有得到妥善解决
  1. function deepClone(obj){ 
  2.  const cloneObj = {}; 
  3.   //遍历对象键名 
  4.   for(let key in obj){ 
  5.     //判断是否为对象类型 
  6.     if(typeof obj[key]==="object"){ 
  7.       //是对象就再次调用函数进行递归拷贝 
  8.       cloneObj[key] = deepClone(obj[key]); 
  9.     }else
  10.       //是基本数据类型的话,就直接进行复制值 
  11.       cloneObj[key] = obj[key]; 
  12.     } 
  13.      
  14.   } 
  15.   return cloneObj; 
  16.  
  17. const obj1 = { 
  18.  user:{ 
  19.    name:"yichuan"
  20.     age:18 
  21.   }, 
  22.   school:"实验小学" 
  23.  
  24. let obj2 = deepClone(obj1); 
  25. obj1.user.age = 19; 
  26. console.log(obj2);//{school: "实验小学",user: {name'yichuan', age: 18}} 

4.2.3 优化版手写深拷贝

对于上面简易版的深拷贝,很显然面试官是不买账的,为此我们针对递归进行升级处理。

  • 针对能够遍历对象的不可枚举属性以及Symbol类型,我们可以使用Reflect.ownKeys方法
  • 当参数为Date、RegExp类型,则直接生成一个新的实例返回
  • 利用Object的getOwnPropertyDescriptors方法可以获得对象的所有属性,以及对应的特性,顺便结合Object.create()方法创建新对象,并继承传入原对象的原型链
  • 利用WeakMap类型作为Hash表,因为WeakMap是弱引用类型,可以有效防止内存泄漏,作为检测循环引用有很大的帮助。如果存在循环,则引用直接返回WeakMap存储的值
  1. const isComplexDataType = (obj) => (typeof obj === 'object' || typeof obj === 'function') && obj !== null
  2.  
  3. function deepClone(obj, hash = new WeakMap()) { 
  4.   //判断是否为日期类型 
  5.   if (obj.constructor === Datereturn new Date(obj); 
  6.   //判断是否正则对象 
  7.   if (obj.constructor === RegExp) return new RegExp(obj); 
  8.   //如果循环引用了,就使用weakMap进行解决 
  9.   if (hash.has(obj)) return hash.get(obj); 
  10.  
  11.   const allDesc = Object.getOwnPropertyDescriptors(obj); 
  12.   //遍历传入参数所有键的特性 
  13.   const cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc); 
  14.   //继承原型链 
  15.   hash.set(obj, cloneObj); 
  16.   for (const key of Reflect.ownKeys(obj)) { 
  17.     cloneObj[key] = isComplexDataType(obj[key]) && typeof obj[key] !== 'function' ? deepClone(obj[key]) : obj[key]; 
  18.   } 
  19.   return cloneObj; 
  20.  
  21. const obj1 = { 
  22.   num: 2021, 
  23.   str: 'jue'
  24.   bool: true
  25.   nul: null
  26.   arr: ['ref''green''blue'], 
  27.   date: new Date(0), 
  28.   reg: new RegExp('/123/g'), 
  29.   user: { 
  30.     name'yichuan'
  31.     age: 18 
  32.   }, 
  33.   school: '实验小学' 
  34. }; 
  35. const obj2 = deepClone(obj1); 
  36. obj1.user.age = 19; 
  37. console.log(obj2);//{arr: ['ref''green''blue'],bool: true,date: Thu Jan 01 1970 08:00:00 GMT+0800 (中国标准时间) ,nul: null,num: 2021,reg: /\/123\/g/,school: "实验小学",str: "jue",user: {name'yichuan', age: 18}} 

5参考学习

《如何写出一个惊艳面试官的深拷贝?》

《JavaScript基本数据类型和引用数据类型》

《Javascript核心原理精讲》

6写在最后

 

其实在实际开发和使用过程中,很多人对于深拷贝的细节问题理解并不是很透彻,如果能够更深层次的研究细节,你就会发现此部分内容对于了解更深层次js的底层原理有所帮助。这篇文章是作为对数据类型、数据类型的检测、数据类型强制和隐藏转换、深浅拷贝的简要总结,希望对大家有所帮助。

 

责任编辑:武晓燕 来源: 前端万有引力
相关推荐

2021-12-04 11:17:32

Javascript继承编程

2021-12-10 07:47:30

Javascript异步编程

2021-02-25 07:08:30

JavaScript 前端面试题

2016-08-18 14:13:55

JavaScript基本数据引用数据

2010-10-08 15:11:28

JavaScript数

2021-12-11 18:59:35

JavascriptJSON应用

2021-12-05 08:27:56

Javascript 高阶函数前端

2021-12-07 08:01:33

Javascript 垃圾回收机制前端

2010-10-08 09:02:03

JavaScript基

2011-07-29 10:12:12

JavaScript

2012-02-13 22:50:59

集群高可用

2021-12-06 07:15:48

Javascript作用域闭包

2018-11-15 09:45:47

JavaScript数据类型变量

2010-08-16 09:15:57

2013-12-23 14:00:31

Windows 8.2Windows 8.1

2017-07-14 10:51:37

性能优化SQL性能分析

2022-08-12 16:12:34

JavaScript数据类型字符串

2022-05-18 20:01:07

K8sIP 地址云原生

2017-02-27 08:34:09

JavaScript数据引用

2021-07-22 07:20:24

JS 遍历方法前端
点赞
收藏

51CTO技术栈公众号