Works by

Ren's blog

@rennnosuke_rk 技術ブログです

【JavaScript】オブジェクト・配列・関数引数の末尾カンマ

末尾カンマ

JavaScriptでは、配列リテラルの末尾要素後ろに対してカンマを記述することを許容しています。またECMAScript5にてオブジェクトリテラルの末尾カンマが、ECMAScript2017にて関数引数の末尾カンマが許容されるようになっています。

// 配列末尾カンマ
let ary = [1,2,3,4,]; //OK

// オブジェクト末尾カンマ
let obj = { hoge: "hoge", fuga: "fuga", };

// 関数引数の末尾カンマ
function f  = (a, b) = {a + b};
f(1,2,); // OK

末尾カンマのメリット

末尾カンマを許容することで、要素やプロパティ、引数追加時のカンマ未挿入によるシンタックスエラーを防止できます。

// Syntax Error
{
    name: "Taro",
    age: 20
    weight: 60
}

また末尾カンマによって、コード間差分をより明確にすることができます。

例えば、下記のような改行区切りの末尾カンマなしオブジェクトがあり、新しいプロパティを追加するとした場合、行単位diffを取るときにカンマ行のdiffも混入してしまいます。

diff --git a/obj.js b/obj.js
index 1f16f89..5952c5a 100644
--- a/obj.js
+++ b/obj.js
@@ -1,4 +1,5 @@
 let obj = {
     name: "Taro",
-    age: 20
+    age: 20,
+    weight: 60
 };

最初からオブジェクトの末尾カンマを許容していた場合、追加プロパティ行の差分が明確になります。

diff --git a/obj.js b/obj.js
index 1f16f89..5952c5a 100644
--- a/obj.js
+++ b/obj.js
@@ -1,4 +1,5 @@
 let obj = {
     name: "Taro",
     age: 20,
+    weight: 60
 };

末尾カンマのデメリット

末尾カンマのデメリットとして、カンマ区切りをサポートできないブラウザで対応できない点が挙げられます。

具体的には、

  • ECMAScript2015をサポートしていないブラウザはオブジェクト末尾カンマ不可能
  • ECMA2017をサポートしていないブラウザは関数引数末尾カンマ不可能

となります。

ただし、babelなどのトランスコンパイラを使って末尾カンマが削除されたスクリプトを生成することで対応することができます。

また、JSONJavaScriptのオブジェクトフォーマットを踏襲していますが、末尾カンマを許容していないので注意が必要です。

例えば、以下のようなJSONはNGです。

{
     name: "Taro",
     age: 20,
     weight: 60, 
 }

【AngularJS】$rootScopeに対してイベントリスナを登録・解除する

メモ。

Angularでは`$rootScope.bind`経由でスクロールやページ遷移等に対するイベントリスナーを登録することができます。

 

angular.module('myApp', [])
.controller('MyController', ['$rootScope', function($rootScope){
  $rootScope.bind('scroll', function(){
    // on scroll...
  });
}]);          

リスナーの解除は`bind()`の返戻値を関数コールすることで行うことができます。

angular.module('myApp', [])
.controller('MyController', ['$rootScope', function($rootScope){
  let removeListener = $rootScope.bind('scroll', function(){
    // on scroll...
  });
  // リスナー解除
  let removeListener();
}]);          

【AngularJS】Serviceを用いた画面遷移時のパラメータ受け渡し

画面遷移時にパラメータを受け渡す

AngularJSでは画面遷移が発生する(厳密には画面遷移というよりng-viewによる一部HTMLの差し替え)と、多くの場合Controllerも遷移先のViewに合わせて切り替わります。

このとき、遷移前に利用していたController上のパラメータを遷移後のControllerでも利用したいことがよくあります。

パラメータの渡し方はいくつか方法があるのですが、今回はServiceを利用したパラメータ渡しを紹介します。

なぜServiceなのか

$rootScopeを使うことでも、画面遷移時のパラメータ受け渡しは可能です。

let module = angular.module('myApp');
module.controller('ControllerA', [$rootScope, function($rootScope){
    // Controller A
    $rootScope.value = "value";
    $location.path("/b");
}]);
module.controller('ControllerB', [$rootScope, function($rootScope){
    // Controller B
    $scope.value = $rootScope.value;
}]);

