2026/08/04(火)PHPとPerlでは配列変数を変数に代入してもシャローコピーされない

私は今までプログラミング言語の鉄則として、プリミティブを代入すれば値が、オブジェクトを代入すれば参照が渡され、オブジェクトの実体を渡す場合にはディープコピーが必要だと考えていたが、PHPでは違うことに気が付き、常識を打ち破られたので備忘録として残す。

調べている中でPerlでもPHPと同じ振る舞いをすることに気づいた。

言語別の比較

以下は言語別に配列変数を別の変数に代入し、代入側変数を操作したときに、元の変数に影響を与えるか、つまり参照代入になるか調べた結果だ。

❌通常代入=プリミティブと同じ、✅参照代入=いわゆるポインタ渡しされる、という意味合いで書いている。頭の絵文字は視認性のために付けているだけで、特に意味はない。

言語 PHP Perl Ruby Python JavaScript C#
配列 ❌通常代入 ❌通常代入 ✅参照代入 ✅参照代入 ✅参照代入 ✅参照代入
連想配列 ❌通常代入 ❌通常代入 ✅参照代入 ✅参照代入 ✅参照代入 ✅参照代入
クラス ✅参照代入 ✅参照代入 ✅参照代入 ✅参照代入 ✅参照代入 ✅参照代入

PHPは公式ドキュメントにもそう書いてあった。

配列への代入においては、常に値がコピーされることに注意してください。 配列をリファレンスでコピーする場合には、 リファレンス演算子を使う必要があります。

PHP: 配列 - Manual

そもそもPHPではオブジェクトと配列が別の概念として書かれているので、扱いが異なるのかもしれない。

各言語による処理結果

PHP

PHP 8.5.8で確認。

<?php
$arr1 = array('1', '2');
# ARR1 1, 2
echo "ARR1 {$arr1[0]}, {$arr1[1]}\n";

$arr2 = $arr1;
$arr2[0] = '3';
# ARR2 1, 2
echo "ARR2 {$arr1[0]}, {$arr1[1]}\n";
# ARR3 3, 2
echo "ARR3 {$arr2[0]}, {$arr2[1]}\n";


$hash1 = array(
    'hoge' => 'a',
    'fuga' => 'b'
);
# HASH1 a, b
echo "HASH1 {$hash1['hoge']}, {$hash1['fuga']}\n";

$hash2 = $hash1;
$hash2['hoge'] = 'z';
# HASH2 a, b
echo "HASH2 {$hash1['hoge']}, {$hash1['fuga']}\n";
# HASH3 z, b
echo "HASH3 {$hash2['hoge']}, {$hash2['fuga']}\n";


$cls1 = new stdClass();
$cls1->hoge = 'A';
$cls1->fuga = 'B';
# CLS1 A, B
echo "CLS1 {$cls1->hoge}, {$cls1->fuga}\n";

$cls2 = $cls1;
$cls2->hoge = 'Q';
# CLS2 Q, B
echo "CLS2 {$cls1->hoge}, {$cls1->fuga}\n";
# CLS3 Q, B
echo "CLS3 {$cls2->hoge}, {$cls2->fuga}\n";


# ARR1 1, 2
# ARR2 1, 2
# ARR3 3, 2
# HASH1 a, b
# HASH2 a, b
# HASH3 z, b
# CLS1 A, B
# CLS2 Q, B
# CLS3 Q, B

Perl

Perl v5.38.2で確認。

use strict;
use warnings;

my @arr1 = ('1', '2');
# ARR1 1, 2
print "ARR1 $arr1[0], $arr1[1]\n";

my @arr2 = @arr1;
$arr2[0] = '3';
# ARR2 1, 2
print "ARR2 $arr1[0], $arr1[1]\n";
# ARR3 3, 2
print "ARR3 $arr2[0], $arr2[1]\n";


my %hash1 = (
    'hoge' => 'a',
    'fuga' => 'b'
);
# HASH1 a, b
print "HASH1 $hash1{'hoge'}, $hash1{'fuga'}\n";

my %hash2 = %hash1;
$hash2{'hoge'} = 'z';
# HASH2 a, b
print "HASH2 $hash1{'hoge'}, $hash1{'fuga'}\n";
# HASH3 z, b
print "HASH3 $hash2{'hoge'}, $hash2{'fuga'}\n";


my $cls1 = bless {
    hoge => 'A',
    fuga => 'B'
}, 'StdClass';
$cls1->{'hoge'} = 'A';
$cls1->{'fuga'} = 'B';
# CLS1 A, B
print "CLS1 $cls1->{hoge}, $cls1->{fuga}\n";

