第一種:深拷貝原型上的屬性
function clone(Obj) {
var buf;
if (Obj instanceof Array) {
buf = []; // 創建一個空的數組
var i = Obj.length;
while (i--) {
buf[i] = clone(Obj[i]);
}
return buf;
} else if (Obj instanceof Object){
buf = {}; // 創建一個空對象
for (var k in Obj) { // 爲這個對象添加新的屬性
buf[k] = clone(Obj[k]);
}
return buf;
}else{
return Obj;
}
}
//測試
function O() {
this.yyy = 'yyy';
}
function X() {
this.xxx = 'xxx';
}
X.prototype = new O();
x = new X();
var obj = { a : {k1:'a'}, b : 'b',x:x};
var obj2 = clone(obj);
obj.x.yyy = 'zzz'
console.log(obj2);
console.log(obj2.x.yyy);
以上對嗎,對象x具有原型上的屬性,通過函數clone也深拷貝了原型上的屬性;測試可知
改變對象obj上的原型屬性,obj2不變。
obj.x.yyy = 'zzz'
第二種:不深拷貝原型上的屬性(以及構造函數非和構造函數非Object的對象)
$ = function() {
var copyIsArray,
hasOwn = Object.prototype.hasOwnProperty,
isPlainObject = function(obj) {
if (obj.constructor && !hasOwn.call(obj, "constructor")
&& !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
var key;
for (key in obj) {
}
return key === undefined || hasOwn.call(obj, key);
},
extend = function(deep, target, options) {
var name,copy,clone;
for (name in options) {
src = target[name];
copy = options[name];
if (target === copy) { continue; } //爲了避免無限循環
if (deep && copy
&& (isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))) {
if (copyIsArray) {//如果copy是數組
copyIsArray = false;//迴歸初始值
clone = src && Array.isArray(src) ? src : [];//因爲這是對象合併,所以可能target[name]是存在的,存在則不需要拷貝。
} else {
clone = src && isPlainObject(src) ? src : {};
}
target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) {
target[name] = copy;
}
}
return target;
};
return { extend : extend };
}();
obj1 = { a : 'a', b : 'b', y : '1' };
obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' };
var combineObj = $.extend(true,obj1,obj2);
console.log(combineObj);
注意:(1)關鍵代碼是
isPlainObject = function(obj) {
if (obj.constructor && !hasOwn.call(obj, "constructor")
&& !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
var key;
for (key in obj) {
}
return key === undefined || hasOwn.call(obj, key);
},
(2)把遞歸的寫法搞清楚
第三種:不深拷貝原型上的屬性(本題答案)
//判斷是對象
var isObject = function(obj){
return Object.prototype.toString.call(obj) === '[object Object]'
}
//判斷是數組
var isArray = Array.isArray || function(obj){
return Object.prototype.toString.call(obj) === '[object Array]'
}
//獲取對象不在原型上的鍵
var get_keys = function(obj){
var keys = [];
for(key in obj){
if(hasOwnProperty.call(obj,key)){
keys.push(key);
}
}
return keys;
}
//實現深拷貝
var deepCopy = function(obj){
var temp ;
if(isArray(obj)){
temp = [];
for(var i = 0 ; i < obj.length ; i++){
if(isArray(obj[i])||isObject(obj[i])){
//當前項是數組或者對象,遞歸調用
temp.push(deepCopy(obj[i]));
}else{
temp.push(obj[i]);
}
}
}else if(isObject(obj)){
temp = {};
var keys = get_keys(obj);
var length = keys.length ;
for(var i = 0 ; i < length ; i++){
//將對象值遞歸,結果賦值到新創建的temp對象的key[i]屬性。
temp[keys[i]] = deepCopy(obj[keys[i]])
}
}else{
//傳入其他類型直接返回。
return obj;
}
return temp ;
}
注意:(1)主要是hasOwnProperty的用法:該方法可以檢測一個屬性是存在於實例中,還是存在於原型中。這個方法只有在只在給定屬性存在於對象實例中時,纔會返回true。
(2)第2種和第3種的區別:第2種是隻要對象的構造函數不是object,則不進行深拷貝,而第3種是對象的構造函數可以是非Object,不深拷貝對象原型上的屬性,只拷貝實例上的屬性。
舉例如下:
function O() {
this.yyy = 'yyy';
}
function X() {
this.xxx = 'xxx';
}
X.prototype = new O();
x = new X();
//var obj = { a : {k1:'a'}, b : 'b',x:x };
var obj = { a : {k1:'a'}, b : 'b',x:x};
var obj2 = deepCopy(obj);
obj.x.xxx = 'zzz'
obj.x.yyy = 'zzz'
console.log("obj ",obj); //{ a: { k1: 'a' }, b: 'b', x: O { xxx: 'zzz', yyy: 'zzz' } }
console.log("obj2 ",obj2); //{ a: { k1: 'a' }, b: 'b', x: { xxx: 'xxx' } }
console.log(obj2.x.xxx); //xxx 已經深拷貝,值不變
console.log(obj2.x.yyy); //undefined
console.log(x.hasOwnProperty("xxx"));//true
(3)deepCopy 函數是直接不拷貝(跳過)相關對象,而$.extend對於不滿足isPlainObject
的對象,則執行淺拷貝(即 target[name] = copy;
)。
第四種:不深拷貝原型上的屬性($.extend()的完整實現)
$ = function() {
var copyIsArray,
toString = Object.prototype.toString,
hasOwn = Object.prototype.hasOwnProperty,
class2type = {
'[object Boolean]' : 'boolean',
'[object Number]' : 'number',
'[object String]' : 'string',
'[object Function]' : 'function',
'[object Array]' : 'array',
'[object Date]' : 'date',
'[object RegExp]' : 'regExp',
'[object Object]' : 'object'
},
//對type的註釋:如果obj=null,則返回String(obj),即null,
type = function(obj) {
return obj == null ? String(obj) : class2type[toString.call(obj)] || "object";
},
isWindow = function(obj) {
return obj && typeof obj === "object" && "setInterval" in obj;
},
//如果瀏覽器有內置的Array.isArray 實現,就使用瀏覽器自身的實現方式,否則將對象轉爲String,看是否爲"[object Array]"。(1.這裏所說的將對象轉爲String即toString.call(obj) 2.考慮了兼容性 )
isArray = Array.isArray || function(obj) {
return type(obj) === "array";
},
isPlainObject = function(obj) {
if (!obj || type(obj) !== "object" || obj.nodeType || isWindow(obj)) {
return false;
}
if (obj.constructor && !hasOwn.call(obj, "constructor")
&& !hasOwn.call(obj.constructor.prototype, "isPrototypeOf")) {
return false;
}
var key;
for (key in obj) {
}
return key === undefined || hasOwn.call(obj, key);
},
extend = function(deep, target, options) {
var name,copy,clone;
for (name in options) {
src = target[name];
copy = options[name];
if (target === copy) { continue; } //爲了避免無限循環
//以下if將copy是對象和數組合併在一起
if (deep && copy
&& (isPlainObject(copy) || (copyIsArray = isArray(copy)))) {
if (copyIsArray) {//如果copy是數組
copyIsArray = false;//迴歸初始值
clone = src && isArray(src) ? src : [];//因爲這是對象合併,所以可能target[name]是存在的,存在則不需要拷貝。
} else {
clone = src && isPlainObject(src) ? src : {};
}
target[name] = extend(deep, clone, copy);
} else if (copy !== undefined) {//說明copy既不是對象,也不是數組,是基本類型,
直接賦值
target[name] = copy;
}
}
return target;
};
return { extend : extend };
}();
//測試
function O() {
this.yyy = 'yyy';
}
function X() {
this.xxx = 'xxx';
}
X.prototype = new O();
x = new X();
obj1 = { a : 'a', b : 'b', y : '1' };
obj2 = { x : { xxx : 'xxx', yyy : 'yyy' }, y : 'y' };
var combineObj = $.extend(true,obj1,obj2);
console.log(combineObj);