web-dev-qa-db-ja.com

"new"の代わりに "Object.create"を使用する

Javascript 1.9.3/ECMAScript 5では、Object.createが導入されました。これは、特にDouglas Crockfordによるものです 提唱 。以下のコードのnewObject.createに置き換えるにはどうすればよいですか?

var UserA = function(nameParam) {
    this.id = MY_GLOBAL.nextId();
    this.name = nameParam;
}
UserA.prototype.sayHello = function() {
    console.log('Hello '+ this.name);
}
var bob = new UserA('bob');
bob.sayHello();

MY_GLOBAL.nextIdが存在すると仮定します)。

私が思い付くことができる最高のものは:

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.create(userB);
bob.init('Bob');
bob.sayHello();

何の利点もないように思われるので、私はそれを得ていないと思います。私はおそらく新古典派過ぎている。 Object.createを使用してユーザー「bob」を作成する方法

355
Graham King

継承のレベルが1つしかないため、この例では Object.create の本当の利点を理解できない可能性があります。

このメソッドを使うと、差分継承を簡単に実装できます。この場合、オブジェクトは他のオブジェクトから直接継承できます。

あなたのuserBの例では、あなたのinitメソッドがパブリックであるべきか、あるいは存在さえするべきではないと思います。既存のオブジェクトインスタンスでこのメソッドを再度呼び出すと、idおよびnameプロパティは変わります。

Object.createを使うと、2番目の引数を使ってオブジェクトのプロパティを初期化できます。

var userB = {
  sayHello: function() {
    console.log('Hello '+ this.name);
  }
};

var bob = Object.create(userB, {
  'id' : {
    value: MY_GLOBAL.nextId(),
    enumerable:true // writable:false, configurable(deletable):false by default
  },
  'name': {
    value: 'Bob',
    enumerable: true
  }
});

ご覧のとおり、プロパティはObject.createおよびObject.definePropertiesメソッドで使用されているのと同様の構文を使用して、オブジェクトリテラルでObject.definePropertyの2番目の引数で初期化できます。

プロパティの属性(enumerablewritable、またはconfigurable)を設定できます。これは非常に便利です。

239
CMS

new objectよりObject.create(...)を使用することに実際に利点はありません。

この方法を提唱している人たちは一般的にかなりあいまいな利点を述べています: "スケーラビリティ" 、または " よりJavaScriptにとって自然 "など.

しかし、私はまだObject.createnewを使用するよりもanyの利点があることを示す具体例を見ていません。それどころか、それには既知の問題があります。 Sam Elsammanは、ネストされたオブジェクトがあり、Object.create(...)が使われているときに何が起こるかを説明します

var Animal = {
    traits: {},
}
var lion = Object.create(Animal);
lion.traits.legs = 4;
var bird = Object.create(Animal);
bird.traits.legs = 2;
alert(lion.traits.legs) // shows 2!!!

これは、Object.create(...)dataを使用して新しいオブジェクトを作成する方法を推奨しているために発生します。ここでAnimalデータはlionbirdのプロトタイプの一部となり、共有されると問題を引き起こします。 newを使うとき、プロトタイプ継承は明白です:

function Animal() {
    this.traits = {};
}

function Lion() { }
Lion.prototype = new Animal();
function Bird() { }
Bird.prototype = new Animal();

var lion = new Lion();
lion.traits.legs = 4;
var bird = new Bird();
bird.traits.legs = 2;
alert(lion.traits.legs) // now shows 4

Object.create(...)に渡されるオプションの属性については、 Object.defineProperties(...) を使って追加することができます。

50
Noel Abrahams

Object.createは、いくつかのブラウザではまだ標準ではありません。たとえば、IE8、Opera v11.5、Konq 4.3にはありません。これらのブラウザにはDouglas CrockfordのObject.createのバージョンを使用できますが、これにはCMSの回答で使用される2番目の「初期化オブジェクト」パラメータは含まれていません。

クロスブラウザのコードでは、その間にオブジェクトの初期化を行うための1つの方法は、CrockfordのObject.createをカスタマイズすることです。これが一つの方法です: -

Object.build = function(o) {
   var initArgs = Array.prototype.slice.call(arguments,1)
   function F() {
      if((typeof o.init === 'function') && initArgs.length) {
         o.init.apply(this,initArgs)
      }
   }
   F.prototype = o
   return new F()
}

これにより、Crockfordのプロトタイプ継承が維持され、オブジェクト内のinitメソッドもチェックされてから、new man( 'John'、 'Smith')のように、パラメータとともに実行されます。あなたのコードは次のようになります -

MY_GLOBAL = {i: 1, nextId: function(){return this.i++}}  // For example

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};
var bob = Object.build(userB, 'Bob');  // Different from your code
bob.sayHello();