my $cls2 = $cls1;
$cls2->{'hoge'} = 'Q';
# CLS2 Q, B
print "CLS2 $cls1->{hoge}, $cls1->{fuga}\n";
# CLS3 Q, B
print "CLS3 $cls2->{hoge}, $cls2->{fuga}\n";

# ARR1 1, 2
# ARR2 1, 2
# ARR3 3, 2
# HASH1 a, b
# HASH2 a, b
# HASH3 z, b
# CLS1 A, B
# CLS2 Q, B
# CLS3 Q, B

Ruby

Ruby 4.0.5で確認。

arr1 = ['1', '2']
# ARR1 1, 2
puts "ARR1 #{arr1[0]}, #{arr1[1]}"

arr2 = arr1
arr2[0] = '3'
# ARR2 3, 2
puts "ARR2 #{arr1[0]}, #{arr1[1]}"
# ARR3 3, 2
puts "ARR3 #{arr2[0]}, #{arr2[1]}"


hash1 = {
  'hoge' => 'a',
  'fuga' => 'b'
}
# HASH1 a, b
puts "HASH1 #{hash1['hoge']}, #{hash1['fuga']}"

hash2 = hash1
hash2['hoge'] = 'z'
# HASH2 z, b
puts "HASH2 #{hash1['hoge']}, #{hash1['fuga']}"
# HASH3 z, b
puts "HASH3 #{hash2['hoge']}, #{hash2['fuga']}"


class HogeCls
  attr_accessor :hoge, :fuga

  def initialize(_hoge, _fuga)
    @hoge = _hoge
    @fuga = _fuga
  end
end

cls1 = HogeCls.new('A', 'B')
# CLS1 A, B
puts "CLS1 #{cls1.hoge}, #{cls1.fuga}"

cls2 = cls1
cls2.hoge = 'Q'
# CLS2 Q, B
puts "CLS2 #{cls1.hoge}, #{cls1.fuga}"
# CLS3 Q, B
puts "CLS3 #{cls2.hoge}, #{cls2.fuga}"

# ARR1 1, 2
# ARR2 3, 2
# ARR3 3, 2
# HASH1 a, b
# HASH2 z, b
# HASH3 z, b
# CLS1 A, B
# CLS2 Q, B
# CLS3 Q, B

Python

Python 3.12.0で確認。

arr1 = ['1', '2']
# ARR1 1, 2
print(f"ARR1 {arr1[0]}, {arr1[1]}")

arr2 = arr1
arr2[0] = '3'
# ARR2 3, 2
print(f"ARR2 {arr1[0]}, {arr1[1]}")
# ARR3 3, 2
print(f"ARR3 {arr2[0]}, {arr2[1]}")


hash1 = {
    'hoge': 'a',
    'fuga': 'b'
}
# HASH1 a, b
print(f"HASH1 {hash1['hoge']}, {hash1['fuga']}")

hash2 = hash1
hash2['hoge'] = 'z'
# HASH2 z, b
print(f"HASH2 {hash1['hoge']}, {hash1['fuga']}")
# HASH3 z, b
print(f"HASH3 {hash2['hoge']}, {hash2['fuga']}")


class HogeCls:
    def __init__(self, _hoge, _fuga):
        self.hoge = _hoge
        self.fuga = _fuga

cls1 = HogeCls('A', 'B')
# CLS1 A, B
print(f"CLS1 {cls1.hoge}, {cls1.fuga}")

cls2 = cls1
cls2.hoge = 'Q'
# CLS2 Q, B
print(f"CLS2 {cls1.hoge}, {cls1.fuga}")
# CLS3 Q, B
print(f"CLS3 {cls2.hoge}, {cls2.fuga}")

# ARR1 1, 2
# ARR2 3, 2
# ARR3 3, 2
# HASH1 a, b
# HASH2 z, b
# HASH3 z, b
# CLS1 A, B
# CLS2 Q, B
# CLS3 Q, B

JavaScript

Node.js v24.16.0、Microsoft Edge 150.0.4078.105で確認。

const arr1 = ['1', '2'];
// ARR1 1, 2
console.log(`ARR1 ${arr1[0]}, ${arr1[1]}`);

const arr2 = arr1;
arr2[0] = '3';
// ARR2 3, 2
console.log(`ARR2 ${arr1[0]}, ${arr1[1]}`);
// ARR3 3, 2
console.log(`ARR3 ${arr2[0]}, ${arr2[1]}`);


