質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Q&A

解決済

3回答

3749閲覧

オブジェクトの初期値

退会済みユーザー

退会済みユーザー

総合スコア0

Node.js

Node.jsとはGoogleのV8 JavaScriptエンジンを使用しているサーバーサイドのイベント駆動型プログラムです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

0グッド

1クリップ

投稿2017/10/08 01:38

編集2017/10/08 01:42

携帯からの投稿で失礼します。

配列を代入する変数の初期値は[]にしているのですが、オブジェクトの初期値は、{}にすべきでしょうか?それともnullにすべきでしょうか?

質問の経緯としては、配列の場合、例えば、list.map(item => item.cards)のような処理を行う前に、list.lengthが0ではないことを確認してから実行すれば、list内にcardsプロパティを持ったオブジェクトが存在してる時にのみ実行出来るのでエラーにはなりませんが、オブジェクトの場合は初期値を{}にしてしまうと、obj.cardsが存在してるかどうかを確認してから、obj.cardsに対する処理を行わないといけなくなって、いちいち、cardsというキーがオブジェクトに存在してるかどうかをキー名指定してチェックするのは煩わしく感じてしまいます。ちなみにcardsは配列と仮定しています。

なので、オブジェクトの初期値はnullの方が良いのかなと思ったのですが、どういう考え方が良いのでしょうか?

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

オブジェクトの初期値

初期値はオブジェクト毎に独立しているので、画一的な定義は存在しないと思います。
例えば、new Object が「Object のインスタンスの初期値」であり、new Array が「Array のインスタンスの初期値」ですが、定義されるプロパティには違いがあります。

JavaScript

1var obj = new Object, array = new Array; 2 3console.log(Object.keys(obj)); // [] 4console.log(Object.keys(array)); // [] 5console.log(Object.getOwnPropertyNames(obj)); // [] 6console.log(Object.getOwnPropertyNames(array)); // ["length"] 7 8console.log(Object.keys(obj.__proto__)); // [] 9console.log(Object.keys(array.__proto__)); // [] 10console.log(Object.getOwnPropertyNames(obj.__proto__)); // ["constructor", "__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__", "isPrototypeOf", "propertyIsEnumerable", "toString", "valueOf", "__proto__", "toLocaleString"] 11console.log(Object.getOwnPropertyNames(array.__proto__)); // ["length", "constructor", "concat", "pop", "push", "shift", "unshift", "slice", "splice", "includes", "indexOf", "keys", "entries", "forEach", "filter", "map", "every", "some", "reduce", "reduceRight", "toString", "toLocaleString", "join", "reverse", "sort", "lastIndexOf", "copyWithin", "find", "findIndex", "fill"]

ただし、オブジェクトの初期値は Object 型のみであり、それ以外は存在しません。

Null 型

Null 型は「オブジェクトが存在しない状態」を表します。

getElementById は要素ノードを発見できなかった時に null を返しますが、その理由は「new Element の初期値がnull だから」ではなく、オブジェクトが存在しなかったからです。
オブジェクトが存在しないということは「初期化もされない」ということなので、null は new Element の初期値ではないと考える事が出来ます。

document.onclick プロパティで click イベントハンドラが未定義の場合に null で初期化されているのも「オブジェクトが存在しないから」であって、new Function の初期値が null ではないと考えることが出来ます。

配列の初期値

前述の通り、new Array の初期値は [] です。

配列を代入する変数の初期値は[]にしているのですが、

この設計は一つの解ですが、ある関数が配列を返す時、「存在しない事」を表す為に [] を返すべきかどうか、というのは別の問題になります。

String.prototype.match はマッチした時に配列を返し、マッチしなかった時に null を返します。

JavaScript

1console.log(Array.isArray('abc'.match(/a/))); // true 2console.log('abc'.match(/d/)); // null

querySelectorAllArray.prototype.filter 等では決められた配列形式のオブジェクトを返す設計ですが、String.prototype.matchg フラグ等のオプションによって返り値が変化します。
また、「マッチしたか否か」は重要な判断要素になる為、「存在しなかったこと」を明示する為に null を返す設計にしているものと考えられます。

結局、どうするべきなのか

