概要
function式とarrow式の違いをサッカー選手を使いながら軽く説明するよ
問題
早速、問題を出します。
aとbの違いわかりますか?
const a = function() {
console.log('neymar')
}
const b = () => {
console.log('neymar')
}
この場合だとbのほうが合計文字数がすくなくなったくらいですかね。
次
// let, const であればトップレベルで宣言してもwindowオブジェクトのプロパティを生成しませんが今回は問題の都合上varでいきます
// よほどのことがない限りこんな書き方をしないけどグローバルのnameに代入するために記載
// var宣言が怖い理由の一つもこれですね。
var name = '😈😈global-messhi😈😈'
const printName = function() {
console.log('printName', this.name)
}
const arrowPrintName = () => {
console.log('arrowPrintName', this.name)
}
const obj1 = {
name: 'nagatomo',
printName
}
const obj2 = {
name: 'king-kazu',
arrowPrintName
}
// 出力される名前はなんでしょう
obj1.printName()
obj2.arrowPrintName()
答え
obj1.printName() // nagatomo
obj2.arrowPrintName() // 😈😈global-messhi😈😈
アロー関数式とfunction式の違い
先程の問題の結果を見て、文字数が短くなっただけではないことはすでにわかったと思います。
this
の指している場所が違います。
通常のfunction式の中でthisを使うと、その呼び出し元のオブジェクトを指します。
var name = '😈😈global-messhi😈😈'
const printName = function() {
console.log('printName', this.name)
}
const obj1 = {
name: 'nagatomo',
printName
}
const obj2 = {
name: 'king-kazu',
printName
}
obj1.printName() // 'nagatomo'
obj2.printName() // 'king-kazu'
なのでこの場合printName関数内で使っているthis.name
の値は呼び出し元がobj1とobj2なのかで値が異なってきます。
function式だとthisが呼ばれたタイミングで決定されちゃんと自立できていない人のような振る舞いです。
なのでfunction式は直感でわかりにくいので苦労します。
アロー関数は結論から言うとthis
は宣言された時点で、this
を確定します。
var name = '😈😈global-messhi😈😈'
const arrowPrintName = () => {
console.log('arrowPrintName', this.name) // この時点でのthisはグローバルになる
}
const obj1 = {
name: 'nagatomo',
arrowPrintName
}
const obj2 = {
name: 'king-kazu',
arrowPrintName
}
obj1.arrowPrintName() // '😈😈global-messhi😈😈'
obj2.arrowPrintName() // '😈😈global-messhi😈😈'
なので、呼び出し元がobj1だろうが、obj2だろうが、関係なくて直感的にthisの指している場所がわかります。
今学んだことを再確認するために、もっかい問題出します。
class Person {
constructor(name) {
this.name = name
}
printName = function () {console.log('printName', this.name)}
arrowPrintName = () => {console.log('arrowPrintName', this.name)}
}
const printName = function() {
console.log('printName', this.name)
}
const arrowPrintName = () => {
console.log('arrowPrintName', this.name)
}
class Person2 {
constructor(name) {
this.name = name
}
printName = printName
arrowPrintName = arrowPrintName
}
const kakitani = new Person('kakitani')
const maezono = new Person2('maezono')
// 出力される名前はなんでしょう
kakitani.printName()
kakitani.arrowPrintName()
maezono.printName()
maezono.arrowPrintName()
答え
kakitani.printName() // kakitani
kakitani.arrowPrintName() // kakitani
maezono.printName() // maezono
maezono.arrowPrintName() // 😈😈global-messhi😈😈
次
const bindPrintName = function() {
console.log(this.name)
}.bind(this) // function式をarrow関数と同じ感じでthisをつかえるようにするための魔法のコトバだよ
class Person3 {
constructor(name) {
this.name = name
}
bindPrintName = bindPrintName
}
// 出力される名前はなんでしょう
const zaccheroni = new Person3('zaccheroni')
zaccheroni.bindPrintName()
答え
const zaccheroni = new Person3('zaccheroni')
zaccheroni.bindPrintName() // 😈😈global-messhi😈😈
Reactでよくあるパターン
これだとボタンを押した時Uncaught TypeError: Cannot read property 'state' of undefined
が出ます。
理由わかりますか?
export default class Hoge extends Component {
constructor(props) {
super(props);
this.state = {
name: 'zico'
};
}
handlePrintName() {
const { name } = this.state;
return name
}
render() {
const { name } = this.state;
return (
<button onClick={this.handlePrintName}>
print name
</button>
);
}
}
解決方法
handlePrintName()
をアロー関数にする
// 変更前
handlePrintName() { // これでthisが定まっていない
const { name } = this.state;
return name
}
// 変更後
handlePrintName = () => { // これでthisをHogeにbind
const { name } = this.state;
return name
}
ただReactの場合これには問題点があります。
レンダー内でアロー関数を利用するとコンポーネントがレンダーされるたびに新しい関数が作成されるため、子コンポーネントでReact.memoやPureComponentを使ってた場合に正しく比較されなくなる
ClassComponentではbind(this)
を使う場合が多い
export default class Hoge extends Component {
constructor(props) {
super(props);
this.state = {
name: 'zico'
};
this.handlePrintName = this.handlePrintName.bind(this)
}
handlePrintName() {
const { name } = this.state;
return name
}
render() {
const { name } = this.state;
return (
<button onClick={this.handlePrintName}>
print name
</button>
);
}
}
参考記事
JavaScriptの「this」は「4種類」??
JavaScript の this を理解する多分一番分かりやすい説明