
基本类型
js中基本数据类型主要是:undefined,boolean,number,string,null。基本数据类型存放在栈中,并且基本数据类型值不可变,动态修改了基本数据类型的值,它的原始值也是不会改变的。
如字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值。
var str = "abc";
console.log(str[1]="f"); // f
console.log(str); // abc
基本类型的比较是值的比较
var a = 1;
var b = 1;
console.log(a === b);//true
在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,会创建这个值的一个副本,再将这个副本赋值到新的栈中,例如:
var a = 10;
var b = a;
a ++ ;
console.log(a); // 11
console.log(b); // 10
引用类型
js中基本数据类型主要是:Object,Array,Date,Function,js引用类型存放在堆中 引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配,例如。
var person1 = {name:'jozo'};
var person2 = {name:'xiaom'};
var person3 = {name:'xiaoq'};

引用类型值可变,引用类型是可以直接改变其值的,例如:
var a = [1,2,3];
a[1] = 5;
console.log(a[1]); // 5
引用类型的比较是引用的比较,所以比较两个引用类型,是看其的引用是否指向同一个对象。例如
var a = [1,2,3];
var b = [1,2,3];
console.log(a === b); // false
引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。例如
var a = {}; // a保存了一个空对象的实例
var b = a; // a和b都指向了这个空对象
a.name = 'jozo';
console.log(a.name); // 'jozo'
console.log(b.name); // 'jozo'
b.age = 22;
console.log(b.age);// 22
console.log(a.age);// 22
console.log(a == b);// true
传递参数的特点
ECMAScript 中所有函数的参数都是按值传递的。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另一个变量一样。基本类型值的传递如同基本类型变量的复制一样,而引用类型的传递,则如同引用类型变量的复制一样
-
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量( 即 arguments 对象中的一个元素 )。
-
在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此该局部变量的变化会反映到函数的外部:
function setName(obj) {
obj.name = 'Fly_001';
obj = new Object();
obj.name = 'juejin';
}
var person = new Object();
setName(person);
alert(person.name); // 'Fly_001';
很多小伙伴会认为该参数是按引用传递的,为了证明对象是按值传递的,再看下这个修改过的代码:
function setName(obj) {
obj.name = 'Fly_001';
obj = new Object();
obj.name = 'juejin';
}
var person = new Object();
setName(person);
alert(person.name); // 'Fly_001';
实际上,当在函数内部重写obj时,这个变量引用的就是一个局部对象了,来看看这时person和obj的关系图:

