1. 程式人生 > >JS:判斷物件相等 物件深度克隆

JS:判斷物件相等 物件深度克隆

在JS中可以使用"=="和"==="來判斷兩個變數是否相等。"=="只判斷兩個變數值是否相等,不判斷型別是否相等,如果"=="兩邊的變數型別不同,則會先進行隱式型別轉換,嘗試將兩個變數都轉換為"=="左邊的型別,再判斷值是否相等。"==="則必須兩個變數的型別和值全都相等才判定為相等。

由於JS中的物件是一個引用型別資料,當使用物件的變數名進行比較時,事實上是在比較兩個變數名在棧記憶體中儲存的對應物件在堆記憶體中的地址,因此只有兩個變數名引用同一個物件的地址時才會判定為相等。

var obj1 = {
    name:"Benjamin",
    sex : "male"
}
 
var obj2 = {
    name:"Benjamin",
    sex : "male"
}
 
var obj3 = obj1;

//Outputs: false
console.log(obj1 == obj2);
 
//Outputs: false
console.log(obj1 === obj2);

//Outputs: true
console.log(obj1 == obj3);
 
//Outputs: true
console.log(obj1 === obj3);
 
//Outputs: false
console.log(obj2 == obj3);
 
//Outputs: false
console.log(obj2 === obj3);

通過上面的程式碼可以看出,由於obj1和ob3的指標指向了記憶體中的同一個地址,所以判定相等,而obj1和obj2即使其屬性完全相同,也不會判定為相等,obj2和obj3同理。

當需要判斷兩個物件的屬性是否相等時,需要根據需求考慮以下幾點:

  1. 是否只比較物件的可列舉屬性,排除不可列舉屬性
  2. 是否只比較物件的自有屬性,排除原型屬性

確定需求之後可以使用下面列出的方法來嘗試建立一個用於實現物件比較的函式:

方法 適用範圍 描述
for..in 陣列,物件 遍歷例項和原型的可列舉的屬性
Object.keys() 陣列,物件 返回例項自有的可列舉的屬性名組成的陣列
Object.getPropertyNames() 陣列,物件 返回例項自有的全部(可列舉和不可列舉)屬性名組成的陣列

下面我們嘗試構建一個可用於比較兩個物件自有全部屬性的函式

function isObjectValueEqual(a, b) {

	//如果a和b不是null或undefined且都是物件或陣列才進行判斷
	if (a && b && typeof a === "object" && typeof b === "object") {
    
	    // 獲取兩個物件的自有全部屬性名的陣列
	    var aProps = Object.getOwnPropertyNames(a);
	    var bProps = Object.getOwnPropertyNames(b);
	 
	    // 如果屬性數量不一致則直接判定為不相等
	    if (aProps.length != bProps.length) {
	        return false;
	    }
	     
	    //遍歷a物件全部自有屬性
	    for (var i in aProps) {
	        var propName = aProps[i];
	        
	        //如果屬性是引用型別(陣列,物件),且b也具有該屬性
	        if (b[propName] && typeof a[propName] === "object"){

	        	//則遞迴呼叫isObjectValueEqual函式
	            var isObjPropEqual = isObjectValueEqual(a[propName],b[propName]);
	            if(!isObjPropEqual) {
	                return false;
	            }
	        } else if (typeof a[propName] === "function") {
                
                //注意:並沒有判斷物件方法相等,只是判斷是具有相同方法名
                if ( !b[propName] ){
                    return false;
                } 
            } else if (a[propName] !== b[propName]){  // 如果屬性是值型別

	        	//則直接比較值和型別,注意使用全等判斷,避免隱式型別轉換
		        return false;
	        }
	    }

	    //如果a或b存在空值或不是都是物件或陣列則判定不等
	    return true;
    } else {

    	//如果a或b存在空值或不是都是物件或陣列則判定不等
    	return false;
    }
    
}
 
var obj1 = {
    name:"Benjamin",
    sex : "male",
    desc:{
    	title:'title',
    	content:'content'
    }
};
 
var obj2 = {
    name:"Benjamin",
    sex : "male",
    desc:{
    	title:'title',
    	content:'content'
    }
};

