JavaScriptの一意のオブジェクト識別子


119

いくつかの実験を行う必要があり、JavaScriptでオブジェクトのある種の一意の識別子を知る必要があるため、それらが同じであるかどうかを確認できます。等価演算子を使いたくありません。Pythonのid()関数のようなものが必要です。

このようなものは存在しますか?


2
私は少し気になります、なぜ等値演算子を避けたいのですか?
CMS 2010年

22
シンプルなものが欲しいし、明確な数字が欲しいからです。この言語は子猫を泣かせます、私はすでにそれを十分に戦っています。
Stefano Borini、2010年

4
厳密な等価演算子(===)は、オブジェクトに対して要求することを実行し(数値/文字列などを比較する場合は同じではありません)、すべてのオブジェクトに秘密の一意のIDを作成するよりも簡単です。
Ben Zotto

4
@CMS @Ben一意のIDを持つことは、IdentitySetなどのデバッグまたは実装に役立ちます。
Alex Jasmin、2010年

9
私は、javascriptの理解に大きな進歩を遂げたことを伝えたいと思います。一種のシナプスを閉じました。これですべてが明確になりました。私は物を見てきました。私はJavaScriptプログラマーのレベルを取得しました。
Stefano Borini、2010年

回答:


68

更新以下の私の元の回答は、6年前に時代と私の理解に適した方法で書かれました。コメントでの会話に応えて、これに対するより現代的なアプローチは次のとおりです。

(function() {
    if ( typeof Object.id == "undefined" ) {
        var id = 0;

        Object.id = function(o) {
            if ( typeof o.__uniqueid == "undefined" ) {
                Object.defineProperty(o, "__uniqueid", {
                    value: ++id,
                    enumerable: false,
                    // This could go either way, depending on your 
                    // interpretation of what an "id" is
                    writable: false
                });
            }

            return o.__uniqueid;
        };
    }
})();

var obj = { a: 1, b: 1 };

console.log(Object.id(obj));
console.log(Object.id([]));
console.log(Object.id({}));
console.log(Object.id(/./));
console.log(Object.id(function() {}));

for (var k in obj) {
    if (obj.hasOwnProperty(k)) {
        console.log(k);
    }
}
// Logged keys are `a` and `b`

古いブラウザ要件がある場合は、ここでブラウザの互換性を確認してください。Object.defineProperty

比較は価値があると思うので、元の答えは(変更履歴ではなく)以下に保持されます。


次のスピンを与えることができます。これにより、コンストラクターまたはその他の場所でオブジェクトのIDを明示的に設定することもできます。

(function() {
    if ( typeof Object.prototype.uniqueId == "undefined" ) {
        var id = 0;
        Object.prototype.uniqueId = function() {
            if ( typeof this.__uniqueid == "undefined" ) {
                this.__uniqueid = ++id;
            }
            return this.__uniqueid;
        };
    }
})();

var obj1 = {};
var obj2 = new Object();

console.log(obj1.uniqueId());
console.log(obj2.uniqueId());
console.log([].uniqueId());
console.log({}.uniqueId());
console.log(/./.uniqueId());
console.log((function() {}).uniqueId());

一意のIDを内部的に格納するために使用するメンバーが、自動的に作成される別のメンバー名と競合しないように注意してください。


1
@Justinこれらのプロパティはすべてのオブジェクトで列挙可能であるため、ECMAScript 3ではObject.prototypeにプロパティを追加するのに問題があります。したがって、Object.prototype.aを定義すると、(prop in {})alert(prop)を実行するときに「a」が表示されます したがって、Object.prototypeを拡張することと、for..inループを使用してレコードのようなオブジェクトを反復処理できることの間で妥協する必要があります。これは図書館にとって深刻な問題です
Alex Jasmin

29
妥協はありません。ループを使用するobject.hasOwnProperty(member)場合は常に使用することがベストプラクティスであると長い間考えられてきましたfor..in。これは十分に文書化された慣行であり、jslintによって実施されている
Justin Johnson

3
私もどちらもこれを行うことをお勧めしません、少なくともすべてのオブジェクトに対してではなく、処理するオブジェクトだけで同じことを行うことができます。残念ながら、ほとんどの場合、外部JavaScriptライブラリを使用する必要があります。残念ながら、すべてのライブラリが適切にプログラムされているわけではないため、Webページに含まれるすべてのライブラリを完全に制御できない場合、または少なくともそれらが適切に処理されていることがわかっている場合を除きます。 。
役に立たない

