この記事を書くことになった動機
Quoraより…
JavaScriptの開発者がよくする間違いは何ですか?
という質問に回答させていただいた際の答えに準じたソースコードが必要であると気がついたためです。
コンストラクタを継承する時。
(Javaでいう、クラスの継承)
初心者の方は素直にクラス構文を使うことをおすすめします。
class Foo {
constructor (foo) {
this.foo = foo;
}
sayFoo () {
alert(this.foo);
return this;
}
}
class Bar extends Foo{
constructor (foo, bar) {
super(foo);
this.bar = bar;
}
sayBar () {
alert(this.bar);
return this;
}
}
const bar = new Bar("hello", "bye");
bar.sayFoo(); //hello
上記を詳細に設定したものにしたい場合は、
function Foo (foo) {
this.foo = foo;
}
Object.assign(Foo.prototype, {
sayFoo () {
alert(this.foo);
return this;
}
});
function Bar (foo, bar) {
Foo.call(this, foo);
this.bar = bar;
}
Bar.prototype = Object.create(Foo.prototype, {
constructor: {
configurable: true,
writable: true,
value: Bar
}
});
Object.assign(Bar.prototype, {
sayBar () {
alert(this.bar);
return this;
}
});
const bar = new Bar("hello", "bye");
bar.sayFoo(); //hello
eventHandlerの中でのthisがeventTargetを指すことを忘れている。
これは初歩的な失敗ですが、実際には以下のような解決策があります。
const MyButtonHandler = function (value) {
this.value = value;
this.click = this.value !== undefined ? this.what : this.that;
}
Object.assign(MyButtonHandler.prototype, {
handleEvent (e) {
this[e.type](e);
},
what (e) {
this.value = e.target.innerText;
},
that (e) {
alert(this.value);
}
});
const myButtonHandler = new MyButtonHandler("I am not eventTarget");
document.getElementById("myButton")
.addEventListener("click", myButtonHandler, false);
handleEvent という名前のメソッドを継承することで、this は eventTarget ではなく、そのメソッドを持つオブジェクトになります。
addEventListener の追加時は関数名ではなくオブジェクトをまるごと追加します。
イベントの補足先がクロージャになっていて、メモリーをリークしてしまう。
上記の様に実装していれば、その危険性は格段に下がるので割愛させていただきます。
jQueryこそjavascriptだと思っている。
残念なことですが本気で思い込んでいる方は多いです。
jQueryを使わずに、生のjavascriptを使って見ることで、本当はこう書けるという実感が必要だと感じます。
ただし、現在は CSS Selector によるクエリ検索が、生のjavascriptでも出来ます。
詳細は、以下をご参照ください。
querySelector
querySelectorAll
lodashが無いと動くコードが書けない。
いや、あの頃は良かったですね!
確かに便利な関数群を実装したとてつもなく強力なライブラリで、後のjavascriptへの発想を大きく転換していただけたライブラリです。
今、その精神は生のjavascriptに脈々と受け継がれています。
Array.prototype
分割代入については、以下のような使い方もできます。
Object.entries({a: 1, b: 2, c: 3}).forEach(([k, v]) => console.log(`{${k}: ${v}}`));
(({a, b, c}) => ({a, c}))({a: 1, b: 2, c: 3});
//{a: 1, c: 3}
スプレッドオペレータという書き方もあります。
// argumentsを配列化したり、配列を展開代入したりに ピリオド3つを使う。
((...v) => v.reduce((p, c) => p + c))(...[2, 3, 4])
関数は第1級オブジェクトだと言われても、その意味を理解できていない。
関数は、Functionをコンストラクタに取るオブジェクトなので、プロパティを追加できます。
その関数に関連のある名前の関数を定義する際には便利かもしれません。
const Foo = Object.assign(function (foo) {
this.foo = foo
}, {
of (foo) {
return new Foo(foo);
}
});
Object.assign(Foo.prototype, {
of(foo) {
return Foo.of.call(this, foo);
},
sayFoo () {
alert(this.foo);
return this;
}
});
const foo = Foo.of("hello");
foo.sayFoo(); // hello
const foo2 = foo.of("Bye");
foo2.sayFoo(); // Bye
オブジェクト指向にこだわりすぎて、柔軟にコードを書けない。
アラン・ケイ氏によると、
『「さらに良く」や「完璧」は、「必要なこと」に対する敵です』
と言明されました。
つまり我々は美しいコードを書くために必要なことだけに着目すればよいということになります。
関数型の人々は、javascriptのオブジェクト指向的な側面の利点を言わない。
場合もあるという冗談にとどめておきたい回答です。
オブジェクト指向を圏論的に俯瞰できない方々は、オブジェクト指向を叩きがちです。
これも上記のアラン・ケイと同じ意見です。
非同期処理で関数を入れ子にし続けて、あとで何書いたか分からなくなる。
Promiseを使うほうが良いです。
出来ればそれに対応したものに書き換えたほうが良いですが、UIEvent系を非同期処理に対応しようとした際、酷い目に会いました(笑)
async awaitで記述できる場合はそれも視野にいれると良いでしょう。
(async () => {
return await fetch //...
})()
.then(ans => JSON.parse(ans))
.then(data => Object.assign(foo, data));
コピーしたはずの元のオブジェクトのプロパティまで変更されている。
これについては、こういう事故が起こります。
const foo = {bar: 3};
const foobar = foo;
foobar.bar = 5;
console.log(foo.bar === 3) // Ouch!
正しいオブジェクトのコピー方法は、以下で説明します。
オブジェクトをコピーしたら、継承しているメソッドは消えると思い込む。
オブジェクトの正確なコピーは、以下のソースコードで得られます。
const Foo = function (foo) {
this.foo = foo
};
Object.assign(Foo.prototype, {
sayFoo () {
alert(this.foo);
return this;
}
});
let foo = new Foo("hello")
let copiedFoo = Object.assign(Object.create(Foo.prototype), foo);
copiedFoo.foo = "bye";
foo.sayFoo(); // hello
copiedFoo.sayFoo(); // bye
以上になりますが、言いたいことは…
Object.assignは超便利!
失礼いたしました…