LoginSignup
0
0

More than 1 year has passed since last update.

JavaScriptの概念

Last updated at Posted at 2023-04-20

はじめに

Twitterでこんな記事を見かけました。知らなくても形にして動かすことはできますが、裏での処理がわからなくてうまく説明できなかったり、何より当然抑えておくべきだと思いましたので書くことにしました。

内容は参考元と同じなので、体に染みたら削除しようと思います。

参考元の著者: Twitter: @tsin1rou Qiita: @tsin1rou

0. 関数の宣言

const bar = (arg) => {
  return arg * 2;
};

まずは関数の宣言です。returnの行しかない関数は省略して(引数)=>(返り値)と書けます。

const bar = (arg) => (arg * 2);
const bar = arg => arg * 2; // 更に()の省略も可

よく見かける謎の記法です。

const bar = x => x + y;
const foo = x => (y => x + y); // 上記と同じ
const piyo = foo(4); // piyo = y => 4 + y;

console.log(piyo(3)); // -> 7
console.log(foo(4)(3)); // -> 7

1. コールスタック

JavaScriptでは関数を呼び出すとコールスタックと呼ばれる部分に呼び出された関数が積み上がっていきます。常に一番上の関数が実行されており、関数の実行が終わって値を返すとスタックから取り除かれ、上から2番目に載っていた関数へと戻っていきます。

    const bar = x => x+6;
    const foo = x => bar(x * 2) * 3;
->  console.log(foo(5));

上記は例です。上から順に実行され、console.log(foo(5));の行まで来ました。

コールスタック
console.log(foo(5))
main()

常にコールスタックの一番上を実行します。今回はfoo(5)という関数を実行する命令が再び登場するので更に積み上げます。

コールスタック
foo(5)
console.log(foo(5))
main()

fooを実行すると今度はbarが登場しているので積み上げます。

    const bar = x => x+6;
->  const foo = x => bar(x * 2) * 3;
    console.log(foo(5));
コールスタック
bar(10)
foo(5)
console.log(foo(5))
main()

一番上にあるbar(10)を実行します。

->  const bar = x => x+6;
    const foo = x => bar(x * 2) * 3;
    console.log(foo(5));

16という返り値を得られたのでbar(10)をコールスタックから取り除きます。

コールスタック
foo(5)
console.log(foo(5))
main()

一番上にあるfoo(5)の処理に戻ります。bar(10)=16ということがわかったので、48という返り値が求まります。そしてこのfoo(5)をコールスタックから取り除き、一番上に現れたconsole.log(foo(5))を実行して、コンソールに48が表示される流れです。その後はmain()に従って次の処理へと進んでいきます。

2. プリミティブ型

基本的なデータ型です。全部で6種類あります。

boolean: true, false
string: "abcde"文字列
number: 普通の数字。100

null undefined: nullは「無い」状態で、undefinedは「何も定義されていない」状態を表します。

symbol: ES2015で導入された新しい型。ユニークなIDが生成出来ます。

3. 値型、参照型

先程のプリミティブ型は全て値型です。値型は 「変数をコピーした時に中身もコピーされる」 ということです。

値型
let x = 20;
let y = 'abc';

const a = x;
const b = y;

x = 7;
y = 'def';

console.log(x, y, a, b); // -> 7 "def" 20 "abc"
変数
x 7
y "def"
a 20
b "abc"

参照型

一方、オブジェクトや配列などは参照型です。

参照型
const a = [0, 1, 2];

aにはアドレスがセットされます。

変数
a <#001>
アドレス データ
#001 [0,1,2]

ここで変数をコピーしてみると...

参照型
const b = a;

参照だけがコピーされます。

変数
a <#001>
b <#001>
アドレス データ
#001 [0,1,2]

つまり、aの中身を変えるとbの中身まで変わります。

4.型変換

JavaScriptはプリミティブ型に併せて

  1. Stringへの変換
  2. Booleanへの変換
  3. Numberへの変換

