JavaScript の覚え書き

初版作成  2018.1.27
最終更新  2024.07.08


◆◆ そもそも何のためにあるのか? ◆◆


html ファイルの内容を動的に書き換えるためにある。

◆◆ スクリプトの埋めこみ方 ◆◆


下記のように、スクリプトを書く。
body の中に書いてもよい。

<html>
<head>
<title>タイトル</title>
<script>
ここにスクリプトを書く。
</script>
</head>
<body>

</body>
</html>

◆◆ デバッグ ◆◆


通常状態では、ブラウザにエラーは表示されない。

デバッグモードにする必要がある。
Chrome が使いやすい。
F12 を押すとデバッグ領域が表示される。
console タブにエラーが表示される。

Excel VBA のイミディエイトウィンドウのように、

console.log("a = %d",a)

で console に出力することができる。

alert("文字列")  でメッセージボックスを作成して出力する
方法もある。


◆◆ 最初に覚えるべきこと ◆◆


・関数名と変数名は同じ名前を使えない!!!

これでハマることが多いので注意!

・大文字小文字の区別がある

innerHTML を間違えて innerHtml と書いてしまうと、
何も起こらない。


◆◆ 変数の宣言 ◆◆


「古い JavaScript」は変数宣言不要。
プログラム先頭に

'use strict';

と書くと、「新しい JavaScript」を実行するモードになり、
変数宣言が必須となる。

    -----------

整数と実数の区別はない。

変数に型はないが、Excel のセルように、中に入っている
データは型を持つ。例えば「数値」「文字列」「オブジェクト」

a = 123;
b = "123";
document.write("型は " +(typeof a));

id はグローバル変数と同じらしい。
id と同一の変数名を使用すると、プログラムは実行可能なので、
バグの原因となる。

ハイフンは通常の変数には使えないが、id には使えるので、

message1 = document.getElementById("id-message1");

のように、id にはハイフンを入れた文字列を使うと良いと思われる。

◆◆ 変数のスコープ ◆◆


・トップレベルで宣言した変数はグローバル
・関数内で宣言した変数は関数内ローカル

変数宣言には var, let, const の 3 種類がある。

・let は関数ローカルな変数
・const は再代入不可
・var は古い仕様なので、使う必要はない。

var と let は少しスコープが異なる。var はどこで宣言しても
その関数内ローカル。let は if や for のブロック内で宣言
したとき、そのブロック内ローカル

● C の static

関数の中に関数を書くことができる。

function parent(){
    let a = 1;
    child();
    alert(a);     // a は 2 になっている

    function child(){
        a = 2;
    }
}

以下のように書くと
変数 a は関数 parent, child1, child2 のみで使える。
parent は宣言だけにしておくと、C の static のような
感じで使える。

function parent(){
  let a;
  function child1(){

  }
  function child2(){

  }
}


◆◆ 発見しづらいバグ ◆◆


プロパティを間違えてもエラーは起こらない。
例えば

obj.style.backgroundColor = "red" を
obj.style.backgroundColo  = "red"

と間違えると何も起こらない。
デバッガにもエラーが表示されないので、
この手の誤りは発見しづらい。


◆◆ 基本構文 ◆◆


if, for, while は C と全く同じ
ループからの脱出も C と同じく break, continue

ラベル付きの break により多重ループから一気に脱出可能。
ラベルの位置に注意!
脱出するループの for 文の手前に書かないとエラーになる

label1:
for(i = 0; i < 4; i++){
    for(j = 0; j < 4; j++){
        document.write("i j = " + i + " " + j + "  ");
        if ( i == 2 && j == 1 ) {
            break label1;
        }
    }
}


◆◆ データの型 ◆◆


文字型は "abc"  'abc'  どちらも可

数値型と文字列型の変換

num = 123;
str = String(num);
str = num.toString();

str = "123";
num = Number(str);    // str に数値以外の文字が含まれていると NaN
num = parseInt(str);  // 整数化 数値以外の文字は無視
num = parseFloat(str);// 数値以外の文字は無視

