変奏現実

パソコンやMMORPGのことなどを思いつくまま・・・記載されている会社名・製品名・システム名などは、各社の商標、または登録商標です。

この画面は、簡易表示です

2024年2月9日

[javascript]サンプルを実行

jsファイルにコードを書かないと簡単なサンプルも実行できない。

勿論、そんなものはオンラインにあるけど、そういうところにソースをパリッと貼り付けるのはご法度なケースも多いので・・・

パパッとjavascriptのサンプルをローカルなHTMLで実行するサンプルを作ってみた。

但し、new Functionからはグローバルな変数が見えてしまう。

それだけなら特に危なくないけど

某MS操縦士のセリフ:俺を踏み台にしたぁ~~~

なんてコードを書かれても不思議ではない碌でもない世の中なので、

ソースだけ公開。

<DOCTYPE html>
  <html lang="ja">

  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>javascriptを実行させるサンプル</title>
    <link href="jstest.css" media="all" rel="stylesheet" type="text/css" />
    <script src="createelement.js"></script>
    <script src="download.js"></script>
    <script src="jstest.js"></script>
    <link href="loader.css" media="all" rel="stylesheet" type="text/css" />
    <script src="loader.js"></script>
  </head>

  <body>
    <!-- loader begin -->
    <div id="js-loader" class="loader">
      <p>***</p>
      <p id="js-loader-text"></p>
      <p>***</p>
    </div>
    <!-- loader end -->
    <textarea class="jsText" id="jsText1" scrolling="auto">
// sample code
return new Promise((resolve, reject) => {
  let result = new Array(10);
  for (let index = 0; index < 10; index++) {
    result[index]=index + 1;
  }
  console.log(result); console.log(10);
  console.log('aaaaaaaaaaaaaa');
  console.log({log: 'data=> result=data' });
  resolve();
});
</textarea><br />
    <button type="button" id="executeJsText">実行</button><br />
    <div class="status" id="status"></div><br />
    <textarea class="jsResult" id="jsResult1" scrolling="auto"></textarea><br />
    <button type="button" id="downloadJsText">javaScriptダウンロード</button><br />
    <button type="button" id="downloadResult">結果ダウンロード</button><br />
  </body>

  </html>
@charset "UTF-8";

textarea.jsText {
    width: 95%;
    /* 親要素の幅と合わせる */
    height: 200px;
    resize: both;
    border: 2mm ridge rgba(211, 220, 50, .6);
}

textarea.jsResult {
    width: 95%;
    /* 親要素の幅と合わせる */
    height: 100px;
    resize: both;
    border: 2mm ridge rgba(211, 220, 50, .6);
}
let CRLF = '\r\n';
window.addEventListener('load', () => init());
/**
 * 初期化処理
 */
const init = () => {
    // ボタンクリックイベントの登録
    document.querySelector('#downloadJsText').addEventListener('click', () => downloadText('jsText1'));
    document.querySelector('#downloadResult').addEventListener('click', () => downloadText('jsResult1'));
    document.querySelector('#executeJsText').addEventListener('click', () => dexecuteJsText('jsText1', 'jsResult1'));
    loaderClose();
};
/**
 * サンプルをエンコーディングしたファイルをダウンロードする
 */
const downloadText = async (target) => {
    try {
        loaderOpen();
        try {
            let dateBegin = Date.now();
            displayStatus(`${target}ダウンロード処理中`);
            let jsText = document.querySelector(`#${target}`).value;
            download(jsText, `${target}.js`, "text/plan");
            displayStatus(`${target}ダウンロード完了。処理時間:${dateDateBetween(dateBegin, Date.now())}`);
        } catch (ex) {
            alert(`${target}ダウンロード異常終了${CRLF}${ex}`);
        }
        loaderClose();
    } catch (ex) {
        alert(ex);
    }
};
/**
 * 実行する
 * @param {*} jsText 
 * @param {*} jsResult 
 */
const dexecuteJsText = (jsText, jsResult) => {
    let jsTextElmText = document.querySelector(`#${jsText}`).value;
    let jsResultElm = document.querySelector(`#${jsResult} `);
    let dateBegin = Date.now();
    try {
        loaderOpen();
        try {
            displayStatus(`${jsText} 実行処理中`);
            let func = new Function('console', jsTextElmText);
            let result = [];
            let radix = 10;
            let rc = func({
                log: (data) => {
                    if (data === undefined) {
                        data = 'undefined';
                    }
                    if (data === null) {
                        data = 'null';
                    }
                    switch (typeof data) {
                        case 'object':
                            data = JSON.stringify(data);
                            break;
                        case 'string':
                            break;
                        default:
                            data = data.toString(radix);
                            break;
                    }
                    result.push(data);
                }
            });
            new Promise((resolve, reject) => {
                if (rc instanceof Promise) {
                    displayStatus(`${jsText} 実行中 Promise.then待ち`);
                    rc.then(() => {
                        resolve(result);
                    });
                } else {
                    resolve(result);
                }
            }).then((result) => {
                jsResultElm.innerHTML = result.join(CRLF);
                displayStatus(`${jsText} 実行完了。処理時間:${dateDateBetween(dateBegin, Date.now())} `);
            });
        } catch (ex) {
            displayStatus(`${jsText} 実行異常終了。処理時間:${dateDateBetween(dateBegin, Date.now())}${CRLF}${ex} `);
        }
        loaderClose();
    } catch (ex) {
        alert(ex);
    }
};
/**
 * ステータスを表示する
 * @param {string} text 
 */