の3種類が存在します。

Stringへの変換

String(123)           // '123'
String(-12.3)         // '-12.3'
String(null)          // 'null'
String(undefined)     // 'undefined'
String(true)          // 'true'
String(false)         // 'false'

// 暗黙の型変換。 +の片方がStringならもう一方もStringに変換される
String(''+0+1)        // '01'
String(0+''+1)        // '01'
String(0+1+'')        // '1' <- 0+1を先に計算

Boolean

'' 0 -0 NaN null undefind false のみがfalse に変換されます。他はすべてtrueです。

Boolean('')           // false
Boolean(0)            // false     
Boolean(-0)           // false
Boolean(NaN)          // false
Boolean(null)         // false
Boolean(undefined)    // false
Boolean(false)        // false

Boolean({})           // true
Boolean([])           // true

|| && ! 3つの倫理演算子は暗黙のBoolean型変換を行います。
|| && は型変換を内部でのみ行い、返り値は元の値・型がそのまま返ってきます。

// player.nameが設定されていなければ'NoName'
const name = player.name || 'NoName';

// if文で書くとこうなる
let name;
if(player.name){
  name = player.name;
} else {
  name = 'NoName';
}

Number

Number(null)           // 0
Number(undefined)      // NaN
Number(true)           // 1
Number(false)          // 0
Number(" 12 ")         // 12
Number("-12.34")       // -12.34
Number("\n")           // 0
Number(" 12s ")        // NaN
Number(123)            // 123

以下の演算子はNumberへの暗黙の型変換を行います。

  • 比較演算子 < > <= =>
  • ビット演算子 | $ ^ ~
  • 算術演算子 - + * / % ※+の片方がStringならString。
  • 比較演算子 == ! ※両方とも、比較対象がStringならString。

また、==はnullとundefinedの型変換を行わないという特別ルールがあります。

null == 0  // false

5. ==と===の違い

結論から言うと可能な限り===推奨とのことです。
==は暗黙の型変換を行うので意図せぬ挙動の原因となるからです。

false == 0              // true
false === 0             // false
0 == ""                 // true
0 === ""                // false

nullundefined==においてそれぞれにのみ一致します。

null == undefined       // true
null === undefined      // false

NaNは自分自身を含めて何とも一致しないというルールがあります。NaNであるかどうか確認したい場合はNumber.isNaN()を使用します。

const a = NaN;
console.log(a === a);     // false
console.log(a === NaN);   // false
console.log(Number.isNaN(a));  // true

typeof

どの型なのか調べる場合に使用します。

typeof 6                 // "number"
typeof 'ker'             // "string"
typeof {}                // "object"
typeof true              // "boolean"
typeof undefined         // "undefined"
typeof (()=>{})          // "function"

6. スコープ

ifでもforでも関数でも、{}で囲まれてさえいればスコープが発生すると考えればOKのようです。

const bar = 'foobar';
{
  console.log(bar); // Uncaught ReferenceError: bar is not defined
  const bar = 'foobar';
  console.log(bar); // -> "foobar"
}
console.log(bar); // -> "foobar"

JavaScriptでも基本的には上のスコープを見にいくのですが、同名の変数が同じスコープで定義されていると代わりにエラーが出ます

const bar = 'foobar';
{
  console.log(bar); // -> "foobar"
}

{
  console.log(bar); // ReferenceError
  const bar = 'foobar';
}

これは 「宣言の巻き上げ」 というJavaScriptの仕様で、全ての変数はスコープの一番上で宣言されたのと同じ扱いになります。ただし、明示的に宣言した場合にはエラーが出ません。

{
  let bar;
  console.log(bar); // -> undefined
  bar = 'foobar';
}

{
  console.log(bar); // ReferenceError
  let bar = 'foobar';
}

7. 文Statementと式Expression

JavaScriptのソースコードはStatementExpressionという2つの構文から成り立っています。