const hash1 = {
    hoge: 'a',
    fuga: 'b'
};
// HASH1 a, b
console.log(`HASH1 ${hash1['hoge']}, ${hash1['fuga']}`);

const hash2 = hash1;
hash2['hoge'] = 'z';
// HASH2 z, b
console.log(`HASH2 ${hash1['hoge']}, ${hash1['fuga']}`);
// HASH3 z, b
console.log(`HASH3 ${hash2['hoge']}, ${hash2['fuga']}`);


class HogeCls {
  constructor(_hoge, _fuga) {
    this.hoge = _hoge
    this.fuga = _fuga
  }
}

const cls1 = new HogeCls('A', 'B');
// CLS1 A, B
console.log(`CLS1 ${cls1.hoge}, ${cls1.fuga}`);

const cls2 = cls1;
cls2.hoge = 'Q';
// CLS2 Q, B
console.log(`CLS2 ${cls1.hoge}, ${cls1.fuga}`);
// CLS3 Q, B
console.log(`CLS3 ${cls2.hoge}, ${cls2.fuga}`);

// ARR1 1, 2
// ARR2 3, 2
// ARR3 3, 2
// HASH1 a, b
// HASH2 z, b
// HASH3 z, b
// CLS1 A, B
// CLS2 Q, B
// CLS3 Q, B

C#

バージョンの基準が謎だが、Visual Studio 2022の標準的な環境で確認した。

var arr1 = new[] { "1", "2" };
// ARR1 1, 2
Console.WriteLine($"ARR1 {arr1[0]}, {arr1[1]}");

var arr2 = arr1;
arr2[0] = "3";
// ARR2 3, 2
Console.WriteLine($"ARR2 {arr1[0]}, {arr1[1]}");
// ARR3 3, 2
Console.WriteLine($"ARR3 {arr2[0]}, {arr2[1]}");


var hash1 = new Dictionary<string, string> {
    ["hoge"] = "a",
    ["fuga"] = "b"
};
// HASH1 a, b
Console.WriteLine($"HASH1 {hash1["hoge"]}, {hash1["fuga"]}");

var hash2 = hash1;
hash2["hoge"] = "z";
// HASH2 z, b
Console.WriteLine($"HASH2 {hash1["hoge"]}, {hash1["fuga"]}");
// HASH3 z, b
Console.WriteLine($"HASH3 {hash2["hoge"]}, {hash2["fuga"]}");


var cls1 = new HogeCls("A", "B");
// CLS1 A, B
Console.WriteLine($"CLS1 {cls1.Hoge}, {cls1.Fuga}");

var cls2 = cls1;
cls2.Hoge = "Q";
// CLS2 Q, B
Console.WriteLine($"CLS2 {cls1.Hoge}, {cls1.Fuga}");
// CLS3 Q, B
Console.WriteLine($"CLS3 {cls2.Hoge}, {cls2.Fuga}");

class HogeCls {

    public string Hoge { get; set; }
    public string Fuga { get; set; }

    public HogeCls(string hoge, string fuga) {
        this.Hoge = hoge;
        this.Fuga = fuga;
    }
}

// ARR1 1, 2
// ARR2 3, 2
// ARR3 3, 2
// HASH1 a, b
// HASH2 z, b
// HASH3 z, b
// CLS1 A, B
// CLS2 Q, B
// CLS3 Q, B

あとがき

PHPのクラスなんて連想配列の派生形だと思ってたら、PHPではクラスこそがオブジェクトということで大変驚いた。一般論としては文字列・数値・真偽型がプリミティブ、それ以外の配列や連想配列(ハッシュ)・クラス・構造体はオブジェクトとして考えていただけに強く常識を打ち破られた気持ちだ。

とはいえ、配列は構造体ではないはずなので、構造体から派生するクラスと配列が別になるという思想そのものもわからなくはない。わからなくはないけど気持ち悪い。

PHPも進化しているので将来的には見直されるかもしれないし、されないかもしれない。後方互換性をしょっちゅう切り捨ててる印象があるので、何があっても不思議がないのも、またPHPだと思う。そしてPerlは恐らく変わらないだろう。

しかしいわゆるLL、軽量プログラミング言語と呼ばれているPerl、PHP、Ruby、Pythonのうち、RubyとPythonは違う振る舞いを示したのも興味深かった。一体何があったのか…。

2026/03/05(木)HTMLの標準機能でカラーパレットを使ったカラーコード入力UIを作る方法

投稿日:

なんかこんな感じのやつの作り方。

内容

左側にあるカラーパレットを出すボタンはinput type="color"で実現できるので、単純なカラー入力だけならこれでも構わない。現時点ではAndroid版Firefoxを除き、主要ブラウザは

