Skip to content

数据类型

一、类型

JavaScript 中的类型分为两种,一种是基本类型,一种是引用类型。

  • 基本类型:(存在于栈当中)

NullUndefinedNumberStringBooleanSymbol

  • 引用类型:(存在于堆当中)

Object, 数组、函数、正则等,都属于js的对象。

JavaScript不允许直接访问内存当中的位置,也就是不允许直接操作对象的内存空间。

1、Number

JavaScript 中的 Number 类型有 18437736874454810627(即 2^64-2^53+3) 个值。
JavaScript 中的 Number 类型基本符合 IEEE 754-2008 规定的双精度浮点数规则,但是 JavaScript 为了表达几个额外的语言场景(比如不让除以 0 出错,而引入了无穷大的概念),规定了几个例外情况:

  • NaN,占用了 9007199254740990,这原本是符合 IEEE 规则的数字;
  • Infinity,无穷大;
  • Infinity,负无穷大。

2、String

String 用于表示文本数据。String 有最大长度是 2^53 - 1,这在一般开发中都是够用的,但是有趣的是,这个所谓最大长度,并不完全是你理解中的字符数。
字符串的最大长度,实际上是受字符串的编码长度影响的。

3、Boolean

Boolean 类型有两个值, truefalse
表示逻辑意义上的真和假,有关键字 truefalse 来表示两个值。

4、Null

定义了,但是为空,是JavaScript关键词,可以使用null来获取null值。

5、Undefined

表示未定义,他的类型只有一个值,就是undefined
任何变量再赋值前是Undefined,值为undefined

6、Symbol

SymbolES6 中引入的新类型,它是一切非字符串的对象 key 的集合,
在 ES6 规范中,整个对象系统被用 Symbol 重塑。
创建:

javascript
var mySymbol = Symbol("my symbol");

7、Object

Object 是 JavaScript 中最复杂的类型,也是 JavaScript 的核心机制之一。Object 表示对象的意思,它是一切有形和无形物体的总称。

对象分类

JavaScript 中的对象分类我们可以把对象分成几类。

宿主对象(host Objects)

由 JavaScript 宿主环境提供的对象,它们的行为完全由宿主环境决定。

内置对象(Built-in Objects)
由 JavaScript 语言提供的对象。
  • 固有对象(Intrinsic Objects )

由标准规定,随着 JavaScript 运行时创建而自动创建的对象实例。

  • 原生对象(Native Objects)

可以由用户通过 ArrayRegExp 等内置构造器或者特殊语法创建的对象

普通对象(Ordinary Objects)

由{}语法、Object 构造器或者 class 关键字定义类创建的对象,它能够被原型继承。

二、类型转换

因为 JS 是弱类型语言,所以类型转换发生非常频繁,大部分我们熟悉的运算都会先进行类型转换。

StringToNumber

NumberToString

装箱装换

在 JavaScript 中,没有任何方法可以更改私有的 Class 属性,因此 Object.prototype.toString 是可以准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。但需要注意的是,call 本身会产生装箱操作,所以需要配合 typeof 来区分基本类型还是对象类型。

拆箱转换

三、变量的赋值(Copy)

如果是基本类型的赋值,前后互相不影响,

如果是引用类型的赋值,拷贝的其实是内存地址的引用,所以当改变其中某一个的时候,领一个也会发生改变。

浅拷贝(Shallow Copy)

对象属性的拷贝,如果是基本类型,拷贝的是基本类型的值,
如果是引用类型,拷贝的是内存地址的引用,
所以使用浅拷贝的话,改变其中一个,另外一个也会跟着改变。

场景

  • Object.assign()

其实是一个浅拷贝,并非一个深拷贝

拷贝的是对象中所有可枚举属性的值,从源对象复制到目标对象,并返回目标对象。

  • 展开语法 Spread
javascript
let a = {
    name: '',
    info: {
        gender: 'man',
    }
}

let b = {...a};

其实是和Object.assign()同样的效果。

  • Array.prototype.slice和Array.prototype.concat

如果数组的项是一个基本类型的值,相互是不影响的,
如果是引用类型的值,一个改变都会发生改变。
说明sliceconcat方法是一个浅拷贝的方法。(Deep Copy)

深拷贝

深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。
深拷贝实现速度慢,花销较大,拷贝前后两个对象互不影响。

全局方法 structuredClone 实现深拷贝

js
var arr = structuredClone(target)

场景

  • JSON.parse(JSON.stringify(object))

不管是数组还是对象,使用序列化之后,改变前后互不影响

但是该方法有以下几个问题。

1、会忽略 undefined

忽略掉

2、会忽略 symbol

忽略掉

3、不能序列化函数

忽略掉

4、不能解决循环引用的对象(类似套娃一样)

报错,

javascript
// 
let obj = {
    a: 1,
    b: {
        c: 2,
   		d: 3
    }
}
obj.a = obj.b;
obj.b.c = obj.a;

let b = JSON.parse(JSON.stringify(obj));
// Uncaught TypeError: Converting circular structure to JSON

5、不能正确处理new Date()

javascript
new Date();
// Mon Dec 24 2018 10:59:14 GMT+0800 (China Standard Time)

JSON.stringify(new Date());
// ""2018-12-24T02:59:25.776Z""

