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外,其它的均无法进行判断。

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

2.2 instanceof

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

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

2.3 Object.prototype.toString.call()

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

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

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

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

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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

切记:

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

三、数据类型转换

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转为原始数据类型判断

小试牛刀:

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

3.5 隐式类型转换+

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

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

3.6 object的转换规则

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

四、深拷贝和浅拷贝

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

有两个重要问题:

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

4.1 浅拷贝的原理和实现

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

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

4.1.1 Object.assign

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

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

注意:

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

例如:

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

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

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

不可拷贝不可枚举属性

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

4.1.2 展开运算符

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

4.1.3 concat拷贝数组

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

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

4.1.4 slice拷贝数组

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

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

4.1.5 手写浅拷贝

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

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.

4.2 深拷贝的原理和实现

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

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

4.2.1 JSON.stringify()

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

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

4.2.2 简易手写深拷贝

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

  • 不能拷贝不可枚举的属性以及symbol类型
  • 只能针对普通的引用类型的值做递归复制
  • 对象的属性里面成环,即循环引用没有得到妥善解决
function deepClone(obj){ 
 const cloneObj = {}; 
  //遍历对象键名 
  for(let key in obj){ 
    //判断是否为对象类型 
    if(typeof obj[key]==="object"){ 
      //是对象就再次调用函数进行递归拷贝 
      cloneObj[key] = deepClone(obj[key]); 
    }else
      //是基本数据类型的话,就直接进行复制值 
      cloneObj[key] = obj[key]; 
    } 
     
  } 
  return cloneObj; 

 
const obj1 = { 
 user:{ 
   name:"yichuan"
    age:18 
  }, 
  school:"实验小学" 

 
let obj2 = deepClone(obj1); 
obj1.user.age = 19; 
console.log(obj2);//{school: "实验小学",user: {name'yichuan', age: 18}} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.

4.2.3 优化版手写深拷贝

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

  • 针对能够遍历对象的不可枚举属性以及Symbol类型,我们可以使用Reflect.ownKeys方法
  • 当参数为Date、RegExp类型,则直接生成一个新的实例返回
  • 利用Object的getOwnPropertyDescriptors方法可以获得对象的所有属性,以及对应的特性,顺便结合Object.create()方法创建新对象,并继承传入原对象的原型链
  • 利用WeakMap类型作为Hash表,因为WeakMap是弱引用类型,可以有效防止内存泄漏,作为检测循环引用有很大的帮助。如果存在循环,则引用直接返回WeakMap存储的值
const isComplexDataType = (obj) => (typeof obj === 'object' || typeof obj === 'function') && obj !== null
 
function deepClone(obj, hash = new WeakMap()) { 
  //判断是否为日期类型 
  if (obj.constructor === Datereturn new Date(obj); 
  //判断是否正则对象 
  if (obj.constructor === RegExp) return new RegExp(obj); 
  //如果循环引用了,就使用weakMap进行解决 
  if (hash.has(obj)) return hash.get(obj); 
 
  const allDesc = Object.getOwnPropertyDescriptors(obj); 
  //遍历传入参数所有键的特性 
  const cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc); 
  //继承原型链 
  hash.set(obj, cloneObj); 
  for (const key of Reflect.ownKeys(obj)) { 
    cloneObj[key] = isComplexDataType(obj[key]) && typeof obj[key] !== 'function' ? deepClone(obj[key]) : obj[key]; 
  } 
  return cloneObj; 

 
const obj1 = { 
  num: 2021, 
  str: 'jue'
  bool: true
  nul: null
  arr: ['ref''green''blue'], 
  date: new Date(0), 
  reg: new RegExp('/123/g'), 
  user: { 
    name'yichuan'
    age: 18 
  }, 
  school: '实验小学' 
}; 
const obj2 = deepClone(obj1); 
obj1.user.age = 19; 
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}} 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.

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基本数据引用数据

2021-12-11 18:59:35

JavascriptJSON应用

2010-10-08 15:11:28

JavaScript数

2021-12-05 08:27:56

Javascript 高阶函数前端

2021-12-07 08:01:33

Javascript 垃圾回收机制前端

2011-07-29 10:12:12

JavaScript

2010-10-08 09:02:03

JavaScript基

2012-02-13 22:50:59

集群高可用

2024-08-06 10:07:15

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数据引用
点赞
收藏

51CTO技术栈公众号