ただ、コードも欲しいことが往々だと思うので、その場合は横にinput type="text"を生やして、次に示すサンプルコードのように双方の入力値をバインディングしてやればよい。

サンプルコード

動きはデモページで確認できる。

<input id="palette" type="color">
<input id="code" type="text">
<script>
  const getInput = () => {
    return {
      palette: document.getElementById('palette'),
      code: document.getElementById('code')
    };
  };

  const registerHandlers = (paletteEl, codeEl) => {
    paletteEl.addEventListener('input', (e) => {
      codeEl.value = e.target.value;
    });
    codeEl.addEventListener('input', (e) => {
      paletteEl.value = e.target.value;
    });
  };

  window.addEventListener('DOMContentLoaded', () => {
    const input = getInput();
    registerHandlers(input.palette, input.code);
  });
</script>

Safariでも表示できるが見た目がだいぶ異なる

PC Safari 18.2とiOS 26.3のSafariで確認した見た目。

一行目が初期表示、二行目がカラーパレット展開表示。iOS Safariの方は素の状態だとUIが小さすぎて見えないので、かなり拡大して表示している。

PC Safari iOS Safari
PC Safari初期表示
iOS Safari初期表示
PC Safariカラーパレット展開表示
iOS Safariカラーパレット展開表示

2025/07/28(月)久々にクラスを書いたので、設計時の考えを書いてみる

投稿日:

自動生成されたHTMLから社員情報を引っこ抜くスクリプトを作った時に考えたことを書き出してみる。ブラウザのDevToolsを使った簡易ツールを書いていて、頭に浮かんだことだ。データやコードは架空の内容なので実在しない。

ベースのHTML

大まかに最初に目に入ってきたのはこんな内容だった。機械生成されているせいかフォーマットがガタガタ気味だ。

<span class="Content">
    <span id="undefined">SHAIN.XLS page1</span>
</span>
<span class="Content">
    <br>
    <span id="undefined">社員リスト</span>
    <span id="undefined"></span>
</span>
<span class="Content">
    <br>
    <span id="hoge1">00001</span>
    <span id="fuga1">:山菱喜一</span>
</span>
<span class="Content">
    <br>
    <span id="hoge2">00002</span>
    <span id="fuga2">:鈴木与一</span>
</span>
<span class="Content">
    <br>
    <span id="hoge3">00011</span>
    <span id="fuga3">:茨城妙子</span>
</span>
<span class="Content">
    <span id="undefined">SHAIN.XLS</span>
    <span id="undefined"> page2</span>
</span>
<span class="Content">
    <br>
    <span id="hoge4">00012</span>
    <span id="fuga4">:奈良楓</span>
</span>
...

まずは関数ベースで考える

ひとまず普段は関数ベースで実装しているので関数ベースで実装する。書いていてgetEmployee(name, list)の仮引数にあるlistは冗長ではないかということに気づく。

const emp_list = document.getElementsByClassName('Content');
[...emp_list].reduce((acc, cur) => {
  if (cur.id === 'undefined' || cur.children.length !== 3) {
    return acc;
  } else {
    const id = cur.children[1].innerText;
    const name = cur.children[2].innerText.replace(/^:/, '');
    if (/^\d+$/.test(id)) {
      acc.push({ id, name });
    }

    return acc;
  }
}, []);

const getEmployee = (name, list) => {
  const re = new RegExp(`.*${name}.*`);
  const result = list.filter((emp) => re.test(emp));
  console.log(result);
};

クラスにしてみる

そしてクラスにしたほうが素直にならないかと考え、クラスにしてみた。getEmployee(name, list)search(name)となり、冗長性が減ったほか、抽象的な表現で分かりやすくなった気がする。

class EmployeeSearch {
  constructor() {
    const emp_list = document.getElementsByClassName('Content');
    this.list = [...emp_list].reduce((acc, cur) => {
      if (cur.id === 'undefined' || cur.children.length !== 3) {
        return acc;
      } else {
        const id = cur.children[1].innerText;
        const name = cur.children[2].innerText.replace(/^:/, '');
        if (/^\d+$/.test(id)) {
          acc.push({ id, name });
        }

        return acc;
      }
    }, []);
  }

  search(name) {
    const re = new RegExp(`.*${name}.*`);
    const result = this.list.filter((emp) => re.test(emp.name));
    console.log(result);
  }
}

追加仕様に合わせ関数を新設し、クラスを改良する

動作確認をしていく中で表示と名前が分かれたパターンを発見したので、これに合わせて直してゆく。

