processing:p5js:08.jsの文法基礎

JavaScriptの文法基礎

ここまでの教材である程度jsにはなれてきたかなと思いますが、いざ自分で思いついた内容をまっさらなコードから書き始めるとなると、手がとまる人が多いのでは無いかなと思います。応用ができないのは基礎ができていないからですが、最初から基礎をやるとつまらなくなってしまうので、この教材ではこのタイミングでJavaScriptの基本的な文法に関して触れておきたいともいます。

JavaScriptの入門ページとして、基本的な構文(シンタックス)を理解しておきます。ある程度なれてしまえば結構色々プログラムを書くことはできますが、少し細かなところまでを踏まえて抑えていきます。

全集中、jsの型、基本は自動、です。例えば

var value1 = 1 + 1;
var value2 = 1.0 + 1.0;

とすれば、value1 は自動的に整数(int)として扱われ、value2は自動的に浮動小数(float)として扱われます。jsではこれら数値はすべて number として扱われています。実際に型を調べる typeof という関数を使ってみます。

print(typeof('文字列'));
print(typeof(1));
print(typeof(true));
print(typeof([]));

手元の環境でどのような方が帰ってくるのか確認してみましょう。

以上のようにjsでは代入される値に応じて適切な型が決められていますが、ユーザ側が型を自分で決めたいときもあります。例えば、dom要素から文字列値を取得してきたが、これは実際には数値として扱いたい、といった場合です。この場合はユーザが自ら型の変更(キャスティング)を行うこともできます。

var value = '100'; // <-- これを数値で扱いたい
print(typeof(value)); // string
value = parseInt(value); // int型にキャスティング
print(typeof(value)); // <-- どうなるかを見てみましょう

上記でさらっと使っている varですが、これは変数を宣言するときに先頭につける宣言方法です。昔はvarしかありませんでしたが、2015年以降からは let, constが採用されているため、ネットでjsサンプルを見たときにletやconstで記述されているのを目にすることがあると思います。ざっくりといえば

  • var : 基本は全部これでもOK
  • let : 変数をその場だけで使うとき
  • const : 一度代入した値は以後変更しないとき

と考えておきましょう。ただこれだけだと少しもやっとするので、もう少し突っ込むと

  • var : スコープ範囲({}の範囲)が広い、再宣言できる、再代入OK
  • let : スコープ範囲が狭い、再宣言できない、再代入OK
  • const : スコープ範囲が狭い、再宣言できない、再代入できない

という具合に、var からconstに向けて徐々に制約がきつくなります。スコープの範囲が狭いというのは下記の場合を指します。

{
var x = 'var test';
let y = 'let test';
const z = 'const test';
}
console.log(x); //OK
console.log(y); //error
console.log(z); //error

ここまで書くと、じゃあvarでいいじゃん。と思うかと思います。実際のところみなさんの課題制作においてもvarだけで構わないと思います。ただし意識して使い分けておくと、コードのデバッグがしやすくなり、結果として自分のみを助けることもあります。例えばfor分で例を示してみます。

for( let i = 0; i < 10; i++ ){
}
print(i);

とした場合は、print(i)はエラーになります。これは「つかえたっていいじゃない」と思う反面、「気づかせてくれてありがとう」と思うこともできます。

もう少しforループを使って挙動を確認してみます。

for( var i = 0; i < 2; i++ ){
    for( var j = 0; j < 2; j++ ){
      for( var k = 0; k < 2; k++ ){
        print(i,j,k);
      }
    }
 }

すべて var で宣言しています。このようなforループを記述している際、記述の量が増え、いろんな修正や試行錯誤をしている間にvar kで宣言している箇所を誤って、var i としてしまいました。そんな場合を考えます。コードでいえば以下のような誤りです。

for( var i = 0; i < 2; i++ ){
    for( var j = 0; j < 2; j++ ){
      for( var i = 0; i < 2; i++ ){ // <-- i はすでに利用しているがここで使ってしまった
        print(i,j,i);
      }
    }
 }

この場合は、最後のforループで新しく変数 iを宣言することになるため、動作がおかしくなってしまいます。実行結果も4行しか表示されておらず、2^3の処理が行われていません。例えばここをletで宣言していた場合はどうなるでしょうか?