StatementはJavaScriptにおける文の単位のことで、例えばif文だとif (式) 別の文という形ですね。基本的に「戻り値」がないものがStatementです。Expressionは評価できて値が返るものです。

Statementの例
if (a > b) {
  console.log("a is greater than b");
}
Expressionの例
const sum = a + b;
  • const let
  • if(else) switch
  • for (do)while break continue
  • throw try catch(finally)

StatementとStatementの間には;セミコロンを使います。ただし、{}で囲まれたブロック文でStatementが終わる際には;を付けてはいけません。これがif(){}とかwhile(){}の後ろに;が付いていない理由です。

逆にif()bar();みたいな{}を使わないifの後ろには通常通り;が必要です。

8. ブラウザで使用できるタイマー

setTimeoutとsetIntervalは共にブラウザで使用できるタイマーです。

  • setTimeout(callback, delay)は delayミリ秒以降にcallbackが実行されます。
  • setInterval(callback, interval)は intervalミリ秒ごとにcallbackが実行されます。

setTimeout, setInterval

index.js
console.log('startTime:', new Date());

setTimeout(()=>{
  console.log('timeout:', new Date());
}, 1000);
const timeout = setInterval(()=>{
  console.log('interval:', new Date());
}, 1000);

setTimeout(()=>{
  clearInterval(timeout);
}, 5000);

callbackに引数を渡したい時は、
setTimeout(callback, delay, 引数1, 引数2, ...)
or
setTimeout(()=>{callback(引数1, 引数2, ...)}, delay)

requestAnimationFrame

JavaScriptでアニメーションを作る際に使用します。
requestAnimationFrame(callback)とすると次の描画の直前にcallbackが実行されます。
これを再帰させて下のコードの様にすると描画ごとに実行される関数の完成です。

const animation = ()=>{
  requestAnimationFrame(animation);
  // 描画処理
};
requestAnimationFrame(animation);

9. DOMとレイアウトツリー

DOM(ドキュメント・オブジェクト・モデル)。要素のツリー。

DOCUMENT (always the root)
 └─HTML
   ├─HEAD
   │  └─...
   └─BODY
      ├─DIV
      │  └─...
      ├─SCRIPT
      └─SCRIPT

ブラウザはhtml文章をパースしてDOMを生成します。画面に描画されているのはDOMなので、これを適当に編集すれば画面の表示も変わります。ReactやVueの使用が多いので、直接編集することはあまりないと思います。

要素を取得する

よく使用されるのはgetElementById()getElementsByClassName()querySelectorAll()などです。

// idが"bar"の要素を取得
const element1 = document.getElementById('bar');

// <... class="foo bar baz">のようにクラスに含まれていればまとめて取得。返り値はリスト。
const element2 = document.getElementsByClassName('foo');

// cssのセレクター形式で要素を取得。返り値はリスト。
const element3 = document.querySelectorAll('#bar'); // id="bar"の要素を取得
const element4 = document.querySelectorAll('div.highlighted > p'); // p要素のうち直接の親が'highlighted'クラスを持つものを取得

要素の編集

const element = document.getElementById('...');

// 要素を作成
const foobar = document.createElement('div');

// 要素を追加
element.appendChild(foobar);

// 要素を削除
element.removeChild(foobar);

// 子要素を全削除
while(element.firstChild){
  element.removeChild(element.firstChild);
}