ただし$rootScopeは他の用途でも用いられる上、名前空間の管理をきちんと行わないと
名前が競合してパラメータが上書きされるなどの問題が発生します。

let module = angular.module('myApp');
module.controller('ControllerA', [$rootScope, function($rootScope){
    // Controller A
    $rootScope.value = "value";
    $location.path("/b");
}]);
module.controller('ControllerB', [$rootScope, function($rootScope){
    // Controller B
    // Controller Aから遷移
    $scope.value = $rootScope.value;
}]);
module.controller('ControllerC', [$rootScope, function($rootScope){
    // Controller C
    // 別の用途で$rootScopeを利用
    $scope.value = 3;
}]);

一方、画面遷移パラメータ保持専用のServiceを作ることで、そのインスタンスは「パラメータ受け渡し専用」ということを明示でき、パラメータが上書きされる危険性をぐっと抑えることができます。

実装、Usage

ディレクトリ構成は以下の通り。

.
├── index.html
├── js
│   └── app.js
└── view
    ├── a.html
    └── b.html
index.html
<!DOCTYPE html>
<html ng-app="myApp">
<head>
    <meta charset="utf-8"/>
    <title>AngularJS - Passing Parameter to another window with Service. </title>
</head>
<body>
    <div ng-View></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.5/angular-route.min.js"></script>
    <script src="js/app.js"></script>
</body>
</html>
app.js
let mod = angular.module('myApp', ['ngRoute']);

// パラメータコンテナとして働くService
// 実際は単一のパラメータコンテナだけを用意するのではなく、各Controller用にコンテナ用意するなどして競合を防ぐ
mod.service('paramService', function(){
  this.firstMessage = null;
  this.secondMessage = null;
  this.thirdMessage = null;
});

// 遷移前画面に対応するController
mod.controller('ACtrl', ['$scope', '$location', 'paramService', function($scope, $location, paramService){

  $scope.firstMessage = "first message .";
  $scope.secondMessage = "second message .";
  $scope.thirdMessage = "third message .";

  // ボタンを押したらパラメータをServiceにセット+画面遷移
  $scope.onButtonClick = function() {
    paramService.firstMessage = $scope.firstMessage;
    paramService.secondMessage = $scope.secondMessage;
    paramService.thirdMessage = $scope.thirdMessage;
    console.log("test");
    $location.path("/b");
  };

}]);

// 遷移後画面に対応するController
mod.controller('BCtrl', ['$scope', 'paramService', function($scope, paramService){
    // 受け取ったパラメータをテンプレートにセット
    $scope.firstMessage = paramService.firstMessage;
    $scope.secondMessage = paramService.secondMessage;
    $scope.thirdMessage = paramService.thirdMessage;
}]);

// ルーティング設定
mod.config(['$routeProvider', function($routeProvider) {
  $routeProvider
    .when('/', {
      templateUrl: 'view/a.html',
      controller: 'ACtrl'
    })
    .when('/b', {
      templateUrl: 'view/b.html',
      controller: 'BCtrl'
    })
    .otherwise({
      redirectTo: '/'
    });
}]);
a.html
<!-- A -->
<div>
    <p>{{firstMessage}}</p>
    <p>{{secondMessage}}</p>
    <p>{{thirdMessage}}</p>
    <input type=button value="nextPage" ng-click="onButtonClick()"/>
</div>
b.html
<!-- B -->
<div>
  <p>{{firstMessage}}</p>
  <p>{{secondMessage}}</p>
  <p>{{thirdMessage}}</p>
</div>

参考文献

dev.classmethod.jp

【html】ファイルダイアログから複数ファイルをアップロードする

<input type="file" multiple>

HTMLでは<input type="file">タグを用いることでファイルダイアログを開く機能をページに埋め込むことができますが、デフォルトではファイルを一つしかアップロードできません(ファイルダイアログで一つしかファイルを選択できない)。

HTML
<input id="input_file" type="file" />
JS
function onChange(event) {
  // 取得できるファイルは一つのみ
  let file = event.target.files[0];
}
document.getElementById("input_file").addEventListener('change', onChange, false);