◆◆ 定数 ◆◆


a = 0x1f     // 16進
b = 1.2e-4   // 1.2 × 10^(-4)


◆◆ コメント ◆◆


C++ と同じ
//  コメント
/*  コメント */

◆◆ print ◆◆


i = 10;
x = 3.4;

print "i = " + i + "   x = " + x;

JavaScript には C の printf に対応する書式付きの
print 機能はない。自分で実装する必要がある。

github に sprintf.js というプログラムがある。

https://github.com/alexei/sprintf.js

◆◆ 関数 ◆◆


function rect_area(height, width){
    let area;               // 関数内だけで使う変数はローカルにする
    area = height * width;
    return area;
}

a = rect_area(2,5);

返り値なしの return 文を使うこともできる
 (関数の途中で実行を打ち切る場合)。


◆◆ ページが読み込まれた後、実行する処理の書き方 ◆◆


(1) window.onload を使う方法

<script>
window.onload = init; // html ファイル読込終了直後に実行   init() ではエラー

function init(){
   初期化処理
}
</script>

head に書くのと body に書くので、微妙に違うようだが、
よく分からない。

(2) イベント DOMContentLoaded を使う方法

document.addEventListener("DOMContentLoaded", init);

function init() {
    初期化処理
}

無名関数を使う方法

document.addEventListener("DOMContentLoaded", function() {
    初期化処理
});


◆◆ 基本的な使い方 ◆◆


< グローバル領域 >
let p1;

< init() の中 >
p1 = document.getElementById("id-p1");
p1.innerHTML = "moji";

< body の中 >
<p id="id-p1">ここを書き換える</p>
<input type="button" value="ボタン上の文字" onclick="start()">
<input type="text" value="デフォルトの文字" onclick="moji()">

操作する要素は <p> 以外に <span> <div> <canvas> などが考えられる。

◆◆ innerHTML と textContent ◆◆


p1 = document.getElementById("id-p1");
p1.innerHTML = "<b>moji<b>";
p1.textContent = "<b>moji<b>";

innerHTML は moji が太字で表示される。
textContent は <b>moji<b> と表示される。


◆◆ 文字型オブジェクト ◆◆


str = new String("文字です");
document.write("<p>「" + str + "」の長さは " + str.length + " です。");

◆◆ オブジェクト ◆◆


obj1 = new Object();
obj1.name = "yabu";
obj1.age = "45";

. の後にプロパティを付け加えることができる。

obj2 = obj1;
obj2.name = "mitukuri";

代入ではなく参照なので obj1.name の結果は "mitukuri" になっている。

以下の 2 つは等価と思われる

(1)
let pt = new Object();
pt.x = 10;
pt.y = 20;

(2)
let pt;
pt = {"x": 10, "y" : 20};

(2) は関数の返り値として以下のように用いることができる。

return {"x": 10, "y" : 20};

ret = func(  );  のとき
ret.x, ret.y が得られる。

◆◆ 配列 ◆◆


添字が整数の配列

a = new Array(3);   3 を省略してもよい

a[0] = "a";
a[1] = "b";

のように宣言なしで書いてもよい。

a[] = "c";

とすると自動的に a[2] に割り当てられる。
いきなり

a[3] = "c";

とすると、a[3] のみが生成される。次に

a[] = "d";

とすると a[4] に割り当てられる。

b = new Array("yabu", "mitukuri", "sera");
c = ["yabu", "mitukuri", "sera"];

連想配列

name = new Array();
name["yabu"] = "tetsuro";
name["mitukuri"] = "kazuhiro";
name["sera"] = "keita";

◆◆ 2 次元配列 ◆◆


◆◆ 入力要素 ◆◆


・ボタン

<input type="button" value="座標位置更新" onclick="new_points()">

・文字 数値

<input type="number" id="id-num1" value="10">
<input type="text" id="id-text1" value="abc">

