🌊

JavaScript三十六景Tips集

に公開13

Discussion

shiracamusshiracamus

競技プログラミングならありだけど、プログラミング教育なら動作の違いも把握しておくのがいいですね。

いっぺんに代入する

let left = current = answer = 0

これ、left だけが let(ローカル変数) で current と answer はグローバル変数になります。

function sample() {
    let left = current = answer = 0
}

sample();
console.log(current)  // グローバル変数なので表示できる
console.log(left)       // ReferenceError: left is not defined

n, kどちらかが0の場合0を返す

これ、厳密比較ではなくなります。

> let n = '0'
'0'
> let k = '0'
'0'
> n === 0 || k === 0
false
> n * k === 0
true
somnicattussomnicattus

いっぺんに代入する
let left = current = answer = 0

別の方のコメントにもありますが、これをすると left 以外 (current と answer) は暗黙的な var 宣言グローバル変数の扱いになってしまいます。意図せずスコープが壊れるのでお勧めしません。

あらかじめ個別に let 宣言しておくか、構造分解で宣言するといいと思います。

let left
let current
let answer
left = current = answer = 0
let [left, current, answer] = [0, 0, 0]

n, kどちらかが0の場合0を返す
if(n * k === 0) return 0

整数の場合は良いですが、浮動小数点数の場合はアンダーフローに注意が必要です。

Number.MIN_VALUE * 0.5 === 0 // true

iteratorを使う

Map.prototype.keys() が返却するのはイテレーターでもあり反復可能オブジェクトでもあるので、実は上のコードでもイテレーターを使えています。arr に代入されるのは配列ではなく反復可能オブジェクトで、これは for...of でイテレーターを使ったループができます。

Object.keys(obj) は配列を返却するので、これと混同しているのだと思います。

cacheする

length をキャッシュしても、以前試したときは全く差がありませんでした。ランタイムによって実行時に最適化されると思うので、あまり気にしなくていいと思っています。

shiracamusshiracamus

別の方のコメントにもありますが、これをすると left 以外 (current と answer) は暗黙的な var 宣言の扱いになってしまいます。意図せずスコープが壊れるのでお勧めしません。

var は関数スコープで、何もつけないとグローバルスコープなのでは?


function sample() {
    let left = 0      // ローカル変数
    var current = 0   // ローカル変数
    answer = 0        // グローバル変数
}

sample();
console.log(answer)   // グローバル変数なので表示できる
console.log(current)  // ReferenceError: current is not defined
EdamAmexEdamAmex

length をキャッシュしても、以前試したときは全く差がありませんでした。ランタイムによって実行時に最適化されると思うので、あまり気にしなくていいと思っています。

昔測った時は結構差あったんだけど、今はもうコードのスタイリング意識した方が良くなっちゃった…

somnicattussomnicattus

shiracamus さん、ご指摘ありがとうございます!
普段使うことがないのでずっと勘違いしていました……。

だめぽだめぽ

面白い記事でしたが、何点か気になったのでコメントさせていただきます。

数字の桁数を数えるとき

ECMAScriptの仕様 (https://262.ecma-international.org/#sec-math.log10) 的には Math.log10実装依存の近似値 (implementation-defined approximated Number value) を返すので、桁数の判定には使わない方が安全です。

例えば、手元(x86_64 macOS)のNode.jsでは 1e15 - 1 に対して Math.log10(n) はちょうど15を返しました。

let n = 999999999999999; // 9が15個
console.log(n.toString().length); // 15
console.log(Math.log10(n) + 1); // 16

ビット演算子で整数に変換 / ~~ で高速に整数化

JavaScriptにおけるビット演算子による整数化は、0方向に切り捨てるという挙動となります。一方、Math.floorマイナス無限大方向に切り下げる関数なので、負の小数を与えた時の挙動が異なります。

const a = -1.75;
console.log(a | 0); // -1
console.log(Math.floor(a)); // -2
console.log(Math.trunc(a)); // -1
somnicattussomnicattus

log10の出力は当然浮動小数点数なので、極限まで近いと潰れちゃうんですよね。

現実的な対策としては、入力と 10**n を比較して入力の方が小さければ -1 するとかですかね。

Hidden comment
somnicattussomnicattus

いろいろコメントがついていますが、記事があってはじめていろいろ議論できているので、遠慮なく記事投稿しちゃっていいと思います。

let の使い方を見ると、全体的に JavaScript に慣れていないような印象を受けました(定数やfor...ofの変数宣言は普通 const です)。ネット上の資料は質がまちまちですので、いろいろな情報を比較して、新しめのいいコードを参考にしてみてください。

udyestudyest

短く書く

a["a"] ||= []

これは falsy な値、つまり unefined だけでなく、 null0'' (空文字)なども通してしまうので、

if(a["a"] === undefined) a["a"] = []

と同じではないです。

こういうケースの場合は ||= ではなく ??= を使った方が近い挙動をしますね。

a["a"] ??= []

こちらも undefinednull だけを通すので、まだ === undefined と同じ挙動ではないです。
(が、明確に nullundefined は区別しなくても問題ないケースが多いと感じます。)

kenmorikenmori

いろいろご指摘ありがとうございます。勉強になります。一部「侮辱」と受け取れるコメントには違反報告をして非表示にしました。スクショは撮りましたので情報開示後、現在「侮辱罪」として成立し得るか確認中です

記事にコメントをするとき
技術記事を書くということは時間と労力を伴う大変な作業ですから、まずは記事の著者へ最大限の敬意を払うようにしましょう。中には誤った情報や、意見が合わないこともあると思いますが、相手への配慮を忘れずに建設的なコメントを心がけましょう。コメントもコミュニティへの貢献の一つであることを忘れないでください。