실행 컨텍스트에는 2가지 컨텍스트가 존재한다.
실행 컨텍스트는 javascript 코드가 실행될 때, 전역 컨텍스트가 CallStack 에 쌓이고 이후에 함수 컨텍스트가 추가적으로 CallStack 에 쌓이게 된다.
실질적으로 ExecuteContext(EC) 는 생성 단계와 실행 단계를 거치면서 생성이된다. LexcalEnvironment 는 EC 와 함께 함수의 호출 단계 중 PrepareForOrdinaryCall 단계에서 생성이 된다.
함수의 호출단계
// LexicalEnvironment 가 생성되는 과정
callerContext = runningExecutionContext;
calleeContext = new ExecutionContext;
calleeContext.Function = F;
localEnv = NewFunctionEnvironment(F, newTarget); // <-- 여기서 Lexical Environment 를 생성한다.
calleeContext.LexicalEnvironment = localEnv;
calleeContext.VariableEnvironment = localEnv;
executionContextStack.push(calleeContext);
return calleeContext;
// NewFunctionEnvironment 내부
env = new LexicalEnvironment;
envRec = new functionEnvironmentRecord;
envRec.[[FunctionObject]] = F;
if (F.[[ThisMode]] === lexical) {
envRec.[[ThisBindingStatus]] = 'lexical';
} else {
envRec.[[ThisBindingStatus]] = 'uninitialized';
}
home = F.[[HomeObject]];
envRec.[[HomeObject]] = home;
envRec.[[NewTarget]] = newTarget;
env.EnvironmentRecord = envRec.
env.outer = F.[[Environment]];
return env;
var globalValue = 10;
function foo() {
var fooValue = 20;
function bar() {
var barValue = 30;
console.log(varValue);
console.log(fooValue);
console.log(globalValue);
}
bar();
}
foo();
// 30 20 10
//global environment
environmentRecord:{
globalValue:10
foo:function
},
outerEnvironmentReference:null
//foo environment
environmentRecord:{
fooValue:20
bar:function
},
outerEnvironmentReference:golbalEnvironment
//bar invironment
environmentRecord:{
barValue:30
},
outerEnvironmentReference:fooEnvironment
function doSomething(){}
doSomething.prototype.foo = "bar";
var doSomeInstancing = new doSomething();
doSomeInstancing.prop = "some value";
console.log( doSomeInstancing );
//////////////////////
{
prop: "some value",
__proto__: {
foo: "bar",
constructor: ƒ doSomething(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
}
위의 코드에서 만약 doSomeInstancing.valueOf(); 를 수행하면 아래와 같은 흐름이 생긴다.
그러므로 doSomeInstancing의 __proto__의 __proto__(doSomething.prototype의 __proto__(다시말해, Object.prototype)) 에서 그 속성을 찾아본다.
결국 Object.prototype 의 __proto__는 *null *이기 때문에 더 이상 __proto__ 를 찾지 못하게 된다.
let Foo = function () {
this.a = 1;
this.b = 2;
}
let foo = new Foo(); // {a: 1, b: 2}
// Foo 함수의 prototype 속성 값들을 추가 하자.
Foo.prototype.b = 3;
Foo.prototype.c = 4;
// foo 는 a 의 속성을 가짐
console.log(foo.a); // 1
// foo 는 b 의 속성을 가짐, 그리고 o.prototype 도 b의 값을 가지지만 실제로 더 가까운 foo 의 속성을 출력한다.
console.log(foo.b); // 2
// foo 는 c 의 속성을 가지고 있지 않고, foo.prototype 은 c를 가지고 있으므로 4를 출력
console.log(foo.c); // 4
// foo 는 d 의 속성을 가지고 있지 않고, foo.prototype 또한 d 의 속성이 없다. 그렇다면 foo.prototype.prototype 은 Object.prototype 이므로 null 이다. 종단을 말하므로 종료한다.
console.log(foo.d); // undefined
해당 코드도 같은 맥락을 지니고 있다.
고전적인 상속방법
function ParentClass() {
this.a = 0;
this.b = 0;
}
ParentClass.prototype.testFunction = function(a, b) {
this.a += a;
this.b += b;
console.log(this.a + "||" + this.b + "testFunction Call!!");
}
function ChildClass() {
ParentClass.call(this);
}
ChildClass.prototype = Object.create(ParentClass.prototype);
ChildClass.prototype.constructor = ChildClass;
let child = new ChildClass();
console.log('ChildClass 의 인스턴스 ', child instanceof ChildClass); // true
console.log('ParentClass 의 인스턴스 ', child instanceof ParentClass); // true
child.testFunction(10,10);
기본 객체의 기능 확장의 예
Array.prototype.jin = function () {
console.log(this);
}
Object.prototype.customFunction = function() {
console.log(this);
}
let arr = [1,2,3];
let object = {test:"foo"};
arr.jin(); // [1,2,3]
object.customFunction(); // {test:"foo"}
주의할점은 프로토타입 체인에서 한 객체의 메소드와 속성들이 다른 객체로 복사되는 것이 아님
클로저는 함수와 함수가 선언된 어휘적 환경의 조합
function makeFunc() {
var name = "Mozilla"; // 2
function displayName() {
console.log(name); // 1
}
return displayName;
}
var myFunc = makeFunc();
//myFunc변수에 displayName을 리턴함
//유효범위의 어휘적 환경을 유지
myFunc();
//리턴된 displayName 함수를 실행(name 변수에 접근)
myFunc 는 makeFunc 이 실행될 때, 생성된 displayName 함수의 인스턴스에 대한 참조다.
displayName 의 인스턴스는 변수 name 이 있는 어휘적 환경에 대한 참조를 유지한다. 따라서 myFunc를 실행하게 되면 name 변수를 사용할 수 있는 상태여서 "Mozilla" 가 출력된다.
예제를 보면
결국 이러한 closure 는 함수 context 에서 environment 의 outerEnvironmentReference 에 값으로 인해 상위 참조되는 값을 접근할 수 있기 때문에 어휘적 환경에 대한 참조를 유지한다.
앞서 설명한 ExecuteContext 에 의해 Hoisting 이 일어난다.
// 함수 호출 성공
hoistingFunction();
function hoistingFunction() {
console.log("함수 호출 성공");
}
위 코드로 작성되었을 때, ExecuteContext Stack(Call Stack) 에서는 전역컨텍스트가 생성될 때, environmentRecord 의 해당 함수를 기억해서 실제로 hoistingFunction 이 선언되지 않은 상태에서도 마치 끌어올려져서 실행이 가능한 것 처럼 보인다.
// hoistingFunction is not a function
hoistingFunction();
var hoistingFunction = function() {
console.log("함수 호출 성공");
}
위 코드는 ExecuteContext Stack(CallStack) 에서 전역 컨텍스트가 생성될 때, environmentRecord 에 hoistingFunction 변수 내에 함수를 기억하는 것이 아니라, var hoistingFunction; 변수를 기억함으로써, hoisingFunction is not a function 이라는 에러를 출력한다.
그렇다면, let, const 키워드는 hoisting이 되지 않는건가?
//Cannot access 'test' before initialization
console.log(test);
let test = "test 변수 호출~~";
결론부터 말하자면 hoisting 은 일어난다.
실제로 var 키워드를 사용해서 선언한 객체의 경우에는 선언과 초기화가 동시에 이루어진다. (초기화 = undefined)
var 키워드를 이용하게되면 변수 객체를 생성한 후 바로 메소드를 통해 메모리에 공간을 할당한다.
그러나 let, const 는 다르다. let 과 const 로 생성한 변수 객체들은 메모리 공간을 할당하는 메소드를 바로 호출하지 않고, 해당 코드의 위치를 의미하는 position 값만 정해준다.
이 과정을 TDZ(Temporal Dead Zone) 구간게 들어가는 것이다.
즉, 선언은 되어있지만 아직 초기화가 되지않아 변수에 담길 값을 위한 공간이 메모리에 할당되지 않은 상태이기 때문에 Cannot access before initialization 이라는 에러문구를 출력하게 된다.
// Cannot access 'testFunction' before initialization
testFunction();
let testFunction = function() {
console.log("testFunction Call~");
}
////////////////////////////////
// testFunction1 is not a function
testFunctio1n();
// undefined => 함수 자체를 담고있는것이 아닌 var testFunction = undefined 로 담고있음
console.log(testFunction1);
var testFunction1 = function() {
console.log("testFunction1 Call~");
}
대부분의 경우 this의 값은 함수를 호출한 방법에 의해 결정된다.
실행중에는 할당으로 설정할 수 없고 함수를 호출할 때 마다 다를 수 있다.
const thisTest = {
name: "This name is thisTest",
getName: function() {
console.log(this.name);
}
}
// This name is thisTest
thisTest.getName();
const getNameFunction = thisTest.getName;
// undefined
getNameFunction();
또 다른 예제가 있다.
const one = {};
const two = {};
function thisFunction() {
switch(this) {
case one:
console.log("one!!");
break;
case two:
console.log("two!!");
break;
case global:
console.log("global!!");
break;
}
}
// global!!
thisFunction();
// one!!
thisFunction.call(one);
// two!!
thisFunction.call(two);
기존에 알던 객체의 메소드는 마치 객체가 master 고 메소드는 slave 관계처럼, 메소드들은 객체에 귀속되는 느낌이다.
하지만, 자바스크립트에서의 메소드 혹은 function 은 객체에 귀속되는 것이 아니라, 어느 객체의 메소드로써 구현될 수 있는 형태로 가져갈 수 있다.
var TestClass1 = {
name:null,
legCount:null,
setTestClass: function(name, legCount) {
this.name = name;
this.legCount = legCount;
}
}
// null
console.log(TestClass1.name);
// null
console.log(TestClass1.legCount);
TestClass1.setTestClass("jinwook", 2);
// jinwook
console.log(TestClass1.name);
// 2
console.log(TestClass1.legCount);
function TestClass2() {
this.name = null;
this.legCount = null;
this.setTestClass = function(name, legCount) {
this.name = name;
this.legCount = legCount;
}
}
var testClass2 = new TestClass2();
// null
console.log(testClass2.name);
// null
console.log(testClass2.legCount);
testClass2.setTestClass("jinwook", 2);
// jinwook
console.log(testClass2.name);
// 2
console.log(testClass2.legCount);
function TestClass3() {
this.name = null;
this.legCount = null;
}
TestClass3.prototype.setTestClass = function (name, legCount) {
this.name = name;
this.legCount = legCount;
}
var testClass3 = new TestClass3();
console.log(testClass3.name);
console.log(testClass3.legCount);
testClass3.setTestClass("jinwook",2);
console.log(testClass3.name);
console.log(testClass3.legCount);
function TestClass4() {
TestClass3.call(this);
this.armCount = null;
this.setArmCount = function(armCount) {
this.armCount = armCount;
}
}
TestClass4.prototype = Object.create(TestClass3.prototype);
TestClass4.prototype.constructor = TestClass4;
var testClass4 = new TestClass4();
// null
console.log(testClass4.name);
// null
console.log(testClass4.legCount);
// null
console.log(testClass4.armCount);
testClass4.setTestClass("jinwook",2);
testClass4.setArmCount(2);
// jinwook
console.log(testClass4.name);
// 2
console.log(testClass4.legCount);
// 2
console.log(testClass4.armCount);