// クラスを取得
const classList = element.classList;
if(classList.contains('foobar'){
  // 要素にfoobarクラスが設定されていれば走る
}

// クラスを追加・削除・切り替え
classList.add('foobar');
classList.remove('foobar');
classList.toggle('foobar');

イベント関連

addEventListener()removeEventListener()を使います。
clickイベントの例です。

element.addEventListener('click', (event)=>{
  // preventDefault()を呼ぶと通常の動作が行われなくなる。
  // 例えばaタグをクリックしても移動しなくなる。
  event.preventDefault();
  console.log('要素がクリックされました');
});

// removeの方は両方の引数を同じにする必要がある
const clickHandler = (event)=>{...};
element.addEventListener('click', clickHandler);
element.removeEventListener('click', clickHandler);

10. thisとcallとapplyとbind

function()=>{}で何がthisになるかが異なります。

functionにおいてのthis

thisは呼び出し方に応じて変化します。
呼び出されるまで何がthisになるか分かりません。

function bar(){
  console.log(this);
}

bar();         // -> global

const obj = {
  bar
};
obj.bar();     // -> obj

const obj2 = {
  foo: function(){
    bar();
  }
};
obj2.foo();    // -> global

アロー関数においてのthis

コードを見れば何がthisになるかが分かります (レキシカルなthis)
アロー関数はどこからどうやって呼び出してもthisは常に同じものをさします。

const bar = () => {
  //この段階でthisがglobalなのでどこで呼び出してもglobalになる
  console.log(this);
};

bar();         // -> global

const obj = {
  bar
};
obj.bar();     // -> global

const obj2 = {
  foo: function(){
    bar();
  }
};
obj2.foo();    // -> global

callとapply

callapplyも呼び出す際に何がthisになるのかを明示的に決定するための関数です。共に第一引数にthisにしたいものを入れます

callは第二引数以降に直接引数にしたいものを入れていきますが、applyは引数をまとめて配列にしたものを第二引数にするという違いがあります。ただし共にアロー関数のthisを変えることはできません。

const bar = (arg) => {
  console.log(arg);
  console.log(this);
};
function foo(arg){
  console.log(arg);
  console.log(this);
}

const obj = {};
const args = ['arg1', 'arg2'];
bar.call(obj, args);
// -> ["arg1", "arg2"]
// -> global

foo.call(obj, args);
// -> ["arg1", "arg2"]
// -> Object {}

bar.apply(obj, args);
// -> "arg1"  (配列が展開されて渡される)
// -> global

foo.apply(obj, args);
// -> "arg1"
// -> Object {}

bind

同じように何をthisにするかを決められます。一度決めれば永続的に効果を発揮しますが、アロー関数には使えません。

const bar = () => {
  console.log(this);
};
function foo(){
  console.log(this);
}

const obj = {};

// bindは新しい関数を返す
const bar2 = bar.bind(obj);
const foo2 = foo.bind(obj);

bar();     // -> global (元の関数を変化させない)
foo();     // -> global
bar2();    // -> global (アロー関数には効かない)
foo2();    // -> Object {}

11. newとインスタンスについて

newはインスタンスを作成するのに使用します。

class Bar {
  constructor(args){
    console.log(this);
    const { name } = args || {};
    this.name = name || 'NoName';
  }
}
const Foo1 = (args) => {
  console.log(this);
  const { name } = args || {};
  this.name = name || 'NoName';
}
function Foo2(args) {
  console.log(this);
  const { name } = args || {};
  this.name = name || 'NoName';
}
                              // thisの中身↓
const bar1 = Bar();           // -> TypeError: Class constructor Bar cannot be invoked without 'new'
const bar2 = new Bar();       // -> Object { name:"NoName" }
const foo1_1 = Foo1();        // -> global
const foo1_2 = new Foo1();    // -> TypeError: Foo1 is not a constructor
const foo2_1 = Foo2();        // -> global
const foo2_2 = new Foo2();    // -> Object { name:"NoName" }

newclassは常にセットで使用し、それ以外の場合は使用しないという取り決めが良いようです。

instanceof

インスタンスがどんなクラスを継承してきたのかを確かめるにはinstanceofを使用します。たくさん継承を重ねていても先祖のどこかに存在すればtrueになります。

class bar {
  constructor(args){
    ...
  }
}
class Foo extends Bar {
  constructor(args){
    super(args);
    ...
  }
}
const bar = new Bar();
const foo = new Foo();

console.log(bar instanceof Bar);  // -> true
console.log(bar instanceof Foo);  // -> false
console.log(foo instanceof Bar);  // -> true
console.log(foo instanceof Foo);  // -> true
0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0