<span class="Content">
    <br>
    <span id="hoge2">00002</span>
    <span id="fuga2">:飯田</span>
    <span id="piyo2">太郎</span>
</span>

この対応をしようとすると分岐が必要だと考えたが、愚直にやってしまうとコンストラクタの中がごちゃつく上、テストもしづらいと考え、DOMをパースする処理を外部関数に切り出し、それを呼ぶことにした。

const getEmployeeFromList = (domList) => {
  const id = domList[1].innerText;
  const name1 = domList[2].innerText.replace(/^:/, '');
  if (domList.length === 3) {
    return {
      id,
      name: name1
    }
  } else {
    const name2 = domList[3].innerText;
    return {
      id,
      name: `${name1}${name2}`
    }
  }
};

class EmployeeSearch {
  constructor() {
    const emp_list = document.getElementsByClassName('Content');
    this.list = [...emp_list].reduce((acc, cur) => {
      if (cur.id === 'undefined' || cur.children.length < 3) {
        return acc;
      } else {
        const { id, name } = getEmployeeFromList(cur.children);
        if (/^\d+$/.test(id)) {
          acc.push({ id, name });
        }

        return acc;
      }
    }, []);
  }


  search(name) {
    const re = new RegExp(`.*${name}.*`);
    const result = this.list.filter((emp) => re.test(emp.name));
    console.log(result);
  }
}

更により良くするには?

ブラウザのDevToolsにあるConsoleで書いたコードなので、現実問題テストコードも書かないし、そこまで凝る必要もないのが、よりよい設計にするにはコンストラクタでパースせず、事前にパース処理した結果をコンストラクタを通じて注入して処理するようにするとよいだろう。そうすると責務が分離され、テストがしやすく、コードもきれいになると考える。

クラスにするか、関数にするか

これはまだ正直答えが見えていないが、インスタンスを引き回す場合はクラスのほうが都合がいい可能性がある気がしてきている。

関数でもできるにはできるが、毎回引数にインスタンス相当のものを渡すのは冗長である可能性がある。疎結合という文脈では関数のほうがより良い可能性もあるが、人間の認知負荷や、保守性との兼ね合いだと思う。

明確な損益分岐点はまだ見えていないし、今回のケースだと割とどっちでもいい部分もあるとは思う。

2024/11/13(水)imgタグで画像が取得できなかった時に代替画像を表示する

投稿日:

事前にバイナリをHTTPクライアントで取得してハンドリングするのかと思っていたが、実際にはimgタグのonerrorイベントでハンドリングできた。

確認環境

Microsoft Edge 130.0.2849.56

サンプルコード

<html>
<head>
  <script>
    function handleError(ev) {
      ev.target.src="https://example.com/empty.gif";
    }
  </script>
</head>
<body>
  <img src="https://example.jp/hoge.jpg" onerror="handleError(event)" />
</body>
</html>

最初に考えていたコード

これでも同じようなことはできるにはできるが、CORSやlazy loadとか考えると面倒で、コードも増えるし、採用するメリットはあんまりないと思う。

<html>
<head>
  <script>
    window.onload = () => {
      const img = document.getElementById('img');

      fetch('https://example.jp/hoge.jpg')
        .then((res) => {
          if (res.status === 200) {
              res.blob().then((data) => {
                img.src = URL.createObjectURL(data);
              }).catch(() => {
                img.src = 'https://example.com/empty.gif';
              });
            } else {
              img.src = 'https://example.com/empty.gif';
            }
        }).catch(() => {
          img.src = 'https://example.com/empty.gif';
        });
    }
  </script>
</head>
<body>
  <img id="img" />
</body>
</html>

2024/07/16(火)バックグラウンドのタブでsetIntervalが動かない問題を解消する

ブラウザのタブがバックグラウンド状態になっているとJSの実行が止まることがあり、そうするとsetInterval()が死ぬので、これを回避する技。

結論としてはWeb Worker APIのWorkerインターフェースを使うことで解決できる。

確認環境

Env ver
Microsoft Edge 126.0.2592.87

サンプルコード

/**
 * @param {() => void} cb 実行するコールバック
 * @param {number} interval 実行間隔
 * @returns 停止用の関数
 * */
const createWorkerInterval = (cb, interval) => {
  const src = "self.addEventListener('message', (msg) => { setInterval(() => self.postMessage(null), msg.data) })";
  const wk = new Worker(`data:text/javascript;base64,${btoa(src)}`);

  wk.onmessage = () => cb();
  wk.postMessage(interval);

  return wk.terminate;
}