迅雷面試題:深拷貝對象,除了原型上的屬性?

第一種:深拷貝原型上的屬性

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);
發佈了179 篇原創文章 · 獲贊 82 · 訪問量 55萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章