変奏現実

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

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

配列

[JavaScript]配列のkeyとvalueの両方を使う

const createHtmlElement = (elementName, options) => {
・・・
・・・
  const rc = Object.keys(options).map(
    (key) => (chkOriginal(key) || chkStyle(key) || chkEvent(key) || setAttribute(key)));
  );
}

と書くと、呼び出された各処理で

function chkOriginal(key) {
  const value = options[key];
  ...
}
function chkStyle(key) {
  const value = options[key];
  ...
}
function chkEvent(key) {
  const value = options[key];
  ...
}
function setAttribute(key) {
  const value = options[key];
  ...
}

と書いてたけど・・・

const createHtmlElement = (elementName, options) => {
・・・
const chkOriginal = (key,value) => {
  ...
}
const chkStyle = (key,value) => {
  ...
}
const chkEvent = (key,value) => {
  ...
}
const setAttribute = (key,value) => {
  ...
}

・・・
  const rc = Object.keys(options).map(
    const value = options[key];
    (key) => (chkOriginal(key,value) || chkStyle(key,value) || chkEvent(key,value) || setAttribute(key,value)));
  );
}

としてみた。自分で書いたコードから、ローカル関数で元関数のパラメータを見ても特に支障は無いけど、functionを使わない書き方で統一できた。

MDNを見ると、mapでも第2パラメータ(thisArgs)が使えるみたいなので、

const rc = Object.keys(options).map(function (key) {
    const value = this[key];
    return chkOriginal(key, value) || chkStyle(key, value) || chkEvent(key, value) || setAttribute(key, value);
}, options);

としてみた。

本来はコールバックに特定のオブジェクトをthisとして引き渡したい場合に使うものだし、コールバックの中でoptionsを書きたくないと意地を張ったものの、optionsを2回書いてしまう事に変わりは無いので、無駄なthisArgsの使い方になってしまった。外部の変数を見てる怪しい部分も修正したら結果がコレ。

/**
 * HTMLエレメントを作成する
 * @param {string} elementName                              エレメント名
 * @param {{attributeName:,... eventName:,...}}} options    設定内容
 * @returns {HTMLElement}                                   作成したHTMLElement
 */
const createHtmlElement = (elementName, options) => {
    const fileName = 'js/lib/createHtmlElement.js';
    const funcName = 'createHtmlElement'; //debugLog(`${fileName}:${funcName}(elementName: ${elementName}, options)`);
    /**
     * 独自の処理が必要な場合
     * @param {} param      属性
     * @returns {boolean}   結果
     */
    const chkOriginal = (param) => {
        let rc = false;
        try {
            switch (param.name) {
                case 'className': // classNameはメソッドで、\x20で文字列を区切って複数の属性を指定できる
                case 'classList': // classListはメソッド
                    let v = param.value;
                    if (!Array.isArray(v)) { v = v.split('\x20'); }
                    param.element.classList.add(...v);  // 配列を展開してクラスリストに追加する
                    rc = true;
                    break;
                case 'innerHTML': // innerHTMLはメソッド扱い    ※未使用
                    param.element.innerHTML = param.value;
                    rc = true;
                    break;
                case 'innerText': // innerTextはメソッド扱い
                    param.element.innerText = param.value;
                    rc = true;
                    break;
                case 'parent':      // parentはメソッド
                    param.value.appendChild(param.element);
                    rc = true;
                    break;
                case 'appendChild': // appendChildはメソッド    ※未使用
                    param.element.appendChild(param.value);
                    rc = true;
                    break;
                case 'insertBefore':    // insertBeforeはメソッド   ※スクロール処理で座標の小さい方向へセルを作る際に使用
                    param.value.parentNode.insertBefore(param.element, param.value);
                    rc = true;
                    break;
            }   // end of switch
        } catch (ex) {
            debugLog(`${funcName}(elementName: ${elementName}) '${param.name}'属性の固有処理に失敗 '${ex}'`);
            rc = false;
        }
        return rc;
    };
    /**
     * スタイル属性の場合
     * @param {} param      属性
     * @returns {boolean}   結果
     */
    const chkStyle = (param) => {
        let rc;
        try {
            //  style-xxxxのキーの場合
            let styleChk = param.name.match(/^style_(.*)/);
            if (styleChk) {
                let styleName = styleChk[1];
                switch (styleName) {
                    case 'width':
                        param.element.style.width = param.value;
                        break;
                    case 'height':
                        param.element.style.height = param.value;
                        break;
                    default:
                        styleChk = null;
                        break;
                }   // end of switch
            }
            rc = styleChk !== null;
        } catch (ex) {
            debugLog(`${funcName}(elementName: ${elementName}) '${param.name}'スタイル属性に${param.value}を設定失敗 '${ex}'`);
            rc = false;
        }
        return rc;
    }
    /**
     * eventの場合
     * @param {} param      属性
     * @returns {boolean}   結果
     */
    const chkEvent = (param) => {
        let rc;
        try {
            const eventList = ['ended'];    // イベントリスト
            rc = eventList.includes(param.name);   // イベントリストに含まれているかどうか
            if (rc) {   // イベントリスナを登録する
                param.element.addEventListener(param.name, param.value);
            }
        } catch (ex) {
            debugLog(`${funcName}(elementName: ${elementName}) '${param.name}'イベントリスナの登録に失敗 '${ex}'`);
            rc = false;
        }
        return rc;
    }
    /**
     * HTMLエレメントに属性を追加する
     * @param {} param      属性
     * @returns {boolean}   結果
     */
    const setAttribute = (param) => {
        try {
            param.element.setAttribute(param.name, param.value);
            return true;
        } catch (ex) {
            debugLog(`${funcName}(elementName: ${elementName}) '${param.name}'属性に${param.value}を設定失敗 '${ex}'`);
            return false;
        }
    }
    let elm = document.createElement(elementName);
    //  optionsの設定名を取得しぶん回す!
    const rc = Object.keys(options).map(function (key) {
        const value = this[key];
        const param = { 'name': key, 'value': value, 'element': elm };
        return chkOriginal(param) || chkStyle(param) || chkEvent(param) || setAttribute(param);
    }, options)
        .filter(v => !v);   // trueならば削除する
    if (0 < rc.length) {
        debugLog(`${funcName}(elementName: ${elementName}) 未処理属性数:${rc.length}件`);
    }
    return elm;
}

