1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

もう一度JavaScriptを理解する part1

Last updated at Posted at 2022-07-03

初めに

たくさんの資料調べるうちに数か月前自分の書いた文章を振り返ると、自分の理解があやふやになっていることに気づき、基礎概念ともう一度向き合って文章にしたいと思います。

シリーズ参考資料はこちらです↓
You Don't Know JS (book series)

今回扱うのは
You Don't Know JS: Up & Going - Chapter 2: Into JavaScript
You Don't Know JS: Scope & Closures - Chapter 4: Hoisting
Kinds of Emptiness
の勉強メモです。

07/04/22編集:
元の文章ではundefinedを状態として理解していましたが、正しくはです。

Hoisting

var

Hoistingは、varなどを使って変数宣言するとき、その変数が自分のいるスコープ一番上に巻き上げていくことです。

var hoisting example1
console.log(a) // undefined
// undefined means no value
// we declared it but we doesn't assign any value

var a // compile-time
a = 2 // run-time
console.log(a) // 2

varを使えばcompile-time(parsing)でのhoistingが必ず発生します。
しかしhoisitingの対象は変数です。値ではありません。
変数がrun-time(executing)で値を付与されるまでundefined(値)で初期化されている。(initialize as undefined)

example1実際の順序は、
var a ← compile-timeでは順序に関わらず、一番上に置くのがhoisitingです。
console.log(a) // undefined
a = 2 ← run-timeでコードを実行するとき値を付与する。
console.log(a) // 2

compile-timeというのは、コード執行する前に、JavaScriptエンジンはScopecompilationを行ってコード解析し機械言語に変換する段階です。
同じ作者のシリーズ本ではこちらのYou Don't Know JS: Scope & Closures - 1st Edition扱っています。
少し前にスコープについて自分なりにまとめましたが、ご参考になればと。↓
JavaScriptのクロージャについて - Scope

var hoisting example2
foo() // undefined

function foo() {
  console.log(a)
}

console.log(a) // undefined

var a // compile-time

foo() // undefined

a = 1 // run-time

console.log(a) // 1

foo() // 1

example2の順序は、
var a
function foo() {console.log(a)}
foo() // undefined *一行目の呼び出し
console.log(a) // undefined
foo() // undefined
a = 1
console.log(a) // 1
foo() // 1

function宣言ももちろんhoisitingが発生します。
でもなぜvarhoisitingfunctionより前だと言えるでしょうか?

まずvar aを消してfoo()だけを呼び出したらReferenceError: a is not definedになります。
そして一行目のfoo()console.log(a)undefinedを出力することから、var aの後function foo() {...}ではないとfoo()は実行できないはずです。(ASTにも関連している気がしますが)

それからfunction foo() {...}console.log(a)...が続いていくということがわかります。そしてa = 1run-timeで値が付与されて初めてaの値を出力することが可能になったんです。

functionはどこで書いてもトップにあがりますが、順序としてはvarのような関数宣言より下、ネストした関数(内部関数)を除いてどこで呼び出されても可能です。)

Variable can't be hoisted in function scope?

global scopefunction scopeも、varでの宣言ではhoistingが必ず発生します。

function outer() {
  console.log(a) // undefined
  var a
  console.log(a) // undefined
  a = 2
  console.log(a) // 2
}

outer()

では、なぜ下の例では、上のtest2()が出力できなかったでしょうか...

function outer() {
  test1() // I am test1
  // test2() // TypeError: test2 is not a function

  function test1() {
    console.log('I am test1')
  }

  var test2 = function () {
    console.log('I am test2')
  }

  test2() // I am test2
}

outer()

やはりhoisting作用の対象が誤解されやすいところだと思います。
outer()内部の順序、実際は
var test2
function test1() {...}
test()1
test()2 // TypeError
test2 = function () {...}
test2() // I am test2
となります。

hoistingは変数を巻き上げるけど、値は別の段階で付与されるから、test2の値(function () {console.log('I am test2')})より先に呼び出されたら、
もちろんTypeError: test2 is not a functionが出てきます。
上のtest2()ではtest2はまだundefinedなので、undefinedを関数のように扱うことができません。

let/const

letconstには、hoistingが発生しないのでしょうか?
発生します。 ここではTDZ(Temporal Dead Zone)を紹介したいと思います。
TDZ(Temporal Dead Zone)は一時死亡状態に指します。つまり何もできない状態です。

// Before execution = compile time

never been created      'let/const'         'var' declared
   in any scope           declared            ex. var a
-------------------|-------------------|-------------------> time
     undeclared              TDZ              undefined
                       (uninitailized)

var宣言は変数をhoistingして、execution(run time、値が付与される)まではundefinedで初期化になる (initialize as undefined) ことに対し、
let/const宣言も同じく変数をhoistingして、executionまで変数をuninitialized(初期化されてない)、何も付与できない状態を保っているんです。

uninitialized状態の値をアクセスしたらReferenceError: Cannot access before initializationが出てきます。
そしてexecution 段階になってはじめてundefinedという初期値が付与される。

簡潔にいうと、

varで変数宣言される時点(compile time)すぐundefinedという初期値がつく。executionで新しい値が付与されるまでundefinedです。

let/constで変数宣言される時点(compile time)で何にもできない死亡している状態で、execution(run time) で初めてundefinedが付与され、もし値を与えていなければundefinedになります。

// x // ReferenceError: Cannot access 'x' before initialization

let x
console.log(x) // undefined

x = 3
console.log(x) // 3

結論

var, const, letの違いを理解して、変数をちゃんと宣言してから使うこと。

ほかに

varletconst以外functionfunction*class、JavaScriptではすべての宣言がhoisted状態になります。
こちらにご参考にしてください。↓
Are variables declared with let or const hoisted?

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?