まず、「オブジェクトが存在しないこと」を表す必要があるのか、を設計段階で考えて下さい。

  • getElementByIdnull を返すのは、ドキュメント上に対応するオブジェクトが存在しなかったから、です。「存在しないもの」に対して、new Element を返すのは適切とはいえません。
  • document.onclicknull を返すのは、対応するイベントハンドラ関数(Object型)が存在しなかったから、です。「存在しないもの」に対して、new Function を返すのは適切とはいえません。
  • String.prototype.match null を返すのは、正規表現に対応する文字列が存在しかったから、です。存在判定によって、後述のコードが処理しやすくなるため、でもあります
  • Array.prototype.filter[] を返すのは、Array.prototype に存在する関数をメソッドチェーンによって利用する為です。これは String.prototype.match とは異なる設計であり、宗教上の違いもあります。

Object型が入るべき変数を初期化する事を考えた場合、 「オブジェクトが存在しない」を明示するなら null を代入し、そうでないなら、new Objectnew Foo を代入します。

プロパティの初期化をどうするのか

オブジェクトが存在しないのであれば、プロパティも存在しない為、この部分を考える必要はありません。
オブジェクトが存在するのであれば、new Foo でオブジェクトを初期化するわけですが、プロパティの初期値については設計者の思想に依存しています。
例えば、new Arraylength 値は 0 ですが、これは「length プロパティが配列の要素数を返す」と設計者が定めているからです。

JavaScript

1new Array().length; // 0

String.prototype.indexOf が対象の文字列を発見出来なかった時に -1 を返すのは、「存在した時に 0 以上の自然数を返す事から、存在する時の返り値と重複しない値として適当だったから」です。
同じ Number 型の値を持つプロパティでも、存在しない時の初期値が異なる事が分かると思います。

つまり、必要なのは「Object型の初期値とは何か」という視点ではなく、「その変数が持つ論理的な意味は何か」を考え、そこに合理的な理由を付けて初期値を決めることにあると考えます。

そのオブジェクトがただのデータとしての入れ物であれば、下記3パターンのどれかを採用する事が多いですが、

JavaScript

1{}; 2Object.create(null); 3new Map;

各プロパティに何がしかの意味を込め、値にも制約やルールを設ける(「値は~型でなければならない」「プロパティfooが0以上なら、プロパティpiyoは1以上でなければならない」等)のであれば、ユーザ定義のコンストラクタでprototype プロパティに一定のルールを定める事が出来ます。
そして、instanceof 演算子や Object.getPrototypeOf によって、対象のコンストラクタを特定すれば、自身が定めたルール内でプロパティチェックを省略出来ます。
(例: new Foo().piyo は Number 型の自然数が格納されることが保証されている為、チェック不要など)

※ECMAScript 上で似た性質を持つ関数の仕様を調べてみるといいかもしれません。
引数が省略された時の初期値や型の制約など、設計上の考え方で参考になるものがあります。

プロパティに一定の制約を設ける

プロパティに一定の制約を設けるコード事例を書くと、次のようになります。

JavaScript

1'use strict'; 2const Person = (() => { 3 const privateMap = new WeakMap; 4 5 return class Person { 6 constructor (name, age) { 7 privateMap.set(this, Object.create(null)); 8 9 this.name = name; 10 this.age = age; 11 } 12 13 get name () { 14 return privateMap.get(this).name; 15 } 16 17 set name (nameString) { 18 return privateMap.get(this).name = String(nameString); 19 } 20 21 get age () { 22 return privateMap.get(this).age; 23 } 24 25 set age (ageNumber) { 26 ageNumber = Number(ageNumber); 27 ageNumber = (Number.isNaN(ageNumber) || ageNumber < 0) ? -1 : Math.floor(ageNumber); 28 29 return privateMap.get(this).age = ageNumber; 30 } 31 } 32})(); 33 34const p1 = new Person('taro', 10); 35console.log(p1.name); // "taro" 36console.log(p1.age); // 10 37 38p1.name = 1; 39console.log(p1.name, typeof p1.name); // "1" "string" 40p1.age = 'fugafuga'; 41console.log(p1.age, typeof p1.age); // -1 "number" 42 43const p2 = new Person; 44console.log(p2.name, typeof p2.name); // "undefined" "string" 45console.log(p2.age, typeof p2.age); // -1 "number"

Person には次のルールが設けられています。

  • name プロパティは必ず String 型である (MUST)
  • age プロパティは -1 以上の整数である (MUST)
  • age プロパティは有効な年齢を設定された場合に 0 以上の整数を返す (MUST)
  • age プロパティは無効な年齢を設定された場合に -1 を返す (MUST)