数か月経つと

※未使用

と書かないと、「どこかで使っている(気がする」し、

※スクロール処理で座標の小さい方向へセルを作る際に使用

とか書いておかないと、「要らない(気がする」する、

なぜか、逆バイアスがかかってしまうのは・・・謎。

あ、使い方も忘れた。

こんな感じだった。

let a = createHtmlElement('a', {
    href: url,
    download: fileName,
    text: "re-download",
});

色々設定したいHTMLエレメントを作る時に

const div = createElement('div');
shadow.addChiled(div);
div.className = 'scroll';
div.id = `div${sn}`;
div.style.height = _spreadSheet.width;
div.style.width = _spreadSheet.height;
div.tabIndex =  `${sn}`;

を1行で書ける。更にそんなモノがいっぱいある場合には、

//  シャドールートを作成
_layout.shadowRoot = (!_layout.shadowRoot) ? _spreadSheet.customElement.attachShadow({ mode: "open" }) : _layout.shadowRoot;
const shadow = _layout.shadowRoot;
//  既存のHTMLElementを削除
Array.from(_layout.shadowRoot.children).forEach((ch) => ch.remove());
//  スタイルシート部
const style = _layout.createStyle(sn, _spreadSheet.rows, _spreadSheet.cols, true, true);
shadow.appendChild(style);
//  スプレッドシート部
_layout.divTableN = createHtmlElement('div', { parent: shadow, className: 'scroll', id: `div${sn}`, style_height: _spreadSheet.width, style_width: _spreadSheet.height, tabIndex: `${sn}`, });
_layout.tableN = await _layout.createTableElement(_layout.divTableN, 'spread-sheet', `table${sn}`);
_layout.divScrollX = createHtmlElement('div', { parent: _layout.divTableN, className: 'scroll-x', id: `divScrollX${sn}`, style_height: '17px', style_width: _spreadSheet.height, tabIndex: `${sn}`, });
_layout.divScrollY = createHtmlElement('div', { parent: _layout.divTableN, className: 'scroll-y', id: `divScrollY${sn}`, style_height: _spreadSheet.width, style_width: '17px', tabIndex: `${sn}`, });

と書け、すっきりする。

もっとも、インターネットの記事でそんなケースはまず有り得ないし、必要に応じてcreateHtmlElementの機能を増設していかないと「すっきり」しないので、仕事で作るソースにも使ったことはないね。

最初はcreateDivElementとかcreateAElementとかタグ毎に作っていたけど多すぎるので、createTableElementを除いて一本化した。

createTableElementみたいに複雑で同期を取る必要もあるモノは別物にするしかないが、コード全般の流れを単純化するとスッキリした。勿論、createTableElementの中のアチコチでcreateHtmlElementを呼び出している。

今見ると、イベント登録まで入れたのはやり過ぎ感があるなぁ。



「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;
}



top