Array JavaScript  iOS裏的通性

 昨天寫的編輯器自動保存了,今天來了發現頁面關了重新打開只剩標題了(淚崩)

 最近在做前端項目,遇到一個數組對象修改問題。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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章