堆和栈的区别
堆和栈都是内存中划分出来的用于存储的区域。
深拷贝与浅拷贝的区别就是其在内存中存储的类型不同。
栈(stack)为自动分配的内存空间,它由系统自动释放;而堆(heap)则是动态分配的内存,大小不定也不会自动释放。
ECMAScript的数据类型
基本数据类型 (number
string
boolean
undefined
null
)。
上面5个是ECMAScript中的5种基本数据类型。
基本数据类型存放在栈中
存放在栈内存中的简单数据段,数据大小确定,内存空间大小可以分配,是直接按值存放的,所以可以直接访问。
基本数据类型数据值不可改变
javascript中的原始值(undefined、null、布尔值、数字和字符串)与对象(包括数组和函数)有着根本区别。原始值是不可更改的:任何方法都无法更改(或“突变”)一个原始值。对数字和布尔值来说显然如此 —— 改变数字的值本身就说不通,而对字符串来说就不那么明显了,因为字符串看起来像由字符组成的数组,我们期望可以通过指定索引来假改字符串中的字符。实际上,javascript 是禁止这样做的。字符串中所有的方法看上去返回了一个修改后的字符串,实际上返回的是一个新的字符串值。
例如:
// 基本类型的值不可改变
var str='html'
str[0]='c'
console.log(str) // html
基本类型的比较是值的比较
基本类型的比较是值的比较,只要它们相等就认为它们是相等的。
// 基本类型的比较是值的比较
var a='html'
var b='html'
console.log(a===b) // true
但是注意,比较的时候请使用 === 三个等号,表示严格等于。
比如:
// js会默认做类型转换
var a=1
var b=true
console.log(a==b) // true
引用类型
引用类型(object
)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。 每个空间大小不一样,要根据情况进行特定的分配,例如。
var person1 = {name:'apple'};
var person2 = {name:'xiaomi'};
var person3 = {name:'huawei'};
![](https://img.haomeiwen.com/i6177839/67867edf8891318d.png)
引用类型值可变
// 引用类型值可变
var arr=[1,2,3,4,5]
arr[0]=10
console.log(arr) // [10, 2, 3, 4, 5]
引用类型的比较是引用的比较
所以每次我们对js中的引用类型进行操作的时候,都是操作其对象的引用。(保存在栈内存中的指针),所以比较两个引用类型,是看其引用是否指向同一个对象。 例如:
var a = [1,2,3];
var b = [1,2,3];
console.log(a === b); // false
虽然变量 a 和变量 b 都是表示一个内容为 1,2,3 的数组,但是其在内存中的位置不一样,也就是说变量 a 和变量 b 指向的不是同一个对象,所以他们是不相等的。
![](https://img.haomeiwen.com/i6177839/e2667fa6e10b0e7a.png)
传值与传址
在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中。例如:
var a = 10;
var b = a;
a ++ ;
console.log(a); // 11
console.log(b); // 10
![](https://img.haomeiwen.com/i6177839/d2a7e9aba4e97121.png)
所以说,基本类型的赋值的两个变量是两个独立相互不影响的变量。
但是引用类型的赋值是传址
。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。例如:
// 引用类型的赋值
var a={}
var b=a
a.name='jozo'
console.log(a.name) // jozo
console.log(b.name) // jozo
![](https://img.haomeiwen.com/i6177839/14e6f3c06548a741.png)
浅拷贝
上面的=只是引用,不是浅拷贝。
赋值(=)与浅拷贝的区别
下面是一个例子:
// 赋值(=)与浅拷贝的区别
var obj1={
name: 'xiaomi',
age: 13,
language:['English','Chinese','Germany']
}
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="huawei"
obj3.age=18
obj2.language='Germany'
obj3.language='Greek'
console.log(obj1)
/*
{
age: 13
language: (3) ["Greek", "French", "Germany"]
name: "huawei"
__proto__: Object
}
*/
console.log(obj2)
/*
{
age: 13
language: Array(3)
0: "Greek"
1: "French"
2: "Germany"
length: 3
__proto__: Array(0)
name: "huawei"
}
*/
console.log(obj3)
/*
{
age: 18
language: Array(3)
0: "Greek"
1: "French"
2: "Germany"
length: 3
__proto__: Array(0)
name: "xiaomi"
}
*/
先定义个一个原始的对象 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 里面的子对象
![](https://img.haomeiwen.com/i6177839/ef321d2a5884c3c4.png)
深拷贝
深拷贝是对对象以及对象的所有子对象进行拷贝。
怎么进行深拷贝呢?
思路就是递归调用刚刚的浅拷贝,把所有属于对象的属性类型都遍历赋给另一个对象即可。我们直接来看一下 Zepto 中深拷贝的代码:
// 内部方法:用户合并一个或多个对象到第一个对象
// 参数:
// target 目标对象 对象都合并到target里
// source 合并对象
// deep 是否执行深度合并
function extend(target, source, deep) {
for (key in source)
if (deep && (isPlainObject(source[key]) || isArray(source[key]))) {
// source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,否则递归会出错的
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {}
// source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,否则递归会出错的
if (isArray(source[key]) && !isArray(target[key]))
target[key] = []
// 执行递归
extend(target[key], source[key], deep)
}
// 不满足以上条件,说明 source[key] 是一般的值类型,直接赋值给 target 就是了
else if (source[key] !== undefined) target[key] = source[key]
}
// Copy all but undefined properties from one or more
// objects to the `target` object.
$.extend = function(target){
var deep, args = slice.call(arguments, 1);
//第一个参数为boolean值时,表示是否深度合并
if (typeof target == 'boolean') {
deep = target;
//target取第二个参数
target = args.shift()
}
// 遍历后面的参数,都合并到target上
args.forEach(function(arg){ extend(target, arg, deep) })
return target
}
在 Zepto 中的 $.extend
方法判断的第一个参数传入的是一个布尔值,判断是否进行深拷贝。
在 $.extend
方法内部,只有一个形参 target
,这个设计你真的很巧妙。因为形参只有一个,所以 target 就是传入的第一个参数的值,并在函数内部设置一个变量 args
来接收去除第一个参数的其余参数,如果该值是一个布尔类型的值的话,说明要启用深拷贝,就将 deep
设置为 true
,并将 target
赋值为 args
的第一个值(也就是真正的 target
)。如果该值不是一个布尔类型的话,那么传入的第一个值仍为 target
不需要进行处理,只需要遍历使用 extend
方法就可以。
而在 extend 的内部,是拷贝的过程。
网友评论