We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
大多数情况下,我们可以通过typeof属性来判断。只不过有一些例外,比如:
typeof
var fn = new Function ('a', 'b', 'return a + b') typeof fn // function
关于function属不属于js的数据类型,这里也有相关的讨论JavaScript 里 Function 也算一种基本类型?
function
我们来看一下 MDN 中对基本数据类型的一些定义:
除 Object 以外的所有类型都是不可变的(值本身无法被改变)。例如,与 C 语言不同,JavaScript 中字符串是不可变的(译注:如,JavaScript 中对字符串的操作一定返回了一个新字符串,原始字符串并没有被改变)。我们称这些类型的值为“原始值”。
var a = 'string' a[0] = 'a' console.log(a) // string
我们通常情况下都是对一个变量重新赋值,而不是改变基本数据类型的值。在 js 中是没有方法是可以改变布尔值和数字的。倒是有很多操作字符串的方法,但是这些方法都是返回一个新的字符串,并没有改变其原有的数据。比如:
引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配,例如。
var person1 = {name:'jozo'}; var person2 = {name:'xiaom'}; var person3 = {name:'xiaoq'};
引用类型的值是可变的:
person1['name'] = 'muwoo' console.log(person1) // {name: 'muwoo'}
了解了基本数据类型与引用类型的区别之后,我们就应该能明白传值与传址的区别了。 在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中。例如:
var a = 10; var b = a; a ++ ; console.log(a); // 11 console.log(b); // 10
所以说,基本类型的赋值的两个变量是两个独立相互不影响的变量。
但是引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。例如:
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
先来看一段代码的执行:
var obj = {a: 1, b: {c: 2}} var obj1 = obj var obj2 = shallowCopy(obj); function shallowCopy(src) { var dst = {}; for (var prop in src) { if (src.hasOwnProperty(prop)) { dst[prop] = src[prop]; } } return dst; } var obj3 = Object.assign({}, obj) obj.a = 2 obj.b.c = 3 console.log(obj) // {a: 2, b: {c: 3}} console.log(obj1) // {a: 2, b: {c: 3}} console.log(obj2) // {a: 1, b: {c: 3}} console.log(obj3) // {a: 1, b: {c: 3}}
这段代码可以说明赋值得到的对象 obj1 只是将指针改变,其引用的仍然是同一个对象,而浅拷贝得到的的 obj2 则是重新创建了新对象。但是,如果原对象obj中存在另一个对象,则不会对对象做另一次拷贝,而是只复制其变量对象的地址。这是因为浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据。 对于数组,更长见的浅拷贝方法便是slice(0)和 concat() ES6 比较常见的浅拷贝方法便是 Object.assign
obj1
obj2
obj
slice(0)
concat()
Object.assign
通过上面的这些说明,相信你对深拷贝大致了解了是怎样一个东西了:深拷贝是对对象以及对象的所有子对象进行拷贝。那么如何实现这样一个深拷贝呢?
对于常规的对象,我们可以通过JSON.stringify来讲对象转成一个字符串,然后在用JSON.parse来为其分配另一个存储地址,这样可以解决内存地址指向同一个的问题:
JSON.stringify
JSON.parse
var obj = {a: {b: 1}} var copy = JSON.parse(JSON.stringify(obj)) obj.a.b = 2 console.log(obj) // {a: {b: 2}} console.log(copy) // {a: {b: 1}}
但是 JSON.parse()、JSON.stringify也存在一个问题,JSON.parse() 和J SON.stringify()能正确处理的对象只有Number、String、Array等能够被 json 表示的数据结构,因此函数这种不能被 json 表示的类型将不能被正确处理。
JSON.parse()
SON.stringify()
Number、String、Array
var target = { a: 1, b: 2, hello: function() { console.log("Hello, world!"); } }; var copy = JSON.parse(JSON.stringify(target)); console.log(copy); // {a: 1, b: 2} console.log(JSON.stringify(target)); // "{"a":1,"b":2}"
既然浅拷贝只能实现非object第一层属性的复制,那么遇到object只需要通过递归实现浅拷贝其中内部的属性即可:
object
function extend (source) { var target if (typeof source === 'object') { target = Array.isArray(source) ? [] : {} for (var key in source) { if (source.hasOwnProperty(key)) { if (typeof source[key] !== 'object') { target[key] = source[key] } else { target[key] = extend(source[key]) } } } } else { target = source } return target } var obj1 = {a: {b: 1}} var cpObj1 = extend(obj1) obj1.a.b = 2 console.log(cpObj1) // {a: {b: 1}} var obj2 = [[1]] var cpObj2 = extend(obj2) obj2[0][0] = 2 console.log(cpObj2) // [[1]]
我们再来看一下 Zepto 中深拷贝的代码:
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] }
内部实现其实也是差不多。
js 深拷贝 vs 浅拷贝
The text was updated successfully, but these errors were encountered:
深拷贝还要考虑函数和正则等吧
Sorry, something went wrong.
No branches or pull requests
js 数据类型
大多数情况下,我们可以通过
typeof
属性来判断。只不过有一些例外,比如:关于
function
属不属于js的数据类型,这里也有相关的讨论JavaScript 里 Function 也算一种基本类型?基本类型 和 引用数据类型 的相关区别
基本数据类型
我们来看一下 MDN 中对基本数据类型的一些定义:
我们通常情况下都是对一个变量重新赋值,而不是改变基本数据类型的值。在 js 中是没有方法是可以改变布尔值和数字的。倒是有很多操作字符串的方法,但是这些方法都是返回一个新的字符串,并没有改变其原有的数据。比如:
引用数据类型
引用类型(object)是存放在堆内存中的,变量实际上是一个存放在栈内存的指针,这个指针指向堆内存中的地址。每个空间大小不一样,要根据情况开进行特定的分配,例如。
引用类型的值是可变的:
传值与传址
了解了基本数据类型与引用类型的区别之后,我们就应该能明白传值与传址的区别了。
在我们进行赋值操作的时候,基本数据类型的赋值(=)是在内存中新开辟一段栈内存,然后再把再将值赋值到新的栈中。例如:
所以说,基本类型的赋值的两个变量是两个独立相互不影响的变量。
但是引用类型的赋值是传址。只是改变指针的指向,例如,也就是说引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。例如:
浅拷贝
先来看一段代码的执行:
这段代码可以说明赋值得到的对象
obj1
只是将指针改变,其引用的仍然是同一个对象,而浅拷贝得到的的obj2
则是重新创建了新对象。但是,如果原对象obj
中存在另一个对象,则不会对对象做另一次拷贝,而是只复制其变量对象的地址。这是因为浅拷贝只复制一层对象的属性,并不包括对象里面的为引用类型的数据。对于数组,更长见的浅拷贝方法便是
slice(0)
和concat()
ES6 比较常见的浅拷贝方法便是
Object.assign
深拷贝
通过上面的这些说明,相信你对深拷贝大致了解了是怎样一个东西了:深拷贝是对对象以及对象的所有子对象进行拷贝。那么如何实现这样一个深拷贝呢?
1. JSON.parse(JSON.stringify(obj))
对于常规的对象,我们可以通过
JSON.stringify
来讲对象转成一个字符串,然后在用JSON.parse
来为其分配另一个存储地址,这样可以解决内存地址指向同一个的问题:但是
JSON.parse()
、JSON.stringify
也存在一个问题,JSON.parse()
和JSON.stringify()
能正确处理的对象只有Number、String、Array
等能够被 json 表示的数据结构,因此函数这种不能被 json 表示的类型将不能被正确处理。2. 遍历实现属性复制
既然浅拷贝只能实现非
object
第一层属性的复制,那么遇到object
只需要通过递归实现浅拷贝其中内部的属性即可:我们再来看一下
Zepto
中深拷贝的代码:内部实现其实也是差不多。
参考资料
js 深拷贝 vs 浅拷贝
The text was updated successfully, but these errors were encountered: