스코프 체인, 프로토타입 체인

ㅎㄱㅎ·2020년 11월 2일
3

상황

let a = 2;
var c = "C"
function f() {	
    const a = "hi"
    console.log(a); // hi
    console.log(b); // B
}
Object.prototype.a = 1;
Object.prototype.b = "B"
f();

b는 선언한 적이 없는데 에러 없이 잘만 출력 된다. 어떻게 된 일일까.

실행 컨텍스트(Execute Context, EC)

실행 컨텍스트는 간단하게 아래 세 가지로 구성 되어 있다.

  • Variable Enviroment(var, 함수 선언 초기화 undefined)
    • outer
    • environment record
  • lexical Enviroment(let, const, 함수 표현 초기화 X, nothing(없음) 상태)
    • outer
    • environment record
  • This Binding
    (따로 this가 할당 안되면 window, strict mode에서는 null, 따로 this가 할당되면 그 객체)

EC Stack

  • stack이니까 LIFO입니다.
  • globacl EC는 오직 단 한개
  • 함수를 호출 : EC 생성.
    • 내부 함수의 호출이 있다면, 새로운 EC가 생성되고 EC Stack에 Push
    • 함수 실행이 완료되면 EC Stack에서 pop되며 EC Stack의 마지막 EC로 돌아갑니다.

위의 함수를 따라가보자

EC Stack = [global EC]
EC Stack Poped = []
"global EC"
{
    outer : null
    global EC.environment record {
    	f : Function
        (함수는 바로 할당이 될 것 같다.. 그렇지 않아면 다음에 호출이 안될테니..)
        c : undefined
    	object Environment Record : {        	
            binding object : {   
                [[prototype]]
            }
      }
      declarative Environment Record : {a : nothing}
    }
    
}
EC Stack = [global EC, f() EC]
EC Stack Poped = []
"f() EC"
{
    lexical environment : {
        a : nothing
    	outer : variable environment
    }
    variable environment : {
    	outer : global EC.environment record {
        	f : Function
            c : "C"
            object Environment Record : {            
            	binding object : {
                    [[prototype]]
                }
            }
            declarative Environment Record : {a : 2}
        }
    }
}
EC Stack = [global EC, f() EC, console.log(a) EC]
EC Stack Poped = []
"console.log(a)" // "hi"
{
    lexical environment : {        
    	outer : {
        	f() EC.lexical environment : {
                a : "hi"
                outer : variable environment
            }
        }
    }
    variable environment : {
    	outer : global EC.environment record {
        	f : Function
            c : "C"
            object Environment Record : {
            	binding object : { 
                    [[prototype]]
                }
            }
            declarative Environment Record : {a : 2}
        }
    }
}

여기서 hi가 출력 되는걸 보니 lexical을 먼저 조회해서 있으면 갖다 쓰고
없으면 outer로 넘어가고 거기서 찾아서 없으면.. 이렇게 올라가는 것 같네요
lexical이 먼저 아닐까..

EC Stack = [global EC, f() EC, console.log(B) EC]
EC Stack Poped = [console.log(a) EC]
"console.log(b)" // b는 선언한 적이 없다!?
{
    lexical environment : {        
    	outer : {
        	outer : {
              f() EC.lexical environment : {
                  a : "hi"
                  outer : variable environment
              }
          }
        }
    }
    variable environment : {
    	outer : global EC.environment record {
        	f : Function
            c : "C"
            object Environment Record : {
            	binding object : {
                    [[prototype]]
                }
            }
            declarative Environment Record : {a : 2}
        }
    }
}

lexical이나 variable에 없다
-> out 따라 간다.
-> 끝까지 가서 outer가 null이고, global environment
record의 binding object까지 간다
-> scope chain에는 없으므로 binding object(global object) 의 prototype에서 찾는다

이렇게 out을 타고타고 변수를 찾아 가는게 scope chain 입니다.

요기잉네? 
window __proto__ 계속 펼치면 있습니다.
window는 최종적으로 object를 상속받기 때문에..
EC Stack = [global EC]
EC Stack Poped = [f() EC, console.log(B) EC]

"global EC"
{
    outer : null
    environment record : {
    	f : Function
    	c : "C"
    	object Environment Record : {
            	binding object : {
                    [[prototype]]
                }
            }
        declarative Environment Record : {a : 2}
    }
}

f가 실행되고 난 후의 global EC이다 a에는 2가 되어 있네요. f도 그대로 있고
c에는 "C"가 있습니다.

호이스팅