for( let i = 0; i < 2; i++ ){
    for( let j = 0; j < 2; j++ ){
      for( let i = 0; i < 2; i++ ){ // <-- i はすでに利用しているがここで使ってしまった
        print(i,j,i);
      }
    }
 }

8行分の処理は出力されますが、どうも結果がおかしいです。正しい結果を観察してどのような振る舞いになっているか一緒に考えてみましょう。letの場合は後で宣言された let i は異なる変数として処理されているため、スコープ外の let i には影響を与えていないことがわかりますね。

let value1 = "scope1";
    {
      let value2 = "scope2";
      {
        let value1 = "scope3";
        print(value1);
      }      
    }
    //let value1;
  }

こちらにあるように、別スコープにある同名の変数value1はそれぞれ別々に動作しますが、同じスコープ内にある変数value1をコメントアウトするとエラーになることがわかります。

少し長くなりましたが、var, let, constに関して少し深めに理解できたでしょうか?小規模プログラムではぜんぜんvarで構わないと思いますが、letやconstを意識して使うと、自分がデバッグをするときにどのような意図をもって変数を使おうとしているのか、までわかるので、ぜひ使いこなしてみてください。

配列は比較的単純です。

let array = [1, 2, 3, 4, 5];
print(array);

for (let i = 0; i < array.length; i++) {
  print(array[i]);
}

二次元配列の場合でも上記と同じような処理を行うとすると

  let array = [
    [1, 2, 3, 4, 5],
    [6, 7, 8]
  ];

  print(array);

  for (let i = 0; i < array.length; i++) {
    for (let j = 0; j < array[i].length; j++) {
      print(array[i][j]);
    }
  }

となります。lengthで配列長を調べる箇所が2次元なので2つ分ある、というわけですね。

以上のやり方でコーディングしていくと、

  • この配列途中で中身を変更したいんだよね
  • 中身を一箇所だけ削除したい

なんて気持ちにすぐなるのではとお察しします。それらを実現するには、jsの場合はpushやspliceといった配列用の関数が用意されています。まずは具体的に見てみましょう。

  let array = [1,2];
  array.push(3);
  print(array);

一般的にこのような配列操作には、unshift, shift, pop, pushがあり、これらをうまく使うことで簡単な配列操作ができるようになります。それぞれがどう違うのかを図示している記事があるので紹介しておきます。

実際に自分でいくつかのコードを動かして動作を確認してみてください。例えば以下のコードはどのような結果を出力するでしょうか?頭で考えた後、実行して動作を確かめてください。

let array = [2];
array.unshift(0,1);
array.push(3,4);
array.shift();
array.pop();
print(array);

では次に任意の場所の配列を削除する場合です。[1,2,3]の配列に対して2の位置だけ削除します。

let array = [1,2,3];
array.splice(1,1);
print(array);
  • splice()
    • param 1: 削除する要素位置
    • param 2: 削除する要素数

として使うことができます。同様にsplice関数を利用して配列の追加も可能です。

let array = [1,3];
array.splice(1,0,2);
print(array);

spliceでは削除と追加ができるので、削除の場合はparam 2は0を指定しておきます。

  • splice()
    • param 1: 削除(追加)する要素位置
    • param 2: 削除する要素数(追加の場合は0にする)
    • param 3: 追加する要素(以後カンマ区切りで追加要素は複数していできる)

といった具合で利用できます。

JavaScriptの配列コピーは参照コピーになります。一般的にコピーと呼ばれるものは代入型をイメージしてしまいますが、 JavaScriptではデフォルトが参照コピーとなるため注意が必要です。

  let array = [1,2,3];
  let array_copy = array;
  print(array);
  print(array_copy);

例えば上記のコードでは array_copy にはarrayの値を参照しているだけです。なので、例えば

  let array = [1,2,3];
  let array_copy = array;
  array.pop();
  print(array);
  print(array_copy);

とした場合、arrayもarray_copyも2が削除された配列になっています。特段このような振る舞いでも問題ないことも多いですが、例えば array にはもともとのデータが入っていて、array_copy ではそのデータをソートしたり正規化したり、修正を加えたりする必要がある場合です。この場合は明確にarrayから参照コピー(参照渡し)ではなく、値コピー(値渡し)を指定する必要があります。具体的には下記のようなコードで可能です。

  let array = [1,2,3];
  let array_copy = array.concat();
  array_copy.pop();
  print(array);
  print(array_copy);

