かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript NodeList, HTMLCollection は配列(Array)ではない

テーブルのtr内の th, td を丸っと処理したいみたいなケースを考えると、querySelectorAll('tr') で tr を取得してループで回し、それぞれの tr で tr.childNodestr.children すれば th, td が取得できそうなイメージになります。
しかしquerySelectorAllElement.childNodesElement.children などで取得される NodeList, HTMLCollection.length で要素数を取得したりはできるのですが、配列ではないので map とか some のような配列のメソッドが使えない場合があります。

NodeList は forEach でループ処理することができる

querySelectorAll, Element.childNodes で取得されるのは NodeList
NodeListforEach を使うことができる。

const table = document.getElementById('#table');
table.querySelectorAll('tr').forEach((row) => {
  row.childNode.forEach((cell) => {
    // <th>, <td> の処理
  });
});

forEach 以外の反復処理させるメソッドを使おうとするとエラーになる

table.querySelectorAll('tr').map((row) => {...});
// => Uncaught TypeError: table.querySelectorAll(...).map is not a function

row.childNode.some((cell) => {...});
// => Uncaught TypeError: row.childNodes.some is not a function

HTMLCollection は forEach も使えない

getElementsByClassNameElement.children で取得されるのは HTMLCollection
HTMLCollectionforEach も使えない。

const table = document.getElementById('#table');
table.getElementByClassName('row').forEach((row) => { ... });
// => Uncaught TypeError: table.getElementsByClassName(...).forEach is not a function

NodeList, HTMLCollection で map などの反復処理メソッドを使いたい場合

Array.from や スプレッド構文で配列に変換すればOK

const table = document.getElementById('#table');
// Array.from で配列に変換
Array.from( table.querySelectorAll('tr') ).map((row) => { ... });
// [...] で配列に変換
[...table.children].filter((child) => { ... }); 

NodeList と HTMLCollection の違い

NodeListHTMLCollection も参照している要素への変更は反映されるが、保持している要素数の増減に違いがある。

  • NodeList は取得した時点の要素を保持する (静的)
  • HTMLCollection は要素数に変化があれば増減後の要素になる (動的・参照みたいな感じ)
<ul id="list">
  <li>list-1</li>
  <li>list-2</li>
</ul>
const list = document.getElementById('list');
const nodeList = list.querySelectorAll('li'); // NodeList
const collection = list.children; // HTMLCollection

nodeList.length;   // => 2
collection.length; // => 2

// 参照しているDOM要素の変更
nodeList[0].textContent += ' edited by NodeList';
collection[1].textContent += ' edited by HTMLCollection';

nodeList[1].textContent;
// => list-2 edited by HTMLCollection
collection[0].textContent;
// => list-1 edited by NodeList

// 要素数の変化
const li = document.createElement('li');
li.textContent = 'list-3';
list.appendChild(li);

nodeList.length;   // => 2
collection.length; // => 3
Sample

See the Pen nodeList vs HTMLCollection by KIKIKI (@chaika-design) on CodePen.

 
最近お仕事で全くコード書く機会がないから忘れる。


[参考]

リスト:超絶技巧練習曲集

リスト:超絶技巧練習曲集

Googleスプレッドシート QUERY で取ってきたデータの複数のセルをそれぞれフォーマットしたい

数値で計算しているシートから QUERYIMPORTRANGE を使ってデータを取ってきて、SELECT文で必要な絡むだけにした上で単位を付けたりフォーマットして表示したい。

元のデータ例 シート名 販売目標

A B C D E
1 エリア 🍺消費量 (kl, 2016) 目標売上量 (kl) 350ml缶換算 (本) 目標販売人数 (人)
2 イバラキスタン 85192 708.57 2024493 34947
3 トチギニア共和国 60654 506.85 1448131 24997
4 グンマー帝国 57577 478.92 1368350 23620
5 サイタマニア共和国 208964 1729.53 4941527 85300
6 チバラキスタン 202300 1685.24 4814960 83115
7 帝都 736655 6591.23 18832076 325078
8 ヨコハマ=カワサキ二重帝国 297639 2478.30 7080871 122229
9 ヒョーゴスラビア連邦 199758 1669.90 814488 82359