num1 = document.getElementById("id-num1");
num = parseFloat(num1.value);

(注意!!)
type="number" でも num1.value は文字列である。
parseInt(), parseFloat() で数値化を怠ると、
ひどいバグの原因となる。

  a = "5";
  b = a * 10;
  c = a + 10;

のとき、b は数値 50, c は文字列 "510" となる。

JavaScript では文字列と数値を演算すると、
掛け算は「文字列が数値に自動型変換」
足し算は「数値が文字列に自動型変換」という
非常にいやらしいことが起こる。
エラーにしてほしい。

text1 = document.getElementById("id-text1");
text = text1.value;

・スライドバー

<input type="range" id="id-slidebar" min="0" max="1" step="0.01" value="0">

slidebar1 = document.getElementById("id-slidebar");
slidebar1.addEventListener('change', move_slide);
slidebar1.value = 0;

・ラジオボタン

<form id="id-radio1">
<input type="radio" name="name1" value="yes"> yes
<input type="radio" name="name1" value="no" checked> no
</form>

radio1 = document.getElementById("id-radio1");
list = radio1.name1;
for(i = 0; i < list.length; i++){
    if ( list[i].checked  == true ){
        radio_value = list[i].value;
        break;
    }
}

以下のように書く方法が簡潔なのだが、
そのように書くと、スライドバーと併用したときに
スライドバーを動かすと、ラジオボタンのチェック場所が
なぜか移動するという妙な現象が発生した。
よくわからない。

radio_value = list.value;


◆◆ html 要素の動的な変更 サンプルプログラム ◆◆


ボタンを押すと、動的に要素が変化する。

<html>
<head>
<title>JavaScript の練習</title>
<script>
let p1, canvas1, dc;

window.onload = init;

function init(){
    p1 = document.getElementById("id-p1");
    canvas1 = document.getElementById("id-canvas1");
    dc = canvas1.getContext('2d');
}

function b1_click(){
    p1.innerHTML = "かきくけこ";
}

function b2_click(){
    canvas1.style.backgroundColor = "rgb(0,255,255)";
    dc.beginPath()
    dc.rect(50,10,100,150);
    dc.fillStyle = "rgb(255,255,0)";
    dc.fill();
}
</script>
</head>
<body>
<p>JavaScriptの練習です。</p>
<canvas id="id-canvas1" width="300px" height="300px" style="background-color: beige"></canvas>
<p>
<input type="button" value="ボタン1" onclick="b1_click()">
<input type="button" value="ボタン2" onclick="b2_click()">
</p>
<p id="id-p1">表示エリア1</p>
</body>
</html>

◆◆ name を使う方法 ◆◆


要素を識別するのに id ではなく name を使う方法もある。
name を使うと、複数の要素に同じ name をつけることができ、
getElementsByName() を使うと、配列として得られるらしい。

ただし、<div> には name 属性がないなど、ややこしいので、
name はいざ使うときに調べることにする。


◆◆ イベント関数の定義法 ◆◆


(1) タグ内に直接書き込む方法

<span onclick="func(arg)">

引数が無い場合は func() と書く。func はダメ。

複数の要素が同じ関数を呼び、呼び元によって
異なった値を関数の引数に加えたいとき、
(1) の形式が良いと思われる。

(2) を使う場合、引数は固定 (event) なので、
要素ごとに別の関数を定義し、
その関数が共通関数を呼ぶ必要がある。

(2) id を使う方法

<span id="span1">この領域</span>

obj = document.getElementById("span1");

この方法で呼ばれる関数の引数は固定でイベントの引数が入る。
ユーザーが定義する引数を使うことはできない。

(2a) 無名関数を使う方法

obj.onclick = func1(e){
   処理内容
}

(2b) 関数を別の場所で定義する

obj.onclick = func2;

func2(e){

}


(2c) イベントリスナを定義する(この方法が最も分かりやすい)