console.log(a)
let a = 10;
global EC에 declarative Environment Record의 a가 nothing이라 접근하면 
Reference Error
console.log(a)
const a = 10;
global EC에 declarative Environment Record의 a가 nothing이라 접근하면 
Reference Error
console.log(a)
var a = 10;
global EC에 environment record에 object Environment Record에 binding object에
a가 undefined이므로 undefined
test()
function test(){}
global EC에 environment record에 object Environment Record에 binding object에
test가 Function이므로 호이스팅!
test()
let test = function(){}
global EC에 declarative Environment Record의 test가 nothing이라 접근하면 
test not defined
test()
const test = function (){}
global EC에 declarative Environment Record의 test가 nothing이라 접근하면 
test not defined
test()
var test = function (){}
global EC에 environment record에 object Environment Record에 binding object에
test가 undefined라
test is not function
test()
let test = function test(){}
global EC에 declarative Environment Record의 test가 nothing이라 접근하면 
test not defined
test()
const test = function test(){}
global EC에 declarative Environment Record의 test가 nothing이라 접근하면 
test not defined
test()
var test = function test(){}
global EC에 environment record에 object Environment Record에 binding object
test가 undefined라
test is not function

클로저

function Test(){
    var a = 0;
    let b = 1;
    const c = 2;
    return function(){
    	console.log(a, b, c)
    }
}
const t = Test();
t()
EC Stack = [global EC]
EC Stack Poped = []
"global EC"
{
    outer : null
    global EC.environment record {
    	Test : Function   
      object Environment Record : {
          binding object : {                                       
              [[prototype]]
          }
      }
      declarative Environment Record : {t : undefined}
    }    
}
EC Stack = [global EC]
EC Stack Poped = []
"Test() EC"
{
    lexical environment : {
        b : nothing
        c : nothing
    	outer : variable environment
    }
    variable environment : {
    	a : undefined
    	outer : global EC.environment record {
        	Test : Function     
          object Environment Record : {
              binding object : {                                         
                  [[prototype]]
              }
          }
          declarative Environment Record : {t : undefined}
        }    
    }
}
EC Stack = [global EC]
EC Stack Poped = [Test() EC]
"global EC"
{
    outer : null
    global EC.environment record {
    	Test : Function   
      object Environment Record : {
          binding object : {                                       
              [[prototype]]
          }
      }
      declarative Environment Record : {t : Function(closure)}
    }    
}
EC Stack = [global EC, t() EC]
EC Stack Poped = []
"t() EC"
{
    lexical environment : {        
    	outer : variable environment
    }
    variable environment : {
    	a : 0
    	outer : Test() EC.lexical environment : {
          b : 1
          c : 2
          outer : Test() EC.variable environment
      }
    }
}
EC Stack = [global EC, console.log(a, b, c) EC]
EC Stack Poped = [t() EC]
"console.log(a, b, c) EC" // 0, 1, 2
{
    lexical environment : {        
    	outer : t() EC. lexical environment : {        
          outer : Test() EC.variable environment
      }
    }
    variable environment : {
    	a : 0
    	outer : Test() EC.lexical environment : {
          b : 1
          c : 2
          outer : Test() EC.variable environment
      }
    }
}
EC Stack = [global EC]
EC Stack Poped = [console.log(a, b, c) EC]
"global EC"
{
    outer : null
    global EC.environment record {
    	Test : Function     
      object Environment Record : {
          binding object : {                                     
              [[prototype]]
          }
      }
      declarative Environment Record : {t : Function(closure)}
    }    
}

클로저는 이렇게 돌아가지 않을까 싶습니다... 이 부분은 확신이 없네요

프로토타입 체인

자신으로부터 프로토타입을 쭈욱 타고 올라가 prototype이 null이 될때까지 탐색 하는 것 입니다.

위의 케이스는 scope chain을 하다가 최상단에도 없으니 global 객체의 porototype을
탐색하여 결국 찾아내는 특이한 케이스 입니다.

만일 this 없이 일반 객체의 prototype을 사용 했을 때는 탐색이 되지 않습니다.
끝까지 가도 그 변수는 없고, global객체의 prototype에도 없을 테니깐요

function Test(){
	this.a = 0;
}
Test.prototype.test = function(){
	console.log(a) // a not defined
    console.log(this.a) // 0 만일 this.a가 없으고 부모가 있다면 부모의 prototype을 탐색.. prototype이 null 될 때 까지 
}

const t = new Test():
t.test();

references

https://homoefficio.github.io/2016/01/16/JavaScript-%EC%8B%9D%EB%B3%84%EC%9E%90-%EC%B0%BE%EA%B8%B0-%EB%8C%80%EB%AA%A8%ED%97%98/
https://velog.io/@paulkim/e
https://stackoverflow.com/questions/23948198/variable-environment-vs-lexical-environment
https://meetup.toast.com/posts/118
https://meetup.toast.com/posts/123
https://meetup.toast.com/posts/129
http://www.ecma-international.org/ecma-262/6.0/#sec-environment-records
https://dev.to/shoupn/javascript-code-nesting-and-lexical-environments-explained-3bi4

오류가 있으면 언제든지.. 감사합니다.
감사합니다.!

profile
dog발자

1개의 댓글

comment-user-thumbnail
2022년 1월 5일

와 이렇게 자세하게 분석한 글 ... 감사합니다.
렉시컬 , 스코프체인, 프로토타입 체인의 우선순위에 대해서 알아보고 있었는데
엄청 깔끔하게 정리해주셨네요 저도 나중에 해봐야겠네요 감사합니다.

답글 달기