このルールは絶対的であり、引数が省略されても、後でプロパティが再設定されても必ず、そうなります。
ですので、プロパティの有無を確認する必要がなければ、プロパティに格納された値の型を確認する必要もないのです。
面倒な処理は全て、Person クラスがやってくれます。

(補足)

Numberは0かnanですか。型によって初期値がどうなるという視点は良くないということでしたが、この辺りは気になるところです。

age プロパティは Number 型ですが、年齢として有効な数値は「0 以上の整数」になります。
従って、無効値が設定された場合に「falsy な 0 を返す」という実装は出来ません。
age プロパティは「整数値を返すプロパティ」なので、String#indexOf に倣い、-1 を返すルールとしました。
このように、考え方次第では、Number 型でも falsy な 0, NaN 以外を返すルールにする場合があります。

例に挙げて頂いた多くは初期値の話というより、関数の戻り値についてだった気がしますが、

はい。その通りですが、プロパティの getter/setter に一定のルールを設けるという意味では関数の返り値でも考え方は変わらないと思います。
例えば、get age ()function getAge () {} に置き換える事が出来ます。

更新履歴

  • 2017/10/08 15:37 「結局、どうするべきなのか」「プロパティの初期化をどうするのか」を追記
  • 2017/10/18 23:38 「プロパティに一定の制約を設ける」を追記

Re: hayatomo さん

投稿2017/10/08 04:54

編集2017/10/18 14:28
think49

総合スコア18156

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/10/08 05:01

ご回答ありがとうございます。どんなケースであれ、何かの型の初期値がnullであるってことは、nullの意味合い的にはおかしいというように理解して大丈夫でしょうか?数字の場合はundefinedを代わりに使えば良いですか?文字列の場合は''と空文字で良いと思いますが。
think49

2017/10/08 07:01

親記事に追記しました。 > 何かの型の初期値がnullであるってことは、nullの意味合い的にはおかしいというように理解して大丈夫でしょうか? 「型の初期値」という視点には違和感がありますが、「オブジェクトが存在しない」なら、nullで良いと思います。 > 数字の場合はundefinedを代わりに使えば良いですか?文字列の場合は''と空文字で良いと思いますが。 falsy な値を求めるなら、Number 型は 0, NaN、String型は "" ですが、「型毎に初期値が決まる」は固定観念を生むので、好ましくないと思います。 falsyな値が初期値として採用されがちではありますが、私の知る範囲では Number 型による初期値が複数パターンあり、親記事に追記しました。 Object 型だけは Object 型、Null 型の二択になっていますが、外の型に関してはそれぞれの型の範囲で値を定めるべきだと思います。 原則として、Number 型が入るべき変数に Undefined 型の値を初期値として明示的に与えることはしません。 var number; や変数の巻き上げを利用すれば、Undefined 型ご入りうるといえますが、変数が初期化されていない事はロジック上は明らかなので、undefined 判定する事に意味はないと考えます。 余談ですが、String型やNumber型が入るべきプロパティを全てnullで埋める設計を時々見かけるのですが、ECMAScriptの意味論的には好ましくないと考えています。 nullは「オブジェクトが存在しないことを表す」と仕様で定めており、ビルトインオブジェクトもこの原則に従っています。 無名関数式のnameプロパティが null ではなく、img要素ノードの width プロパティが null ではない事からも、それは読みとれます。
退会済みユーザー

退会済みユーザー

2017/10/08 19:29

大変詳しいご回答ありがとうございます!参考になります!
退会済みユーザー

退会済みユーザー

2017/10/08 19:31

各プロパティに何がしかの意味を込め、値にも制約やルールを設ける(「値は~型でなければならない」「プロパティfooが0以上なら、プロパティpiyoは1以上でなければならない」等)のであれば、ユーザ定義のコンストラクタでprototype プロパティに一定のルールを定める事が出来ます。 そして、instanceof 演算子や Object.getPrototypeOf によって、対象のコンストラクタを特定すれば、自身が定めたルール内でプロパティチェックを省略出来ます。 (例: new Foo().piyo は Number 型の自然数が格納されることが保証されている為、チェック不要など) ↑ ここの説明が何の話かよく分からなかったのですが、具体例をもしよろしかったら教えて頂けますでしょうか?
退会済みユーザー

退会済みユーザー

2017/10/08 19:34 編集

undefindで初期化を明示的にすることはないのですね。raccyさんの場合はundefinedのものなら、そもそも、objectの初期値にそのキーをいれず省略するとありましたし。
退会済みユーザー

退会済みユーザー

2017/10/08 19:36