プログラムを記述していて配列操作でどうも挙動がおかしいなと思ったときはこの話を思い出してください。

さあ、もういっちょいきます。変数や配列が利用できれば比較的多くのことができますが、更にここでオブジェクトというものを学習しましょう。配列を扱っていくうちに、配列番号じゃなくて、アクセスしたい要素に対してそのまま名前をつけて値をみれるようになると便利だなと思う人がいるかと思います。例えば、human という変数があって、その中に身長や体重といった要素が含まれている場合、配列的に human[0]を体重、human[1]を身長 等とやっていると、その都度、0番目は体重だったな、1番目は身長だったな、なんてことを考える必要があります。そんなんだったら最初から、human.height, human.weightという形でアクセスしたいですよね。それを実現するのがオブジェクトという型です。

let human = {
    height:180,
    weight:80
  }
  print(human.height, human.weight);
}

上記のコードは先程の話をコードに置き換えたものです。配列では[]でくくっていたものをオブジェクト型では{}でくくります。ちなみに

  • (): 丸括弧(parentheses)
  • []: 角括弧(Brackets)
  • {}: 波括弧(Braces)
  • 「」:鉤括弧(日本語のみ)
  • 【】:隅付き括弧(日本語のみ)

の呼び名を覚えておきましょう。

  • 「」:鉤括弧

なお、上記コードでは、 human.heightとしてアクセスしていますが、もともとが配列の拡張的な位置づけなので、human['height']としてもアクセスができます。この記述のことを連想配列と読んでいます。

新しい要素例えば、誕生日のdateを追加してみましょう。追加は簡単です。human.date = “2000年1月1日”; 等とすれば追加されます。やってみましょう。

  let human = {
    height:180,
    weight:80
  }
  human.date = "2000年1月1日";
  print(human);

削除したい場合はdelete演算子というものを使います。human.weightだけを削除する場合は以下のコードです。

  let human = {
    height:180,
    weight:80
  }
  delete human.weight;
  print(human);

配列とことなりobjectの場合は伝統的な記述ではforループを作ることができません。そこでこれまでも出てきている拡張for文を用いることでobjectでもforループを作成することができます。

  let human = {
    height:180,
    weight:80
  }
  for( let item in human){
    print(item,human[item]);
  }

私が学生のころはjsにclassはなかったのですが、2015以降、正式採用となりました。大規模プログラムやインタラクティブプログラムだとclassなしでは書く気すらおきないおじさんにとっては嬉しい機能です。というわけでまずは基本から。

以下は、MyClass という簡単なサンプルです。クラスを生成した際に呼ばれるコンストラクタはC++のようにクラスと同名で指定するのではなく、constructorとして記述します。またコンストラクタに限らず、class内のメンバ関数(メソッド)は全て型宣言がありません。

class MyClass{
  constructor(){
  }
  sayHello(){
    console.log("Hello");
  }
}

class内で参照可能な変数のことをメンバ変数と呼びますが、jsの場合はこのメンバ変数には事前の宣言は特に必要ありません。利用したい場合はthis参照子をつけて、この変数はこのクラス内の変数ですよ、という定義をして医療することになります。具体的には以下のようにすることで、メンバ変数として利用できるようになります。C++的にはpublic扱いです。privateとかprotectみたいな使い方をするとなると、これまたちょっとset, get構文を用いることになります。

class MyClass{
  constructor(){
    this.str = "Hello";
  }
  sayHello(){
    console.log(this.str);
  }
}

もちろん

var my_class = new MyClass();
console(my_class.str);

としてもアクセスできますが、jsではset,getを利用してアクセスすることが推奨されています。エラー処理のことを考えての機能です。別にプロトタイプでは使わなくたっていいです。ただし、あ、このプログラム大きくなりそうだな、とか、このサンプル後で使いまわしそうだな、という直感が働いた場合は積極的にset, get構文をつかってメンバ変数を参照する癖を付けておくとよいでしょう。

class MyClass {
  constructor() {}
  set str_hello(str) {
    this._str_hello = str+" world";
  }
  get str_hello() {
    return this._str_hello + " !!";
  }
}

function setup() {
  var my_class = new MyClass();
  my_class.str_hello = "Hello";
  console.log(my_class.str_hello)
}

Reference

  • processing/p5js/08.jsの文法基礎.txt
  • 最終更新: 2020/08/30 13:19
  • by baba