먼저 들어가기 전에 가장 기본적으로 알아야 할 것

promise의 경우, promise 의 콜백함수 안에 동기처리 하고싶은 비동기 처리를 담는 것이라는 점입니다.


콜백의 경우, 비동기가 예상되는 함수A의 인수로써 그 이후에 처리되어야 하는 함수B를 넣으므로, A->B처리되게 하는 것


//선언부
const func1 = function() {
return new Promise(function(resolve, reject){
...(동기처리 하고 싶은 비동기 처리)...
});
};

function A(callback){
...
callback(param1,param2);
}




1.then() 메서드는 Promise를 리턴하고 두개의 콜백 함수를 인수로 받습니다. 이때, 인수로 들어온 콜백함수의 리턴값이 promise객체일 때, then()이 콜백함수의 리턴값인 promise객체를 받습니다.


[이상경우]

//선언부
const func1 = function() {
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('a');
resolve();
//reject('func1 fail');
}, 2000);
});
};

const func2 = function() {
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('b');
resolve();
//reject('func2 fail');
}, 3000);
});
};

const func3 = function() {
console.log('c');
};


//실행부
func1()
.then(func2())
.then(func3) //기대치는 a b c인데 a c b 반환
//then의 파라미터는 함수가 들어가야 하는데, func2()는 promise객체이기 때문에 .then(func2())이 작동하지 않음




[정상경우]

//선언부
const func1 = function() {
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('a');
resolve();
//reject('func1 fail');
}, 2000);
});
};

const func2 = function() {
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('b');
resolve();
//reject('func2 fail');
}, 3000);
});
};

const func3 = function() {
console.log('c');
};


//실행부
func1()
.then(func2)
.then(func3)
// a b c 반환


func1()
.then(function(){return func2()})
.then(func3)
// a b c 반환


func1()
.then(function(){func2()})
.then(func3)

// a c b반환

// function(){func2()}는 return값이 없기때문에 제대로 작동하지 않는다.



2. .then() 메서드의 파라미터 두개중, 하나는 Promise가 성공(success)했을 때를 위한 콜백 함수이고, 다른 하나는 실패(failure)했을 때를 위한 콜백 함수입니다. 


promise 선언부에서 resolve 콜백함수를 두고, 이 콜백함수를 정의하는 부분이 then(resolve,reject)에서 resolve부분입니다. 따라서, then(resolve,reject)에서 resolve를 정의하기 위해선 선언부의 resolve콜백함수는 필수적으로 존재해야합니다.


//선언부
const func1 = function() {
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('a');
//resolve();
//reject('func1 fail');
}, 2000);
});
};

const func2 = function() {
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('b');
//resolve();
//reject('func2 fail');
}, 3000);
});
};

const func3 = function() {
console.log('c');
};

//실행부
func1()
.then(func2)
.then(func3)

//기대값은 a b c 인데 a만 반환된다

//resolve()함수가 선언되지 않았기 때문에, 시행되지 않기 때문이다.




3. 



const func1 = (num) => {
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log('a');
resolve(num);
}, 3000);
});
};
const func2 = (num) => {
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log('b');
resolve(num * num);
}, 2000);
});
};

const func3 = (num) => {
return new Promise((resolve,reject) => {
setTimeout(() => {
console.log('c');
resolve(num * num * num);
}, 2000);
});
};

//실행부1
func1()
.then(func2)
.then(func3)
.then((result)=>{console.log(result)})

//실행부2
func1(2)
.then((result)=>{
func2(result).then((result) => {
func3(result).then((result)=>{
console.log(result);
});
})
})




4. then자체는 비동기 처리가 되는 함수이다. 

func1, func2, func3를 순서대로 동기처리 하기 위해서는 promise 체인 안에 세 함수를 위치시켜야 한다.


동일한 promise 체인안에 위치하는 func1과 func2는 동기 처리 되지만, func3는 동일한 promise체인 안에 존재하지 않으므로 

1. 비동기적으로 func3의 결과를 반환 후 

2. func1 -> func2의 결과를 반환한다. 



//선언부
const func1 = function() {
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('a');
resolve();
//reject('func1 fail');
}, 2000);
});
};

const func2 = function() {
return new Promise(function(resolve, reject){
setTimeout(function(){
console.log('b');
resolve();
//reject('func2 fail');
}, 3000);
});
};

const func3 = function() {
console.log('c');
};


//실행부
func1()
.then(func2)

func3() // c a b 반환
//then의 파라미터는 함수가 들어가야 하는데, func2()는 promise객체이기 때문에 .then(func2())이 작동하지 않음


















Class inheritance, super
https://javascript.info/class-inheritance

Classes can extend one another. There’s a nice syntax, technically based on the prototypal inheritance.

To inherit from another class, we should specify "extends" and the parent class before the brackets {..}.

Here Rabbit inherits from Animal:


