JSON.parse(JSON.stringify(new Date()));
// "2018-12-24T02:59:41.523Z"

解决方法转成字符串或者时间戳就好了。

javascript
// 木易杨
let date = (new Date()).valueOf();
// 1545620645915

JSON.stringify(date);
// "1545620673267"

JSON.parse(JSON.stringify(date));
// 1545620658688

6、不能处理正则

结果为{},

javascript
// toDO
// PS:为什么会存在这些问题可以学习一下 JSON。

7、会抛弃对象的constructor,所有的构造函数会指向Object

javascript
let Stu = function(name) {
this.name = name
}

let stu = new Stu('fff')

stu.constructor
// ƒ (name) {
//     this.name = name
// }

stu.constructor === Stu // true

let stuC = JSON.parse(JSON.stringify(stu))

stuC.constructor
// ƒ Object() { [native code] }

stu.constructor === Object // true
  • jQuery.extend()lodash.cloneDeep()实现深拷贝。

实现一个深拷贝

javascript

/**
 * @Description 实现一个引用类型的深拷贝
 * @Author forguo
 * @Date 2020/1/14
 */

// let array = [
//     {number: 1},
//     {number: 2},
//     {number: 3}
// ];

let array = {
    number: 1,
    name: 'www',
    info: {
        name: 'forguo',
        age: 26
    }
};

function deepCopy(obj) {
    // 舒适化返回结果,判断是否是数组
    let newobj = obj.constructor === Array ? [] : {};
    if (typeof obj !== 'object' || obj == null) {
        // obj是null,或者不是数组或者对象,直接返回即可
        return obj;
    }
    for (let key in obj) {
        // 遍历每个属性,递归拷贝
        if (obj.hasOwnProperty(key)) {
            // 保证key不是原型的属性
            newobj[key] = deepCopy(obj[key]);
        }
    }
    return newobj
}

Object.keys(array).map((item) => {
    console.log(item);
});
let copyArray = deepCopy(array);
copyArray.number = 100;
console.log(array); // [{number: 1}, { number: 2 }, { number: 3 }]
console.log(copyArray); // [{number: 100}, { number: 2 }, { number: 3 }]

循环引用的解决

https://juejin.cn/post/6844903998823088141
循环引用

javascript
var circle = {}
circle.circle = circle
//或者
var a = {}, b = {}
a.b = b
b.a = a

对于循环引用的对象使用深拷贝会直接栈溢出。

相同引用

javascript
var arr = [1,2,3]
var obj = {}
obj.arr1 = arr
obj.arr2 = arr

而对于包含相同对象引用的问题在于,因为复制之前obj.arr1和obj.arr2是指向相同对象的,修改其中一个另一个也会改动

使用Map来解决

javascript
/**
 * 循环引用的解决
 */

/**
 * @Description 实现一个引用类型的深拷贝
 * @Author forguo
 * @Date 2020/1/14
 */

// let array = [
//     {number: 1},
//     {number: 2},
//     {number: 3}
// ];

let array = {
    number: 1,
    name: 'www',
    info: {
        name: 'forguo',
        age: 26
    }
};

function deepCopy(obj, map = new Map) {
    // 舒适化返回结果,判断是否是数组
    let newobj = obj.constructor === Array ? [] : {};
    if (typeof obj !== 'object' || obj == null) {
        // obj是null,或者不是数组或者对象,直接返回即可
        return obj;
    }
    if (map.get(obj)) {
        return map.get(obj);
    }
    map.set(obj, newobj);
    for (let key in obj) {
        // 遍历每个属性,递归拷贝
        if (obj.hasOwnProperty(key)) {
            // 保证key不是原型的属性
            newobj[key] = deepCopy(obj[key], map);
        }
    }
    return newobj
}

// a循环引用
array.a = array;
let copyArray = deepCopy(array);
console.log(copyArray);

四、问题及解答

  • 1、为什么使用 void 0 代替 undefined?

这是 JavaScript 语言公认的设计失误之一。

JavaScript中,undefined是一个变量,并非一个关键词,
避免无意中的篡改,建议使用void 0 代替undefined

  • 2、0.1 + 0.2 不是等于 0.3 么?为什么 JavaScript 里不是这样的?

根据双精度浮点数的定义,Number 类型中有效的整数范围是 -0x1fffffffffffff0x1fffffffffffff,
所以 Number 无法精确表示此范围外的整数。

  • 3、ES6 新加入的 Symbol 是个什么东西?

可以用字符串来描述,即使描述相同,Symbol也不相同,可以用来做对象的属性名。

他的创建不需要使用 new关键词。直接 var mySymbol = Symbol("my symbol")

  • 4、为什么给对象添加的方法能用在基本类型上?

基本类型的构造函数,

Number,String,Boolean,Symbol,直接使用可以用来做强制类型转换,

使用new 来创建该数据类型(除了Symbol,直接Symbol('symbol'))

可以在构造器的原型上添加属性或者方法,那在基本类型上就可以访问了。

如下代码:

javascript
Number.prototype.say = function () {console.log(this)}
let num = 100
num.say()
Number(100)
  • 5、==和===的区别

== 会进行类型转换,=== 不会进行类型转换。