そのため、bobはsayHelloメソッドを継承し、独自のプロパティid = 1とname = 'Bob'を持ちます。これらのプロパティは、もちろん書き込み可能で列挙可能です。特に書き込み可能、​​列挙可能、そして設定可能な属性を気にしていないのであれば、これはECMA Object.createよりも初期化するのがはるかに簡単な方法です。

Initメソッドを使用しないで初期化する場合は、次のCrockford modを使用できます。

Object.gen = function(o) {
   var makeArgs = arguments 
   function F() {
      var prop, i=1, arg, val
      for(prop in o) {
         if(!o.hasOwnProperty(prop)) continue
         val = o[prop]
         arg = makeArgs[i++]
         if(typeof arg === 'undefined') break
         this[prop] = arg
      }
   }
   F.prototype = o
   return new F()
}

これは、userBパラメータの後に左から右へObject.genパラメータを使用して、定義された順序でuserB自身のプロパティを埋めます。 for(prop in o)ループを使用しているため、ECMA規格では、プロパティの列挙順序をプロパティの定義順序と同じにすることはできません。ただし、(4)主要ブラウザでテストされたいくつかのコード例は、hasOwnPropertyフィルタが使用されている場合、および使用されていなくても同じであることを示しています。

MY_GLOBAL = {i: 1, nextId: function(){return this.i++}};  // For example

var userB = {
   name: null,
   id: null,
   sayHello: function() {
      console.log('Hello '+ this.name);
   }
}

var bob = Object.gen(userB, 'Bob', MY_GLOBAL.nextId());

UserBはinitメソッドを必要としないので、Object.buildよりもいくぶん簡単です。またuserBは特にコンストラクタではありませんが、通常のシングルトンオブジェクトのように見えます。したがって、このメソッドを使用すると、通常のプレーンオブジェクトから構築して初期化できます。

41
John

TL; DR:

new Computer()はコンストラクタ関数Computer(){}を一度だけ呼び出しますが、Object.create(Computer.prototype)は呼び出しません。

すべての利点はこの点に基づいています。

エンジン内部での最適化のため、Object.createは遅くなるかもしれませんが、直感的にはわかりません。

20
Nami WANG

initメソッドからthisを返すようにしてから、次のように呼び出しを連鎖させることができます。