Numberは0かnanですか。型によって初期値がどうなるという視点は良くないということでしたが、この辺りは気になるところです。例に挙げて頂いた多くは初期値の話というより、関数の戻り値についてだった気がしますが、プロパティの初期値をgetterで取得した場合、どんな戻り値が適切かみたいな考え方で初期値決めると良いのですかね。
think49

2017/10/11 02:46

この件、回答はあるのですが、コードをまとめるのに時間がかかっています。 申し訳ありませんが、もう少しお待ちください。
退会済みユーザー

退会済みユーザー

2017/10/11 03:56

すみません。。。ありがとうございます!
think49

2017/10/18 14:29

遅くなりましたが、親記事に追記しました。
guest

0

私が(素の)Objectを使う場合はどんなプロパティ(キー)が入るかが決まっていることを前提にしています。ですので、それぞれのプロパティの初期値を入れますが、undefinedが初期値の場合は省略する場合があります。

JavaScript

1const obj = { 2 name: '名無し' 3}; 4 5obj.name = '山田太郎'; 6obj.age = 20;

TypeScriptやFlowを使っていれば、型アノテーションでエラーチェックもできます。(下記はFlowでの例)

JavaScript

1// @flow 2const obj /*: { 3 name: string, 4 age: ?number 5} */ = { 6 name: '名無し', 7 age: undefined 8}; 9 10obj.name = '山田太郎'; 11obj.age = 20;

指定のプロパティ名が存在確認は、そのプロパティ値がnullまたはundefinedでないかで判断します。

JavaScript

1if (obj.age != null) { 2 console.log(`${obj.name}${obj.age}才です。`); 3} else { 4 console.log(`${obj.name}は年齢不詳です。`); 5} 6

なお、不定なキーを扱う場合はObjectではなくMapを使用します。

投稿2017/10/08 02:55

raccy

総合スコア21733

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/10/08 03:15

ご回答ありがとうございます!ageみたいに初期値を特定の数値に設定しないほうが良さそうなものの場合は、初期値をundefinedにするのですね。参考になりました。質問するときに例としてageもオブジェクトに含めようと思ったのですが、そのageの初期値をどうしようか迷って結局、例から外していたので(笑)不定なキーを扱う場合はObjectではなくMapを使用とありますが、これは何故なのでしょうか?MapやSetはimmutable.jsで使ってるくらいで素のJSでは使ったことがないです。
raccy

2017/10/08 03:28

いわゆる連想配列としてObjectは使えないことはないのですが、Objectは他にも汎用的な動きが可能であるため、思わぬ落とし穴があったりするからです。連想配列専用に作られたMapの方が安全に管理できます。
退会済みユーザー

退会済みユーザー

2017/10/08 03:40

なるほど。Mapというのは連想配列用の用途がより限定されたものなのですね。参考になりました! 近いうち使えるようになりたいと思います。
guest

0

オブジェクトの初期値をnullとした場合、null参照の可能性があるのであれば、オブジェクトがnullかどうかを確認してから使用する必要があると思いますので手間は同じなのではないかと思います。

JSON等で取得する場合も含め、オブジェクトを利用する場合はある程度決まったデータ構造があるから使用しているものと思います。
そのため、必要なプロパティを定義したオブジェクトを作成し、返却するような関数を用意して初期値として使用するという対応も考えられます。

ES6ではクラスが使用可能となりましたので、ES6が利用可能であれば、このような場合はクラスとして定義して利用されるとよいのではないでしょうか。

クラス - JavaScript | MDN

投稿2017/10/08 02:22

退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2017/10/08 02:41

ご回答ありがとうございます。なるほど! つまり、オブジェクトを使うのであれば、{}を初期値にするのではなく、{id: '', name: ''}のように予め決まっているデータ構造をもたせたオブジェクトを初期値とするということですね!納得です。 クラスを利用する場合は、決まったデータ構造をインスタンスプロパティーとして持つインスタンスを初期値にするということになりますでしょうか? const person = new Person(); class Person { constructor(id = '', name = ''){ this.id = id; this.name = name; } // setterなど }
退会済みユーザー

退会済みユーザー

2017/10/08 02:45

ご認識の通りかと思います。 この辺り(クラス構文等)は一般的なオブジェクト指向言語の考え方をJavaScriptで実現するためのものですので、オブジェクト指向の解説サイト等も参考になると思います。
退会済みユーザー

退会済みユーザー

2017/10/08 03:15

ありがとうございます!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問