console.log(isObjectValueEqual(obj1, obj2)); // true

注意,這個函式只判斷了兩個物件內的全部自有屬性,不包括原型方法,如果需要判斷原型屬性,可以考慮使用for in、原型鏈遞迴等方式來改寫上面的方法,還有一點值得注意的是,這個函式不支援判斷兩個物件的方法,只支援判斷屬性。

下面這個方法是Underscore中isEqual的部分原始碼,實現了更深的物件判斷,可以參考其思路:

// Internal recursive comparison function for `isEqual`.
var eq = function(a, b, aStack, bStack) {
  // Identical objects are equal. `0 === -0`, but they aren't identical.
  // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
  if (a === b) return a !== 0 || 1 / a === 1 / b;
  // A strict comparison is necessary because `null == undefined`.
  if (a == null || b == null)return a === b;
  // Unwrap any wrapped objects.
  if (ainstanceof _) a = a._wrapped;
  if (binstanceof _) b = b._wrapped;
  // Compare `[[Class]]` names.
  var className = toString.call(a);
  if (className !== toString.call(b)) return false;
  switch (className) {
    // Strings, numbers, regular expressions, dates, and booleans are compared by value.
    case '[object RegExp]':
    // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
    case '[object String]':
      // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
      // equivalent to `new String("5")`.
      return '' + a === '' + b;
    case '[object Number]':
      // `NaN`s are equivalent, but non-reflexive.
      // Object(NaN) is equivalent to NaN
      if (+a !== +a) return +b !== +b;
      // An `egal` comparison is performed for other numeric values.
      return +a === 0 ? 1 / +a === 1 / b : +a === +b;
    case '[object Date]':
    case '[object Boolean]':
      // Coerce dates and booleans to numeric primitive values. Dates are compared by their
      // millisecond representations. Note that invalid dates with millisecond representations
      // of `NaN` are not equivalent.
      return +a === +b;
  }
  if (typeof a != 'object' || typeof b != 'object') return false;
  // Assume equality for cyclic structures. The algorithm for detecting cyclic
  // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
  var length = aStack.length;
  while (length--) {
    // Linear search. Performance is inversely proportional to the number of
    // unique nested structures.
    if (aStack[length] === a) return bStack[length] === b;
  }
  // Objects with different constructors are not equivalent, but `Object`s
  // from different frames are.
  var aCtor = a.constructor, bCtor = b.constructor;
  if (
    aCtor !== bCtor &&
    // Handle Object.create(x) cases
    'constructor' in a && 'constructor' in b &&
    !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
      _.isFunction(bCtor) && bCtor instanceof bCtor)
  ) {
    return false;
  }
  // Add the first object to the stack of traversed objects.
  aStack.push(a);
  bStack.push(b);
  var size, result;
  // Recursively compare objects and arrays.
  if (className === '[object Array]') {
    // Compare array lengths to determine if a deep comparison is necessary.
    size = a.length;
    result = size === b.length;
    if (result) {
      // Deep compare the contents, ignoring non-numeric properties.
      while (size--) {
        if (!(result = eq(a[size], b[size], aStack, bStack))) break;
      }
    }
  }else {
    // Deep compare objects.
    var keys = _.keys(a), key;
    size = keys.length;
    // Ensure that both objects contain the same number of properties before comparing deep equality.
    result = _.keys(b).length === size;
    if (result) {
      while (size--) {
        // Deep compare each member
        key = keys[size];
        if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break;
      }
    }
  }
  // Remove the first object from the stack of traversed objects.
  aStack.pop();
  bStack.pop();
  return result;
};
 
// Perform a deep comparison to check if two objects are equal.
_.isEqual = function(a, b) {
  return eq(a, b, [], []);
}; 

利用前面的思路可以嘗試建立一個用於深度克隆物件的函式:

