js的對象引用傳遞理解起來很簡單,參考以下代碼:javascript
var a = {name:'wanger'} var b = a ; a===b // true b.name = 'zhangsan' a.name //'zhangan'
上述代碼中,使用了=
進行賦值,因而b指向了a所指向的棧的對象,也就是a與b指向了同一個棧對象,因此在對b.name賦值時,a.name也發生了變化。爲了不上面的狀況,能夠對對象進行拷貝,代碼以下:html
var a = {name:'wanger'} var b = Object.assign({}, a) a===b // false b.name = 'zhangsan' a.name //'wanger'
上面代碼將原始對象拷貝到一個空對象,就獲得了原始對象的克隆,這時候a與b指向的是不一樣的棧對象,因此對b.name從新複製也不會影響到a.name。可是若是a.name是一個對象的引用,而不是一個字符串,那麼上面的代碼也會遇到一些問題,參考以下代碼:java
var a = {name:{firstName:'wang',lastName:'er'}} var b = Object.assign({}, a) a===b // false b.name.firstName = 'zhang' a.name.firstName //'zhang'
b.name.firstName又影響到了a.name.firstName,這是由於Object.assign()方法只是淺層拷貝,a.name是一個棧對象的引用,賦值給b時,b.name也一樣是這個棧對象的引用,不少時候,咱們不想讓這種事情發生,因此咱們就須要用到對象的深拷貝。算法
一般狀況下,咱們可使用JSON.parse()與 JSON.stringify()實現對象的深克隆,以下:json
var clone = function (obj) { return JSON.parse(JSON.stringify(obj)); }
這種方法只適用於純數據json對象的深度克隆,由於有些時候,這種方法也有缺陷,參考以下代碼:數組
var clone = function (obj) { return JSON.parse(JSON.stringify(obj)); } var a = {a:function(){console.log('hello world')},b:{c:1},c:[1,2,3],d:"wanger",e:new Date(),f:null,g:undefined} var b = clone(a)
打印以下:
咱們發現,上述的方法會忽略值爲function以及undefied的字段,並且對date類型的支持也不太友好。函數
更要緊的是,上述方法只能克隆原始對象自身的值,不能克隆它繼承的值,參考以下代碼:ui
function Person (name) { this.name = name } var wanger = new Person('王二') var newwanger = clone(wanger) wanger.constructor === Person // true newwanger.constructor === Object // true
打印以下:
this
咱們發現,克隆的對象的構造函數已經變成了Object,而原來的對象的構造是Person。spa
王二在網上參考了很多文章,方法都不盡完美,因而在前人基礎上改造了一下,方法以下,目前沒有發現有什麼bug:
var clone = function (obj) { if(obj === null) return null if(typeof obj !== 'object') return obj; if(obj.constructor===Date) return new Date(obj); var newObj = new obj.constructor (); //保持繼承鏈 for (var key in obj) { if (obj.hasOwnProperty(key)) { //不遍歷其原型鏈上的屬性 var val = obj[key]; newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除與函數名的耦合 } } return newObj; };
這裏有三點須要注意:
一、用new obj.constructor ()
構造函數新建一個空的對象,而不是使用{}
或者[]
,這樣能夠保持原形鏈的繼承;
二、用obj.hasOwnProperty(key)
來判斷屬性是否來自原型鏈上,由於for..in..
也會遍歷其原型鏈上的可枚舉屬性。
三、上面的函數用到遞歸算法,在函數有名字,並且名字之後也不會變的狀況下,這樣定義沒有問題。但問題是這個函數的執行與函數名 factorial 牢牢耦合在了一塊兒。爲了消除這種緊密耦合的現象,須要使用 arguments.callee
。
2017-10-03添加,以前沒有考慮正則對象的問題,這裏再作一下修改:
原文:https://www.cnblogs.com/wangyulue/articles/7684515.html#4096147var clone = function (obj) { if(obj === null) return null if(typeof obj !== 'object') return obj; if(obj.constructor===Date) return new Date(obj); if(obj.constructor === RegExp) return new RegExp(obj); var newObj = new obj.constructor (); //保持繼承鏈 for (var key in obj) { if (obj.hasOwnProperty(key)) { //不遍歷其原型鏈上的屬性 var val = obj[key]; newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除與函數名的耦合 } } return newObj; };