ファイルを複数選択するためには、inputタグに属性multipleを付加します。この属性を有効にした状態で、ファイルダイアログ上でShilf``Command(Ctrl)を押しながらファイルアイコンをクリックすると、ファイルの複数選択が可能になります。

f:id:rennnosukesann:20180502002915p:plain

HTML
<input id="input_file" type="file" multiple/>
JS
function onChange(event) {
  // 複数選択したファイルをFileList型オブジェクトとして取得
  let files = event.target.files;
  for (let file of files) {
    console.log(file); // [object file]...
  }
}
document.getElementById("input_file").addEventListener('change', onChange, false);

ちなみに、<input type="file">にイベントリスナchangeを設定したときに取得できるファイルオブジェクトのリスト$event.target.filesFileList型のオブジェクトであり、Array型のようにイテレート可能ですがArrayが持つプロパティの殆どを持っていません(要素を取得するitem()、リスト長を取得するlengthのみ持つ)。

【JavaScript】Spread Operator

Spread Operatorとは

JavaScriptではECMA2015からSpread Operatorと呼ばれる仕様を導入しました。これは、配列やオブジェクト、関数に渡す引数を展開するための糖衣構文です。

// 配列を展開
let num_array = [4, 5, 6];
console.log([1, 2, 3, ...num_array, 7, 8, 9]); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

// オブジェクトを展開
let num_obj = { one : 1, two : 2};
let str_obj = { hoge : 'hoge', fuga : 'fuga'};
console.log({...num_obj, str_obj}); // { "one": 1, "two": 2, "hoge": "hoge", "fuga": "fuga" }

// 関数引数の展開
let f_sum = (x, y, z) => x + y + z; 
let num_array = [4, 5, 6];
console.log(f_sum(...num_array)); // 15

pythonの引数アンパックのように関数引数指定の記述を簡略化できます。また配列・オブジェクトの結合を容易に記述することもできます。

Spread Operatorによる展開はコピー扱い

Spread Operatorによって展開された値は、元のオブジェクトのコピーとなります。ただし、値が参照値であった場合、その参照先は共有されます。

let mutate = function(x, y, z, w){
  x[1] = 3;
};
let sub_array = [1, 2, 3];
let num_array = [sub_array, 4, 5, 6];
// Spread Operator(sub_arrayへの参照値、4、5、6のコピーが渡される)
mutate(...num_array);
console.log(sub_array);// [1, 3, 3]

【html】相対配置・絶対配置

position

html要素の配置方法には複数の種類があり、指定した配置方法によって要素の配置のされ方が異なってきます。

html要素の配置方法はプロパティpositionで設定でき、以下の4種類の中から選べます。

static

初期値。配置方法指定なし。
top/bottom/left/rightプロパティを指定しても何も適用されません。

<div class="container">
    <div class="box1">BOX1</div>  
    <div class="box2">BOX2</div>  
</div>
.container {
  width: 200px;
  height:200px;
  border: solid 5px #CCCCCC;
}

.container div {
  width: 200px;
  height: 100px;
}

.box1 {
    background-color: #CCCC00;
}

.box2 {
    background-color: #00CCCC;
    position: static;
}

f:id:rennnosukesann:20180429203121p:plain

relative

相対配置。親要素からの相対位置を指定します。

.box2 {
    background-color: #00CCCC;
    position: relative;
    top: 50px;
    left: 30px;
}

f:id:rennnosukesann:20180429203526p:plain

absolute

絶対位置。ルート要素からの相対位置を指定します。

f:id:rennnosukesann:20180429203713p:plain

.box2 {
    background-color: #00CCCC;
    position: absolute;
    top: 50px;
    left: 30px;
}

ただし、親要素がstatic以外の配置指定の場合は、親要素を基準とした配置になります。

f:id:rennnosukesann:20180429204646p:plain

.container {
  position: relative;
  width: 200px;
  height:200px;
  border: solid 5px #CCCCCC;
}

.box2 {
    background-color: #00CCCC;
    position: absolute;
    top: 50px;
    left: 30px;
}
fixed

絶対位置。スクロールしても要素位置が固定されます。

f:id:rennnosukesann:20180429204810p:plain:w200 f:id:rennnosukesann:20180429205001p:plain:w200