obj.addEventListener("click"    ,click);
obj.addEventListener("mousemove",move);

window.addEventListener("keydown",key); // window はデフォルトで存在する
                                        // keydown は window しか受けられない
function click(e){
    let x = e.offsetX;
    let y = e.offsetY;
}

function move(e){
    let x = e.offsetX;
    let y = e.offsetY;
}

function key(e){
    let code = e.keyCode;
}


◆◆ イベントの種類 ◆◆


<input type="text" onkeyup="func(arg)" id="text1">
<div onclick="func(arg)" style="background:beige;" id="div1">

obj = document.getElementById("text1");
obj.onmouseover = func1;
obj.onmouseout = func2;
obj.value = "str"          // テキストボックスの内容

obj = document.getElementById("div1");
obj.style.backgroundColor =
obj.style.left =            // position:absolute が必要
obj.style.top =
obj.style.display = "block" or "none"
obj.innerHTML = "moji";    // <div> <p> <span> など

タグの中に書く時は on が付く。
関数の定義のときもイベントは on がつく。


◆◆ オートコンプリート ◆◆


デフォルトで on になっているので、ヒント機能を
テキストボックスに付加するとき

<input type="text" id="myid" name="myname" size="5" autocomplete="off">


◆◆ canvas の使い方 ◆◆


作成
<canvas id="canvas1" width="300px" height="300px"></canvas>

オブジェクトとデバイスコンテキストの取得
canvas = document.getElementById("canvas1");
d = canvas.getContext('2d');

線を描く
d.beginPath();
d.moveTo(x,y);
d.lineTo(x2,y2);
d.lineTo(x3,y3);
d.lineWidth = 5;
d.strokeStyle = "rgb(255,0,0)";
d.stroke();

長方形
d.beginPath();
d.rect(x, y, xlen, ylen);
d.fillStyle = "rgb(255,255,0)";    "#ff00aa" も可
d.fill();
d.lineWidth = 1;
d.strokeStyle = "rgb(0,0,0)";
d.stroke();

円(扇形)
d.beginPath();
d.arc(x,y,radius,start_angle,end_angle,true);
d.fillStyle = "rgb(255,255,0)";
d.fill()
d.lineWidth = 10
d.strokeStyle = "rgb(0,255,0)";
d.stroke();

背景色で塗りつぶす
d.clearRect(0,0,300,300);

文字
d.font = '14pt Arial';
d.fillText("abc", x, y)

キャンバス上のマウスの位置を取得

canvas.addEventListener("click"    ,click);
canvas.addEventListener("mousemove",move);

function click(e){
    let x = e.offsetX;
    let y = e.offsetY;
}

function move(e){
    let x = e.offsetX;
    let y = e.offsetY;
}

キャンバスの左上位置の取得(マウスの相対位置を知るのに使う)
let canvas_location = canvas.getBoundingClientRect();

function mouse_move(e){
    let cx = e.clientX - canvas_location.left;  // マウスの相対位置 x
    let cy = e.clientY - canvas_location.top;   // マウスの相対位置 y
}

◆◆ 再読み込み ◆◆


location.reload();


◆◆ 指定された url へジャンプ ◆◆


location.href=(url)

url を http:// から始めない場合は、
現在の位置からの相対位置

◆◆ sleep ◆◆


C 言語の sleep() に相当する機能や Arduino 言語の delay() に
相当する機能は以下のように実現する。

// 関数 sleep を定義
const sleep = ms => new Promise(res => setTimeout(res, ms));

async function yobidasi_moto(){  // 呼び出し元の関数は async をつける
  await sleep(1000);             // sleep には await をつける。
}

◆◆ タイマー関係 ◆◆


setInterval と setTimeout の 2 つの方法がある。

timer_id = setInterval(func1,5000);  // func1 を 5000 ms ごとに実行
clearInterval(timer_id);             // それを取り消す

