一篇文章读懂javascript数据类型相关知识

在前端开发中,很多小伙伴在使用javascript的时候只重视其功能性而忽视了其语言性,这不利于我们养成良好的编程习惯。这里给大家详细总结一下javascript的数据类型。

熟悉java的同学知道,java里分为八大数据类型分别是boolean byte char short int long float double,而javascript的数据类型要多于java,可以按照内存使用的不同分为2大类。

两种数据类型及内存分布

第一类叫基本类型 有 null undefined boolean string number symbol 基本类型顾名思义就是由基本数据构成的,是直接存在内存的栈中的,基本类型的值是不可以改变的,当给一个基本类型重新赋值的时候,会在内存中开辟一个新的空间,旧值和新值是不同的指向。

基本类型中除了null都是值类型,值类型在栈内存中的形态如下图,其中包含了变量的名称和变量的值。let a = 1,flag = true,name = "panda"

null这个值比较特殊,按照大类划分属于基本类型,按照内存又属于引用类型,不过它的指针指向为空。

栈内存区域
a 1
flag true
name panda

第二类叫引用类型 有 object array function ,引用类型其实都可以称为对象。引用类型的值是可以改变的,并可以添加属性和方法,和基本类型不同,引用类型实际上是通过存储在栈内存中的值指向到了堆内存中的,构成两者的关系叫做指向或者指针,下面画一个图表示引用类型在堆内存中的形态。function属于特殊引用类型,但不属于数据储存,所以没有下面会提到的深浅拷贝问题。

let a = {};

let b = a;

新建一个对象a,在栈内存中的key是对象名a value是内存地址,同时,会在堆内存中开辟一个空间,key是内存地址,value是对象的值。

当我们执行b=a的时候,会在栈中开辟个新空间,而不会在堆中开辟一个新空间,因此a和b的内存地址都等于a的内存地址。

 什么是深拷贝?什么是浅拷贝?

 从图上我们可以看出来,引用类型的赋值,其实就是栈区的指针的赋值。设想一下,这个时候我们把a赋值给b,b和a指向的就是同一个堆地址,这个时候无论改变a还是b都会改变另一个。这里强调一点,引用类型的比较其实是引用的比较,而不是大家理解的值的比较,用代码表示就是 

let obj1 = {};
let obj2 = {};
obj1==obj2 // false

回到刚才对象赋值会互相影响的问题,在我们日常开发中,会经常遇到需要copy对象的情况,比如data1为模板数据,想拷贝data1赋值给data2,我们改变data2的时候不想改变模板的数据,这里就引出了深拷贝和浅拷贝的概念。这里申明一点,深拷贝和浅拷贝都是为了解决对象赋值的问题的,有的同学理解为let data2 = data1 这样就是浅拷贝,连同引用地址一起改变了这样就是深拷贝,这种理解是错误的。

从概念上来说 只改变数据第一层的指向的拷贝叫做浅拷贝,改变数据所有内容指向的拷贝叫做深拷贝。一半我们写代码的时候浅拷贝基本可以满足需求,但是遇到复杂的数据的时候是需要深拷贝实现的

浅拷贝的方法:

第一种:

  let a = {
    age: 1
  }
  let b = Object.assign({}, a)
  a.age = 2
  console.log("Object.assign方法:"+b.age) // 1
第二种
  let c = {
    age:1,
  }
  let d = {...c}
  c.age = 2
  console.log("扩展运算符方法:"+d.age)

深拷贝的方法:

第一种: 通过二次转换实现,局限性:会忽略掉函数和undefined,循环引用的无效
let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = "be"
console.log("通过二次转换实现:",b)

第二种:给大家提供一个深拷贝函数,原理是通过递归

      function deepClone(target) {
        // 定义一个变量
        let result;
        // 如果当前需要深拷贝的是一个对象的话
        if (typeof target === 'object'||typeof target== null) {
        // 如果是一个数组的话
            if (Array.isArray(target)) {
                result = []; // 将result赋值为一个数组,并且执行遍历
                for (let i in target) {
                    // 递归克隆数组中的每一项
                    result.push(deepClone(target[i]))
                }
            // 判断如果当前的值是null的话;直接赋值为null
            } else if(target===null) {
                result = null;
            // 判断如果当前的值是一个RegExp对象的话,直接赋值    
            } else if(target.constructor===RegExp){
                result = target;
            }else {
            // 否则是普通对象,直接for in循环,递归赋值对象的所有值
                result = {};
                for (let i in target) {
                    result[i] = deepClone(target[i]);
                }
            }
        // 如果不是对象的话,就是基本数据类型,那么直接赋值
        } else {
            result = target;
        }
        // 返回最终结果
        return result;
      }
      let c = {
        name:undefined,
        fun:function(){},
        val:1
      }
      c.age = c.name
      c.val2 = c.val
      let d = deepClone(c)
      console.log("递归复制:",d)
 

 如何判断数据类型

了解了两种数据类型的不同之后,我们还要了解如何去判断一个数据类型,这里给大家列举了三个方法:

第一种是typeof 返回字符串 用法如下: 

需要注意的是 typeof 返回6种结果,对象和数组返回object ,函数返回function ,基本类型中null返回object(算是js的一个bug),NaN代表空数字也返回nubmer,

用途:1 可以识别所有值类型(除了null之外的5种基本类型) 2 识别函数 3 判断是否是引用类型(判断不出来具体是哪一种引用类型)

  var a = [34,4,3,54],
        b = 34,
        c = 'adsfas',
        d = function(){console.log('我是函数')},
        e = true,
        f = null,
        g;

        console.log(typeof(a));//object
        console.log(typeof(b));//number
        console.log(typeof(c));//string
        console.log(typeof(d));//function
        console.log(typeof(e));//boolean
        console.log(typeof(f));//object
        console.log(typeof(g));//undefined

第二种是 instanceof 返回布尔值:

instanceof一般用来判断 a是不是b的实例,利用这一点可以精确判断出对象的数据类型,涉及原型和原型链的知识后面会单独写一篇文章为大家讲解

用法:

[] instanceof Array //true
{} instanceof Object//true
function(){}  instanceof Function//true

100 instanceof Array //false
"panda" instanceof object //false

第三种是 Object.prototype.toString.call() 方法,用这个方法可以精确判断出所有数据类型(推荐)

Object.prototype.toString.call(null); // "[object Null]"
Object.prototype.toString.call(undefined); // "[object Undefined]"
Object.prototype.toString.call(“abc”);// "[object String]"
Object.prototype.toString.call(123);// "[object Number]"
Object.prototype.toString.call(true);// "[object Boolean]"
**函数类型**
Function fn(){
  console.log(“test”);
}
Object.prototype.toString.call(fn); // "[object Function]"

**日期类型**
var date = new Date();
Object.prototype.toString.call(date); // "[object Date]"

**数组类型**
var arr = [1,2,3];
Object.prototype.toString.call(arr); // "[object Array]"

**正则表达式**
var reg = /[hbc]at/gi;
Object.prototype.toString.call(reg); // "[object RegExp]"
 
原文地址:https://www.cnblogs.com/panda-programmer/p/12990418.html