1
@JustinJohnson:ES5には妥協点がありますdefineProperty(…, {enumerable:false})とにかくuidメソッド自体Object名前空間にある必要があります
Bergi

@JustinJohnsonの場合、オブジェクトIDは4、id 4のobjを変数にどのように割り当てれば、プロパティにアクセスできるように変数を使用できるようになりますか?
2015年

50

私の見解では、ここに投稿された回答は予期しない副作用をもたらす可能性があります。

ES2015互換の環境では、WeakMapを使用して副作用を回避できます。

const id = (() => {
    let currentId = 0;
    const map = new WeakMap();

    return (object) => {
        if (!map.has(object)) {
            map.set(object, ++currentId);
        }

        return map.get(object);
    };
})();

id({}); //=> 1

1
なんでreturn currentId
Gust van de Wal

なぜでWeakMapはなくてMap?文字列または数値を指定すると、その関数はクラッシュします。
Nate Symer、

35

最新のブラウザーは、Object.prototypeを拡張するためのより明確な方法を提供します。このコードは、プロパティ列挙からプロパティを隠します(oのpの場合)

definePropertyを実装するブラウザでは、次のようにuniqueIdプロパティを実装できます。

(function() {
    var id_counter = 1;
    Object.defineProperty(Object.prototype, "__uniqueId", {
        writable: true
    });
    Object.defineProperty(Object.prototype, "uniqueId", {
        get: function() {
            if (this.__uniqueId == undefined)
                this.__uniqueId = id_counter++;
            return this.__uniqueId;
        }
    });
}());

詳細については、https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/definePropertyを参照してください


2
「最新のブラウザ」には、Firefox 3.6が含まれていないようです。(はい、Firefoxの新しいバージョンでのアップグレードレースをオプトアウトしています。私だけではないと思います。その上、FF3.6は1歳です。)
bart

7
このスポーツは1歳だけではありません。ウェブは動的に進化します-それは良いことです。最新のブラウザには、それを可能にする目的で正確に自動アップデーターがあります。
Kos

1
数年後、これは私にとって受け入れられた答えよりもうまく機能しているようです。
Fitter Man

11

実際には、objectプロトタイプを変更して関数を追加する必要はありません。以下はあなたの目的のためにうまくいくはずです。

var __next_objid=1;
function objectId(obj) {
    if (obj==null) return null;
    if (obj.__obj_id==null) obj.__obj_id=__next_objid++;
    return obj.__obj_id;
}

4
nocase、snake_case camelCaseの3つすべてが6行のコードスニペットになりました。あなたが毎日表示されていない
ガストファンデウォルマート

これは、objectハックよりもはるかに説得力のあるメカニズムのようです。OPのオブジェクトマッチングが必要な場合にのみ実行する必要があり、残りの時間は邪魔になりません。
JL Peyret

6

を実装しているブラウザの場合 Object.defineProperty()メソッドを場合、以下のコードは、所有する任意のオブジェクトにバインドできる関数を生成して返します。

このアプローチには、拡張しないという利点があります。 Object.prototypeます。

コードは、指定されたオブジェクトに __objectID__プロパティ非表示の(列挙できない)読み取り専用プロパティとして定義することで機能します。

そのため、読み取り専用obj.__objectID__プロパティが定義された後に変更または再定義しようとする試みに対して安全であり、警告なしに失敗するのではなく、常に素晴らしいエラーをスローします。

最後__objectID__に、特定のオブジェクトで他のコードがすでに定義されている極端なケースでは、この値が返されます。

var getObjectID = (function () {

    var id = 0;    // Private ID counter

    return function (obj) {

         if(obj.hasOwnProperty("__objectID__")) {
             return obj.__objectID__;

         } else {

             ++id;
             Object.defineProperty(obj, "__objectID__", {

                 /*
                  * Explicitly sets these two attribute values to false,
                  * although they are false by default.
                  */
                 "configurable" : false,
                 "enumerable" :   false,

                 /* 
                  * This closure guarantees that different objects
                  * will not share the same id variable.
                  */
                 "get" : (function (__objectID__) {
                     return function () { return __objectID__; };
                  })(id),

                 "set" : function () {
                     throw new Error("Sorry, but 'obj.__objectID__' is read-only!");
                 }
             });

             return obj.__objectID__;

         }
    };

})();

4

jQueryコードは、独自のdata()メソッドをそのようなIDとして使用します。

var id = $.data(object);

backstageメソッドでdataは、次のような一意のIDのストリームの次のIDをput put でobject呼び出すという非常に特殊なフィールドを作成します"jQuery" + now()

id = elem[ expando ] = ++uuid;