class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name} stopped.`);
  }

}

// Inherit from Animal
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.hide(); // White Rabbit hides!

The extends keyword actually adds a [[Prototype]] reference from Rabbit.prototype to Animal.prototype, just as you expect it to be, and as we’ve seen before.

So now rabbit has access both to its own methods and to methods of Animal.

Any expression is allowed after extends

Class syntax allows to specify not just a class, but any expression after extends.

For instance, a function call that generates the parent class:








function f(phrase) {
  return class {
    sayHi() { alert(phrase) }
  }
}

class User extends f("Hello") {}

new User().sayHi(); // Hello

Here class User inherits from the result of f("Hello").

That may be useful for advanced programming patterns when we use functions to generate classes depending on many conditions and can inherit from them.

Overriding a method

Now let’s move forward and override a method. As of now, Rabbit inherits the stop method that sets this.speed = 0 from Animal.

If we specify our own stop in Rabbit, then it will be used instead:

class Rabbit extends Animal {
  stop() {
    // ...this will be used for rabbit.stop()
  }
}

…But usually we don’t want to totally replace a parent method, but rather to build on top of it, tweak or extend its functionality. We do something in our method, but call the parent method before/after it or in the process.

Classes provide "super" keyword for that.

  • super.method(...) to call a parent method.
  • super(...) to call a parent constructor (inside our constructor only).

For instance, let our rabbit autohide when stopped:





























class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  run(speed) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  stop() {
    this.speed = 0;
    alert(`${this.name} stopped.`);
  }

}

class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }

  stop() {
    super.stop(); // call parent stop
    this.hide(); // and then hide
  }
}

let rabbit = new Rabbit("White Rabbit");

rabbit.run(5); // White Rabbit runs with speed 5.
rabbit.stop(); // White Rabbit stopped. White rabbit hides!

Now Rabbit has the stop method that calls the parent super.stop() in the process.

Arrow functions have no super

As was mentioned in the chapter Arrow functions revisited, arrow functions do not have super.

If accessed, it’s taken from the outer function. For instance:

class Rabbit extends Animal {
  stop() {
    setTimeout(() => super.stop(), 1000); // call parent stop after 1sec
  }
}

The super in the arrow function is the same as in stop(), so it works as intended. If we specified a “regular” function here, there would be an error:

// Unexpected super
setTimeout(function() { super.stop() }, 1000);

Overriding constructor

With constructors it gets a little bit tricky.

Till now, Rabbit did not have its own constructor.

According to the specification, if a class extends another class and has no constructor, then the following constructor is generated:






class Rabbit extends Animal {
  // generated for extending classes without own constructors
  constructor(...args) {
    super(...args);
  }
}

As we can see, it basically calls the parent constructor passing it all the arguments. That happens if we don’t write a constructor of our own.

Now let’s add a custom constructor to Rabbit. It will specify the earLength in addition to name:





































class Animal {
  constructor(name) {
    this.speed = 0;
    this.name = name;
  }
  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    this.speed = 0;
    this.name = name;
    this.earLength = earLength;
  }

  // ...
}

// Doesn't work!
let rabbit = new Rabbit("White Rabbit", 10); // Error: this is not defined.

Whoops! We’ve got an error. Now we can’t create rabbits. What went wrong?

The short answer is: constructors in inheriting classes must call super(...), and (!) do it before using this.

…But why? What’s going on here? Indeed, the requirement seems strange.

Of course, there’s an explanation. Let’s get into details, so you’d really understand what’s going on.

In JavaScript, there’s a distinction between a “constructor function of an inheriting class” and all others. In an inheriting class, the corresponding constructor function is labelled with a special internal property [[ConstructorKind]]:"derived".

The difference is:

  • When a normal constructor runs, it creates an empty object as this and continues with it.
  • But when a derived constructor runs, it doesn’t do it. It expects the parent constructor to do this job.

So if we’re making a constructor of our own, then we must call super, because otherwise the object with this reference to it won’t be created. And we’ll get an error.

For Rabbit to work, we need to call super() before using this, like here:







































class Animal {

  constructor(name) {
    this.speed = 0;
    this.name = name;
  }

  // ...
}

class Rabbit extends Animal {

  constructor(name, earLength) {
    super(name);
    this.earLength = earLength;
  }

  // ...
}

// now fine
let rabbit = new Rabbit("White Rabbit", 10);
alert(rabbit.name); // White Rabbit
alert(rabbit.earLength); // 10

Super: internals, [[HomeObject]]

Let’s get a little deeper under the hood of super. We’ll see some interesting things by the way.

First to say, from all that we’ve learned till now, it’s impossible for super to work.

Yeah, indeed, let’s ask ourselves, how it could technically work? When an object method runs, it gets the current object as this. If we call super.method() then, how to retrieve the method? Naturally, we need to take the method from the prototype of the current object. How, technically, we (or a JavaScript engine) can do it?

Maybe we can get the method from [[Prototype]] of this, as this.__proto__.method? Unfortunately, that doesn’t work.

Let’s try to do it. Without classes, using plain objects for the sake of simplicity.

Here, rabbit.eat() should call animal.eat() method of the parent object:














let animal = {
  name: "Animal",
  eat() {
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  name: "Rabbit",
  eat() {
    // that's how super.eat() could presumably work
    this.__proto__.eat.call(this); // (*)
  }
};

rabbit.eat(); // Rabbit eats.

At the line (*) we take eat from the prototype (animal) and call it in the context of the current object. Please note that .call(this) is important here, because a simple this.__proto__.eat() would execute parent eat in the context of the prototype, not the current object.

And in the code above it actually works as intended: we have the correct alert.

Now let’s add one more object to the chain. We’ll see how things break:

























let animal = {
  name: "Animal",
  eat() {
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  eat() {
    // ...bounce around rabbit-style and call parent (animal) method
    this.__proto__.eat.call(this); // (*)
  }
};

let longEar = {
  __proto__: rabbit,
  eat() {
    // ...do something with long ears and call parent (rabbit) method
    this.__proto__.eat.call(this); // (**)
  }
};

longEar.eat(); // Error: Maximum call stack size exceeded

The code doesn’t work anymore! We can see the error trying to call longEar.eat().

It may be not that obvious, but if we trace longEar.eat() call, then we can see why. In both lines (*) and (**) the value of this is the current object (longEar). That’s essential: all object methods get the current object as this, not a prototype or something.

So, in both lines (*) and (**) the value of this.__proto__ is exactly the same: rabbit. They both call rabbit.eatwithout going up the chain in the endless loop.

Here’s the picture of what happens:

  1. Inside longEar.eat(), the line (**) calls rabbit.eat providing it with this=longEar.

    // inside longEar.eat() we have this = longEar
    this.__proto__.eat.call(this) // (**)
    // becomes
    longEar.__proto__.eat.call(this)
    // that is
    rabbit.eat.call(this);
  2. Then in the line (*) of rabbit.eat, we’d like to pass the call even higher in the chain, but this=longEar, so this.__proto__.eat is again rabbit.eat!

    // inside rabbit.eat() we also have this = longEar
    this.__proto__.eat.call(this) // (*)
    // becomes
    longEar.__proto__.eat.call(this)
    // or (again)
    rabbit.eat.call(this);
  3. …So rabbit.eat calls itself in the endless loop, because it can’t ascend any further.

The problem can’t be solved by using this alone.

[[HomeObject]]

To provide the solution, JavaScript adds one more special internal property for functions: [[HomeObject]].

When a function is specified as a class or object method, its [[HomeObject]] property becomes that object.

This actually violates the idea of “unbound” functions, because methods remember their objects. And [[HomeObject]] can’t be changed, so this bound is forever. So that’s a very important change in the language.

But this change is safe. [[HomeObject]] is used only for calling parent methods in super, to resolve the prototype. So it doesn’t break compatibility.

Let’s see how it works for super – again, using plain objects:

























let animal = {
  name: "Animal",
  eat() {         // [[HomeObject]] == animal
    alert(`${this.name} eats.`);
  }
};

let rabbit = {
  __proto__: animal,
  name: "Rabbit",
  eat() {         // [[HomeObject]] == rabbit
    super.eat();
  }
};

let longEar = {
  __proto__: rabbit,
  name: "Long Ear",
  eat() {         // [[HomeObject]] == longEar
    super.eat();
  }
};

longEar.eat();  // Long Ear eats.

Every method remembers its object in the internal [[HomeObject]] property. Then super uses it to resolve the parent prototype.

[[HomeObject]] is defined for methods defined both in classes and in plain objects. But for objects, methods must be specified exactly the given way: as method(), not as "method: function()".

In the example below a non-method syntax is used for comparison. [[HomeObject]] property is not set and the inheritance doesn’t work:















let animal = {
  eat: function() { // should be the short syntax: eat() {...}
    // ...
  }
};

let rabbit = {
  __proto__: animal,
  eat: function() {
    super.eat();
  }
};

rabbit.eat();  // Error calling super (because there's no [[HomeObject]])

Static methods and inheritance

The class syntax supports inheritance for static properties too.

For instance:

class Animal {

  constructor(name, speed) {
    this.speed = speed;
    this.name = name;
  }

  run(speed = 0) {
    this.speed += speed;
    alert(`${this.name} runs with speed ${this.speed}.`);
  }

  static compare(animalA, animalB) {
    return animalA.speed - animalB.speed;
  }

}

// Inherit from Animal
class Rabbit extends Animal {
  hide() {
    alert(`${this.name} hides!`);
  }
}

let rabbits = [
  new Rabbit("White Rabbit", 10),
  new Rabbit("Black Rabbit", 5)
];

rabbits.sort(Rabbit.compare);

rabbits[0].run(); // Black Rabbit runs with speed 5.

Now we can call Rabbit.compare assuming that the inherited Animal.compare will be called.

How does it work? Again, using prototypes. As you might have already guessed, extends also gives Rabbit the [[Prototype]] reference to Animal.

So, Rabbit function now inherits from Animal function. And Animal function normally has [[Prototype]] referencing Function.prototype, because it doesn’t extend anything.

Here, let’s check that:

class Animal {}
class Rabbit extends Animal {}

// for static propertites and methods
alert(Rabbit.__proto__ === Animal); // true

// and the next step is Function.prototype
alert(Animal.__proto__ === Function.prototype); // true

// that's in addition to the "normal" prototype chain for object methods
alert(Rabbit.prototype.__proto__ === Animal.prototype);

This way Rabbit has access to all static methods of Animal.

No static inheritance in built-ins

Please note that built-in classes don’t have such static [[Prototype]] reference. For instance, Object has Object.definePropertyObject.keys and so on, but ArrayDate etc do not inherit them.

Here’s the picture structure for Date and Object:

Note, there’s no link between Date and Object. Both Object and Date exist independently. Date.prototype inherits from Object.prototype, but that’s all.

Such difference exists for historical reasons: there was no thought about class syntax and inheriting static methods at the dawn of JavaScript language.

Natives are extendable

Built-in classes like Array, Map and others are extendable also.

For instance, here PowerArray inherits from the native Array:

// add one more method to it (can do more)
class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }
}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false

let filteredArr = arr.filter(item => item >= 10);
alert(filteredArr); // 10, 50
alert(filteredArr.isEmpty()); // false

Please note one very interesting thing. Built-in methods like filtermap and others – return new objects of exactly the inherited type. They rely on the constructor property to do so.

In the example above,

arr.constructor === PowerArray

So when arr.filter() is called, it internally creates the new array of results exactly as new PowerArray. And we can keep using its methods further down the chain.

Even more, we can customize that behavior. The static getter Symbol.species, if exists, returns the constructor to use in such cases.

For example, here due to Symbol.species built-in methods like mapfilter will return “normal” arrays:




























class PowerArray extends Array {
  isEmpty() {
    return this.length === 0;
  }

  // built-in methods will use this as the constructor
  static get [Symbol.species]() {
    return Array;
  }
}

let arr = new PowerArray(1, 2, 5, 10, 50);
alert(arr.isEmpty()); // false

// filter creates new array using arr.constructor[Symbol.species] as constructor
let filteredArr = arr.filter(item => item >= 10);

// filteredArr is not PowerArray, but Array
alert(filteredArr.isEmpty()); // Error: filteredArr.isEmpty is not a function

We can use it in more advanced keys to strip extended functionality from resulting values if not needed. Or, maybe, to extend it even further.

Tasks

importance: 5

Here’s the code with Rabbit extending Animal.

Unfortunately, Rabbit objects can’t be created. What’s wrong? Fix it.

















class Animal {

  constructor(name) {
    this.name = name;
  }

}

class Rabbit extends Animal {
  constructor(name) {
    this.name = name;
    this.created = Date.now();
  }
}

let rabbit = new Rabbit("White Rabbit"); // Error: this is not defined
alert(rabbit.name);
importance: 5

We’ve got a Clock class. As of now, it prints the time every second.

Create a new class ExtendedClock that inherits from Clock and adds the parameter precision – the number of msbetween “ticks”. Should be 1000 (1 second) by default.

  • Your code should be in the file extended-clock.js
  • Don’t modify the original clock.js. Extend it.

Open the sandbox for the task.

importance: 5

As we know, all objects normally inherit from Object.prototype and get access to “generic” object methods like hasOwnProperty etc.

For instance:












class Rabbit {
  constructor(name) {
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

// hasOwnProperty method is from Object.prototype
// rabbit.__proto__ === Object.prototype
alert( rabbit.hasOwnProperty('name') ); // true

But if we spell it out explicitly like "class Rabbit extends Object", then the result would be different from a simple "class Rabbit"?

What’s the difference?

Here’s an example of such code (it doesn’t work – why? fix it?):

class Rabbit extends Object {
  constructor(name) {
    this.name = name;
  }
}

let rabbit = new Rabbit("Rab");

alert( rabbit.hasOwnProperty('name') ); // true


JavaScript の ジェネレータ を極める!
(https://qiita.com/kura07/items/d1a57ea64ef5c3de8528)

 この記事は最終更新日から1年以上が経過しています。

ECMAScript 6(2015年6月に公開され、今もなお比較的新しい JavaScript)の大目玉である イテレータ と ジェネレータ。なかなかに複雑で巨大な仕組みになっていてややこしいです。
そこで今回は ジェネレータ を、順を追って理解できるように解説したいと思います。

また、実用的なサンプルを「3. 実用サンプル」に示しました。
初めにこちらを見て、何ができるのかを知ってから読み始めるのもオススメです。

(2017年3月現在、オープンなページでの使用はまだ避けたほうがいいかもしれませんが、実装は確実に進んでいます。ECMAScript 6 compatibility table

1. ジェネレータ、ジェネレータ関数 とは

ジェネレータ は、イテレータ を強力にサポート するものです。

例えば、1~20の数を順番に取り出す for-of文 は、以下のように書くことができます。
ジェネレータ は使っていません。)

1~20の数を順番に取り出すfor-of文
var ary = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20];
for(var num of ary) console.log(num);
/*
  1
  2
  3
  ...
  20
*/

この書き方でも十分に分かりやすいです。
しかし、取り出す数が1ずつ増えていくという処理を、関数でスマートに書きたいものです。
そこで、ジェネレータ を使えばもっとスマートに書くことができます

ジェネレータを使って1~20の数を順番に取り出すfor-of文
function* gfn(from, to){ while(from <= to) yield from++; }
var g = gfn(1, 20);
for(var num of g) console.log(num);
/*
  1
  2
  3
  ...
  20
*/

このような簡単な例だけでなく、2倍ずつにして順番に取り出したり、フィボナッチ数列を順番に取り出したりすることもできます。
このように、ジェネレータ は イテレータ を強力にサポートすることができるのです。

ここにおいて、

  • ジェネレータ関数 は、function* gfn(from, to){ while(from <= to) yield from++; }
  • ジェネレータ は、gfn(1, 20) のように ジェネレータ関数 から得ることのできるオブジェクト

を、それぞれ指す用語です。

ジェネレータ は、イテラブル であり、なおかつ イテレータ でもあります。
つまり、Qiita: JavaScript の イテレータ を極める! の 2.6.2. イテラブルなオブジェクト の利用法 で紹介したような利用法ができます。

2. ジェネレータ を使う

2.1. ジェネレータ関数 の書き方、使い方

便利な ジェネレータ関数 の書き方を学びましょう。といっても、普通の関数の書き方とほとんど違いはありません。
普通の関数と違う点は、以下のたった2点だけです。

  • ジェネレータ関数 は、function* gfn(){} または var gfn = function*(){}; のように、functionのあとに * を記述する必要がある
  • ジェネレータ関数 では、yield 及び yield* を使うことができる

例として簡単な ジェネレータ関数 を見てみましょう。

ジェネレータ関数
function* gfn(){
    var a = yield 0;
    yield* [1, a, 5];
}

これで ジェネレータ関数は完成しました。
ジェネレータ関数 から ジェネレータ を作るには、単に gfn() のように記述すればオッケーです。
ただし、gfn() の時点では関数の中身が実行されないことは要注意です
関数の中身は、gfn() で生成された ジェネレータ から .next() で値を取り出す時点で実行されます。

ジェネレータ関数からジェネレータを作って実行する
function* gfn(){
    var a = yield 0;
    yield* [1, a, 5];
}

var g = gfn(); // ジェネレータを作った。この時点ではまだ関数の中身は実行されない

// g.next() を実行すると、関数の中身が順番に実行される
console.log( g.next() ); //  { value: 0, done: false }
console.log( g.next(3) ); // { value: 1, done: false }
console.log( g.next() ); //  { value: 3, done: false }
console.log( g.next() ); //  { value: 5, done: false }
console.log( g.next() ); //  { value: undefined, done: true }

それでは、もっと簡単なコードを例にして、ジェネレータ関数 の仕組みを見てみましょう。

2.2. yield

yield式
function* gfn(n){
    n++;
    yield n;
    n *= 2;
    yield n;
    n = 0;
    yield n;
}

var g = gfn(10); // ジェネレータを作った

console.log( g.next() ); // { value: 11, done: false }
// n++; が実行された後、yield n; によって n の値が返された。

ジェネレータ を作って .next() を実行すると、最初の yield が出てくるまで関数が実行されます
yield まで関数が実行されると、関数の実行はいったん停止し、イテレータリザルトとして値が返されます

再び .next() を実行すると、いったん停止した位置から再び関数が再開され、次の yield まで実行されます
最後まで関数が実行されると、イテレータリザルトの .done が true になり、関数の実行が終了します。

yield式
function* gfn(n){
    n++;
    yield n;
    n *= 2;
    yield n;
    n = 0;
    yield n;
}

var g = gfn(10); // ジェネレータを作った

console.log( g.next() ); // { value: 11, done: false }
// n++; が実行された後、yield n; によって n の値が返された。

console.log( g.next() ); // { value: 22, done: false }
// n *= 2; が実行された後、yield n; によって n の値が返された。

console.log( g.next() ); // { value: 0, done: false }
// n = 0; が実行された後、yield n; によって n の値が返された。

console.log( g.next() ); // { value: undefined, done: true }
// 関数の実行が終了したので、.done が true になった。

2.3. ジェネレータに値を渡す

.next(val) のように値を渡してやることで、ジェネレータに値を渡すことができます

ジェネレータに値を渡す
function* gfn(){
    var a = yield "first";
    var b = yield "second";
    yield a + b;
}

var g = gfn();

console.log( g.next() ); // { value: "first", done: false }

console.log( g.next(3) ); // { value: "second", done: false }
// yield "first" の部分が 3 に置き換えられる

console.log( g.next(5) ); // { value: 8, done: false }
// yield "second" の部分が 5 に置き換えられる

console.log( g.next() ); // { value: undefined, done: true }

g.next(3) などを実行することで、ジェネレータ関数の中身に値を渡しています。
渡した値は、直前にいったん停止した yield と置き換えられたように渡されます。

2.4. yield*

yield のほかに yield* という便利な式があります。
yield* には イテラブルなオブジェクト を与えます。
すると、イテラブルなオブジェクト から順番に値を取り出し、それぞれの値に対して yield を行ってくれます

yield*式
function* gfn(){
    yield* [1, 3, 5];
}

var g = gfn();

console.log( g.next() ); // { value: 1, done: false }
console.log( g.next() ); // { value: 3, done: false }
console.log( g.next() ); // { value: 5, done: false }
console.log( g.next() ); // { value: undefined, done: true }
yield*式
function* gfn(){
    yield* "ひよこ";
}

var g = gfn();

console.log( g.next() ); // { value: "ひ", done: false }
console.log( g.next() ); // { value: "よ", done: false }
console.log( g.next() ); // { value: "こ", done: false }
console.log( g.next() ); // { value: undefined, done: true }

つまり、for(var v of iterable) yield v; と同様の処理を行っているというわけです。

2.5. 簡単なサンプル

全て、ジェネレータが イテラブルなオブジェクト であることを利用したサンプルです。

ジェネレータを利用する
function* gfn(){
    yield 1;
    yield* [2, 1, 2];
}

for(var num of gfn()) console.log(num);
/*
  1
  2
  1
  2
*/

console.log( [...gfn()] ); // [1, 2, 1, 2]

console.log( Math.max(...gfn()) ); // 2

var [a, b, c, d] = gfn();
console.log(a, b, c, d); // 1, 2, 1, 2

console.log( new Set(gfn()) ); // Set {1, 2}

2.6. ジェネレータ のもう一つの利用法

今まで見てきた ジェネレータ は、イテレータ として利用することに重点を置いてきました。

しかし、見方を変えれば、ジェネレータ はもう一つの使い方ができます。
それは、自由に途中でいったん停止できる関数 という見方です。

ページをクリックするたびにひとつずつアラートを出す
function* gfn(){
    alert("こんにちは!"); yield;
    alert("良い天気ですね。"); yield;
    alert("さようなら!");
}
var g = gfn();
document.onclick = function(){ g.next(); }; // ページをクリックするたびに g.next(); を実行する

コード中の yield の時点で、関数の実行をいったん停止することができる と考えると、分かりやすいかと思います。
これは、非同期処理をする際に、かなりの力を発揮します。

3. 実用サンプル

ジェネレータ で説明することは以上ですが、実際のサンプルがないと、どのようにつかえるかのイメージがつきにくいと思います。
いくつかサンプルを挙げますので、参考にしてください。

1000以下のフィボナッチ数を列挙する
function* fibonacci(){
    var a = 0, b = 1, temp;
    while(true){
        temp = a + b;
        a = b; b = temp;
        yield a;
    }
}

var g = fibonacci();
for(var num of g){
    if(num > 1000) break;
    console.log(num);
}
/*
  1
  2
  3
  5
  8
  13
  21
  34
  55
  89
  144
  233
  377
  610
  987
*/
ランダムな自然数の配列を作る
function* randomIntArray(max, len){
    for(var i=0;i<len;i++) yield Math.floor(Math.random() * max) + 1;
}

console.log( [...randomIntArray(2, 10)] ); // 例:[1, 2, 1, 1, 1, 2, 2, 2, 1, 2]
console.log( [...randomIntArray(6, 4)] ); // 例:[1, 6, 2, 4]
組み合わせを順番に取り出す
function* combination(ary, len){
    yield* (function* gfn(a, ary){
        if(a.length < len){
            for(var i=0;i<ary.length-len+a.length+1;i++){
                yield* gfn(a.concat(ary[i]), ary.slice(i+1));
            }
        }
        else yield a;
    })([], ary);
}

for(v of combination([1,2,3], 2)) console.log(v);
/*
  [1, 2]
  [1, 3]
  [2, 3]
*/

for(v of combination(["A", "B", "C", "D", "E"], 3)) console.log(v.join(""));
/*
  ABC
  ABD
  ABE
  ACD
  ACE
  ADE
  BCD
  BCE
  BDE
  CDE
*/
非同期処理を分かりやすく書く
function easyAsync(gfn){
    var g = gfn();
    (function ok(value){
        var result = g.next(value);
        if(!result.done) result.value(ok);
    })();
}

easyAsync(function*(){
    alert("こんにちは!");
    yield function(ok){
        setTimeout(ok, 3000);
    };
    alert("3秒たちました!");
    yield function(ok){
        document.onclick = ok;
    };
    document.onclick = null;
    alert("ページをクリックしました!");
    var sourse = yield function(ok){
        var xhr = new XMLHttpRequest();
        xhr.open("GET", location.href, true);
        xhr.send(null);
        xhr.onload = function(){ ok(xhr.responseText); };
    };
    alert("このページのソースは" + sourse.length + "文字です!");
});

4. 参考

ECMAScript 2015 Language Specification – ECMA-262 6th Edition
Iterators and generators - JavaScript | MDN
ジェネレータについて - JS.next
ECMAScript 6 compatibility table


https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/function*

function* 선언 (끝에 별표가 있는 function keyword) 은 generator function 을 정의하는데이 함수는 Generator 객체를 반환니다.

generator function 은 GeneratorFunction 생성자와 function* expression 을 사용해서 정의할 수 있습니다.

문법

function* name([param[, param[, ... param]]]) {
   statements
}
name
함수명.
param

함수에 전달되는 인수의 이름. 함수는 인수를 255개까지 가질 수 있다.
statements
함수의 본체를 구성하는 구문들.

return
generator 객체

설명

Generator는 빠져나갔다가 중간에 처리를 멈추어 놓고 나중에 다시 돌아올 수 있는 함수입니다. 이때 컨텍스트(변수 값)는 출입 과정에서 저장된 상태로 남아 있습니다.

Generator 함수는 호출되어도 즉시 실행되지 않고, 대신 함수를 위한 Iterator 객체가 반환됩니다. Iterator의 next() 메서드를 호출하면 Generator 함수가 실행되어 yield 문을 만날 때까지 진행하고, 해당 표현식이 명시하는 Iterator로부터의 반환값을 반환합니다. yield* 표현식을 마주칠 경우, 다른 Generator 함수가 위임(delegate)되어 진행됩니다.

이후 next() 메서드가 호출되면 진행이 멈췄던 위치에서부터 재실행합니다. next() 가 반환하는 객체는 yield문이 반환할 값(yielded value)을 나타내는 value 속성과, Generator 함수 안의 모든 yield 문의 실행 여부를 표시하는 boolean 타입의 done 속성을 갖습니다. next() 를 인자값과 함께 호출할 경우, 진행을 멈췄던 위치의 yield 문을  next() 메서드에서 받은 인자값으로 치환하고 그 위치에서 다시 실행하게 됩니다.

generator와 yield는 promise와는 아무런 관련이 없다. yield는 그 자체만으로는 동기처리를 해주는 마법의 도구가 아니다. 결국 동기처리를 위해서는 promise가 필요하며, 중지와 재개라는 성격을 가진 generator-yield는 promise처리를 보기 쉽게 도와주는 기능만 할 뿐이다.

예시

간단한 예제

function* idMaker(){
  var index = 0;
  while(index < 3)
    yield index++;
}

var gen = idMaker();

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // undefined
// ...

yield* 를 사용한 예제

function* anotherGenerator(i) {
  yield i + 1;
  yield i + 2;
  yield i + 3;
}

function* generator(i){
  yield i;
  yield* anotherGenerator(i);
  yield i + 10;
}

var gen = generator(10);

console.log(gen.next().value); // 10
console.log(gen.next().value); // 11
console.log(gen.next().value); // 12
console.log(gen.next().value); // 13
console.log(gen.next().value); // 20

Generator 에 인자값을 넘기는 예제

function* logGenerator() {
  console.log(yield);
  console.log(yield);
  console.log(yield);
}

var gen = logGenerator();

// the first call of next executes from the start of the function
// until the first yield statement
gen.next(); 
gen.next('pretzel'); // pretzel
gen.next('california'); // california
gen.next('mayonnaise'); // mayonnaise

Generator 는 생성자로서 사용될 수 없다

function* f() {}
var obj = new f; // throws "TypeError: f is not a constructor"




Promise와 Yield

const co = require('co');
const gT2 = require(__dirname + '/generatorTest2');

function p(str) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(str);
resolve(str+1);
}, 1000);
});
}

//동기화를 generator로 구현한 경우
co(function* genTest1() {
var res1 = yield p(1);
var res2 = yield p(res1);
console.log('generator1 then end');
});

//1,2, generator1 then end 가 호출된다.

yield p(1)의 결과값은 promise의 resolve()에서 ()괄호 안의 값이 된다.
・왜 위의 소스 코드에서는 generator를 .next 하지 않았는데, yield값이 반환되는지 의아할 것이다. 사실 .next로 제너레이터를 일일이 핸들링 하는 것은 매우 귀찮고 까다로운 일이다. 이를 자동으로 해주는 모듈이 co 모듈이다. co모듈 안에 제너레이터를 담으면, 제너레이터의 yield 이후의 구문이 끝날때 까지 기다렸다가 자동으로  다음 yield로 넘겨준다.


co모듈과 Yield

//generatorTest1.js
const co = require('co');
const gT2 = require(__dirname + '/generatorTest2');

function p(str) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(str);
resolve(str+1);
}, 1000);
});
}

//동기화를 generator로 구현한 경우
co(function* genTest1() {
var res1 = yield p(1);
var res2 = yield p(res1);
var res3 = yield gT2.genTest2(res2);
console.log('generator1 then end');
});

//generatorTest2.js
function p(str) {
return new Promise(function(resolve) {
setTimeout(function() {
console.log(str);
resolve(str+1);
}, 1000);
});
}


exports.genTest2 = function* (res0) {
var res1 = yield p(res0);
var res2 = yield p(res1);
console.log('generator2 then end');
}

・co 모듈의 가장 좆되는 점은, 가장 상위의 모듈에서만 co를 사용하면, 상위의 모듈에서 import하는 하위의 모듈에서 까지 yield를 자동 next처리 해준다는 점이다!!
이때 알아야 하는 것은 언제 co가 next를 호출해 주느냐 하는 것인데, co는 yield 이후 함수와 그 함수와 연결된 함수들을 체인처럼 쭉 보고 있다가 promise함수가 끝나면 next()를 자동으로 처리하게 되어 다음 yield로 순번을 넘겨준다.
 위의 예에서보면, generatorTest2.js는 co모듈을 따로 사용하고 있지 않지만, co모듈을 사용하는
generatorTest1.js에서 호출 되고 있기 때문에 next()처리를 자동으로 하게된다.
yield p(1)의 결과값은 promise의 resolve()에서 ()괄호 안의 값이 된다. 
・왜 위의 소스 코드에서는 generator를 .next 하지 않았는데, yield값이 반환되는지 의아할 것이다. 사실 .next로 제너레이터를 일일이 핸들링 하는 것은 매우 귀찮고 까다로운 일이다. 이를 자동으로 해주는 모듈이 co 모듈이다. co모듈 안에 제너레이터를 담으면, 제너레이터의 yield 이후의 구문이 끝날때 까지 기다렸다가 자동으로  다음 yield로 넘겨준다.



Javascript의 Iterator와 Generator

원문
http://www.ociweb.com/resources/publications/sett/javascript-iterators-and-generators

ECMAScript 2015(a.k.a. ES6)에서 새롭고 많은 기능들이 추가되었다. 대부분은 이해하기 쉽지만, Iterator와 Generator를 이해하기 위해선 조금 더 노력이 필요하다.
이 글은 여러분이 Iterator와 Generator를 활용할 수 있도록 가이드(base line)를 제공한다.

(역: 예제코드는 NodeJS-v4.2.1(V8) 지원 문법을 기준으로 변경하였다. 원문은 ES6, Firefox 지원 문법 기준)

이 글은 ES6의 문법인 arrow, class, destructing, for-of, let/const, spread operator등을 사용하였는데, 문법들에 대한 이해가 필요하다면 아래 사이트들을 참고하도록 한다.

  1. https://github.com/lukehoban/es6features
  2. http://java.ociweb.com/mark/
  3. (역: NHNEnt-javascript에 ES6의 문법들을 잘 설명한 글들이 있다.)

Iterator

Iterator는 next메서드를 가지고있는 객체이다. 이 메서드는 순차적으로 원소들을 탐색하며, next메서드의 호출시마다 새로운 객체를 반환한다. 반환되는 객체는 value 와(또는 = and/or) done 프로퍼티를 가지고 있으며, 탐색이 완료될 때 done프로퍼티의 값이 true가 된다. 대부분의 Iterator들은 탐색이 완료될때, value를 생략한다. —> return {done: true};

Iterable은 Symbol.iterator라는 메서드를 가지고있는 객체이다. 이 메서드가 Iterator를 반환한다.

Iterable/Iterator Example

피보나치 수열을 생성하는 코드를 작성하였다. fibonacci변수가 참조하고 있는 객체는 Iterable이다.

const fibonacci = {
    [Symbol.iterator]() {
        let n1 = 0, n2 = 1, value;
        return {
            next() {
                value = n1;
                n1 = n2;
                n2 = value + n2;
                // [value, n1, n2] = [n1, n2, n1 + n2]

                if (value > 100) {
                    return {done: true};
                } else {
                    return {value};
                }
            }
        };
    }
};

for (const n of fibonacci) {
    console.log(n);
}

fibonacci

Iterable Object

아래는 기본적인 Iterable 객체/타입들이다.

  1. Array
  2. Set
  3. Map - 원소들을 key/value 쌍으로 탐색한다.
  4. DOM NodeList - Node객체들을 탐색한다.
  5. primitive string - 각 유니코드별로 탐색한다.

Array(typed array 포함)SetMap의 아래 메서드들은 Iterator를 반환한다.

  1. entries - key,value쌍 [key, value]
  2. keys
  3. values

이 메서드들에서 리턴되는 객체는 Iterable이면서 Iterator이다.
Array에서 key는 인덱스들을 나타내며, Set에서는 key,value가 둘 다 value로 같다.

커스텀 객체는 Symbol.iterator 메서드를 추가함으로써 iterable이 될 수 있다.

function objectEntries(obj) {
    let index = 0,
        keys = Object.getOwnPropertyNames(obj)
                .concat(Object.getOwnPropertySymbols(obj)); // Reflect.ownKeys(obj);

    return {
        [Symbol.iterator]() {
            return this;
        },
        next() {
            if (index === keys.length) {
                return {done: true};
            } else {
                let k = keys[index],
                    v = obj[k];
                index += 1;

                return {value: [k, v]};
            }
        }
    };
}

let obj = {foo: 1, bar: 2, baz: 3};
for (const pair of objectEntries(obj)) { // for (const [k, v] of objectEntries(obj))
    console.log(pair[0], 'is', pair[1]); // console.log(k, 'is', v);
}

iterable

Iterable Consumers

다음 문법들은 Iterable을 사용한다.

for-of loop

for (const value of someIterable) {
    // ...
}

spread Operator

// Iterable의 모든  value들이 arr에 추가된다.
let arr = [firstElem, ...someIterable, lastElem];

// Iterable의 모든 value들이 arguments에 추가된다. 
someFunction(firstArg, ...someIterable, lastArg);

spread

Positional Destructing

// Iterable의 첫 3개 값들이 저장된다.
let [a, b, c] = someIterable;

positional-destructing



Generators

Generator는 Iterable이면서 Iterator인 객체의 특별한 종류이다. 이 객체는 일시정지와 재시작 기능을 여러 반환 포인트들을 통해 사용할 수 있다. 이러한 반환 포인트들은 yield 키워드를 통해 구현할 수 있으며, 오직 generator 함수에서만 사용할 수 있다. next호출시마다 다음 yield의 expression이 반환된다.
yield value를 사용하면 한가지 값을 반환할 수 있고, yield* iterable을 사용하면 해당되는 Iterable의 값들을 순차적으로 반환시킬 수 있다.

Generator의 반복이 끝나는 시점은 3가지 경우인데, generator 함수에서 return 사용, 에러 발생 그리고 마지막으로 함수의 끝부분까지 모두 수행된 이후, 이렇게 3가지 경우이다. 그리고 이때 done 프로퍼티가 true가 될 것이다.

이러한 generator함수는 Generator객체를 반환하며, function*키워드로 정의할 수 있다. 또는 클래스에서 메서드 이름 앞에 *을 붙여 정의할 수도 있다.

A Basic Generator

function* myGenFn() {
    yield 1;
    yield 2;
    return 3;
}

let iterator = myGenFn(),
    capture;

console.log('--------------- do-while loop ---------------');
do {
    capture = iterator.next();
    console.log(capture);
} while (!capture.done);

//또는
console.log('\n--------------- for-of loop ---------------');
for (const n of myGenFn()) {
    console.log(n);
}

generator

return문을 사용하지 않았다면, 마지막 next호출에서 value가 3이 아닌 undefined가 반환되었을 것이다.

Fibonacci Generator

다음은 Generator를 이용한 fibonacci 수열 생성이다.

function* fibonacci() {
    let prev = 0, curr = 1; // [prev, curr] = [0, 1];
    yield prev;
    yield curr;
    while (true) {
        let temp = prev;
        prev = curr;
        curr = temp + curr;
        // [prev, curr] = [curr, prev + curr];
        
        yield curr;
    }
}

for (const n of fibonacci()) {
    if (n > 100) break;
    console.log(n);
}

또한 generator 메서드를 포함하고 있는 객체로 구현할 수 있다.

let fib = {
    *[Symbol.iterator]() {
        let prev = 0, curr = 1; // [prev, curr] = [0, 1];
        yield prev;
        yield curr;
        while (true) {
            let temp = prev;
            prev = curr;
            curr = temp + curr;
            // [prev, curr] = [curr, prev + curr];
            
            yield curr;
        }
    }
};

for (const n of fib) {
  if (n > 100) break;
  console.log(n);
}

Generator Methods

  • next(value)
    이 메서드는 다음 값을 얻는 역할을 하며, Iteraotr의 next메서드와 유사하지만, optional argument를 받는다는 점이 다르다.(첫번째 호출에서는 받지 않고 무시한다.) 이 매개변수는 바로 이전의 yield [expression]의 반환값으로 사용된다. (아래 예시를 참고.)
    generator-next-arguments
    function* fibonacci() {
        var fn1 = 1;
        var fn2 = 1;
        while (true) {  
            var current = fn2;
            fn2 = fn1;
            fn1 = fn1 + current;

            var reset = yield current;
            console.log('----> reset', reset);
            if (reset) {
                fn1 = 1;
                fn2 = 1;
            }
        }
    }

    var sequence = fibonacci();
    console.log(sequence.next().value);     // 1
    console.log(sequence.next().value);     // 1
    console.log(sequence.next().value);     // 2
    console.log(sequence.next().value);     // 3
    console.log(sequence.next().value);     // 5
    console.log(sequence.next().value);     // 8
    console.log(sequence.next(true).value); // reset = true -> yield 1
    console.log(sequence.next().value);     // 1
    console.log(sequence.next().value);     // 2
    console.log(sequence.next().value);     // 3
  • return(value)
    이 메서드는 매개변수로 온 값을 value로써 반환하고, Generator를 종료시킨다.
    {value: value, done: true}

  • throw(exception)
    이 메서드는 인자로 받은 에러 발생시키고, Generator를 종료시킨다. generator함수 내부의 catch 구문을 통해 처리할 수도 있다. 또한 내부에서 catch 구문으로 처리한 경우 Generator는 종료되지 않는다.
    generator-throw-error

Summary

Iterator가 훌륭하다면 Generator는 더욱 훌륭하다. for-of loop, spread operator, destructing을 완벽하게 활용하기 위해서 Iterator와 Generator에 대한 이해가 중요하다

Node.jsのexportsについて
(https://numb86-tech.hatenablog.com/entry/2016/07/20/202744)

Node.jsにはモジュール機能があり、他のファイルの内容を読み込むことが出来る。
だが、何となくでしか使い方を理解していなかったので、調べてみた。

このエントリのコードは全て、v4.4.3で動かした。

基本

まず、呼び出される側のファイルで、モジュール化する値やオブジェクトをexportsしておく。
そうすることで、その値やオブジェクトに外部からアクセスすることが可能になる。

呼び出す側ではrequire()関数を使い、ファイルを読み込む。
読み込んだファイルから、exportsされている値やオブジェクトにアクセスできるようになる。

// parts.js
var value = '値';
var foo = 'foo';
exports.value = value;

// index.js
var parts = require('./parts.js');
console.log(parts.value);   // 値
console.log(parts.foo); // undefined

ファイルを読み込んでも、exportsされていない値(上記のケースではfoo)には、アクセス出来ない

下記のような書き方も出来る。

// parts.js
module.exports = {
    value: '値',
    foo: 'foo'
};

// index.js
var parts = require('./parts.js');
console.log(parts.value);   // 値
console.log(parts.foo); // foo
// parts.js
module.exports.value = '値';
module.exports.foo = 'foo';

// index.js
var parts = require('./parts.js');
console.log(parts.value);   // 値
console.log(parts.foo); // foo

module.exportsとexports

module.exportsexportsという書き方が出てきた。
何が違うのだろうか。
ネットで軽く調べた限り、同じものとして扱って構わないらしい。

exports = module.exports = {}

という図式であり、exportsmodule.exportsを参照している、らしい。

だから、

exports.hoge = 'hoge';
exports.fuga = 'fuga';
.
.
.

という書き方をしていれば、exports = module.exportsの図式は壊れない。

だが例えば、module.exportsに新しくオブジェクトを割り当ててしまうと、exports = module.exportsのリンクは途切れてしまう。

それにより、以下の様な挙動が発生する。

// parts.js
var value = '値';
var foo = 'foo';
module.exports = {
    fuga: 'fuga'
};
exports.value = value;

// index.js
var parts = require('./parts.js');
console.log(parts.value);   // undefined
console.log(parts.fuga);    // fuga

なぜvalueundefinedになってしまうのかと言えば、module.exportsに新しくオブジェクトを割り当ててしまい、リンクが途切れ、module.exportsexportsはそれぞれ別のものに指すようになってしまったから。

parst.jsを実行させてみることで、そのことが確認出来る。

var value = '値';
var foo = 'foo';
console.log(module.exports === exports);    // true
module.exports = {
    fuga: 'fuga'
};
exports.value = value;
console.log(module.exports === exports);    // false

元々両者は同じものを指していたが、module.exportsの再定義によってそれが変わってしまっている。

そして、require()で読み込むのは、読み込んだファイルのmodule.exportsである。
そのため、この状態では、exportsにいくら値を設定しても、外部ファイルがそれを呼び出すことは出来ない。

まとめると、

  • module.exportsというオブジェクトを、require()によって外部ファイルが呼び出すことが出来る
  • 初期状態では、exportsmodule.exportsを参照しており、両者は同じものを指している

ということになる。

require()すると、対象となるファイルの中身が実行される

require()は、ただ単にmodule.exportsを取得するだけでなく、そのファイルの中身を実行する。

// parts.js
console.log('ファイルが読み込まれました。');

// index.js
var parts = require('./parts.js');  // ファイルが読み込まれました。
console.log(parts); // {}
// 以下は何も表示されない
require('./parts5.js');

ファイルの中身を実行してmodule.exportsを返す、というのがrequire()の挙動のようだ。

注意点として、require()は、同じファイルに対しては1度しか実行されないようだ。
そのため上記のケースでは、2回目のrequire()では何も表示されなかった。

// parts.js
var int = 0;
int++;
module.exports.int = int;

// index.js
var parts = require('./parts.js');
console.log(parts.int);   // 1
require('./parts5.js');
console.log(parts.int); // 1

require()の結果を別の変数に割り当てようとしても同様。require()自体が1度しか実行されない。

// parts.js
var int = 0;
int++;
module.exports.int = int;

// index.js
var parts = require('./parts.js');
console.log(parts.int);   // 1
var parts2 = require('./parts5.js');
console.log(parts2.int); // 1

この、「require()は同一ファイルに対して1度のみ実行され」、「その結果は記憶されている」という仕様により、次のような特徴が生まれる。

クロージャ

記述の通り、module.exportsというオブジェクトに入っていない値は、呼び出せない。
しかし、module.exportsを通して間接的にアクセスすることは出来る。

// parts.js
var int = 10;
function myFunc(arg){
    console.log(arg * int);
};
module.exports.myFunc = myFunc;

// index.js
var parts = require('./parts.js');
console.log(parts.int);   // undefined
parts.myFunc(2);    // 20

上記の例では、エクスポートされているのはmyFuncのみ。だから、エクスポートされていないintにアクセスしようとしてもundefinedになる。
しかしmyFuncにおいてintを使っているため、myFuncを通じて間接的にアクセスできる。

エクスポートしていなくても、必要に応じて値を読みに行く。これは、関数やオブジェクトでも同様である。

// parts.js
function add(a,b){
    return a + b;
};
function showResult(a,b){
    console.log( add(a,b) );
};
module.exports.showResult = showResult;

// index.js
var parts = require('./parts.js');
parts.showResult(2,3);  // 5
console.log('showResult' in parts);  // true
console.log('add' in parts); // false

showResult()の中で、エクスポートされていないadd()が使われているが、問題なく動く。
だが、add()そのものがpartsに入っているわけではない。あくまでも、必要になった時(この場合はshowResult()実行時)に呼び出されるだけである。

この特徴を利用すると、クロージャのような仕組みを作ることが出来る。

// parts.js
var int = 0;
function increment(){
    int++;
};
function get(){
    return int;
};
module.exports.increment = increment;
module.exports.get = get;

// index.js
var parts = require('./parts.js');
console.log(parts.inc); // undefined
console.log(parts.get());   // 0
parts.increment();
console.log(parts.get());   // 1

変数intはエクスポートされていないため、それに直接アクセスすることは出来ない。
だが、関数get()increment()を通して、操作、取得することが出来る。

ディレクト

require()の引数にディレクトリを渡すことも出来る。
そうすると、そのディレクトリのindex.jsを読み込む。
その時、index.jsで他のファイルを読み込みエクスポートしておくことで、複数のファイルを簡単に読み込める。

// ディレクトリ構造
- app.js
- modules/
    - index.js
    - data.js
    - cal.js
// data.js
var int = 1;
module.exports.int = int;

// cal.js
function add(a,b){
    return a + b;
};
module.exports.add = add;

// index.js
module.exports.cal = require('./cal.js');
module.exports.data = require('./data.js');

このような状況では、以下の様な書き方をすることで、data.jscal.jsにアクセスすることが出来る。

// app.js
var myModules = require('./modules');
console.log(myModules.data.int);  // 1
console.log( myModules.cal.add(myModules.data.int,2) );   // 3

参考記事


log4jsをインストールする

$ npm install log4js

ログ出力設定ファイルを作成する注意)Log4jsバージョン2以上から設定ファイルの仕様が変わりました。

log-config.json
{
    "appenders": [
        {
            "type":     "dateFile",
            "category": "system",
            "filename": "logs/system.log",
            "pattern":  "-yyyy-MM-dd"
        },
        {
            "type":     "dateFile",
            "category": "access",
            "filename": "logs/access.log",
            "pattern":  "-yyyy-MM-dd"
        },
        {
            "type":     "dateFile",
            "category": "error",
            "filename": "logs/error.log",
            "pattern":  "-yyyy-MM-dd"
        }
}
  • appenders
    ログの出力処理の指定。ログの出力方式を配列で複数指定可能。

  • type
    ログの出力タイプを指定。

説明
fileファイルに書き出す。
datefile日付毎にローテーションしてファイルに書き出す。
consoleコンソールに書き出す。
  • category
    出力するログのカテゴリ。カテゴリ毎に出力内容を分けることができる。

  • filename
    ログの出力先(ファイル名)

  • pattern
    ファイルの出力パターン

書式説明
yyyy西暦を4桁
yy西暦2桁
MM
dd
hh時間(24時間表示)
mm
ss
sssミリ秒
Oタイムゾーン

上の例では、filenameが"system.log"でpatternが”-yyyy-MM-dd”なのでローテーションのタイミング(AM:0:00)でsystem-yyyy-MM-dd.logになります。


$ ls logs access.log error.log system.log access.log-2014-11-29 error.log-2014-11-29 system.log-2014-11-2



Log4jsバージョン2以上から設定ファイルの仕様

Log4js v1.Xではappendersは配列だったけれど、Log4js v2.Xではappendersはオブジェクト
という大きな仕様変更があったみたいです。

ドキュメントを整理をすると、

  • log4js v2ではappenndersプロパティ、categoriesプロパティが必須のJSONで設定を行う。
  • appendersプロパティはオブジェクトとして、 設定名:{出力設定} というプロパティの列挙で各出力設定を用意する。
  • categoriesプロパティはオブジェクトとして、 カテゴリ名:{適用設定,出力ログレベル} というプロパティの列挙でログ定義をする。
  • categories::defaultプロパティは必須。

コレを上の設定ファイルに当てはめると以下のようになりました。

development.json.うごくやつ
{
    "log4js": {
        "appenders": {
            "access": {
                "type":     "dateFile",
                "filename": "/var/log/app/access.log"
            },
            "error": {
                "type":     "dateFile",
                "filename": "/var/logs/app/error.log"
            },
            "system": {
                "type":     "dateFile",
                "filename": "/var/log/app/system.log"
            },
            "console": {
                "type": "console"
            },
            "stdout": {
              "type": "stdout"
            }
        },
        "categories": {
            "default": {
                "appenders": [
                    "access"
                    ,"console"
                    ,"stdout"
                ]
                ,"level": "INFO"
            },
            "access": {
                "appenders": [
                    "access"
                    ,"console"
                    ,"stdout"
                ]
                ,"level": "INFO"
            },
            "system": {
                "appenders": [
                    "system"
                    ,"console"
                    ,"stdout"
                ]
                ,"level": "ALL"
            },
            "error": {
                "appenders": [
                    "error"
                    ,"console"
                    ,"stdout"
                ]
                ,"level": "WARN"
            }
        }
    }
}

この設定では、出力設定としては

  • access
  • error
  • system
  • console
  • stdout

の5つを用意していて、ログ設定としては

  • access
    • 出力設定→access,console,stdout
  • error
    • 出力設定→error,console,stdout
  • system
    • 出力設定→system,console,stdout

の3つを用意しています。この時、アプリケーションから使えるログはaccess,error,systemです。
categoriesプロパティで設定する項目がログ項目となるわけです。




ログを出力してみる

作成したapp.jsは以下です。

var Log4js = require('log4js');

// 設定ファイル(log-config.json)の読み込み
Log4js.configure('log-config.json');

// ログ出力 
var systemLogger = Log4js.getLogger('system');
var accessLogger = Log4js.getLogger('access');
var errorLogger = Log4js.getLogger('error');

// Informationログを出力する
systemLogger.info('this is system log!!!');
accessLogger.info('this is access log!!!');
errorLogger.info('this is error log!!!');
$ ls logs          まだファイルは作成されていない
$ node app.js      上記で作成したapp.jsを実行
$ ls logs          ログファイルが作成されている
access.log  error.log   system.log

作成されたログファイルは次の通り。

/logs/system.log
[2014-11-29 01:44:54.322] [INFO] system - this is system log!!!
logs/access.log
[2014-11-29 01:44:54.324] [INFO] access - this is access log!!!
error.log
[2014-11-29 01:44:54.324] [INFO] error - this is error log!!!

ログレベルに応じたログ出力

log4jsではFATAL、ERROR、WARN、INFO、DEBUG、TRACEのログ出力ができる。

var Log4js = require('log4js');

// 設定ファイル(log-config.json)の読み込み

Log4js.configure('log-config.json');

// ログ出力 
var systemLogger = Log4js.getLogger('system');

// Fatalログを出力する
systemLogger.fatal('this is system log!!!');
// Errorログを出力する
systemLogger.error('this is system log!!!');
// Warningログを出力する
systemLogger.warn('this is system log!!!');
// Informationログを出力する
systemLogger.info('this is system log!!!');
// Debugログを出力する
systemLogger.debug('this is system log!!!');

// traceログを出力する
systemLogger.trace('this is system log!!!');

実際に出力するとこんな感じになります。

system.log
[2014-11-29 21:16:34.628] [FATAL] system - this is system log!!!
[2014-11-29 21:16:34.630] [ERROR] system - this is system log!!!
[2014-11-29 21:16:34.630] [WARN] system - this is system log!!!
[2014-11-29 21:16:34.630] [INFO] system - this is system log!!!
[2014-11-29 21:16:34.630] [DEBUG] system - this is system log!!!
[2014-11-29 21:16:34.631] [TRACE] system - this is system log!!!

出力するログレベルを設定するには、設定ファイルにlevelsプロパティを設定できます。

log-config.json
{
    "appenders": [
        {
            "type":     "dateFile",
            "category": "system",
            "filename": "logs/system.log",
            "pattern":  "-yyyy-MM-dd"
        }
    ],
    "levels": {"system": "INFO"}  ココ
}

これで実行すると、category = "system"ののログがINFOレベル以上のログになります。

system.log
[2014-11-29 23:12:49.403] [FATAL] system - this is system log!!!
[2014-11-29 23:12:49.406] [ERROR] system - this is system log!!!
[2014-11-29 23:12:49.406] [WARN] system - this is system log!!!
[2014-11-29 23:12:49.407] [INFO] system - this is system log!!!

指定できるログレベルは以下の様です。

ログレベル説明
OFFログファイルにログを出力ません。
FATALFATAL以上のレベルのログを出力。
ERRORERROR以上のレベルのログを出力。
WARNWARN以上のレベルのログを出力。
INFOINFO以上のレベルのログを出力。
DEBUGDEBUG以上のレベルのログを出力。
TRACETRACE以上のレベルのログを出力。
ALL全てのログレベルの出力を行う

コンソールにログを出力する。

appendersに「type:console」で追記します。

log-config.json
{
    "appenders": [
        {
            "type":     "dateFile",
            "category": "system",
            "filename": "logs/system.log",
            "pattern":  "-yyyy-MM-dd"
        },
        {
            "type":     "dateFile",
            "category": "access",
            "filename": "logs/access.log",
            "pattern":  "-yyyy-MM-dd"
        },
        {
            "type":     "dateFile",
            "category": "error",
            "filename": "logs/error.log",
            "pattern":  "-yyyy-MM-dd"
        },
        { "type": "console" } ←ココ追加
    ],
    "levels": {"system": "DEBUG"}
}

実行するとこんな感じで出力されます。

$ node app.js
[2014-11-29 23:31:05.041] [FATAL] system - this is system log!!!
[2014-11-29 23:31:05.043] [FATAL] access - this is access log!!!
[2014-11-29 23:31:05.043] [FATAL] error - this is error log!!!
[2014-11-29 23:31:05.044] [ERROR] system - this is system log!!!
[2014-11-29 23:31:05.044] [ERROR] access - this is access log!!!
[2014-11-29 23:31:05.044] [ERROR] error - this is error log!!!
[2014-11-29 23:31:05.044] [WARN] system - this is system log!!!
[2014-11-29 23:31:05.044] [WARN] access - this is access log!!!
[2014-11-29 23:31:05.044] [WARN] error - this is error log!!!
[2014-11-29 23:31:05.045] [INFO] system - this is system log!!!
[2014-11-29 23:31:05.045] [INFO] access - this is access log!!!
[2014-11-29 23:31:05.045] [INFO] error - this is error log!!!
[2014-11-29 23:31:05.045] [DEBUG] system - this is system log!!!
[2014-11-29 23:31:05.045] [DEBUG] access - this is access log!!!
[2014-11-29 23:31:05.045] [DEBUG] error - this is error log!!!
[2014-11-29 23:31:05.046] [TRACE] access - this is access log!!!
[2014-11-29 23:31:05.047] [TRACE] error - this is error log!!!

replaceConsoleプロパティを追加すると、console.logで出力した内容がログ形式になります。

replaceConsoleプロパティ追加前

[2014-11-29 23:38:50.833] [TRACE] error - this is error log!!!
ログ出力のテスト!!!

replaceConsoleプロパティ追加

log-config.josn
{
    "appenders": [
        {
            "type":     "dateFile",
            "category": "system",
            "filename": "logs/system.log",
            "pattern":  "-yyyy-MM-dd"
        },
        {
            "type":     "dateFile",
            "category": "access",
            "filename": "logs/access.log",
            "pattern":  "-yyyy-MM-dd"
        },
        {
            "type":     "dateFile",
            "category": "error",
            "filename": "logs/error.log",
            "pattern":  "-yyyy-MM-dd"
        },
        { "type": "console" }
    ],
    "levels": {"system": "DEBUG"},
    "replaceConsole": true ← ココ追加
}

実行すると、こんな感じです。

[2014-11-29 23:41:14.642] [TRACE] error - this is error log!!!
[2014-11-29 23:41:14.642] [INFO] console - ログ出力のテスト!!!

設定ファイルのリロード時間を設定

設定ファイルの読み込み部分を次の様に変更すると指定した時間(秒単位)でリロードしてくれる様になります。ここでは60秒毎にリロードします。

Log4js.configure('log-config.json', { reloadSecs: 60 });

設定ファイルの最大サイズやバックアップ数を指定する。

最大ログサイズの指定やバックアップ数も指定できる様です。

  • maxLogSize
    ファイルの最大ファイルサイズ(単位:byte)

  • backups
    バックアップ数

どうも、appendersのtypeがfileの時に機能するみたいです。

log-config.json
{
    "appenders": [
        {
            "type":     "file",
            "category": "system",
            "maxLogSize": 1048576,
            "backups": 3,
            "filename": "logs/system.log"
        },
        {
            "type":     "dateFile",
            "category": "access",
            "filename": "logs/access.log",
            "pattern":  "-yyyy-MM-dd"
        },
        {
            "type":     "dateFile",
            "category": "error",
            "filename": "logs/error.log",
            "pattern":  "-yyyy-MM-dd"
        },
        { "type": "console" }
    ],
    "levels": {"system": "INFO"},
    "replaceConsole": true
}

expressとのログの統合

expressの設定時に次の内容を追記すれば良い様です。

Log4js = require('log4js');
:
:

Log4js.configure Config.log.configure
logger = Log4js.getLogger 'system'

app = express()
:
:

app.use(log4js.connectLogger(logger));
// app.use(Log4js.connectLogger(logger, { level: Log4js.levels.INFO }));


Node.jsで設定ファイルを読み込む(https://qiita.com/_daisuke/items/0d3a76a1290f08f4fea5)

 この記事は最終更新日から1年以上が経過しています。

Node.jsでアプリを作った時に、パラメータを設定ファイルに書き出して開発環境やステージング、プロダクション環境などで切り替えたことがあります。この時に使用できるのが、node-configモジュール。

設定ファイルとして対応しているフォーマット

  • Javascript Object Notation - .json
  • Javascript5 Object Notation - .json5
  • Yet another markup language - .yaml
  • Javascript - .js
  • CoffeeScript - .coffee
  • CoffeeScript Object Notation - .cson
  • Properties file format - .properties

ファイルの読み込み順

通常は実行しているアプリの./configディレクトリ下の設定ファイルを読み込みます。また、$NODE_ENV環境変数の値を見て読み込むファイルを切り替える様にすることもできます。

./configディレクトリ以下のファイルは次の順番で読み込まれる様です。

default.EXT
default-{instance}.EXT
{hostname}.EXT
{hostname}-{instance}.EXT
{deployment}.EXT
{deployment}-{instance}.EXT
{hostname}-{deployment}.EXT
{hostname}-{deployment}-{instance}.EXT
local.EXT
local-{instance}.EXT
local-{deployment}.EXT
local-{deployment}-{instance}.EXT  
  • .EXT 対応しているフォーマットの拡張子
  • {instance} Multi-Instnance Deploymentsのためのオプションのインスタンス名文字列
  • {hostname} サーバーのホスト名。HOSTorHOSTNAME環境変数の値
  • {deproyment} デプロイメント名。$NODE_ENV環境変数の値

defalut.EXTは他のファイルでパラメータを上書きしてマージすることができます。

サンプル

準備

$ mkdir sample-config
$ cd sample-config/
$ mkdir config
$ npm install config
config@1.8.1 node_modules/config
$ npm install js-yaml
js-yaml@3.2.3 node_modules/js-yaml
├── esprima@1.0.4
└── argparse@0.1.15 (underscore@1.4.4, underscore.string@2.3.3)
$ ls
config      node_modules
$ cd config

設定ファイル作成

configディレクトリ以下に設定ファイルを作成します。

default.yaml

config:
    fname: "defalut.yaml"
    defaultParam: "default.yaml parameter"

hostname.yaml

config:
    fname: "hostname.yaml"
    hostnameParam: "hostname.yaml parameter"

myDevelopment.yaml

config:
    fname: "myDevelopment.yaml"
    myDevelopmentParam: "myDevelopment.yaml parameter"

hostname-myDevelopment.yaml

config:
    fname: "hostname-development.yaml"
    hostDevParam: "hostname-development.yaml parameter"

local.yaml

config:
    fname: "local.yaml"
    localParam: "local.yaml parameter"

設定ファイルを読み込む実行スクリプトを作成

app.js

ConfigFile = require('config');

console.log(ConfigFile.config);

// 全ての設定ファイルで共通項目
console.log("config.fname              : " + ConfigFile.config.fname);
// default.yaml only
console.log("config.defaultParam       : " + ConfigFile.config.defaultParam);
// hostname.yaml only
console.log("config.hostnameParam      : " + ConfigFile.config.hostnameParam);
// myDevelopment.yaml only
console.log("config.myDevelopmentParam : " + ConfigFile.config.myDevelopmentParam);
// hostname-myDevelopment.yaml only
console.log("config.hostDevParam       : " + ConfigFile.config.hostDevParam);
// local.yaml only
console.log("config.localParam         : " + ConfigFile.config.localParam);

実行する

$HOST(or $HOSTNAME)、$NODE_ENV未設定

環境変数を設定しないで事項すると、次の通り。

$ node app.js
{ fname: 'local.yaml',
  defaultParam: 'default.yaml parameter',
  localParam: 'local.yaml parameter' }
config.fname              : local.yaml
config.defaultParam       : default.yaml parameter
config.hostnameParam      : undefined
config.myDevelopmentParam : undefined
config.hostDevParam       : undefined
config.localParam         : local.yaml parameter

default.yaml、local.yamlでマージされている。
両ファイルの共通項目はファイルの読み込み順に則って、local.yamlの値で上書きされる。

$HOST環境変数を設定

$ export HOST=hostname
$ node app.js
{ fname: 'local.yaml',
  defaultParam: 'default.yaml parameter',
  hostnameParam: 'hostname.yaml parameter',
  localParam: 'local.yaml parameter' }
config.fname              : local.yaml
config.defaultParam       : default.yaml parameter
config.hostnameParam      : hostname.yaml parameter
config.myDevelopmentParam : undefined
config.hostDevParam       : undefined
config.localParam         : local.yaml parameter

hostname.yamlが読み込み対象となる。

$NODE_ENV環境変数を設定

$ export HOST=hostname
$ export NODE_ENV=myDevelopment

$ node app.js
{ fname: 'local.yaml',
  defaultParam: 'default.yaml parameter',
  myDevelopmentParam: 'myDevelopment.yaml parameter',
  hostnameParam: 'hostname.yaml parameter',
  hostDevParam: 'hostname-development.yaml parameter',
  localParam: 'local.yaml parameter' }
config.fname              : local.yaml
config.defaultParam       : default.yaml parameter
config.hostnameParam      : hostname.yaml parameter
config.myDevelopmentParam : myDevelopment.yaml parameter
config.hostDevParam       : hostname-development.yaml parameter
config.localParam         : local.yaml parameter

HOSTNODE_ENVに設定してファイルの情報が読み込まれている。

runtime.json

実行後にconfigディレクトリにruntime.jsonファイルができている場合があります。
これは、node-configが自動で設定を反映してくれるファイルです。

詳細は、GitHubにあるnode-cofigのWikiを見ると色々書いてあります。(英語ですが・・・)


var, let, const 차이점은?

  • var는 function-scoped이고, letconst는 block-scoped입니다.

  • function-scoped와 block-scoped가 무슨말이냐?

var(function-scoped)

jsfiddle 참고주소

// var는 function-scope이기 때문에 for문이 끝난다음에 i를 호출하면 값이 출력이 잘 된다.
// 이건 var가 hoisting이 되었기 때문이다.
for(var j=0; j<10; j++) {
  console.log('j', j)
}
console.log('after loop j is ', j) // after loop j is 10


// 아래의 경우에는 에러가 발생한다.
function counter () {
  for(var i=0; i<10; i++) {
    console.log('i', i)
  }
}
counter()
console.log('after loop i is', i) // ReferenceError: i is not defined

그럼 항상 function을 만들어서 호출해야 할까? 그건 아니다.

javascript에서는 immediately-invoked function expression (or IIFE, pronounced "iffy")라는것이 있다.

IIFE로 function-scope인거 처럼 만들 수가 있다.

// IIFE를 사용하면
// i is not defined가 뜬다.
(function() {
  // var 변수는 여기까지 hoisting이 된다.
  for(var i=0; i<10; i++) {
    console.log('i', i)
  }
})()
console.log('after loop i is', i) // ReferenceError: i is not defined

근데 javascript는 여기서 좀 웃긴 부분이 있다.

위에서 잠깐 말했지만 IIFE는 function-scope처럼 보이게 만들어주지만 결과가 같지는 않다.

// 이 코드를 실행하면 에러없이 after loop i is 10이 호출된다.
(function() {
  for(i=0; i<10; i++) {
    console.log('i', i)
  }
})()
console.log('after loop i is', i) // after loop i is 10

위에 코드가 아무 에러 없이 실행되는 이유는 i가 hoisting이 되어서 global variable이 되었기 때문이다.

그래서 아래와 같이 된 것이다.

var i
(function() {
  for(i=0; i<10; i++) {
    console.log('i', i)
  }
})()
console.log('after loop i is', i) // after loop i is 10

IIFE는 쓰는데 이렇게 hoisting이 된다면 무슨 소용이 있겠는가?!

그래서 이런 hoisting을 막기 위해 use strict를 사용한다.

// 아까랑 다르게 실행하면 i is not defined라는 에러가 발생한다.
(function() {
  'use strict'
  for(i=0; i<10; i++) {
    console.log('i', i)
  }
})()
console.log('after loop i is', i) // ReferenceError: i is not defined

어떤가? 뭔가 변수 선언때문에 너무 많은 일을 한다고 생각하지 않는가?

그럼 letconst에 대해서 알아보자.

let, const(block-scoped)

  • es2015에서는 letconst가 추가 되었다.

javascipt에는 그동안 var만 존재했기 때문에 아래와 같은 문제가 있었다.

// 이미 만들어진 변수이름으로 재선언했는데 아무런 문제가 발생하지 않는다.
var a = 'test'
var a = 'test2'

// hoisting으로 인해 ReferenceError에러가 안난다.
c = 'test'
var c

위와 같은 문제점으로 인해 javascript를 욕 하는 사람이 참 많았다.

하지만 letconst를 사용하면 var를 사용할때보다 상당히 이점이 많다.

두개의 공통점은 var와 다르게 변수 재선언 불가능이다.

let과 const의 차이점은 변수의 immutable여부이다.

let은 변수에 재할당이 가능하지만,

const는 변수 재선언, 재할당 모두 불가능하다.

// let
let a = 'test'
let a = 'test2' // Uncaught SyntaxError: Identifier 'a' has already been declared
a = 'test3'     // 가능

// const
const b = 'test'
const b = 'test2' // Uncaught SyntaxError: Identifier 'a' has already been declared
b = 'test3'    // Uncaught TypeError:Assignment to constant variable.

letconst가 hoisting이 발생하지 않는건 아니다.

var가 function-scoped로 hoisting이 되었다면

letconst는 block-scoped단위로 hoisting이 일어나는데

c = 'test' // ReferenceError: c is not defined
let c

위에 코드에서 ReferenceError가 발생한 이유는 tdz(temporal dead zone)때문이다.

let은 값을 할당하기전에 변수가 선언 되어있어야 하는데 그렇지 않기 때문에 에러가 난다.

이건 const도 마찬가지인데 좀 더 엄격하다.

// let은 선언하고 나중에 값을 할당이 가능하지만
let dd
dd = 'test'

// const 선언과 동시에 값을 할당 해야한다.
const aa // Missing initializer in const declaration

이렇게 javascript에 tdz가 필요한 이유는 동적언어이다 보니깐 runtime type check 가 필요해서이다.

Reference


+ Recent posts