Array JavaScript  iOS里的通性

20 篇文章 0 订阅
7 篇文章 0 订阅

 昨天写的编辑器自动保存了,今天来了发现页面关了重新打开只剩标题了(泪崩)

 最近在做前端项目,遇到一个数组对象修改问题。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 <NSCopying>
@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 <NSCopying> 
@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协议)

这里对我之前文章深拷贝,浅拷贝做个总结

对于不可变类型

  • copy 是浅拷贝,只拷贝指针
  • mutableCopy 是深拷贝,拷贝了value,分配了新内存

对于可变类型

  • copy 深拷贝,拷贝了value,分配了新内存
  • mutableCopy 是深拷贝,拷贝了value,分配了新内存

对于不可变集合(NSArray,NSSet,NSDictionary,NSHashTable)

  • 对于集合本身,Copy只是拷贝了指针,指针仍然指向最初的Array对象
  • 对于集合本身,MutableCopy拷贝了value,分配了新内存
  • 对于集合中存储的对象,不管是copy还是mutableCopy,都是浅拷贝,,指针指向,没有开辟新内存

对于可变集合(NSMutableArray,NSMutableSet,NSMutableDictionary)

  • 对于集合本身,Copy拷贝了value,并且变成了不可变集合,分配了新内存
  • 对于集合本身,MutableCopy拷贝了value,新的可变集合,分配了新内存
  • 对于集合中存储的对象,不管是copy还是mutableCopy,都是浅拷贝,指针指向,没有开辟新内存

说到这里,不由探究一下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]一样

  • _used:当前对象数目
  • _offset:实际对象数组的起始偏移,这个字段的用处稍后会讨论
  • _size:已分配的_list大小(能存储的对象个数,不是字节数)
  • _mutations:修改标记,每次对__NSArrayM的修改操作都会使_mutations加1
  • id *_list是个循环数组.并且在增删操作时会动态地重新分配以符合当前的存储需求.

还有扩展知识,比如数组和链表的区别,如何用链表实现一个数组,他们的优缺点试用场景。

这里过多不再讲述,可以参考文章

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Asia_ZhangQQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值