いつもお世話になっております。質問をさせて下さい。
サンプルコードを拝見した際に以下のコードがありました。
javascript
1var e, i, a = a || {}, o = o || {}, r = r || {}, l = "", d = 関数
複数の変数を宣言しているのはなんとなくわかるのですが、下記の部分でご質問があります。
javascript
1var a = a || {}, o = o || {}, r = r || {}
ここでの論理演算子の意味がわかりません。実際に実行してみると
javascript
1console.log(a); 2//実行結果 Object {}
となります。それなら初めから
javascript
1var a = {}
とするのは駄目なのでしょうか?
どうしてこのような書き方をしているのか...
大変お手数ではございますがご教授頂けますと幸いです。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答6件
0
ベストアンサー
var a = a || {};
というコードは避けるべき書き方、または、意味をなさない書き方です。
【注意】
var
がないa = a || {}
やwindow.a = window.a || {}
という書き方や、||
の左辺がa
ではないvar a = window.a || {}
という書き方を否定する訳ではありません。
この文章はES5以上を想定しており、ES3は一切考慮していません。ES3についてはthink49さんの回答を参考にしてください。
###varの巻き上げ
JavaScriptではvar
の巻き上げという仕様があります。
JavaScript
1// 前に書かれたコード 2var a = a || {}; 3// 後に書かれたコード
こういうコードがあった場合、
JavaScript
1var a; // undefiendが入る 2// 前に書かれたコード 3a = a || {}; 4// 後に書かれたコード
と解釈されます。var
宣言された変数は、そのスコープ(トップレベルまたは関数がスコープになります)の先頭で宣言され、かつ、undefined
が代入された見なされます。これはトップレベルでも関数ブロック内でも同様です。これを前提に話をします。
###事前にaがありえるのか?
var a = a || {};
の前にa
に何らかの値が入る場合の想定として、下記が考えられます。
- 単純に代入している。
- 関数の引数として使用している。
- クロージャーで外側で定義されている。
- グローバル変数として定義している。
- 他ファイルでグローバル変数として定義している。
- 最初からグローバル変数として定義されている。
それぞれ見ていきます。
####1. 単純に代入している。
JavaScript
1a = 1; 2var a = a || {};
は、var
巻き上げにより、
JavaScript
1var a; 2a = 1; 3a = a || {};
と同じになります。言ってしまえば、var
で宣言していた変数を事前に使っていただけに過ぎません。このJavaScriptの仕様は多くのバグをもたらす要因の一つであり、そのような混乱を避けるべきです。つまり、最初から後者の書き方をすべきであって、後から、var
宣言を書くべきではありません。最初からa
が宣言されていることが自明であれば、同じa
を使用していると理解することが可能になります。
####2. 関数の引数として使用している。
JavaScript
1function f(a) { 2 var a = a || {}; 3 // ... 4}
これはどうでしょうか?var
宣言の前にa
に代入されているように見えます。
いいえ、これはvar
宣言自体が無意味です。なぜなら、関数の引数はvar
宣言されたのと同じだからです。つまり、引数は次のように解釈できます。
JavaScript
1funciton f() { 2 var a = arguments[0]; 3 var a = a || {}; 4 // ... 5}
var
は何度でも宣言できますが、一回宣言されたとの同じになります。つまり、次のようになります。
JavaScript
1funciton f() { 2 var a; 3 a = arguments[0]; 4 a = a || {}; 5 // ... 6}
つまり、次のように書いてしまえば良いと言うことです。
JavaScript
1funciton f(a) { 2 a = a || {}; 3 // ... 4}
結局var
は不要です。そして、a
はそもそも引数であると言うことがこの方がわかりやすいです。なお、引数のデフォルト値を設定するという意味で使っているのであればさらに意味がありません。なぜなら、JavaScriptではundefined
以外にもnull
、false
、+0
、-0
、NaN
、""
も偽だからです。引数が与えられない場合にundefined
になることを利用してデフォルト値を設定するのであれば、次のようにすべきでしょう。
JavaScript
1funciton f(a) { 2 a = (typeof a !== 'undefined') ? a : {}; 3 // ... 4}
ECMAScript2015以降であれば、さらに簡単に書けます。
JavaScript
1funciton f(a = {}) { 2 // ... 3}
####3. クロージャーで外側で定義されている。
期待通りに動きません。
JavaScript
1(funciton(){ 2 var a = 1; 3 function f() { 4 var a = a || {}; 5 // ... 6 } 7 f(); 8})();
は下記と同じです。
JavaScript
1(funciton(){ 2 var a; 3 a = 1; // ① 4 function f() { 5 var a; 6 a = a || {}; // ② 7 // ... 8 } 9 f(); 10})();
つまり、①で代入しているa
と②で参照しているa
は同名の別の変数であり、①が②に影響を与えることはありません。もし、そのような動作を期待しているのであれば、全くの無意味としか言わざるを得ません。
####4. グローバル変数として定義している。
こちらも同じです。
JavaScript
1a = 1; 2function f() { 3 var a = a || {}; 4 // ... 5} 6f();
は下記と同じです。
JavaScript
1a = 1; 2function f() { 3 var a; 4 a = a || {}; 5 // ... 6} 7f();
関数内のa
がグローバル変数a
を見に行く事はありません。var
により関数内のa
はその関数限定のローカル変数であることを細書し宣言してしまっているからです。グローバル変数がどのように変化しても、関数内のa
に影響をあてることは無く、それを期待しているのであれば、無意味です。
####5. 他ファイルでグローバル変数として定義している。
scriptタグで単純に二つのJavaScriptファイルを読み込んだ場合は、グローバル変数が衝突する場合があります。その場合に、既にグローバル変数が定義されていれば、それをそのまま使うというのは有効のように見えます。
JavaScript
1var a = 1;
JavaScript
1var a = a || {}; 2// ...
しかし、このようにして衝突を回避するという考え方自体が間違っています。そもそも、グローバル変数が衝突しないように即時関数やモジュールベースの書き方を採用すべきです。
JavaScript
1(function() { 2 var a = a || {}; 3 // ... 4})();
即時関数の中に書いてしまえば、意味が無いことは既に述べたとおりです。
全体を即時関数にした場合、ライブラリなのでグローバル変数が定義できないとか思うかもしれませんが、グローバル変数はグローバルオブジェクトのプロパティに過ぎませんので、グローバル変数のプロパティとして定義すれば良いだけです。そのとき、var
の出番はありませんので、該当のコードが現れる事はありません。
####6. 最初からグローバル変数として定義されている。
Polyfillに使う場合、既にグローバル変数があればオブジェクトを再設定する必要はありませんので、ある意味それは正しいように思えます。
しかし、それは完全なPolyfillとはいえません。理由はthink49さんの回答を見てください。また、グローバル変数名衝突回避のためにコード全体を即時関数で囲っているのであれば、var
の所では必ず再設定になるため、意味がありません。グローバル変数を見たかったら、var a = window.a || {}
と書く必要があるでしょう。
###まとめ
よって、私の結論は最初の文に戻ります。var a = a || {};
というコードは、そもそもコードとしてどこかが不適切である(非推奨な方法を取っている)か、無意味な物です。そのため、極めて奇妙なコードに思えます。そのようなコードは良くないサンプルであると判断し、私としては、そのような事が書いてあるサイトや本自体を信頼しません。
投稿2017/08/04 13:18
編集2017/08/09 10:12総合スコア21735
0
|| 演算子
JavaScript
1a || b;
||
は左辺が truthy なら左辺を返し、左辺が falsy なら右辺を返す演算子です。
一見、truthy な方を返すように見えますが、両方とも truthy なら左辺を返し、両方とも falsy なら右辺を返す点に注意が必要です。
Object 型は truthy なので左辺が {}
なら左辺を返すことになります。
変数宣言の重複
JavaScript
1var a = a || {};
まず、前提としてこのコードは var
を使う事に意味があります。
var
は変数宣言の重複を許しますが、let
や const
は変数宣言の重複を許しません。
HTML
1<script> 2'use strict'; 3{ 4 let a = 1; 5 let a = a || 2; // SyntaxError: Identifier 'a' has already been declared 6} 7</script> 8<script> 9'use strict'; 10{ 11 const b = 1; 12 const b = b || 2; // SyntaxError: Identifier 'b' has already been declared 13} 14</script>
Polyfill
JavaScript
1var a = a || {};
このコードが有効となる状況として考えられるパターンは2つあります。
一つは Polyfill としての役割を持たせ、ネイティブコードと互換コードのどちらか一方を有効にする事です。
JavaScript
1var JSON = JSON || {parse: function parse () {}, stringify: function stringify () {}};
上記コードはブラウザが JSON を持っているならそのまま使用し、持っていないなら Polyfill コードを適用します。
ただし、ネイティブな JSON は {enumerable: false}
ですが、var 宣言された JSON は {enumerable: true}
なので厳密には異なります。
解決するには、Object.defineProperty()
を使用する必要があります。
JavaScript
1Object.defineProperty(this, 'JSON', { 2 writable: true, 3 enumerable: false, 4 configurable: true, 5 value: {parse: function parse () {}, stringify: function stringify () {}} 6});
最も、JSON は ES5 仕様であり、Polyfill を適用するターゲットとなる実装は ES3 互換になるので、Object.defineProperty()
はまだ実装されていません。
Object.defineProperty()
が実装されていない以上、var
宣言の Polyfill で妥協するのは致し方ない側面はあります。
(ES6 の Polyfill なら Object.defineProperty()
を使うべきでしょう。)
オブジェクトを共有する
もう一つは、グローバル変数が衝突した場合に、既存のオブジェクトを流用してプロパティを共有させる事で、変数の上書きを回避する事です。
JavaScript
1/** 2 * a-1.js 3 */ 4var a = {foo: 1}; 5 6/** 7 * a-2.js 8 */ 9var a = a || {}; 10a.piyo = 2;
a-1.js と a-2.js は別の動きをしますが、使用しているプロパティ名が衝突しない限りは共存できます。
勿論、プロパティ名が衝突する可能性がある以上、根本的な解決には至っていませんが、a-1.js と a-2.js の作者が同一人物である場合においては、衝突を回避する事は容易といえます。
ただ、実際のところ、他人が制作したライブラリとグローバル変数名が衝突してもオブジェクトを共有する意図でおまじない的にコードを書いている人が大半であるというのが私の認識です。
その場合、変数 a
が truthy であっても Object 型であるとは限らないので、下記のような悲劇が起こる可能性があります。
JavaScript
1/** 2 * b.js (他作ライブラリ) 3 */ 4var a = true; 5 6/** 7 * a.js (自作ライブラリ) 8 */ 9var a = a || {}; 10a.piyo = 2; // TypeError: Cannot create property 'piyo' on boolean 'true'
他作ライブラリとのグローバル変数の衝突を危惧するのであれば、最低限、該当グローバル変数値が Object 型である事を保証しなければ意味がありません。
JavaScript
1/** 2 * b.js (他作ライブラリ) 3 */ 4var a = true; 5 6/** 7 * a.js (自作ライブラリ) 8 */ 9if (a && Object(a) !== a) { // 変数 a が truthy かつ Object 型でなければ 10 throw new TypeError(a + ' is not a object'); // TypeError: true is not a object 11} 12var a = a || {}; 13a.piyo = 2;
自作ライブラリが他作ライブラリの変数で上書きされる可能性はある為、変数を上書き出来ないように Object.defineProperty()
で定義してやるとより安全になります。
HTML
1<script> 2'use strict'; 3/** 4 * a-1.js (自作ライブラリ) 5 */ 6if ('a' in this) { 7 if (Object(a) !== a) { 8 throw new TypeError(a + ' is not a object'); 9 } 10 11 a.foo = 1; 12} else { 13 Object.defineProperty(this, 'a', { 14 writable: false, 15 enumerable: false, 16 configurable: true, 17 value: {foo: 1} 18 }); 19} 20</script> 21<script> 22'use strict'; 23/** 24 * a-2.js (自作ライブラリ) 25 */ 26if ('a' in this) { 27 if (Object(a) !== a) { 28 throw new TypeError(a + ' is not a object'); 29 } 30 31 a.bar = 2; 32} else { 33 Object.defineProperty(this, 'a', { 34 writable: false, 35 enumerable: false, 36 configurable: true, 37 value: {bar: 2} 38 }); 39} 40 41console.log(a); // var value = {bar: 2}; 42</script> 43<script> 44/** 45 * b.js (他作ライブラリ) 46 */ 47var a = {piyo: 3, hoge: function () { return this.piyo; }}; // writable: false なので書き込みされない 48console.log(a); // {foo: 1, bar: 2} 49console.log(a.hoge()); // TypeError: a.hoge is not a function 50</script>
上記コードは分かりやすさ重視で書いていますが、a.foo = 1;
と value: {foo: 1}
で二重に同じ初期化を強いるので保守性が落ちています。
実用のコードは下記のようになります。
HTML
1<script> 2'use strict'; 3/** 4 * a-1.js (自作ライブラリ) 5 */ 6(function () { 7 var value = {foo: 1}; 8 9 if ('a' in this) { 10 var a = this.a; 11 12 if (Object(a) !== a) { 13 throw new TypeError(a + ' is not a object'); 14 } 15 16 Object.assign(a, value); // ES6 (要 Polyfill) 17 } else { 18 Object.defineProperty(this, 'a', { 19 writable: false, 20 enumerable: false, 21 configurable: true, 22 value: value 23 }); 24 } 25}.call(this)); 26</script>
Object.defineProperty() と const
ES3 への配慮が不要なら、グローバル名前空間の衝突対策として、次の2つが有力な選択肢となります。
- ES5 実装が対象なら、
Object.defineProperty()
を使う - ES6 実装が対象なら、
let
またはconst
を使う
Object.defineProperty()
の有効性は説明済みなので省くとして、ES6 では特に const
が衝突検出器として有力です。
Object.defineProperty()
による上書き禁止のコードでは上書きはできないものの、上書きを試みたタイミングでは何も発生しない静かなコードでした。
HTML
1<script> 2Object.defineProperty(this, 'a', { 3 writable: false, // 上書き禁止 4 enumerable: false, 5 configurable: true, 6 value: {foo: 1} 7}); 8</script> 9<script> 10var a = {bar: 2, piyo: function () { return this.bar; }}; // 上書き禁止だが、ここではエラーが発生しない 11console.log(a.bar); // undefined (ここでもエラーが発生しない) 12console.log(a.piyo()); // TypeError: a.piyo is not a function 13</script>
const
は再代入を禁止する為、上書きする事が出来ません。
HTML
1<script> 2'use strict'; 3const a = {foo: 1}; 4</script> 5<script> 6'use strict'; 7a = {bar: 2}; // TypeError: Assignment to constant variable. (再代入禁止) 8</script> 9<script> 10'use strict'; 11var a = {bar: 2}; // SyntaxError: Identifier 'a' has already been declared (再定義禁止) 12</script>
TypeError
が const
による再代入禁止、SyntaxError
は再定義禁止による規定によるものです。
let
も再定義禁止の為、var
, let
, const
による同じ名前の変数宣言を防ぐ事が可能です。
なお、const
は再代入は禁止されていますが、プロパティの書き換えは許容されている為、複数のスクリプト間で同じオブジェクトを共有する事は出来ます。
HTML
1<script> 2'use strict'; 3const a = {foo: 1}; 4</script> 5<script> 6'use strict'; 7a.bar = 2; // プロパティの書き換えは出来る 8console.log(a); // {foo: 1, bar: 2} 9</script>
型の判定
Object 型を期待する変数は Object 型もしくは Null 型を代入すべき変数です。
HTML
1<script> 2var a = 1; 3</script> 4<script> 5var a = a || {}; 6console.log(a); // 1 7</script>
期待に反して、変数 a
には Number 型の値が代入されています。
この状況は、次のコードで改善されます。
HTML
1<script> 2var a = 1; 3</script> 4<script> 5if (Object(a) !== a) { 6 throw new TypeError(a + ' is not a object'); // TypeError: 1 is not a object 7} 8var a = {}; 9console.log(a); 10</script>
TypeError
を発生させる事で Object 型以外の値が代入されている事が分かります。
まとめ
まとめると、次のようになると思います。
- ES3 実装が対象なら、Polyfill もしくは複数の自作ライブラリ間でオブジェクトを共有する場合に
var
宣言を使う - ES5 実装が対象なら、
Object.defineProperty()
を使う - ES6 実装が対象なら、
let
またはconst
を使う
最も、Microsoft は2017/04/11(Windows Vista のサポート終了日)に IE10- のサポートを終了している為、ES3 に配慮する場面は少ないと思います。
ただし、「型の判定」節で触れている通り、var a = a || {};
は Object 型以外の値が代入するケースを排除できない問題があります。
判定条件が緩い為、Object()
で正しく判定するコードがより好ましいといえます。
更新履歴
- 2017/08/05 00:58 「Object.defineProperty() と const」を追記
- 2017/08/09 10:56 const による再代入禁止のコード事例が再定義禁止になっていた為、再代入禁止のコードに修正
- 2017/08/09 11:51 「型の判定」節を追記
Re: LanHma さん
投稿2017/08/04 15:10
編集2017/08/09 02:51総合スコア18162
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
論理和を利用した「短絡評価」というテクニックです。
主に未定義の変数を確実に初期化するために利用されます。
変数aが定義済みで具体的なオブジェクトが入っていれば、自分自身を代入なので、実質そのまま素通りします。
変数aが 0,null,undefined,false,""などだった場合、値としてはfalseとして評価されるため、||
の右側である{}が代入されます。
三項演算子で表現したほうがわかりやすいかもしれませんね。
javascript
1a = a ? a : {};
やっているのは、こういうことです。
ですので、
javascript
1var a = {}
とは、明確に違います。
わかりやすそうな使用例は、こんな感じです。
javascript
1function Paint(image,x,y,scale){ 2 x = x || 0; 3 y = y || 0; 4 scale = scale || 1; 5 // なんやかんや 6}
第一引数imageを、なんやかんや処理して描画するメソッドを想像してください。
第二引数以降は、必須ではありません。
Paint("hoge.png")
のように呼び出された場合、x
,y
,scale
にはすべてundefinedが入っています。
そこで、この短絡評価を通ることで、それぞれ0,0,1に初期化されますので、これ以降の処理で細かい分岐処理なしで処理が可能になるのです。
投稿2017/08/04 09:12
総合スコア1441
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/08/04 10:31
2017/08/04 10:55
2017/08/04 11:14
2017/08/05 07:14
0
javascriptを使うとき、いろいろな人が開発して公開しているjsを寄せ集めて使いますよね?
some.js
javascript
1var a = { name: "piyo", say : function() { return "I am " + this.name } }; 2
any.js
javascript
1a = { x : 1, y : 3 }; 2 3//省略すると document.aになる document.aのつもりで書いた
と宣言されていると
some.jsのaがany.jsで上書きされて動かなくなってしまいます。
aという名前の変数をグローバルに宣言する安易な実装をしたjsファイルが集まるとバグを引き起こすのです。
aという変数を自分で宣言しなくても、minifyというjsを圧縮するツールによって変数を短くされてしまうこともあります。
短い変数名を使っている有名なライブラリもあります。
$ (jQuery)
_ (underscore)
なので、開発者は衝突しないように、色々な工夫をするのです。
###namespaceパターン
ライブラリを作るときに他のライブラリと変数が衝突しないように
objectの階層を作る。
javascript
1var jp = jp || {}; 2jp.co = jp.co || {}; 3jp.co.piyo = jp.co.piyo || {}; 4//このpiyoに対して機能を追加していく
###即時関数パターン
javascript
1//関数を宣言して即実行 2(function() { 3 var piyo = jp.co.piyo; 4 var xxx = piyo.getXXX(); 5})();
書き直すと
javascript
1var f = funtion() { 2 var piyo = jp.co.piyo; 3 var xxx = piyo.getXXX(); 4}; 5 6f(); //これを一行で書いた
関数の内部で宣言した変数は外部とは衝突しない。
投稿2017/08/04 12:56
総合スコア1408
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/08/05 07:14
2017/08/08 15:57
0
論理 OR (||)
expr1 || expr2
expr1 を true と見ることができる場合は、expr1 を返します。そうでない場合は、expr2 を返します。
論理演算子 - JavaScript | MDN
a
に何か入っていればそのまま、何も入っていなければ{}
ということです。
投稿2017/08/04 09:06
総合スコア36089
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/08/05 07:18
2017/08/08 16:27
2017/08/08 21:43 編集
2017/08/09 00:35 編集
2017/08/09 01:22
2017/08/09 01:32
2017/08/09 03:26
2017/08/09 09:25
2017/08/09 10:19
2017/08/09 10:38