このシートからデータを取ってきて、A列、C列、E列だけを表示し、C, E 列は3桁ごとにコンマ区切りにして単位を付けて表示したい

QUERY でフォーマットする

The syntax of the query language is composed of the following clauses. Each clause starts with one or two keywords. All clauses are optional. Clauses are separated by spaces. The order of the clauses must be as follows:
format Formats the values in certain columns using given formatting patterns.
cf. Query Language Reference (Version 0.7)  |  Charts  |  Google Developers

QUERY関数の第2引数は SELECT XXX〜SQL文だけでなく format 句も使用することができる。

QUERY関数の第2引数で format 句を使用するには format {対象} '{フォーマット形式}' のように書く。 例えば、「B列を3桁区切りで小数点第二位まで表示して kl を付けてヘッダーも含めて表示」なら次のような感じ

QUERY(B:B, "format B '#,##0.00 kl'", true)

フォーマット形式はシングルクォート ' で加工必要があるので注意! (シングルクォートで囲わないとエラーになる)

今膜区切りで複数のセルのフォーマットをすることができる

format 句はフォーマットをコンマ区切りで複数設定することが可能

QUERY(A:E, "format B '#,##0.00 kl', E '#,## 人'", true)

B列は 3桁区切り小数点第二位まで表示して最後に klを付け、E列は 3桁区切りで最後に を付けた形式で表示される

QUERY の第2引数の SELECT 文 に続けて format 句を付けることが可能

A、C、E列だけ、表示してフォーマットするならこんな感じ (SQLっぽく大文字にしてます)

QUERY(A:E, "SELECT A, C, E FORMAT C '#,##0.00 kl', E '#,## 人'", true)

👇

A C E
1 エリア 目標売上量 (kl) 目標販売人数 (人)
2 イバラキスタン 708.57 kl 34,947 人
3 トチギニア共和国 506.85 kl 24,997 人
4 グンマー帝国 478.92 kl 23,620 人
5 サイタマニア共和国 1,729.53 kl 85,300 人
6 チバラキスタン 1,685.24 kl 83,115 人
7 帝都 6,591.23 kl 325,078 人
8 ヨコハマ=カワサキ二重帝国 2,478.30 kl 122,229 人
9 ヒョーゴスラビア連邦 1,669.90 kl 82,359 人

✧\\ ٩( 'ω' )و //✧ ヨシ!

SELECT と FORMAT を一緒に使う時の注意

次のようなものはエラーになる

  1. select で選択されてないカラムをフォーマットしよとしたらエラー
    QUERY(A:E, "SELECT A, B, E FORMAT C '#,##0.00'", true)
    
    => #VALUE! SELECT していない C列をフォーマットしようとしている
  2. select より前に format があるとエラー
    QUERY(A:E, "FORMAT B '#,##0.00' SELECT A, B, E", true)
    
    => #VALUE! どの列が SELECT されるかわからない状態で FORMAT しようとしてるからエラーになるのだと思う

他のスプレッドシートからデータを取ってくる場合

