昨天写的编辑器自动保存了,今天来了发现页面关了重新打开只剩标题了(泪崩)
最近在做前端项目,遇到一个数组对象修改问题。eg:
[{"age":24,"sex":"boy","love":girl},{"age":24,"sex":"boy","love":girl},{"age":24,"sex":"boy","love":girl}]发现随便修改一下里面对象age,结果全部对象age修改。在我一脸懵逼中明白了,原来js里也有浅拷贝和深拷贝。我们在copy的数据类型是值类型还是引用类型,对我们后面的使用至关重要。比如这里的我就是copy 引用对象导致浅拷贝,修改一处所有修改。
那么js里面如何实现数组Array的浅拷贝和深拷贝呢,我解决方法用的是引用类型转json再json解析。下面给出几种方法
// 数组的浅拷贝,可用concat、slice返回一个新数组的特性来实现拷贝
var arr = ['old', 1, true, null, undefined];
var new_arr = arr.concat(); // 或者var new_arr = arr.slice()也是一样的效果;
new_arr[0] = 'new';
console.log(arr); // ["old", 1, true, null, undefined]
console.log(new_arr); // ["new", 1, true, null, undefined]
// 但是如果数组嵌套了对象或者数组的话用concat、slice拷贝只要有修改会引起新旧数组都一起改变了,比如:
var arr = [{old: 'old'}, ['old']];
var new_arr = arr.concat();
arr[0].old = 'new';
new_arr[1][0] = 'new';
console.log(arr); // [{old: 'new'}, ['new']]
console.log(new_arr); // [{old: 'new'}, ['new']]
// 如果数组元素是基本类型,就会拷贝一份,互不影响,而如果是对象或者数组,就会只拷贝对象和数组的引用,这样我们无论在新旧数组进行了修改,两者都会发生变化。这种叫浅拷贝
// 深拷贝就是指完全的拷贝一个对象,即使嵌套了对象,两者也相互分离,修改一个对象的属性,也不会影响另一个。
// 数组的深拷贝
// 技巧一:不仅可拷贝数组还能拷贝对象(但不能拷贝函数)
var arr = ['old', 1, true, ['old1', 'old2'], {old: 1}]
var new_arr = JSON.parse(JSON.stringify(arr))
console.log(new_arr);
// 下面是深拷贝一个通用方法,实现思路:拷贝的时候判断属性值的类型,如果是对象,继续递归调用深拷贝函数
var deepCopy = function(obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是一个对象
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
// 遍历obj,并且判断是obj的属性才拷贝
if (obj.hasOwnProperty(key)) {
// 判断属性值的类型,如果是对象递归调用深拷贝
newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
}
}
return newObj;
}
// 下面是浅拷贝一个通用方法,实现思路:遍历对象,把属性和属性值都放在一个新的对象里
var shallowCopy = function (obj) {
// 只拷贝对象
if (typeof obj !== 'object') return;
// 根据obj的类型判断是新建一个数组还是一个对象
var newObj = obj instanceof Array ? [] : {};
// 遍历obj,并且判断是obj的属性才拷贝
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
除了以上。有人说Object.assign()可以进行深拷贝,其实是错误的,当拷贝的属性是引用类型的时候,拷贝到也指向引用那个值。这个也需要注意下!
这里我们发现了数组Array JavaScript iOS里的通性,上面讲了js的深拷贝,浅拷贝。那么iOS的呢,这里再老生常谈下(本来不愿再提了,刚好给公司前端分享iOS顺带整理下)。可以看看我之前引用的深拷贝浅拷贝,文章链接https://blog.csdn.net/Asia_ZhangQQ/article/details/70255643,里面对数组的深拷贝,浅拷贝有详细讲解。
web前端分享,那先来讲讲概念
iOS中有深拷贝和浅拷贝的概念,那么何为深拷贝何为浅拷贝呢?
浅拷贝:浅拷贝并不拷贝对象本身,只是对指向对象的指针进行拷贝
深拷贝:直接拷贝对象到内存中一块区域,然后把新对象的指针指向这块内存
这里主要以Objective-c来谈谈,在iOS中拷贝常用就是Copy和MutableCopy,但是并不是所有对象都支持Copy和MutableCopy,遵循NSCopying协议的类可以发送Copy协议,遵循NSMutableCopying协议的类可以发送MutableCopy消息。如果一个对象没有遵循这两个协议而发送Copy或者MutableCopy消息那么会发生异常。如果要遵循NSCopying协议,那么必须实现copyWithZone方法。如果要遵循NSMutableCopying协议那么必须实现mutableCopyWithZone方法。
比如定义一个person类,虽然继承了我们根类NSObject,根类有-(id)copy,-(id)mutableCopy 方法,但是没有实现NSCopying协议,所以需要实现NSCopying协议(系统类如NSString NSArray系统已经帮我们实现了)
@interface Person : NSObject
@property (nonatomic, strong) NSString *userId;
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone
{
//Person *p = [[Person alloc] init]; // 不要用这种封装你的类(容易出bug见下面子类)
Person *p = [[[self class] alloc] init]; // <== 注意这里要这么写
p.userId = self.userId;
return p;
}
@end
@interface Student : Person
@property (nonatomic, strong) NSString *studentId;
@end
@implementation Student
- (id)copyWithZone:(NSZone *)zone
{
Student *s = [super copyWithZone:zone]; // 第一种写法返回Person实例非Student,父类没studentId,set崩溃,第二种方式才是Student实例
s.studentId = self.studentId;
return s;
}
@end
[person copy]就可以拷贝了([student mutableCopy]的话,就需要实现NSMutableCopying协议)
这里对我之前文章深拷贝,浅拷贝做个总结
对于不可变类型
对于可变类型
对于不可变集合(NSArray,NSSet,NSDictionary,NSHashTable)
对于可变集合(NSMutableArray,NSMutableSet,NSMutableDictionary)
说到这里,不由探究一下iOS里面NSArray,NSMutableArray的底层原理。
可以看看这篇文章
https://blog.csdn.net/qq_27909209/article/details/82689322
清晰易懂,我们可以看到,不管创建的事可变还是不可变的数组,在alloc之后得到的类都是 __NSPlaceholderArray。而当我们init一个不可变的空数组之后,得到的是**__NSArray0**;如果有且只有一个元素,那就是 __NSSingleObjectArrayI;有多个元素的,叫做 __NSArrayI;init出来一个可变数组的话,都是 __NSArrayM。
我们来看一下内部结构:
1.__NSArrayI
__NSArrayI的结构定义为:
@interface__NSArrayI: NSArray
{
NSUInteger_used;
id_list[ 0];
}
@end
_used是数组的元素个数,调用[array count]时,返回的就是_used的值。这里我们可以把id _list[0]当作id *_list来用,即一个存储id对象的buff.由于__NSArrayI的不可变,所以_list一旦分配,释放之前都不会再有移动删除操作了,只有获取对象一种操作.因此__NSArrayI的实现并不复杂.
2.__NSSingleObjectArrayI
__NSSingleObjectArrayI的结构定义为:
@interface__NSSingleObjectArrayI: NSArray
{
idobject;
}
@end
因为只有在"创建只包含一个对象的不可变数组"时,才会得到__NSSingleObjectArrayI对象,所以其内部结构更加简单,一个object足矣.
3.__NSArrayM
__NSArrayM的结构定义为:
@interface__NSArrayM: NSMutableArray
{
NSUInteger_used;
NSUInteger_offset;
int_size: 28;
int_unused: 4;
uint32_t _mutations;
id*_list;
}
@end
__NSArrayM稍微复杂一些,但是同样的,它的内部对象数组也是一块连续内存id* _list,正如__NSArrayI的id _list[0]一样
还有扩展知识,比如数组和链表的区别,如何用链表实现一个数组,他们的优缺点试用场景。
这里过多不再讲述,可以参考文章
https://blog.csdn.net/qq_27909209/article/details/82689322
https://blog.csdn.net/qq_37268201/article/details/80448848
https://blog.csdn.net/m0_37631322/article/details/81777855
https://blog.csdn.net/weibo1230123/article/details/82011889