概要
N番煎じネタ、あまり踏み込んだネタもやらず、クライアントAPIやNodeにしかないものによらない、初心者向けの一般的なネタのみにする予定
基礎編
var, let, const
スコープ | 代入など | |
---|---|---|
var | 関数スコープ | 代入・再宣言ともに可 |
let | ブロックスコープ | 代入可/再宣言不可 |
const | ブロックスコープ | 代入・再宣言ともに不可 |
varは関数スコープなので同一関数内だと同じ名前は再代入になって書き換えられる、これは思わぬバグをうむのでvarの使用は推奨されない。 | ||
+= 、++ なども代入になるためにconst に使用してしまうとエラー(TypeError)になる。 |
||
const は代入もできないため宣言時に値を確定させないとエラー(SyntaxError)になる。 |
const i=0;
i++; // TypeError
const undef; // SyntaxError
const undef2=undefined; // 実はOK
スコープの優先
同じ名前の変数はより狭いスコープのものが優先される。
const c=0;
var v=0;
{
const c=1; // スコープの優先で再宣言にならない
var v=1; // ブロックスコープで縛られないので外のvを書き換える
console.log(c, v); // 1, 1
}
console.log(c, v); // 0, 1 // 外にあるcなので0
スコープの優先度はバグを作りやすいので注意が必要。
プリミティブ型とObject型
JavaScriptではプリミティブ型以外はすべてObject型になります。プリミティブ型はimmutable(変更不可)1でObject型で参照型です(後述)
プリミティブ型
- null
- undefined
- 真偽値
- 文字列
- 数値
- シンボル (es2015で追加)
の6個です。
constなObject型
JavaScriptのconst
は代入不可であるが変更可である。
参照型としてのObject型
Object型は参照型であり関数の引き渡しは参照渡しである。
const obj={ a: 'hoge', b: 'hoge' };
obj.b='fuga'; // 変更可能
const swap=(a)=>{
const tmp=a.a;
a.a=a.b;
a.b=tmp;
}
swap(obj); // objは参照渡しなので呼び出し元も変更される
console.log(obj); // { a: 'fuga', b: 'hoge' }
このようにJavaScriptでは変更されないObjectを作ることはできないので、プログラマーがObjectの状態に常に気にする必要がある。
Classは糖衣構文
糖衣構文だから使うな、ではなく糖衣構文あることを理解して使うべしということを強調しておく。
JavaScriptのClassはObjectでクラスを実現するための糖衣構文なのでプロパティは編集可能である。
また、Class自身はfunction
でインスタンス化された変数はobject
である。
const MyClass=class{}
const myClass=new MyClass();
myClass.hoge='hoge';
console.log(typeof MyClass); // function
console.log(typeof myClass); // object
等価演算子
プリミティブ型
プリミティブ型は値が等しいかどうかの判定をする。==
は型変換をするが、===
は型変換をしない厳密な判定になる。
JavaScriptの型変換では思わぬ値に変換される場合があるので==
は使わないことを強く推奨する。
(非存在を判定するためのif( val==null ) console.log(var, 'は存在しません');
は数少ない例外の一つ)
参照型(オブジェクト)
オブジェクトは参照値が等しいか調べる2ためオブジェクトの中の値は一切関係ない。
中身を見るようなequal
関数を作っても以下のように中身がオブジェクトとプリミティブなのかによって変わる。
const obj1={ a: 'hoge', b: 'fuga' };
const obj2={ a: 'hoge', b: 'fuga' };
const obj3=obj1;
const equal=(obj1, obj2)=>{
if( Object.keys(obj1).length!==Object.keys(obj2).length ) return false;
for( const [ key, val] of Object.entries(obj1) ){
if( obj1[key]!==obj2[key] ) return false;
}
return true;
}
console.log('obj1 == obj2 ', obj1==obj2); // false
console.log('obj1 == obj3 ', obj1==obj3); // true
console.log('equal(obj1, obj2) ', equal(obj1, obj2)); // true
const arr1=[ obj1, obj2 ];
const arr2=[ { ...obj1 }, { ...obj2 } ]; // スプレッド構文による代入
const arr3=[ obj1, obj2 ];
console.log('arr1 == arr2 ', arr1==arr2); // false
console.log('arr1 == arr3 ', arr1==arr3); // true
console.log('equal(arr1, arr2) ', equal(arr1, arr2)); // false 中身のオブジェクトの参照値は違う
console.log('equal(arr1, arr3) ', equal(arr1, arr3)); // true
このようにJavaScriptでオブジェクトの比較をするには型によって再帰をするなどの処置が必要で非常に注意が必要である。
型判定
typeof演算子
console.log(typeof undefined); // undefined
console.log(typeof null); // object
console.log(typeof 0); // number
console.log(typeof 'hello'); // string
console.log(typeof true); // boolean
console.log(typeof Symbol()); // symbol
const arrFunc=()=>{};
console.log(typeof arrFunc); // function
console.log(typeof function(){}); // function
console.log(typeof {}); // object
console.log(typeof []); // object
null
は"object"
である
プリミティブに対しては関数のみ"function"
がありアロー関数も"function"
になる。
Array.isArray(value)
配列かどうか判定する、typeof
では配列かオブジェクトかは判定できないのでコチラを使う。
Object.prototype.toString.call()
組み込み型に対して内部プロパティ[[Class]]
を使って判定する方法[object [[Class]] ]
の形で帰ってくるので適当に切り取って結果を得る
onst getType=(obj)=>{ return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase(); };
console.log(getType(undefined)); // undefined
console.log(getType(null)); // null
console.log(getType(function(){})); // function
console.log(getType(function * (){})); // generatorfunction
console.log(getType(async function(){})); // asyncfunction
console.log(getType(new Map())); // map
console.log(getType(class{})); // object
toLowerCase
を用いて小文字にしている。function*
やasync function
もきちんと判定できる。
独自実装のクラスはObjectになる。
独自クラスの判定
Object.constructor
もしくはinstanceofを使う
JavaScriptの数値関係の判定
NaN判定
グローバルなNaNは使わない。Number.isNaNを使う。
InfinityはNaNに含まれない
有限な数かはNumber.isFiniteを使う。
Number.isInterger
JavaScriptには整数と実数の区別がないためこれらの判定はNumber.isInterger
を使う
Number.isSafeInterger
数値には分解能である数以上は連続の整数にならなくなるその限界の数の範囲内であるかを返す。
限界の整数値はNumber.MAX/MIN_SAFE_INTERGERで定義されている。
型判定のまとめ
JavaScriptで完全で安全な型判定をするのは難しい。その場で必要な分だけするようにしよう。そのためにもいくつかの典型的な方法は知っておいたほうがいいだろう。
スプレッド構文と残余パラメーター
両方共、...
です。使う場面によって呼び名が変わりますがそのほうが機能が分かりやすいので是非、別のものとして覚えてください3。
スプレッド構文は配列リテラル4や関数呼び出しで使った場合で配列を展開して連続のパラメーターにします。Math.sum(...arr)
やMath.min(...arr)
のように使います。
レストパラメーターは関数定義の仮引数に使うことで残りのパラメーターを配列のようにまとめます。
const echo=(...args)=>{ console.log(args); }
echo(); // [] から配列
echo('a', 'b'); // [ 'a', 'b' ]
echo({ x: 1, y: 2}); // [ { x: 1, y: 2} ]
のように配列でラップしたようになります。
分割代入
const [ a, b, c ]=[...elem ];
やconst {a, b}={ a: 1, b: 2}
の様な形式で受けられる。undefinedの場合の既定値も設定できる。
オブジェクトの方はimport/export
等でよく使われる。
文字列操作
String.trim()
前方と後方にある空白文字を削除する。忘れがちだけどブラウザからユーザーインプットを受ける時に意外と使える。
String.split(separator, limit=undefined)
separator
(文字列/正規表現)で分割する、separatorは削除される。
const arr=' 1, 2, 3, 4, '.split(',')
console.log(arr) //[ ' 1', ' 2', ' 3', ' 4', ' ' ]
数値に変換するにはArray.map(callback)
を使う。
String.replace(regexp|substr, newSubstr|function)
第一引数は(文字列/正規表現)、第二引数で関数による操作ができる。
第一引数が文字列の場合、最初の文字だけが置換される
replaceAll的なもの5
"a b c".replace(/ /g, '-') // "a-b-c"
split-joinイディオム
"a b c".split(' ').join('-') // "a-b-c"
joinはArrayについたメソッドである
String/Arrayに共通するメソッド
includes(elem, position=0)
String版、Array版booleanを返す。Array版は第二引数に負の数を設定することが可能。
slice(start, end=this.length)
第一引数は負の場合は最後からの要素を取り出す、第二引数は省略可(最後まで)
Arrayについたメソッド
Arrayには破壊的メソッドと非破壊的メソッドがある。Objectが参照渡しのため破壊的メソッドを呼ぶと関数の外の配列にも影響する。
const add=(arr)=>{
arr=arr.concat([ 'new elem' ]); // 新しい配列を作ってarrに代入
console.log('add arr =', arr);
}
const add2=(arr)=>{
arr.push('new elem'); // arrに要素を追加
console.log('add2 arr =', arr);
}
const arr=[ 'hello', 'world' ];
add(arr); // add arr = [ 'hello', 'world', 'new elem' ]
console.log(arr); // [ 'hello', 'world' ] 外のarrは変わらない
add2(arr); // add2 arr = [ 'hello', 'world', 'new elem' ]
console.log(arr); // [ 'hello', 'world', 'new elem' ] 外のarrにも要素が追加される
Array.concat([ ...elem ])
引数は配列で2つの配列を繋げて新しく構築された配列を返す。
Array.splice(index, howMany, [ ...elem ])(破壊的)
spliceは継ぎ合わせるの意味でindexからhowManyを取り除き[ ...elem ]の配列を入れる。
howManyは省略可でその場合はindexから最後まで取り除かえる。
[ ...elem ]が省略された場合は何もたされない、sliceは新しく構築したものを返すがspliceは破壊的ヴァージョンとしても使える。
Array.push([ ...elem ])(破壊的)
[...elem]を末尾に足すメソッド、足すので配列の要素数は増える。戻り値は新しいArray.length。
Array.pop()(破壊的)
末尾の要素を取り出だすメソッド、取り出すので配列の要素は減る。空の場合はundefinedが帰る。
Array.shift()(破壊的)
先頭の要素を取り出だすメソッド、取り出すので配列の要素は減る。空の場合はundefinedが変帰る。
Array.unshift([ ...elem ])(破壊的)
[...elem]を配列の先頭に足すメソッド、足すので配列の要素数は増える。戻り値は新しいArray.length
Array.some(function)
function(elem){ return (true/false); }
を満たす要素があるかないかを返すメソッド。
Array.includes(elem)
は値を渡すがsomeは関数を渡せる。
Array.every(function)
function(elem){ return (true/false); }
をすべての要素が満たすかどうかを返すメソッド
Array.filter(function)
function(elem, index, self){ return (true/false) }
でフィルターされた部分集合、元の配列より小さい配列、を返す。
functionの引数は省略可能(以下の関数プログラミング的メソッドも同様)
Array.map(function)
function(elem, index, self){ return any; }
による写像、元の配列と同じlengthを持つ配列、を返す。
Array.reduce(function, initialValue)
function(accumulator, value, index, self){ return any; }
(accumulatorは一つ前に返された値)、で作られるある値を返す。
functionの引数がfilter、map
と違うので注意。
あとがき
普段、記事はストックばっかり増えてまとまらないのでアドベントカレンダーに参加して期限を切ってだそうと思いました。
最初はもっとよく使う小技集にするつもりでしたが、書いてるうちに基礎的知識になりました。基礎的知識集という意味ではまだまだ足りないものも多いですが力尽きたのでこれで出します、ちょくちょく追加するかもしれません。
明日は、@wshito さんの記事です。
-
プリミティブの参照はJavaScriptでは触れないためプリミティブ型の参照値を比べるはできない。 ↩
-
ES2018からはオブジェクトリテラルでも使える。 ↩
-
【JavaScript】検索文字列を全置換する方法【replaceAll】
正規表現を使ってグローバルフラッグg
をつける ↩