John ResigがJavaScriptに関するすべてを明らかに知っており、彼の方法はそのすべての知識に基づいているのと同じ方法を使用することをお勧めします。


5
jQueryのdataメソッドには欠陥があります。たとえば、stackoverflow.com / questions / 1915341 /…を参照してください。また、John ResigはJavaScriptについて知っておくべきことをすべて知っているわけではなく、JavaScript開発者としての役には立たないと信じています。
Tim Down

1
@Tim、それは内部で大体同じことをするので、ここに提示された他のメソッドと同じくらいユニークなIDを取得することに関して多くの欠陥があります。そして、はい、私はジョン・レジグが私より多くを知っていると私は信じます、そして、彼がダグラス・クロックフォードでなくても、私は彼の決定から学ぶべきです。
vava

AFAIK $ .dataはJavaScriptオブジェクトでは機能せず、DOM要素でのみ機能します。
mb21 2014年

4

ES6互換の@justin回答のTypescriptバージョン。Symbolを使用してキーの衝突を防止し、便宜上グローバルObject.idに追加します。以下のコードをコピーして貼り付けるか、インポートするObjecId.tsファイルに入れてください。

(enableObjectID)();

declare global {
    interface ObjectConstructor {
        id: (object: any) => number;
    }
}

const uniqueId: symbol = Symbol('The unique id of an object');

export function enableObjectID(): void {
    if (typeof Object['id'] !== 'undefined') {
        return;
    }

    let id: number = 0;

    Object['id'] = (object: any) => {
        const hasUniqueId: boolean = !!object[uniqueId];
        if (!hasUniqueId) {
            object[uniqueId] = ++id;
        }

        return object[uniqueId];
    };
}

使用例:

console.log(Object.id(myObject));

1

私は次のようなコードを使用しました。これにより、オブジェクトは一意の文字列で文字列化されます。

Object.prototype.__defineGetter__('__id__', function () {
    var gid = 0;
    return function(){
        var id = gid++;
        this.__proto__ = {
             __proto__: this.__proto__,
             get __id__(){ return id }
        };
        return id;
    }
}.call() );

Object.prototype.toString = function () {
    return '[Object ' + this.__id__ + ']';
};

__proto__ビットは維持するためにある__id__オブジェクトに現れてからゲッターを。これはFirefoxでのみテストされています。


これ__defineGetter__は非標準であることを覚えておいてください。
トマーシュZato -復活モニカ

1

Object.prototypeを変更しないようにというアドバイスにもかかわらず、これは、限定されたスコープ内でのテストに非常に役立ちます。受け入れられた回答の作成者が変更しましたが、まだ設定Object.id中です。これは私には意味がありません。これは仕事をするスニペットです:

// Generates a unique, read-only id for an object.
// The _uid is generated for the object the first time it's accessed.

(function() {
  var id = 0;
  Object.defineProperty(Object.prototype, '_uid', {
    // The prototype getter sets up a property on the instance. Because
    // the new instance-prop masks this one, we know this will only ever
    // be called at most once for any given object.
    get: function () {
      Object.defineProperty(this, '_uid', {
        value: id++,
        writable: false,
        enumerable: false,
      });
      return this._uid;
    },
    enumerable: false,
  });
})();

function assert(p) { if (!p) throw Error('Not!'); }
var obj = {};
assert(obj._uid == 0);
assert({}._uid == 1);
assert([]._uid == 2);
assert(obj._uid == 0);  // still

1

私は同じ問題に直面しました、そしてここに私がES6で実装した解決策があります

code
let id = 0; // This is a kind of global variable accessible for every instance 

class Animal {
constructor(name){
this.name = name;
this.id = id++; 
}

foo(){}
 // Executes some cool stuff
}

cat = new Animal("Catty");


console.log(cat.id) // 1 

1

2つのオブジェクトを比較する目的でこれを行う最も簡単な方法は、オブジェクトを比較する必要があるときに、オブジェクトの1つに一意のプロパティを追加し、プロパティが他のオブジェクトに存在するかどうかを確認してから、もう一度削除することです。これにより、プロトタイプを上書きする必要がなくなります。

function isSameObject(objectA, objectB) {
   unique_ref = "unique_id_" + performance.now();
   objectA[unique_ref] = true;
   isSame = objectB.hasOwnProperty(unique_ref);
   delete objectA[unique_ref];
   return isSame;
}

object1 = {something:true};
object2 = {something:true};
object3 = object1;

console.log(isSameObject(object1, object2)); //false
console.log(isSameObject(object1, object3)); //true
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.