赋值(=)和浅拷贝的区别
var obj1 = {
'name' : 'zhangsan',
'age' : '18',
'language' : [1,[2,3],[4,5]],
};
var obj2 = obj1;
var obj3 = shallowCopy(obj1);
function shallowCopy(src) {
var dst = {};
for (var prop in src) {
if (src.hasOwnProperty(prop)) {
dst[prop] = src[prop];
}
}
return dst;
}
obj2.name = "lisi";
obj3.age = "20";
obj2.language[1] = ["二","三"];
obj3.language[2] = ["四","五"];
console.log(obj1);
//obj1 = {
// 'name' : 'lisi',
// 'age' : '18',
// 'language' : [1,["二","三"],["四","五"]],
//};
console.log(obj2);
//obj2 = {
// 'name' : 'lisi',
// 'age' : '18',
// 'language' : [1,["二","三"],["四","五"]],
//};
console.log(obj3);
//obj3 = {
// 'name' : 'zhangsan',
// 'age' : '20',
// 'language' : [1,["二","三"],["四","五"]],
//};
先定义个一个原始的对象 obj1,然后使用赋值得到第二个对象 obj2,然后通过浅拷贝,将 obj1 里面的属性都赋值到 obj3 中。也就是说:
- obj1:原始数据
- obj2:赋值操作得到
- obj3:浅拷贝得到
然后我们改变 obj2 的 name 属性和 obj3 的 name 属性,可以看到,改变赋值得到的对象 obj2 同时也会改变原始值 obj1,而改变浅拷贝得到的的 obj3 则不会改变原始对象 obj1。这就可以说明赋值得到的对象 obj2 只是将指针改变,其引用的仍然是同一个对象,而浅拷贝得到的的 obj3 则是重新创建了新对象。
然而,我们接下来来看一下改变引用类型会是什么情况呢,我又改变了赋值得到的对象 obj2 和浅拷贝得到的 obj3 中的 language 属性的第二个值和第三个值(language 是一个数组,也就是引用类型)。结果见输出,可以看出来,无论是修改赋值得到的对象 obj2 和浅拷贝得到的 obj3 都会改变原始数据。
这是因为浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据。所以就会出现改变浅拷贝得到的 obj3 中的引用类型时,会使原始数据得到改变。
-
深拷贝:将 B 对象拷贝到 A 对象中,包括 B 里面的子对象,
-
浅拷贝:将 B 对象拷贝到 A 对象中,但不包括 B 里面的子对象
-- | 和原数据是否指向同一对象 | 第一层数据为基本数据类型 | 原数据中包含子对象 |
---|---|---|---|
赋值 | 是 | 改变会使原数据一同改变 | 改变会使原数据一同改变 |
浅拷贝 | 否 | 改变不会使原数据一同改变 | 改变会使原数据一同改变 |
深拷贝 | 否 | 改变不会使原数据一同改变 | 改变不会使原数据一同改变 |
深拷贝
深拷贝是对对象以及对象的所有子对象进行拷贝。思路就是递归调用刚刚的浅拷贝,把所有属于对象的属性类型都遍历赋给另一个对象即可
深浅拷贝的实现
看一看原生JavaScript中提供的一些复制方法究竟是深拷贝还是浅拷贝以及动手实现深拷贝。
浅拷贝
- Array.prototype.slice()
let a = [1, 2, 3, 4];
let b = a.slice();
console.log(a === b); // -> false
a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
- Array.prototype.concat()
let a = [1, 2, 3, 4];
let b = a.concat();
console.log(a === b); // -> false
a[0] = 5;
console.log(a); // -> [5, 2, 3, 4]
console.log(b); // -> [1, 2, 3, 4]
let a = [[1, 2], 3, 4];
let b = a.slice();
console.log(a === b); // -> false
a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
let a = [[1, 2], 3, 4];
let b = a.concat();
console.log(a === b); // -> false
a[0][0] = 0;
console.log(a); // -> [[0, 2], 3, 4]
console.log(b); // -> [[0, 2], 3, 4]
综上, Array的slice和concat方法都是浅拷贝。
深拷贝
JSON.parse()和JSON.stringify()
- JSON.stringify():把一个js对象序列化为一个JSON字符串
- JSON.parse():把JSON字符串反序列化为一个js对象
let obj = {
name: 'leeper',
age: 20,
friend: {
name: 'lee',
age: 19
}
};
let copyObj = JSON.parse(JSON.stringify(obj));
obj.name = 'Sandman';
obj.friend.name = 'Jerry';
console.log(obj);
// -> {name: "Sandman", age: 20, friend: {age: 19,name: 'Jerry'}}
console.log(copyObj);
// -> {name: "leeper", age: 20, friend: {age: 19,name: 'lee'}}
综上,JSON.parse()和JSON.stringify()是完全的深拷贝。
动手实现深拷贝 利用递归来实现对对象或数组的深拷贝。递归思路:对属性中所有引用类型的值进行遍历,直到是基本类型值为止。
function deepCopy(obj) {
if (!obj && typeof obj !== 'object') {
throw new Error('error arguments');
}
// const targetObj = obj.constructor === Array ? [] : {};
const targetObj = Array.isArray(obj) ? [] : {};
for (let key in obj) {
//只对对象自有属性进行拷贝
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === 'object') {
targetObj[key] = deepCopy(obj[key]);
} else {
targetObj[key] = obj[key];
}
}
}
return targetObj;
}