かもメモ

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

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流 疲れない働き方

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

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

初めての npm パッケージ公開したメモ

よわよわな npm のパッケージ公開したみたので、パッケージ公開までのハマりどころとかをメモ

公開した npm パッケージ

1. npm アカウントの作成

npm のサイト からアカウントを作成する。
Sign Up のフォームにも書かれているが、npm のアカウントのメールアドレスは公開されるので、公開されても問題ないメールアドレスでアカウントを作るのが良いと思う。

2. ターミナルで npm にログインする

npm に接続するためにターミナルから先ほど作成した npm アカウントでログインする。 npm login コマンドを実行して訊かれるものに答えればOK。二段階認証してると二段階認証も訊かれる。

$ npm login
Username: # アカウント名
Password: # パスワード
Email: (this IS public) # 登録したメールアドレス
Enter one-time password from your authenticator app: # 二段階認証

3. 公開するパッケージの作成

npmに公開する場合 git 管理下にある必要があるので、GitHubに公開したいパッケージのリポジトリを作成してローカルにcloneする。(別にGitHubでなくてもいい)
clone したディレクトリに入ってnpm inityarn init でプロジェクトを作成する。
package.json にある情報が npm に公開される情報になる。 (npmyarnとで作成される package.json が微妙に異なる )

package.json

{
  "name": "PACKAGE_NAME", // npm に公開されるパッケージ名
  "version": "1.0.0", // パッケージのバージョン
  "description": "", // パッケージの説明。npm で検索した時に表示される
  "main": "index.js", // entry point になるファイル
  "keywords": [], // npm に公開した時にキーワードタグになる
  "license": "MIT",
  ...
}

GitHubリポジトリ下で npm init すると自動的に repository を GitHub に、 homepage を GitHub の README、bugs を GitHub の issues に設定してくれる。

description はパッケージのページには表示されないけど、検索した時に表示される。似たパッケージがあるとこ description でアクセスするかどうかが決まりそう。
package.json description

4. パッケージを npm に公開

公開するプログラムを作成したらパッケージを公開します。
babel とかでビルドしたものが entry point (実際に使用されるコード)になる場合は、ビルドしたものを git 管理下に置いておくか、公開時にビルドされる用設定しておく必要がある。

ビルドスクリプトの作成

package.json に npm スクリプトを作成する。
公開時のコマンドは色々と変遷があるようで、では

  • prepublish ではなく prepublisOnly を使うのが良さそう
  • 将来的に prepublisOnly は非推奨になり prepublish が推奨されるようになるらしい

公開時に babel を実行する場合はこんな感じ
package.json

{
  "scripts": {
    ...
    "build": "./node_modules/.bin/babel --source-maps",
    "prepublishOnly": "npm run build"
  }
}

パッケージの公開

npm publish ./ コマンドを実行するとファイルが npm にアップされてパッケージが公開される。

$ npm publish ./
...
npm notice
...
+ YOUR_PACKAGE@1.0.0

コマンドを実行して ERR がなく最後にパッケージ名@バージョンが表示されればOK
npm で検索するか自分のアイコンのメニューから packages を選べば公開されているパッケージが表示されているはず 🎉

パッケージのアップデート

npm version コマンドでバージョンを上げることができる。
アップデートの規模によって patch, minor, major を使い分ける。コマンドを実行すると package.jsonversion が変更され、バージョンの tag が作成され git commit される。( git push はされない )

patch

$ npm version patch
v1.0.1  # v1.0.0 から v1.0.1 にバージョンアップ

minor

$ npm version minor
v1.1.0  # v1.0.1 から v1.1.0 にバージョンアップ

patch バージョンは 0 に戻る

major

$ npm version major
v2.0.0  # v1.1.0 から v2.0.0 にバージョンアップ

patch, minor バージョンは 0 に戻る


パッケージ公開のハマりどころ

1. npm のパッケージ名には大文字が使えない

Uppercase characters are disallowed in new package names.
ref. npm publish failed put 400; is invalid for new packages · Issue #15787 · npm/npm · GitHub

e.g.

// package.json
{
  "name": "isObject",
}

大文字を含むパッケージ名にしていると、パッケージ公開時の publishERR! code E400 Bad Request というエラーになる。
エラーからは原因が分かりにくいのでハマりどころ…

2. @付きのパッケージ名( scoped package )にしたい時

@babel/core みたいなスコープのあるパッケージにしたい時、@XXXX なスコープ名は npm のアカウント名か組織名でないと公開できないみたい。
アカウント名と異なる @XXXX/ を指定したパッケージ名だと publish しようとしてもエラーになる。
僕の場合 npm のアカウント名を kikiki_kiki なんて長いのにしてしまったから、Scoped package にしたいと思うと @kikiki_kiki/xxxxx みたいな長い名前のパッケージ名にしかできない。(スコープモジュールにした自作パッケージの import がスゴイ面倒…)

When you sign up for an npm user account or create an Org, you are granted a scope that matches your user or Org name. You can use this scope as a namespace for related packages.
cf. About scopes | npm Documentation

3. public パッケージの場合は公開時に明示的に public であることを明示する必要がある

npm パッケージの公開

$ npm publish ./
npm notice
...
npm ERR! code E402
npm ERR! 402 Payment Required - PUT https://registry.npmjs.org/YOUR_PACKAGE - You must sign up for private packages

ERR! code E402 Payment Required というエラーになる。
private パッケージは有料プランなので、private なものを間違って public で公開してしまわないための処置なのかもしれない。
明示的に --access=public オプションを付けることで public パッケージとして公開される

$ npm publish --access=public ./
...
npm notice
...
+ YOUR_PACKAGE@1.0.0

Scoped package の場合はデフォルトがプライベートパッケージだと判断されるので公開パッケージにする場合は、初回の publish 時には明示的に --access=public を付ける必要がある。

Scoped packages are private by default; you must pass a command-line flag when publishing to make them public.
cf. About scopes | npm Documentation

4. 公開したパッケージは同じバージョンで上書きできない

npm は GitHub みたいに face push できないようなので、一度公開したバージョンをそのまま修正することはできないみたい。
修正する際は npm version patch などでパッケージのバージョンをアップデートして再 publish する必要がある


[参考]

NEW GAME!  (9) (まんがタイムKRコミックス)

NEW GAME! (9) (まんがタイムKRコミックス)

scss-lint に QualifyingElement といわれた。

こんな感じで怒られた。

QualifyingElement: Avoid qualifying attribute selectors with an element.

「属性セレクタで要素を修飾しないで」ということらしい。

input[type="radio"]:checked,
input[type="checkbox"]:checked {
  + label {
    color: $highlight;
  }
}

要素に直接 [type="radio"] みたいなの付けたらダメトノコトなので👇のように直せばOK

input {
  &[type="radio"]:checked,
  &[type="checkbox"]:checked {
    + label {
      color: $highlight;
    }
  }
}

SCSS めんどい。
コミット前とかに自動でコードフォーマット走るようにでもしないとストレスマッハ。