timer_id = setTimeout(func2,5000);   // func2 を 5000 ms 後に実行
clearTimeout(timer_id);              // それを取り消す


◆◆ Web サーバと通信をするサンプルプログラム(非同期) ◆◆


このサンプルでは web サーバは Raspbery Pi
GPIO を使ってハードウェアにアクセスして、
その返答を返してくる。

---------------- html ファイル -----------------

------------ head ---------

<style>
body {
  font-size: xx-large;
  margin: 20px;
  background-color: #ffffd0;
}
p {
  line-height: 2.0;
}
p.button {
  padding: 0em 0em 1.3em 0em; /* 上 右 下 左*/
}
span.blue {
  background-color: #a0e0ff;
  padding: 1em 1.5em;           /* 上下 左右 */
}
</style>

----------- body ------------

<p>
<span id="button1" class="blue" onclick="ondo_sokutei()">温度</span>
</p>
<div style="background-color: beige;">
<p id="ondo_disp">
温度 =
</p>
</div>
<script type="text/javascript">
let button1, ondo_disp;
let xhr, text;

button1     = document.getElementById("button1");
ondo_disp   = document.getElementById("ondo_disp");

function ondo_sokutei(){
    xhr = new XMLHttpRequest();
    const file = "cgi-bin/ondo.py";
    xhr.open("GET", file, true);
    xhr.onreadystatechnge = updatePage;
    xhr.send(null);
}

function updatePage(){
    if ( xhr.status == 200 ){
        text = xhr.responseText;
    } else {
        text = "status = " + xhr.status;
    }
    ondo_disp.innerHTML = text;
}
</script>

----------------- cgi-bin/ondo.py ----------
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
#   apache2:       www-data に visudo で権限を与えておくこと
#

import sys
import os
import cgi
import commands
import serial

#-------------- 温度測定 -------------
#
# root の権限が必要なため ondo_sub.py を使う

cmd = "sudo ./ondo_sub.py"
ret = commands.getoutput(cmd)

#-------------- 結果の送信 ------------

# 最初の 2 行を忘れると apache2 がエラーを出す

print "Content-type: text/html"
print ""
print "温度 = " + ret + " 度"

------------ cgi-bin/ondo_sub.py ------------
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# シリアル端子の向こう側に Arduino が接続されており、
# Arduino に LM35 が接続されている。
# Arduino から 1 行返答が来る。
# その返答を受けて 1 行標準出力に出力する。
#
# GPIO を制御するプログラムは root の権限が必要なので、
# cgi (apache2 の場合 www-data の権限で動作) から sudo で
# 本プログラムを呼び出す
#
import serial

vcc = 4.95

rs = serial.Serial('/dev/ttyAMA0', 9600, timeout=10)

rs.write("t")
line = rs.readline()  # 行末は 13 10
rs.close()

line = line.rstrip()
val = int(line)
ondo = val * ( vcc * 1000 / 1024.0 ) / 10

ondo_str = "%5.1f" % (ondo)

print ondo_str

-----------------------------------------------

arduino のプログラムは
これ


◆◆ 日付と時刻の取得 ◆◆


d = new Date();
year    = d.getFullYear();
month   = d.getMonth() + 1;
day     = d.getDate();
hours   = d.getHours();
minutes = d.getMinutes();
seconds = d.getSeconds();


◆◆ 即時関数 ◆◆


以下のように () で囲むと即座に実行される関数となる。
閉じ括弧の後に、括弧で囲って引数を書く。

(function (arg, arg2){
    console.log("引数は " + arg + " と " + arg2 + "です");
})("あい", "abc");

引数がないときは以下のように空の括弧を書く。

(function (){
    console.log("最初に実行する");
})();

ページがロードされたときに実行する初期化用
関数は以下のように書くのが分かりやすいと思う。

window.onload = init();
function init(){

}

以下のように無名関数の即時関数として書いている
JavaScript を見たことがある。

(function (){
  let global_var;

  func1(){

  }

  func2(){

  }
})();