数据类型
一、类型
JavaScript 中的类型分为两种,一种是基本类型,一种是引用类型。
- 基本类型:(存在于栈当中)
Null
,Undefined
,Number
,String
,Boolean
,Symbol
- 引用类型:(存在于堆当中)
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
类型有两个值, true
和 false
,
表示逻辑意义上的真和假,有关键字 true
和 false
来表示两个值。
4、Null
定义了,但是为空,是JavaScript关键词,可以使用null来获取null值。
5、Undefined
表示未定义,他的类型只有一个值,就是undefined
。
任何变量再赋值前是Undefined
,值为undefined
。
6、Symbol
Symbol
是 ES6
中引入的新类型,它是一切非字符串的对象 key 的集合,
在 ES6 规范中,整个对象系统被用 Symbol 重塑。
创建:
var mySymbol = Symbol("my symbol");
var mySymbol = Symbol("my symbol");
7、Object
Object 是 JavaScript 中最复杂的类型,也是 JavaScript 的核心机制之一。Object 表示对象的意思,它是一切有形和无形物体的总称。
对象分类
JavaScript 中的对象分类我们可以把对象分成几类。
宿主对象(host Objects)
由 JavaScript 宿主环境提供的对象,它们的行为完全由宿主环境决定。
内置对象(Built-in Objects)
由 JavaScript 语言提供的对象。
- 固有对象(Intrinsic Objects )
由标准规定,随着 JavaScript 运行时创建而自动创建的对象实例。
- 原生对象(Native Objects)
可以由用户通过 Array
、RegExp
等内置构造器或者特殊语法创建的对象
普通对象(Ordinary Objects)
由{}语法、Object 构造器或者 class 关键字定义类创建的对象,它能够被原型继承。
二、类型转换
因为 JS 是弱类型语言,所以类型转换发生非常频繁,大部分我们熟悉的运算都会先进行类型转换。
StringToNumber
NumberToString
装箱装换
在 JavaScript 中,没有任何方法可以更改私有的 Class 属性,因此 Object.prototype.toString 是可以准确识别对象对应的基本类型的方法,它比 instanceof 更加准确。但需要注意的是,call 本身会产生装箱操作,所以需要配合 typeof 来区分基本类型还是对象类型。
拆箱转换
三、变量的赋值(Copy)
如果是基本类型的赋值,前后互相不影响,
如果是引用类型的赋值,拷贝的其实是内存地址的引用,所以当改变其中某一个的时候,领一个也会发生改变。
浅拷贝(Shallow Copy)
对象属性的拷贝,如果是基本类型,拷贝的是基本类型的值,
如果是引用类型,拷贝的是内存地址的引用,
所以使用浅拷贝的话,改变其中一个,另外一个也会跟着改变。
场景
- Object.assign()
其实是一个浅拷贝,并非一个深拷贝
拷贝的是对象中所有可枚举属性的值,从源对象复制到目标对象,并返回目标对象。
- 展开语法 Spread
let a = {
name: '',
info: {
gender: 'man',
}
}
let b = {...a};
let a = {
name: '',
info: {
gender: 'man',
}
}
let b = {...a};
其实是和Object.assign()
同样的效果。
- Array.prototype.slice和Array.prototype.concat
如果数组的项是一个基本类型的值,相互是不影响的,
如果是引用类型的值,一个改变都会发生改变。
说明slice
,concat
方法是一个浅拷贝的方法。(Deep Copy)
深拷贝
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。
深拷贝实现速度慢,花销较大,拷贝前后两个对象互不影响。
全局方法 structuredClone
实现深拷贝
var arr = structuredClone(target)
var arr = structuredClone(target)
场景
JSON.parse(JSON.stringify(object))
不管是数组还是对象,使用序列化之后,改变前后互不影响
但是该方法有以下几个问题。
1、会忽略 undefined
忽略掉
2、会忽略 symbol
忽略掉
3、不能序列化函数
忽略掉
4、不能解决循环引用的对象(类似套娃一样)
报错,
//
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
//
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()
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"
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"
解决方法转成字符串或者时间戳就好了。
// 木易杨
let date = (new Date()).valueOf();
// 1545620645915
JSON.stringify(date);
// "1545620673267"
JSON.parse(JSON.stringify(date));
// 1545620658688
// 木易杨
let date = (new Date()).valueOf();
// 1545620645915
JSON.stringify(date);
// "1545620673267"
JSON.parse(JSON.stringify(date));
// 1545620658688
6、不能处理正则
结果为{},
// toDO
// PS:为什么会存在这些问题可以学习一下 JSON。
// toDO
// PS:为什么会存在这些问题可以学习一下 JSON。
7、会抛弃对象的constructor,所有的构造函数会指向Object
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
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()
实现深拷贝。
实现一个深拷贝
/**
* @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 }]
/**
* @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
循环引用
var circle = {}
circle.circle = circle
//或者
var a = {}, b = {}
a.b = b
b.a = a
var circle = {}
circle.circle = circle
//或者
var a = {}, b = {}
a.b = b
b.a = a
对于循环引用的对象使用深拷贝会直接栈溢出。
相同引用
var arr = [1,2,3]
var obj = {}
obj.arr1 = arr
obj.arr2 = arr
var arr = [1,2,3]
var obj = {}
obj.arr1 = arr
obj.arr2 = arr
而对于包含相同对象引用的问题在于,因为复制之前obj.arr1和obj.arr2是指向相同对象的,修改其中一个另一个也会改动
使用Map来解决
/**
* 循环引用的解决
*/
/**
* @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);
/**
* 循环引用的解决
*/
/**
* @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 类型中有效的整数范围是 -0x1fffffffffffff
至 0x1ffffffffffff
f,
所以 Number 无法精确表示此范围外的整数。
- 3、ES6 新加入的 Symbol 是个什么东西?
可以用字符串来描述,即使描述相同,Symbol也不相同,可以用来做对象的属性名。
他的创建不需要使用 new关键词。直接 var mySymbol = Symbol("my symbol")
- 4、为什么给对象添加的方法能用在基本类型上?
基本类型的构造函数,
Number,String,Boolean,Symbol,直接使用可以用来做强制类型转换,
使用new 来创建该数据类型(除了Symbol,直接Symbol('symbol'))
可以在构造器的原型上添加属性或者方法,那在基本类型上就可以访问了。
如下代码:
Number.prototype.say = function () {console.log(this)}
let num = 100
num.say()
Number(100)
Number.prototype.say = function () {console.log(this)}
let num = 100
num.say()
Number(100)
- 5、==和===的区别
==
会进行类型转换,===
不会进行类型转换。