function deepClone(obj) {

    var newObj = null
	
    //判斷obj不是null、undefined、''、0,且是物件或陣列
	if (obj && typeof obj === "object"){

        // Date物件
        if (obj instanceof Date) {
            newObj = new Date();
            newObj.setTime(obj.getTime());

        // 陣列
        }else if (obj instanceof Array) {
            newObj = [];
            for (var i = 0, len = obj.length; i < len; i++) {
                newObj[i] = deepClone(obj[i]);
            }

        // 其他物件
        }else{
        
            // 建立新物件或陣列
    	    newObj = obj instanceof Array ? [] : {}
    
            //定義屬性描述符拷貝函式
            function descriptor(obj1,obj2,prop,value){

                //獲取目標物件的屬性描述符
                var descriptor1 = Object.getOwnPropertyDescriptor(obj1, prop)
                var descriptor2 = {}

                //將目標物件的屬性描述符的值賦給新物件的屬性
                for(key in descriptor1){
                    if(typeof descriptor1[key] === "function"){
                        descriptor2[key] = descriptor1[key]
                    }else{
                        descriptor2[key] = descriptor1[key]
                    }
                }

                //屬性描述符的value要使用傳進來的值
                descriptor2.value = value
                Object.defineProperty(obj2,prop,descriptor2)

                descriptor1 = null
                descriptor2 = null
            }

            // 獲取物件的自有全部屬性名的陣列
	        let props = Object.getOwnPropertyNames(obj)

	        //遍歷obj物件全部自有屬性
	        for (var i in props) {
	            let propName = props[i];

	            //如果屬性是引用型別(陣列,物件)
	            if (typeof obj[propName] === "object"){

                    //則遞迴呼叫deepClone函式
                    descriptor(obj,newObj,propName,deepClone(obj[propName]))

                // 如果屬性是函式
	            } else if (typeof obj[propName] === "function") {
                
                    //則直接引用
                    descriptor(obj,newObj,propName,obj[propName])
                
                // 如果屬性是值型別
                } else {  

	        	    //則直接賦值
                    descriptor(obj,newObj,propName,obj[propName])
	            }
	        }

            //最後把newObj的原型指向obj的原型
            newObj.__proto__ = obj.__proto__
        
            props = null
         }
    }

     //如果obj為空值或不是物件或陣列則返回null
     return newObj;
}

var obj1 = {
    name:"Benjamin",
    sex : "male",
    desc:{
    	title:'title',
    	content:'content'
    }
};
 
var obj2 = deepClone(obj1);

console.log(obj2); //{name: "Benjamin", sex: "male", desc: {…}}

console.log(isObjectValueEqual(obj1, obj2)); //true

這個函式克隆了引數物件內的全部自有屬性,且屬性描述符也和原物件保持一致,並把原型指向了引數物件的原型,使用直接引用方式獲得引數物件的方法。

原文連結:http://www.zuojj.com/archives/775.html,轉載請註明轉自Benjamin-專注前端開發和使用者體驗

相關推薦

JS判斷物件相等 物件深度克隆

在JS中可以使用"=="和"==="來判斷兩個變數是否相等。"=="只判斷兩個變數值是否相等,不判斷型別是否相等,如果"=="兩邊的變數型別不同,則會先進行隱式型別轉換,嘗試將兩個變數都轉換為"=="左邊的型別,再判斷值是否相等。"==="則必須兩個變數的型別和值全都相等才判

JS判斷一個物件是否為空