他のスプレッドシートからデータを取ってくるには IMPORTRANGE() を使う
詳しい使い方は👇を参考にしてください。 [https://chaika.hatenablog.com/entry/2015/11/26/143853:embed]

IMPORTRANGE() で取ってきたデータを同じように QUERY で必要なカラムだけをSELECTしてフォーマットをすればOK。IMPORTRANGE() で取ってくる場合は、列の指定がA, B, C ... ではなく、選択した範囲の先頭からCol1, Col2, Col3 ... のようになるので注意 (B:Cのように選択した場合は B列がCol1になる)

別のスプレッドーシートで先の販売目標シートからA列、C列、E列だけを取ってきて、C, E 列は3桁ごとにコンマ区切りにして単位を付けて表示するなら次のような感じ

QUERY(
  IMPORTRANGE("{SHEET-KEY}", "販売目標!A:E"),
  "SELECT Col1, Col3, Col5 format Col3 '#,##0.00 kl', Col5 '#,## 人' ", true
)

ポエム

QUERY() の SELECT 文に FORMAT くっつけて使える、それも複数のカラムのフォーマットもできるの知らなかった。
今まで QUERY で取ってきたのを ARRAYFORMULA() でフォーマットしたりしてたヨ…
まぁこのタイミングで知れてよかったです。


[参考]

第2話

第2話

👆アマプラにあったメタノールって2012年にチェコで実際に起こった密造酒事件を元にしたドラマ映画面白かった。

JavaScript `{}` なオブジェクトだけを判定したい

個人的に JavaScript の要素は全部 Object で、型の概念は実質存在しないという認識。(Stringも "foo".length とか使える訳だし)
ある程度は typeof() で判別することができるけど、Ruby とかで Hash とか Python で DIctionary (辞書) とかと呼ばれるような {a: 1} みたいなオブジェクトだけを判定したいと思ったのでやってみたメモ。

色々なオブジェクトの typeof()

// String
'String' // 'string'
new String('Foo') // 'object'
// Number
1    // 'number'
-1   // 'number'
1.5  // 'number'
-0.8 // 'number'
NaN  // 'number'
// Boolean
true  // 'boolean'
false // 'boolean'
null  // 'object'
// undefined
undefined // 'undefined'
// Array
[]      // 'object'
[1, 2]  // 'object'
// Object (hash)
{}           // 'object'
{a: 1, b: 2} // 'object'
// Function
function() {} // 'function'
// Date
new Date() // 'object'
// RegExp
/\w+/              // 'object'
new RegExp('\\w+') // 'object'
// Symbol
Symbol()     // 'symbol'
Symbol( {} ) // 'symbol'
// Class
class MyClass {
  constructor() {}
};
new MyClass() // 'object'

殆どのオブジェクトはそれぞれの型が表示されるが、{} なオブジェクト以外で typeof() した時に object と判定されるのは次の通り

Array の判別

配列に関しては Array.isArray() で判定が可能

cf. Array.isArray() - JavaScript | MDN

// 以下の呼び出しはすべてtrueを返す
Array.isArray([]);
Array.isArray([1]);
Array.isArray(new Array());
Array.isArray(new Array('a', 'b', 'c', 'd'));
Array.isArray(new Array(3));
// あまり知られていないものの Array.prototype は配列
Array.isArray(Array.prototype); 

// 以下の呼び出しはすべて false を返す
Array.isArray();
Array.isArray({});
Array.isArray(null);
Array.isArray(undefined);
Array.isArray(17);
Array.isArray('Array');
Array.isArray(true);
Array.isArray(false);
Array.isArray({ __proto__: Array.prototype });

Array.isArray() で配列を除いてもまだ{} なオブジェクトと null, new String(), Date, RegExp, Class を判別することはできない

Object.prototype.toString.call() で型を

Every object has a toString() method that is automatically called when the object is to be represented as a text value or when an object is referred to in a manner in which a string is expected. By default, the toString() method is inherited by every object descended from Object. If this method is not overridden in a custom object, toString() returns "[object type]", where type is the object type.
ref. Object.prototype.toString() - JavaScript | MDN

var o = new Object();
o.toString(); // returns [object Object]

toString() can be used with every object and allows you to get its class. To use the Object.prototype.toString() with every object, you need to call Function.prototype.call() or Function.prototype.apply() on it, passing the object you want to inspect as the first parameter called thisArg.
ref. Object.prototype.toString() - JavaScript | MDN

var toString = Object.prototype.toString;

toString.call(new Date);    // [object Date]
toString.call(new String);  // [object String]
toString.call(Math);        // [object Math]

// Since JavaScript 1.8.5
toString.call(undefined);   // [object Undefined]
toString.call(null);        // [object Null]

Object.toString()[object type] を返し、あらゆるオブジェクトは Object を継承しているので Object.prototype.toString()call()apply()this になるオブジェクトを変更して呼び出せば引数で渡した this の型を表示することができる。

null, new String(), Array Date, RegExp, Class の場合

typeof(), Array.isArray() で除外できなかった null, new String(), Date, RegExp, ClassArray でどうなるか試してみた

function showObjectType(val) {
  const type = Object.prototype.toString.call(val);
  console.log( type.slice(8, -1) );
}

showObjectType( null ); // 'Null'
showObjectType( new String('foo') ); // 'String'
showObjectType( new Date() ); // 'Date'
showObjectType( /\w+/ ); // 'RegExp'
showObjectType( new RegExp('\\w+') ); // 'RegExp'
showObjectType( new MyClass ); // 'Object'
showObjectType( [] ); // 'Array'

Class インスタンス以外はそれぞれの型が表示されるのでこれで除外することができる。
Array.isArray() は内部的にこんな感じで判定しているのかもしれない。

Object.prototype.constructor で判定

All objects (with the exception of objects created with Object.create(null)) will have a constructor property. Objects created without the explicit use of a constructor function (i.e. the object and array literals) will have a constructor property that points to the Fundamental Object constructor type for that object.
ref. Object.prototype.constructor - JavaScript | MDN

var o = {};
o.constructor === Object; // true
var o = new Object;
o.constructor === Object; // true
var a = [];
a.constructor === Array; // true
var a = new Array;
a.constructor === Array; // true
var n = new Number(3);
n.constructor === Number; // true
new String(), Array Date, RegExp, Class の場合

null.constructor はエラーになるので、null 以外でテストしてみる

function getConstructor(val) {
  if( val === null ) return false;
  const type = val.constructor;
  return type;
}

getConstructor( new String('foo') ); // [Function: String]
getConstructor( new Date() ); // [Function: Date]
getConstructor( /\w+/ ); // [Function: RegExp]
getConstructor( new RegExp('\\w+') ); // [Function: RegExp]
getConstructor( new MyClass ); // [Function: MyClass]
getConstructor( [] ); // [Function: Array]

constructor プロパティの場合 Class インスタンスは元のクラスがコンストラクターとして返されるので {} なオブジェクトと判別することができる

{} なオブジェクトを判別する関数

次の条件を満たしていれば {} なオブジェクトだと判断できそうです

  1. typeof()"object"
  2. null でない
  3. constructorObject
function isObject(val) {
  if( val !== null
   && typeof(val) === 'object'
   && val.constructor === Object ) {
    return true;
  }
  return false;
}

👇 TEST

isObject( "String" ); // => false
isObject( new String("Foo") ); // => false
isObject( 1 ); // => false
isObject( -1 ); // => false
isObject( 1.5 ); // => false
isObject( -0.8 ); // => false
isObject( NaN ); // => false
isObject( true ); // => false
isObject( false ); // => false
isObject( null ); // => false
isObject( undefined ); // => false
isObject( [] ); // => false
isObject( [ 1, 2 ] ); // => false
isObject( {} ); // => true
isObject( { a: 1, b: 2 } ); // => true
isObject( function() {} ); // => false
isObject( new Date() ); // => false
isObject( /\w+/ ); // => false
isObject( new RegExp('\\w+') ); // => false
isObject( Symbol() ); // => false
isObject( Symbol({}) ); // => false
isObject( new MyClass() ); // => false
isObject( new Object() ); // => true

判別できる npm パッケージを公開しました


[参考]

Google流 疲れない働き方

Google流 疲れない働き方

最近経年劣化で年中無休な疲労コンパイルなのでこれ読んでる。
鋼鉄の体が欲しいゼ…

鋼鉄の体の立体物あったのか…!! コトブキヤ素晴らしい!!!!