const displayStatus = (text) => {
    let divStatus = document.querySelector('#status');
    divStatus.innerHTML = text;
    let divLoading = document.querySelector('#js-loader-text');
    divLoading.innerHTML = text;
};
/**
 * 時刻差のテキストを作成する
 * @param {Date} dateBegin 
 * @param {Date} dateEnd 
 * @returns 
 */
const dateDateBetween = (dateBegin, dateEnd) => {
    return `${(dateEnd - dateBegin) / 1000} 秒`
};

createelement.js
download.js
loader.css
loader.js

は先の記事

デスクトップに貼ったJSからSJISのCSVをダウンロードさせてみる【その後】サンプル

と同じなので割愛。

ミソはjsソース中に

console.log(result);

と書かれたらどうやって取り出すか?

である。

今回は

let result = "";
let func = new Function( 'console', jsテキスト);☚jsテキストの処理に対してパラメータ'console'を宣言
func({log: (data) => ☚{...}内がconsoleオブジェクトとして扱われ、result変数にセットされる。

これでjsテキストの中でconsole.logメソッドがそれっぽく動き出す。

jsコードがwindowやdocumentを直接参照するのが嫌なら

let func = new Function( 'console', 'window', 'document', ..., jsテキスト);
func(
{log: (data) => result.push(data);},
{console: (data) => xxxx;},
{body: (data) => xxxx;},
);

とパラメータを追加し、見せたい情報を渡してサンドバック風にできるだろう。



「JavaScript」連番の配列を作ってみる

どうでもいいけど、C言語っぽく

let result = new Array(10);
for (let index = 0; index < 10; index++) {
 result[index]= index + 1;
}
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

では色々注意が必要。

から始まって・・・

let result = []
for (let index = 0; index < 10; index++) {
 result.push(index + 1);
}
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

この時点でまだindexをいっぱい書いてあるから

index + 1じゃなくて let index=1すれば良い!

とか余計な修正をしだすと

index < 10の部分を見落としてそう。

つまり、indexの出現は少ない方が良い。

しかし、

var result = [...Array(10)].map((_, i) => i+1) 
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

では作業用配列をいくつも作ってしまう。

Array(10)とか[…xxx]とかね。

とは云え

var result = Array.from({length: 10},(_, index) => index + 1);
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

に至っては {length:10 } でObjectの振りというか {…}は紛れもなくObjectだけど、

length: 10とよくあるプロパティでArray.fromを騙すのはいいとして

indexが唐突に出てくる感じがするけど・・・

Array.from(arrayLike, mapFn)

Array.fromはこんなパラメータになっているので

Array.prototype.map((value, index) => { ... });

を思い浮かべば、理解してもらえそう。

それに無駄な作業用配列も作らないから

1から1万までの連番配列を作るには丁度良い。

気がする。

そこで

Array.from(arrayLike, mapFn, thisArg)

まで使ってみると

var result = Array.from({ length: 10 }, function (_, index) { return index + this.start; }, { start: 1 });
console.log(result);
// (10) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

明示的に連番の初期値を設定できて気持ちはすっきりするけど、

アロー関数にとってthisはwindowしかありえないので

無名functionにしないといけないし

スクロールバーが出るくらい長いので蛇足感がある。

だが、連番+αのテストデータを作るのには向いている気がするので次点とした。

結果は・・・

var result = Array.from({length: 10},(_, index) => index + 1);

が良さげ。

/**
 *  連番作成
 * @param {integer} start           開始番号
 * @param {integer} end             終了番号
 * @return {Array of object}        開始番号~終了番号の配列
 */
const range = (start, end) => {
    let rc = Array.from({ length: (end - start + 1) }, (_, index) => index + start);
    if (callbackMap) {
        rc = rc.map(callbackMap);
    }
    return rc;
};

range().map(callback) をラップしてrangeMapを作ってみたが、使い心地は・・・

range(1, rows).map((nRow) => {
    createTrElement(objTable, nRow, ...);
});

rangeMap(1, rows,(nRow) => {
    createTrElement(objTable, nRow, ...);
});

見分けが付かないくらい同じだった。(笑

ps.

クエスチョン:spreadSheetのコードで上のrangeを使ってみると妙に重い(初期表示2秒前後)。何でだろう?

アンサー:今のEXCELって最大1048576行16384列なのでこの最大値に合わてみたら、初期表示で物凄い量の配列を作っていた!普通のfor文に戻すとサクっと初期表示。配列のままでも動かない訳では無いけど、時々Chromeやタスクバーがマウスに反応しなかった原因のような気がする。

100万×1.6万≒160億 だからなぁ~

でもfor文のままなのも、配列じゃなくても連番指定でsomeとmapっぽく動作すればいいから

/**
 *  連番配列っぽいイテレータを作成する
 * @param {integer} start           開始番号
 * @param {integer} end             終了番号
 * @returns {iterator}              開始番号から終了番号を格納した配列っぽいイテレータ
 */
const range = (start, end) => {
    let step = 1

    const rangeIterator = {
        some: function (func) {//someっぽく動作させる
            for (let cnt = start; cnt <= end; cnt += step) {
                if (func(cnt)) {
                    return true;
                }
            }
            return false;
        },
        map: function (func) {//mapっぽく動作させる
            const f = false;//配列を返さない
            let aRc;
            if (f) {
                aRc = new Array(end - start + 1);
            }
            let index = 0;
            for (let cnt = start; cnt <= end; cnt += step) {
                const rc = func(cnt);
                if (aRc) aRc[index++] = rc;
            }
            return aRc;
        },
    };
    return rangeIterator;
}


「javascript」postMessageとPromise

デスクトップに貼ったJSからSJISのCSVをダウンロードさせてみる【その後】の中で、
iframeへ送信したメッセージの応答をawaitで待機するようにしていますが、postMessageは本当にメッセージをポストするダケの機能なのでレス待ちなんで事ができません。

        displayStatus(`サンプルを${encoding}に変換中`);
        let unicode16CSV = await getSampleTextInnerText();

タネを明かすと、これを実現するために外部変数messageResponseStackにPromiseのresolveを保持し、どこかで応答メッセージを受信したらresolveを呼び出してawaitの待機状態を解除してもらっています。

つまり職場(プロセス)に居れば誰でも見れるホワイトボード(外部変数)に取引先(requestID)と連絡先(resolve)を書いてあるので、暇になった人が気が付けばかかってきた電話の相手先(requestID)の要件(event.data.message)を連絡してもらえる(ハズ

な仕組みです。

ついでにrequestIDはワンタイムなIDなのでホワイトボードから消してます。

/**
 * iframeのサンプルを取得する
 * @returns 
 */
const getSampleTextInnerText = () => {
    return new Promise((resolve, reject) => {
        loaderOpen();
        var us = getUniqueStr();
        messageResponseStack[us] = { 'resolve': resolve, 'reject': reject };
        myPostMessage('iframe', "getSampleText", "", us);
    });
};
    messageProcSetup({ ☚ここでmessageListner の応答メッセージタイプ毎のコールバック先をリストアップ
      ・・・
        'getEncodingListResponse': messageResponseProc,
      ・・・
    });
/**
 * メッセージのレスポンス処理 ☚ messageListnerからコールバックされる。
 */
const messageResponseStack = {};
const messageResponseProc = (data) => {
    loaderClose();
    if (data.requestId !== undefined) {
        messageResponseStack[data.requestId].resolve(JSON.parse(data.message));
        delete messageResponseStack[data.requestId];
    } else {
        alert(`not requestId${CRLF}data.type: ${data.type}`);
    }
};
/**
 * メッセージを受信する
 * @param {MessageEvent} event 
 */
const messageListner = (event) => {
    if (event.origin !== myOriginListener || event.type !== 'message') return;
    let data = event.data;
    let messageInfo = messageInfoList[data.type];
    if (messageInfo === undefined) {
        alert(`unkonwn message type = '${data.type}'${CRLF}message: ${data.message}`);
        return;
    }
    if (messageInfo !== undefined && messageInfo !== null) {
        if (Array.isArray(messageInfo)) {
            if (messageInfo.length > 1) {
                messageInfo[0](data, ...(messageInfo.slice(1)));
            } else {
                messageInfo[0](data);
            }
        } else {
            messageInfo(data);
        }
    }
};
/**
 * メッセージを送信する
 * @param {string} textType 
 * @param {string} textMessage 
 * @param {string} textRequestId
 */
const myPostMessage = (to, textType, textMessage, textRequestId) => {
    switch (to) {
        case 'parent':  //  親ドキュメントに送信する
            to = window.parent;
            break;
        case 'iframe': //   iframeへメッセージを送信する
            to = document.querySelector("iframe").contentWindow;
            break;
    }
    to.postMessage({
        type: textType,
        message: textMessage,
        requestId: textRequestId,
    }, myOriginSender);
};

もっといい方法があったらいいなぁ。




top