function isEmpty(obj) { // 判斷物件是否為空物件 for (var name in obj) { return false; } // 不為空

js判斷字串相等使用==

在 javaScript或者jQuery中字串比較沒有equals()方法,要比較兩個字串是否相等可以直接用==或者is()進行判斷。 一段老的js程式碼示例: var items = document

.NET中如何深度判斷2個物件相等

背景 最近在群裡,有人問如何深度比較2個物件相等,感覺很有意思,就自己研究了一下,並寫了一個開源的小類庫,地址如下https://github.com/lamondlu/ObjectEquality。 如果想直接使用這個類庫,可以使用Nuget進行安裝 Install-Package ObjectE

遞迴方法實現深度克隆原理遍歷物件、陣列直到裡邊都是基本資料型別,然後再去複製,就是深度拷貝。

手寫遞迴方法 //定義檢測資料型別的功能函式 function checkedType(target) { return Object.prototype.toString.call(target).slice(8, -1) } //實現深度克隆---物件/陣列 function clon

JS判斷兩個物件內容是否相等的方法示例

這篇文章主要介紹了JS判斷兩個物件內容是否相等的方法,結合具體例項形式分析了javascript針對字串、陣列及物件的相關判斷技巧,需要的朋友可以參考下 本文例項講述了JS判斷兩個物件內容是否相等的方法。分享給大家供大家參考,具體如下: 我們知道,如果兩個物件即使內容

js 判斷兩個物件是否相同

isObjectEqual (a, b) { // 獲取a、b的型別 var classNameA = toString.call(a) var classNameB = toString.call(b) if (classNameA === '[obj

面試題判斷一個物件是不是陣列型別

<script> //判斷一個物件是不是陣列型別 typeof不能檢視所有型別 var obj1={x:1,y:2}, obj2=[1,2,3], obj3=new Date(); //1.判斷爹(原型物件)

JS-JavaScript String 物件-string物件方法1charAt()

1.charAt():可返回指定位置的字元。   1). 語法:string.charAt(index)  (index必需。表示字串中某個位置的數字,即字元在字串中的位置。)   2). 第一個字元位置為 0, 第二個字元位置為 1,以此類推.   3).

Java 判斷自定義物件是否相等,需要覆寫equals()方法

public class Test { private Integer id; private String name; public Integer getId() { return id; } public void setId(Integer id) { th

物件深度克隆方法

方法1: function deepclone(obj) { if (typeof obj === 'object' && obj !== null) { if (obj instanceof Array) { let newArr

學習JS中建立自己的物件和在定義中包含方法

1.建立自己的物件 要建立自己的物件例項,必須首先為其定義一個建構函式。建構函式建立一個新物件,賦予物件屬性,並在合適的時候賦予物件方法 // people 是有3個引數的構造器 function people(name, age, sex){ // 屬性:姓名

判斷兩個物件相等—網易一面

思路:首先明白,JS的物件型別很多,針對每個型別判斷相等的方法都不同。 物件型別:string、Boolean、number、array、date、建構函式...... 我們認為: NaN 和 NaN 是相等 [1] 和 [1] 是相等 {value: 1} 和 {va

javascript物件深度克隆探索

說到js裡的物件克隆,首先要了解js裡的資料型別有哪些: 1、number string boolean null undefined 基本型別,也叫原始型別 2、object array function 引用型別 對於以上兩種資料型別的克隆不相同,對於原始型別,我們

JS面向物件程式設計(一)

Declare JavaScript Objects as Variables 在我們深入 面向物件程式設計之前 ,讓我們先回顧一下Javascript的 物件(Object) Construct JavaScript Objects with Functions 除了上一種方

scala 判斷物件相等/equals

1 package scala_enhance.scalaextends 2 3 import scala.collection.mutable.HashMap 4 5 /** 6 * scala中判斷物件相等 7 * 原則: 8 * 如果兩個物件相等,那麼其hashcode必

Java比較兩個物件中全部屬性值是否相等

Java:比較兩個物件中全部屬性值是否相等 例如下述Java類: import java.io.Serializable; import java.util.List; public class Bean_Topology implements Serial

JS物件陣列的深度拷貝

直接=是淺拷貝,對於想完全複製可以這樣: 途徑1  let arrObj2=JSON.parse(JSON.stringify(arrObj1)); 途徑2  var objDeepCopy = function (source) {     var sourceCop

javascript 陣列、物件深度克隆

最近專案過程中針對陣列及物件的賦值發現以下問題 情況一: var A={age:12,name:'anne'},B=A; B.grade='五年級'; console.log('A:',A,'B:',B); //A:{age:12,name:'anne'

js篇-判斷陣列物件中是否含有某個值,並返回該條資料

專案背景需求是: 已知: var a=[{name:'jenny',age:18},{name:'john',age:19},{name:'jack',age:20}] var b ='jenny' 返回: {name:'jenny',age:18} 解題思路:現將陣列轉成字串,判斷改陣列中是否存