var userB = {
    init: function(nameParam) {
        this.id = MY_GLOBAL.nextId();
        this.name = nameParam;
        return this;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};

var bob = Object.create(userB).init('Bob');
13
samfrances

Object.createの別の使用方法は、不変オブジェクトを 安価で効果的な方法 で複製することです。

var anObj = {
    a: "test",
    b: "jest"
};

var bObj = Object.create(anObj);

bObj.b = "gone"; // replace an existing (by masking prototype)
bObj.c = "brand"; // add a new to demonstrate it is actually a new obj

// now bObj is {a: test, b: gone, c: brand}

:上のスニペットはソースオブジェクトのクローンを作成します(cObj = aObjのように、参照ではありません)。オブジェクトメンバプロパティをコピーしないという点で、copy-propertiesメソッド( 1 を参照)よりも有利です。その代わりに、ソースオブジェクトにプロトタイプを設定して別のdestinationオブジェクトを作成します。さらにdestオブジェクトでプロパティが変更されると、それらはプロトタイプ(src)のプロパティを隠して "その場で"作成されます。これは不変オブジェクトのクローンを作成するための高速で効果的な方法です。

ここでの注意点は、これが作成後に変更されるべきでないソースオブジェクトに適用されるということです(不変)。作成後にソースオブジェクトが変更されると、クローンのマスクされていないすべてのプロパティも変更されます。

ここをいじる( http://jsfiddle.net/y5b5q/1/ )(Object.create対応のブラウザが必要です)。

7
basos

私が問題にしている主な点はだと思います - newObject.createのアプローチの違いを理解することです。したがって この答え そして このビデオnewというキーワードは次のことをします。

  1. 新しいオブジェクトを作成します。

  2. 新しいオブジェクトをコンストラクタ関数(prototype)にリンクします。

  3. this変数を新しいオブジェクトを指すようにします。

  4. 新しいオブジェクトを使用してコンストラクタ関数を実行し、暗黙的にreturn thisを実行します。

  5. 新しいオブジェクトのプロパティconstructorにコンストラクタ関数名を割り当てます。

Object.create1st2ndステップだけを実行します。

問題のコード例では大した問題ではありませんが、次の例では、

var onlineUsers = [];
function SiteMember(name) {
    this.name = name;
    onlineUsers.Push(name);
}
SiteMember.prototype.getName = function() {
    return this.name;
}
function Guest(name) {
    SiteMember.call(this, name);
}
Guest.prototype = new SiteMember();

var g = new Guest('James');
console.log(onlineUsers);

副作用の結果は次のようになります。

[ undefined, 'James' ]

Guest.prototype = new SiteMember();のため
しかし、親コンストラクタメソッドを実行する必要はなく、Guestで使用できるようにするにはメソッドgetNameを作成するだけです。したがって、Object.createを使用する必要があります。
Guest.prototype = new SiteMember();を置き換える場合
からGuest.prototype = Object.create(SiteMember.prototype);の結果は次のとおりです。

[ 'James' ]
6
Vladimir Kovpak

場合によっては、NEWを使用してオブジェクトを作成できないが、それでもCREATEメソッドを呼び出すことができます。

たとえば、カスタム要素を定義したい場合は、HTMLElementから派生する必要があります。

proto = new HTMLElement  //fail :(
proto = Object.create( HTMLElement.prototype )  //OK :)
document.registerElement( "custom-element", { prototype: proto } )
5
Supersharp

利点は、ほとんどのブラウザでObject.createが通常newより遅いことです。

このjsperfの例では 、Chromiumでは、ブラウザnew倍速いObject.create(obj)と同じですが、どちらもかなり高速です。 newはもっと多くのことをするので(コンストラクタの呼び出しのように)、Object.createは渡されたオブジェクトをプロトタイプとして新しいObjectを作成するだけでよいので(Crockfordの秘密リンク)

おそらくブラウザはObject.createをより効率的にすることに追いついていません(おそらく彼らはそれをカバーの下のnewに基づいています...ネイティブコードでさえ)

3
frooble

Douglas Crockfordは、以前はObject.create()の熱心な支持者であり、基本的にこの構造が実際にJavaScriptで記述されている理由ですが、彼はもはやこの意見を持っていません。

彼はObject.createの使用をやめました。なぜなら、thisキーワードを使用するのをやめたからです。たとえば、注意していないと、グローバルオブジェクトを簡単に指す可能性があり、これは本当に悪い結果をもたらす可能性があります。そして彼は、this Object.createを使わないともう意味がないと主張しています。

あなたは彼がNordic.jsで話す2014年からこのビデオをチェックすることができます:

https://www.youtube.com/watch?v=PSGEjv3Tqo

enter image description here

2
Vojtech Ruzicka

newObject.createは異なる目的を果たします。 newは、オブジェクト型の新しいインスタンスを作成するためのものです。 Object.createは、単に新しいオブジェクトを作成してそのプロトタイプを設定することを目的としています。これはなぜ便利なのでしょうか。 __proto__プロパティにアクセスせずに継承を実装する。 [[Prototype]]と呼ばれるオブジェクトインスタンスのプロトタイプは仮想マシンの内部プロパティであり、直接アクセスされることを意図していません。 [[Prototype]]プロパティとして__proto__に直接アクセスすることが実際に可能な唯一の理由は、それが常にすべての主要な仮想マシンのECMAScriptの実装の事実上の標準であり、この時点で削除すると既存のコードの多くを壊すからです。

7ochemによる上記の答えに対して、同じプロトタイプコンストラクターを複数回呼び出す意味がないだけでなく、同じクラスの2つのインスタンスが生成される可能性があるため、オブジェクトのプロトタイプは絶対にnewステートメントの結果に設定しないでください。プロトタイプが作成された後に変更された場合は動作が異なります。どちらの例も、プロトタイプ継承チェーンの意図した動作が誤解され、意図された動作が破られたために、単に悪いコードとなっています。

__proto__にアクセスする代わりに、インスタンスのプロトタイプは、Object.createを使用して作成された時点、またはObject.setPrototypeOfを使用して作成された時点で書き込まれ、Object.getPrototypeOfまたはObject.isPrototypeOfを使用して読み取られます。

また、 Object.setPrototypeOfのMozillaドキュメント が指摘しているように、オブジェクトのプロトタイプを変更することに加えて、パフォーマンス上の理由から、オブジェクトのプロトタイプを変更した後は変更することはお勧めできません。プロトタイプが変更された後にORの前にそれにアクセスするコードの一部が実行される可能性がある場合、そのコードが現在のプロトタイプをチェックするか、プロパティにアクセスしないように十分注意してください。それは2つの間で異なります。

与えられた

const X = function (v) { this.v = v }; X.prototype.whatAmI = 'X'; X.prototype.getWhatIAm = () => this.whatAmI; X.prototype.getV = () => this.v;

次のVM疑似コードは、ステートメントconst x0 = new X(1);と同等です。

const x0 = {}; x0.[[Prototype]] = X.prototype; X.prototype.constructor.call(x0, 1);

コンストラクタは任意の値を返すことができますが、newステートメントは常にその戻り値を無視し、新しく作成されたオブジェクトへの参照を返します。

そして、次の疑似コードはconst x1 = Object.create(X.prototype);ステートメントと同等です。

const x0 = {}; x0.[[Prototype]] = X.prototype;

ご覧のとおり、2つの違いはObject.createはコンストラクタを実行しないことです。コンストラクタは実際には任意の値を返すことができますが、特に指定がない場合は単に新しいオブジェクト参照thisを返します。

さて、次の定義でサブクラスYを作成したいとします。

const Y = function(u) { this.u = u; } Y.prototype.whatAmI = 'Y'; Y.prototype.getU = () => this.u;

それで__proto__に書くことでこのようにXから継承させることができます。

Y.prototype.__proto__ = X.prototype;

__proto__に以下のように書かなくても同じことが達成できます。

Y.prototype = Object.create(X.prototype); Y.prototype.constructor = Y;

後者の場合、正しいコンストラクタがnew Yステートメントによって呼び出されるようにプロトタイプのconstructorプロパティを設定する必要があります。そうでない場合、new Yは関数Xを呼び出します。プログラマがnew YXを呼び出させたい場合は、YのコンストラクターでX.call(this, u)を使用する方が適切です。

2
paulyc

あなたはカスタムObject.create()関数を作らなければなりません。 Crockfordsの懸念に対処し、またinit関数を呼び出すもの。

これは動作します:

var userBPrototype = {
    init: function(nameParam) {
        this.name = nameParam;
    },
    sayHello: function() {
        console.log('Hello '+ this.name);
    }
};


function UserB(name) {
    function F() {};
    F.prototype = userBPrototype;
    var f = new F;
    f.init(name);
    return f;
}

var bob = UserB('bob');
bob.sayHello();

ここでUserBはObject.createに似ていますが、ニーズに合わせて調整されています。

あなたが望むなら、あなたはまた呼び出すことができます:

var bob = new UserB('bob');
2
edwin

私は閉鎖アプローチを好む。

私はまだnewを使っています。 Object.createは使いません。 thisは使いません。

宣言的な性質が好きなので、まだnewを使用します。

単純な継承のためにこれを考えてください。

window.Quad = (function() {

    function Quad() {

        const wheels = 4;
        const drivingWheels = 2;

        let motorSize = 0;

        function setMotorSize(_) {
            motorSize = _;
        }

        function getMotorSize() {
            return motorSize;
        }

        function getWheelCount() {
            return wheels;
        }

        function getDrivingWheelCount() {
            return drivingWheels;
        }
        return Object.freeze({
            getWheelCount,
            getDrivingWheelCount,
            getMotorSize,
            setMotorSize
        });
    }

    return Object.freeze(Quad);
})();

window.Car4wd = (function() {

    function Car4wd() {
        const quad = new Quad();

        const spareWheels = 1;
        const extraDrivingWheels = 2;

        function getSpareWheelCount() {
            return spareWheels;
        }

        function getDrivingWheelCount() {
            return quad.getDrivingWheelCount() + extraDrivingWheels;
        }

        return Object.freeze(Object.assign({}, quad, {
            getSpareWheelCount,
            getDrivingWheelCount
        }));
    }

    return Object.freeze(Car4wd);
})();

let myQuad = new Quad();
let myCar = new Car4wd();
console.log(myQuad.getWheelCount()); // 4
console.log(myQuad.getDrivingWheelCount()); // 2
console.log(myCar.getWheelCount()); // 4
console.log(myCar.getDrivingWheelCount()); // 4 - The overridden method is called
console.log(myCar.getSpareWheelCount()); // 1

フィードバックを促した。

1
p0wdr.com

概要:

  • Object.create()は2つの引数を取り、新しいオブジェクトを返すJavascript関数です。
  • 最初の引数は、新しく作成されたオブジェクトのプロトタイプとなるオブジェクトです。
  • 2番目の引数は、新しく作成されたオブジェクトのプロパティになるオブジェクトです。

例:

const proto = {
  talk : () => console.log('hi')
}

const props = {
  age: {
    writable: true,
    configurable: true,
    value: 26
  }
}


let Person = Object.create(proto, props)

console.log(Person.age);
Person.talk();

実用的なアプリケーション:

  1. この方法でオブジェクトを作成することの主な利点は、プロトタイプは明示的に定義できるです。オブジェクトリテラル、またはnewキーワードを使用する場合、これを制御することはできません(ただし、もちろん上書きすることができます)。
  2. プロトタイプを作りたい場合newキーワードはコンストラクタ関数を呼び出します。 Object.create()ではコンストラクタ関数を呼び出したり宣言したりする必要さえありませんがあります。
  3. 非常に動的な方法でオブジェクトを作成したい場合、これは基本的には便利なツールになります。受け取った引数に応じて異なるプロトタイプでオブジェクトを作成するオブジェクトファクトリ関数を作ることができます。
1