原始值
最简单的数据,即Javascript基础数据类型中的任意一种
保存原始值的变量是 **按值** 访问的,因为我们操作的就是存储在变量中的实际值
原始值不能有属性,尽管尝试给原始值添加属性不会报错
```
let name = 'echo';
name.age = 12;
console.log(name.age); // undefined
```
如果使用`new`关键字初始化原始类型的变量,则js会创建一个Object类型的实例,但其行为类似原始值
```
let name = new String('echo');
name.age = 12;
console.log(name.age); // 26
console.log(typeof name); // object
```
引用值
多个值构成的对象(Object)
引用值是保存在内存中的对象,JS不允许直接访问内存位置,因此在操作对象时,实际上操作的是该对象的引用(指针),即 保存引用值的变量是 按引用 访问的
基本引用类型
Date、RegExp、原始值包装类型、单例内置对象
集合引用类型
Object、Array、定型数组、Map、WeakMap、Set、WeakSet、迭代与扩展
原始值 VS 引用值 ——> 复制 ——> 浅拷贝&深拷贝
原始值: 通过变量把一个原始值赋值给另一个变量时,原始值会被复制到新变量的位置,这两个变量互相独立
引用值: 而复制引用值时,复制的只是当前引用值的指针,该指针指向堆内存中实际的对象。此时修改其中一个变量会影响另外一个。也就是所谓的浅复制。而在实际开发中,有时需要复制一份完全独立的对象数据(即深拷贝)。可实现的方法有:
-
使用js工具库的现成方法,比如
loadsh的_.cloneDeep(val) -
递归拷贝,即递归遍历对象,复制每一个基础类型值
function deepClone(val) { const baseType = ['string', 'number', 'boolean', 'undefined', 'symbol']; if(baseType.includes(typeof val) || val === null || val.constructor === RegExp) { return val; } if(Array.isArray(val)) { return val.map(item => deepClone(item)); } const result = {}; Object.entries(val).forEach(item => { const [name, value] = item; result[name] = deepClone(value); }) return result; } const obj = { name: 'echo', age: 12, like: [ {name: 'red', level: 1}, {name: 'green', level: 2} ] }; const a = deepClone(obj); console.log(a); -
利用JSON.stringify() + JSON.parse(),缺点:只能正确处理可以转成JSON格式的对象(比如undefined, function, RegExp等类型是无法被正确处理的)
-
利用Object.assign({}, val),缺点:只能深拷贝val(源对象)的第一层数据,深层数据则是浅拷贝
原始值 VS 引用值 ——> 函数传参
ECMAScript中所有函数的参数都是 按值传递 的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样。原始值比较好理解,以下只对引用值举例说明
function setName(obj) {
obj.name = 'echo';
}
let person = new Object();
setName(person);
console.log(person.name); // 'echo'
以上代码说明,即使对象是按值传进函数的,obj也会通过引用访问对象。此时当函数内部给obj设置了name属性时,函数外部的对象也发生了变化。因为obj指向的对象保存在全局作用域的堆内存上。
function setName(obj) {
obj.name = 'echo';
obj = new Object();
obj.name = 'foo';
}
let person = new Object();
setName(person);
console.log(person.name); // 'echo'
以上例子用来证明对象是按值传递的,当变量`obj`被设置为一个新对象,并设置`name`属性为`foo`时,如果`person`是按引用传递的,那么`person`应该自动将指针改为指向`name`为`foo`的对象,然而结果并不是,这表明函数中参数的值改变之后,原始的引用仍然没变。当`obj`在函数内部被重写时,它变成了一个指向本地对象的指针,而那个本地对象在函数执行结束时就被销毁了
原始值 VS 引用值 ——> 类型判断
判断数据类型的方法有:
-
typeof
typeof val; // 如果val为null,typeof会返回'object'对原始值很有用,对引用值用处不大(因为我们通常不关心一个值是不是对象,而是想知道它是什么类型的对象)
使用
typeof操作符检测函数时也会返回function。在
Safari5之前和Chrome7之前,由于实现细节的原因,typeof对于正则表达式的检测,也会返回function -
instanceof
obj instanceof Object; // obj 是 Object 吗? obj instanceof Array; // obj 是 Array 吗? obj instanceof RegExp; // obj 是 RegExp 吗?通过原型链检测
按照定义,所有引用值都是
Object的实例,因此通过instanceof操作符检测任何引用值和Object构造函数都会返回true。类似的,使用instanceof检测原始值,始终返回false。因为原始值不是对象 -
constructor
val.constructor === Object; val.constructor === Array;null,undefined没有constructor方法,因此不能使用constructor检测这两个值。需注意,
constructor的指向有可能被修改 -
Object.prototype.toString.call
const val = 'echo'; Object.prototype.toString.call(val); // [object String]