상세 컨텐츠

본문 제목

Javascript 기초

JS

by devTak 2021. 3. 5. 14:51

본문

반응형

Execute Context

실행 컨텍스트에는 2가지 컨텍스트가 존재한다.

  1. 전역컨텍스트
  2. 함수컨텍스트

실행 컨텍스트는 javascript 코드가 실행될 때, 전역 컨텍스트가 CallStack 에 쌓이고 이후에 함수 컨텍스트가 추가적으로 CallStack 에 쌓이게 된다.

실질적으로 ExecuteContext(EC) 는 생성 단계와 실행 단계를 거치면서 생성이된다. LexcalEnvironment 는 EC 와 함께 함수의 호출 단계 중 PrepareForOrdinaryCall 단계에서 생성이 된다.

함수의 호출단계

  1. PrepareForOrdinaryCall
  2. OrdinaryCallBindThis
  3. OrdinaryCallEvaluateBody
// 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;

LexicalEnvironment

  • environmentRecord
    • 매개변수, 함수 선언, 변수명등을 수집
    • 수집이 끝나도 코드는 실행 되기 전 상태
    • 자바스크립트 엔진은 이미 해당 환경에 속한 정보를 알고있음
    • "따라서 자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 코드를 실행한다." 라고 해석해도 문제가 없음
    • 호이스팅은 따라서 environmentRecord 를 추상화한 개념이다.
  • outerEnvironmentReference
    • 현재 호출된 함수가 선언될 당시의 LexicalEnvironment 를 참조한다.
  • 따라서, 변수의 값을 찾을 때, 자신의 environmentRecord 의 해당 값이 존재 하지 않으면, 가장 가까운 outerEnvironmentReference 의 LexicalEnvironment 의 값을 참조하게된다.

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

모든 객체들이 메소드와 속성들을 상속 받기 위한 템플릿으로써 프로토타입 객체를 가진다는 의미

Prototype

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(); 를 수행하면 아래와 같은 흐름이 생긴다.

  1. doSomethingInstancing 의 속성에 valueOf 메소드가 있는지 확인한다.
  2. 없으면, __proto__ (doSomething 의 prototype) 을 찾아본다.
  3. 또 없으면 __proto__ 의 __proto__ 를 찾게되고 해당 객체 내부에 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"}

주의할점은 프로토타입 체인에서 한 객체의 메소드와 속성들이 다른 객체로 복사되는 것이 아님

Closure

클로저는 함수와 함수가 선언된 어휘적 환경의 조합

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" 가 출력된다.

예제를 보면

  1. displayName 은 name을 찾아 출력하는 함수
  2. 그리고 displayName 은 outer environment 참조로 makeFunc 의 environment 를 저장
  3. global 에서 myFunc(=displayName()) 를 호출
  4. displayName 은 자신의 스코프에서 name을 찾음
  5. 없어서 자신의 outer environment 참조를 찾아간다.
  6. makeFunc 의 스코프에 name 이 있으므로 값을 출력한다.

결국 이러한 closure 는 함수 context 에서 environment 의 outerEnvironmentReference 에 값으로 인해 상위 참조되는 값을 접근할 수 있기 때문에 어휘적 환경에 대한 참조를 유지한다.

Hoisting

앞서 설명한 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~");

}
  • let 선언한 익명함수의 경우 에러가 "Cannot access 'testFunction' before initialization(선언은 되었지만 초기화가 되지않음, 즉 메모리에 현재 할당된 공간이 없음)"
  • var 선언한 익명함수의 경우 에러가 "testFunction1 is not a function(선언과 초기화가 undefined로 되었음으로 function 이 아니라는 에러)"

THIS

대부분의 경우 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 은 객체에 귀속되는 것이 아니라, 어느 객체의 메소드로써 구현될 수 있는 형태로 가져갈 수 있다.

Syntactic Sugar 없이 Class 만들기

Literal 방식으로 만들기

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);

Functional 방식으로 만들기

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);

Prototype 방식으로 만들기

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);
반응형