You don't know everything of arguments object

이번 글에서는 우리 모두가 알고 있는 arguments 객체에 대한 깊고도 깊은 이야기를 다룹니다.

본격적으로 글을 시작하기 전에, 잠시 arguments 객체와 관련된 자바스크립트 퀴즈를 풀어보도록 하지요.

1
2
3
4
5
6
function problem1(a, b, c) {
arguments[0] = 100;
return [a, b, c];
}

problem1(1, 2, 3) // 반환값을 맞추어 보세요!

정답: [100, 2, 3]

1
2
3
4
5
function problem2(a, b, c) {
return Object.hasOwnProperty(arguments, 0);
}

problem2(1, 2, 3) // 반환값을 맞추어 보세요!

정답: false

1
2
3
4
5
6
function problem3(a, b, c = 10) {
arguments[0] = 100;
return [a, b, c];
}

problem3(1, 2) // 반환값을 맞추어 보세요!

정답: [1, 2, 10]

1
2
3
4
5
function problem4(a, b, c = 10) {
return arguments.callee;
}

problem4(1, 2) // 반환값을 맞추어 보세요!

정답: 반환값 없음(TypeError)

1
2
3
4
5
6
function problem5() {
arguments[2] = 3;
return Array.prototype.slice.call(arguments);
}

problem5(1, 2); // 반환값을 맞추어 보세요!

정답: [1, 2]

자, 몇 개나 맞추셨나요?
위 퀴즈를 전부 맞추셨다면, 이 글은 안 읽으셔도 됩니다. 저 문제들의 답이 나오는 이유와, 그 비하인드 스토리를 다루는 게 이 글의 주제이기 때문이죠!
또한 실무에서 사용할 법한 지식을 원하신다면, 이 글을 안 읽으셔도 됩니다. 왜냐면 제가 할 이야기들은 실무와 많이 동떨어진 이야기이거든요!
그렇담, 지금까지 뒤로가기를 안 누르신 여러분들께 지금부터 arguments의 속사정을 들려드리도록 하겠습니다!

What we already knows

먼저, 우리가 알고 있는 기본적인 상식들을 살펴보고 넘어가겠습니다.

  1. arguments 객체는 함수의 전달인자(arguments)를 담고 있는 객체입니다.
  2. arguments 객체는 ArrayLike(정수가 key로서 사용되는 객체, 배열과 같이 데이터 간의 순서가 있는 객체)입니다.
  3. 전달인자는 왼쪽부터 인덱스를 부여받습니다(0.. 1.. 2..).

그러므로 우리는 다음과 같은 방법을 통해 함수에게 전달된 값들을 읽을 수 있습니다:

1
2
3
4
5
function example1(a) {
return arguments[0] === a;
}

example1(1) // true

또한 arguments 객체에 담기는 값들은 전달인자이기 때문에 매개변수의 정의 여부에 상관하지 않고 읽을 수 있습니다!

1
2
3
4
5
function example2() {
return arguments[0];
}

example2(1) // 1

와우! 멋진 객체이군요!

Birth of arguments object

세상 모든 게 그렇듯, arguments 객체도 필요에 의해 만들어진 개념입니다.
도대체 어떤 이유로 이런 객체를 만들었는지 알아보도록 하지요.

Rest parameter

우리가 잘 알고 있는 Rest parameter 문법은 ES6(2015)에서 추가된 문법입니다.
여기서 여러분들은 자바스크립트의 최초 버전이 1997년에 나왔다는 걸 잊지 마셔야 합니다.
도대체 그 18년동안 자바스크립트 개발자들은 어떻게 살았었을까요? 나머지 매개변수를 구현했었을까요?

정답은.. 진짜로 그 18년동안 사람들은 arguments 객체를 사용해서 나머지 매개변수를 구현해서 사용했었습니다.

바로 이렇게요:
출처: Partial Application in JavaScript - John resig

1
2
3
4
5
Function.prototype.curry = function() {
var fn = this, args = Array.prototype.slice.call(arguments);
return function() {
return fn.apply(this, args.concat(Array.prototype.slice.call(arguments)));
};

앞서 살펴보았듯, arguments 객체는 매개변수의 정의 여부에 상관하지 않기 때문에 이렇게 넘쳐버린 전달인자들도 담습니다.
그래서 저렇게 몇 개든 인자를 받아서 처리할 수 있는 것이지요.

Make anonymous function Recursive!

arguments 객체에는 callee라는 특별한 프로퍼티가 하나 있습니다.
이 프로퍼티에는 현재 실행중인 함수가 담겨 있는데요, 굳이 함수의 이름을 쓰지 않고 왜 이 프로퍼티를 사용했었을까요?

그 이유는 시간을 거슬러 내려가, 자바스크립트의 초창기 버전(ES1~ES2)에서 찾을 수 있습니다.

ES3 이전에는, 자바스크립트에 함수 표현식이 존재하지 않았었습니다.
그래서 함수를 만들기 위해 프로그래머들은 함수 선언 혹은 Function 생성자를 사용했었습니다.

1
2
3
function myFunction() {} // 함수 선언
// or
var myFunction2 = new Function(); // Function 생성자

이 중, Function 생성자로 만들어진 함수는 익명 함수입니다. 생성된 함수의 이름이 'anonymous' 이기 때문이죠.

(여담이지만, 자바스크립트의 익명 함수는 함수명(function.name) 이 'anonymous' 인 함수와 '' 인 함수, 두 가지가 있습니다.
이 이야기는 다음 포스트에서 다루도록 하겠습니다.)

자, 여기서 퀴즈 하나 나갑니다. 익명 함수는 어떻게 재귀 호출을 할 수 있을까요?

1
2
3
4
function myFunction1(a) {
return a ? a : a + myFunction1(a - 1);
}
var myFunction2 = new Function('return a ? a : a + /**여기에 뭐가 들어가야 할까요?**/(a - 1);', 'a');

당연하게도 자신을 가르키는 이름이 없기 때문에 식별자를 통한 직접적인 참조가 불가능합니다.

이러한 문제를 해결하기 위해 자바스크립트는 arguments.callee 프로퍼티를 추가했습니다.

1
var myFunction2 = new Function('return a ? a : a + arguments.callee(a - 1);', 'a');

하지만 다들 아시다시피, ES3에서 함수 표현식이 추가된 이후, Function 생성자의 사용 빈도가 급격히 줄었습니다.
그리고 함수 표현식은 선택적으로 식별자를 지정할 수 있게 해 주지요.
그래서 지금은 더 이상 안 쓰이는 테크닉입니다. 정말 희귀한 케이스가 아닌 이상 안 씁니다.

Seeing internal of arguments object

자, 지금부터 본격적으로 arguments 객체의 내부를 열어보도록 하겠습니다.

가장 먼저 arguments 객체가 생성되는 부분을 보도록 하겠습니다.

NOTICE: 이 글에서는 ECMA-262, 9th edition 을 해설 목적으로 사용하고 있습니다.

CreateMappedArgumentsObject

CreateUnmappedArgumentsObject

엇, arguments 객체를 생성하는 Abstract operation 이 두개네요?
네, 보시다시피 arguments 객체는 내부적으로 두 가지 종류가 있습니다.
하나는 Mapped Arguments 객체구요, 하나는 Unmapped Arguments 객체입니다.

Unmapped Arguments 객체는 함수가 strict mode 이거나, 함수의 파라매터 정의에 ES6의 feature(기본 매개변수, 디스트럭쳐링 바인딩 패턴, 나머지 매개변수)가 사용된 경우에 만들어지고,
그렇지 않은 경우에는 Mapped Arguments 객체가 만들어집니다.

두 객체의 차이를 한번 알아본 뒤, 명세를 이어서 읽어보도록 하겠습니다.

Feature Mapped Arguments Object Unmapped Arguments Object
2-way data binding yes no
kind of object exotic ordinary
uses [[ParameterMap]] yes no
throw error when someone tried to access ‘callee’ property no yes

자, 먼저 가장 간단한 Unmapped Arguments 객체를 생성하는 CreateUnmappedArgumentsObject 를 읽어보도록 하지요.

CreateUnmappedArgumentsObject

여기서 argumentList는 전달인자들의 배열이라고 생각하셔도 무방합니다.(ex: f(1, 2) 라면 argumentList[1, 2])

  • 1. len 이라는 변수를 만들고, argumentList의 길이로 초기화합니다.
  • 2. Object.prototype을 프로토타입으로 갖는 객체를 만들고, [[ParameterMap]] 이라는 내부 프로퍼티를 추가합니다. 그 후 만들어진 객체를 obj라는 변수에 넣습니다. 이 객체는 차후 Unmapped Arguments 객체가 됩니다.
  • 3. obj.[[ParameterMap]]undefined로 초기화합니다. (왜냐면 Unmapped Arguments 객체니까요!)
  • 4. obj 에 값이 len이고 키가 length인 프로퍼티를 정의합니다.
  • 5~6. objargumentList에 있는 각 argument을 값으로, 각 인덱스를 키로 하는 프로퍼티들을 정의합니다.
  • 7. objSymbol.iterator을 키로, Array.prototype.values를 값으로 하는 프로퍼티를 정의합니다.
  • 8. objcallee라는 accessor 프로퍼티를 정의합니다. Getter와 Setter 모두 호출 즉시 TypeErrorthrow 하는 함수를 넣었습니다.
  • 9. 완성된 Unmapped Arguments 객체를 반환합니다.

이게 Unmapped Arguments 객체의 전부입니다! 간단하지요?
이제 본격적으로, 좀 더 어려운 녀석을 살펴보도록 하겠습니다.
바로 Mapped Arguments 객체지요.

CreateMappedArgumentsObject

여기서 func는 호출된 함수 객체, formals는 함수의 매개변수가 있는 코드 조각(소스 코드), argumentsList는 매개변수들의 리스트(배열), env는 여러분들이 잘 아시는 Environment Record 입니다.

  • 2. len 이라는 변수를 만들고, argumentList의 길이로 초기화합니다.
  • 3. obj 라는 변수에 [[ParameterMap]] 이라는 내부 프로퍼티를 가지는 빈 객체를 넣습니다.
  • 4~9. obj 객체에 객체의 기본적인 동작(프로퍼티 접근, 변경, 삭제 등등등)을 정의하는 메서드들을 주입합니다.
  • 10. obj의 프로토타입을 Object.prototype으로 설정합니다.
  • 11. obj를 확장할 수 있는 객체로 만듭니다.
  • 12. map 이라는 변수를 만들고 빈 객체로 초기화합니다.
  • 13. obj.[[ParameterMap]]map으로 설정합니다.
  • 14. parameterNames 라는 변수를 만들고, formals에 있는 매개변수 식별자들의 리스트로 초기화합니다.
  • 15~17. objargumentList에 있는 각 argument을 값으로, 각 인덱스를 키로 하는 프로퍼티들을 정의합니다.
  • 18. obj 에 값이 len이고 키가 length인 프로퍼티를 정의합니다.
  • 19~21. obj와 호출된 함수의 스코프간의 양방향 데이터 바인딩을 구현하기 위한 코드들입니다. (이 부분은 잠시 후에 다시 살펴보겠습니다.)
  • 22. objSymbol.iterator을 키로, Array.prototype.values를 값으로 하는 프로퍼티를 정의합니다.
  • 23. obj'callee'을 키로, 호출된 함수를 값으로 하는 프로퍼티를 정의합니다.
  • 24. 완성된 Mapped Arguments 객체를 반환합니다.

자, 여기까지는 Mapped Arguments 객체가 생성되는 과정이였습니다.
그럼 이제 어떻게 양방향 바인딩이 구현되었는지 살펴보도록 하지요.

2-way data binding

  • 19. mappedNames 라는 변수를 만들고 빈 리스트로 초기화합니다.
  • 20. index 라는 변수를 만들고, numberOfParameters - 1 한 값을 넣습니다. <– numberOfParameters의 값은 parameterNames의 길이입니다.
  • 21. index 가 0보다 작아질때까지 아래의 단계를 반복합니다.
    • a. name 이라는 변수를 만들고, 그 값을 parameterNames[index](parameterNames 가 리스트이므로 index 번째 요소를 가져오는 것 입니다.) 로 합니다.
    • b. 만약 namemappedNames에 포함되있지 않다면,
      • i. namemappedNames의 가장 마지막 요소로서 추가합니다(push).
      • ii. 만약 index < len 이라면,
        • 1. g라는 변수를 만들고, MakeArgGetter(name, env)의 결과값으로 초기화합니다. (MakeArgGetter는 이따 살펴보겠습니다.)
        • 2. p라는 변수를 만들고, MakeArgSetter(name, env)의 결과값으로 초기화합니다. (MakeArgSetter도 이따 살펴보겠습니다.)
        • 3. mapString(index)를 키로 하고, g를 Getter로, p를 Setter로 하는 접근자 프로퍼티를 정의합니다.
    • c. index의 값을 1 줄입니다.

처음에 CreateMappedArgumentsObject를 설명해드릴 때 생략한 부분인데요.
이 부분에서는 arguments 객체의 프로퍼티와, 호출된 함수의 스코프를 이어주는 접근자를 생성해주는 MakeArgGetterMakeArgSetter을 이용해
각 매개변수와 arguments에 들어있는 전달인자의 값을 연결시켜주고 있습니다.

자, 그럼 이제 MakeArgGetterMakeArgSetter가 어떻게 둘을 이어주는 지 알아보도록 하겠습니다.

MakeArgGetter

name은 아시다시피 매개변수의 이름이구요, env는 Running Execution Context의 Lexical Environment의 Environment Record 입니다.
(쉽게말해 현재 실행중인 코드의 스코프라는 이야기입니다.)

  • 1. steps 라는 변수를 만들고 아래에 있는 ArgGetter 함수로 초기화합니다.
  • 2. getter 이라는 변수를 만들고, CreateBuiltinFunction(steps, << [[Name]], [[Env]] >>) 의 결과값으로 초기화합니다. ArgGetter 을 호출할 수 있는 자바스크립트의 함수로 만들고, 내부 프로퍼티인 [[Name]][[Env]]를 정의하는 겁니다.)
  • 3. getter.[[Name]]의 값을 name으로 설정합니다.
  • 4. getter.[[Env]]의 값을 env로 설정합니다.
  • 5. getter를 반환합니다.

아쉽게도 MakeArgGetter에는 직접적으로 둘을 이어주는 로직은 없습니다. 그건 ArgGetter의 역할이거든요.
자, 이제 ArgGetter을 보도록 하겠습니다.(ArgGetter에서 f는 호출된 ArgGetter를 의미합니다. arguments.callee와 같게 보셔도 무방합니다.!)

  • 1. name이라는 변수를 선언하고, 그 f.[[Name]]으로 초기화합니다.
  • 2. env이라는 변수를 선언하고, 그 f.[[Env]]으로 초기화합니다.
  • 3. env.GetBindingValue(name, false)의 결과값을 반환합니다.(아까 받은 스코프에서 name과 묶인 값을 찾는 부분입니다.)

그리 어렵지는 않지요? 그럼 이제 MakeArgSetter를 보도록 하겠습니다.

MakeArgSetter

여기서도 name은 매개변수의 이름이구요, env는 Running Execution Context의 Lexical Environment의 Environment Record 입니다.

  • 1. steps 라는 변수를 만들고 아래에 있는 ArgSetter 함수로 초기화합니다.
  • 2. setter 이라는 변수를 만들고, CreateBuiltinFunction(steps, << [[Name]], [[Env]] >>) 의 결과값으로 초기화합니다. ArgSetter 을 호출할 수 있는 자바스크립트의 함수로 만들고, 내부 프로퍼티인 [[Name]][[Env]]를 정의하는 겁니다.)
  • 3. setter.[[Name]]의 값을 name으로 설정합니다.
  • 4. setter.[[Env]]의 값을 env로 설정합니다.
  • 5. setter를 반환합니다.

MakeArgSetter에도 직접적으로 arguments 객체의 프로퍼티와 함수의 스코프에 있는 변수를 이어주는 로직은 없습니다.
그건 ArgSetter의 역할이기 때문이죠.
자, 이제 ArgSetter을 보도록 하겠습니다.(여기서도 f는 호출된 ArgSetter입니다., value는 다들 아시는 setter의 매개변수(새로 set 될 값)입니다.)

  • 1. name이라는 변수를 선언하고, 그 f.[[Name]]으로 초기화합니다.
  • 2. env이라는 변수를 선언하고, 그 f.[[Env]]으로 초기화합니다.
  • 3. env.SetMutableBinding(name, value, false)의 결과값을 반환합니다.(아까 받은 스코프에서 name과 묶인 값을 변경하는 부분입니다.)

자, 이렇게 우리는 기초적인 부분들을 살펴 보았습니다.
하지만 아직 여러분들이 보지 않은 부분이 있습니다. 바로 위에서 우리가 양방향 바인딩을 위해 만들고 arguments에 붙인 접근자들이 호출되는 부분들이죠.
지금부터는 그 부분들을 찾아서 보도록 하겠습니다.

그 전에, 기반 지식에 대한 간단한 설명을 먼저 하도록 하겠습니다.
자바스크립트의 객체들의 시멘틱은 internal method라는 것에 의해 정의됩니다.
어떤 객체에 대한 동작/연산들이 internal method 라는 것을 통해 정의된다는 이야기인데요.
다만, 불규칙하게 정의되면 구현체 뿐만 아니라 명세에서도 각 객체를 위한 로직을 따로 작성해야 하므로 그 불편함을 없에기 위해 자바스크립트 명세에서는 기본적인 동작들을 위한 인터페이스를 정의하고, 최소한의 조건을 명시하고 있습니다.
또한, 자바스크립트 명세에서는 특별한 internal method가 필요하지 않은 객체들을 위해 앞서 언급한 최소한의 조건을 충족하는(또한 default behavior인) internal method들을 제공하고 있습니다.
만약, 한 객체의 기본적인 동작들을 구현하고 있는 internal method들이 전부 default behavior인 internal method라면, 그 객체를 ordinary 객체라고 부릅니다.
그렇지 않다면 Exotic 객체라고 부르지요. 위의 arguments 객체의 경우도 Unmapped arguments 객체는 ordinary 객체, Mapped arguments 객체는 Exotic 객체입니다.

Mapped arguments 객체는 Exotic 객체다. 감이 오지 않으시나요?
Mapped arguments 객체는 객체의 몇몇(Get/Set/Delete …etc) 연산이 default behavior가 아닌, 다른 말로 특별한 로직이 있는 객체입니다.

자, 그럼 지금부터 위에서 살펴본 접근자들을 호출하는 로직이 들어있는, arguments 객체의 프로퍼티 접근/수정 연산을 다루는 [[Get]] internal method와 [[Set]] internal method를 살펴보도록 하겠습니다.

\[\[Get\]\]

여기서 P는 접근할 프로퍼티 키, Receiver는 접근할 프로퍼티가 접근자 프로퍼티라면, 적절한 접근자를 호출하게 되는데 이 때 접근자의 this 값으로서 사용되는 값 입니다.

  • 1. args 라는 변수를 선언하고, 이 internal method가 담겨있는 arguments 객체로 초기화합니다.
  • 2. map 이라는 변수를 선언하고, args.[[ParameterMap]] 으로 초기화합니다.
  • 3. isMapped 라는 변수를 선언하고, ! HasOwnProperty(map, P) 의 결과로 초기화합니다. (자바스크립트 코드로 표현하자면 map.hasOwnProperty(P)와 같은 로직입니다.)
  • 4~5. 만약 isMappedfalse라면 arg(arguments)객체에서 프로퍼티를 찾고 그 결과를 반환합니다.
  • 6. 그렇지 않은 경우, map 객체에서 프로퍼티를 찾고 그 결과를 반환합니다.(프로퍼티를 찾을 때, 찾은 프로퍼티가 접근자 프로퍼티이면, 그 프로퍼티의 Get 접근자를 실행시킵니다. 이 부분이 바로 우리의 ArgsGetter 접근자가 호출되는 부분입니다.)

짠, 거이 다 왔습니다. 힘내세요!
지금부터 볼 녀석은 [[Set]] internal method 입니다.

\[\[Set\]\]

여기서도 P는 접근할 프로퍼티 키, Receiver도 접근할 프로퍼티가 접근자 프로퍼티라면, 적절한 접근자를 호출하게 되는데 이 때 접근자의 this 값으로서 사용되는 값 입니다.
그리고.. V는 새로 설정중인 값 입니다.

NOTICE: isMapped 변수의 스코프가 이상해 보이신다면, 정상이십니다. 저도 방금 알았네요. 이슈로 올려야겠어요.

  • 1. args 라는 변수를 선언하고, 이 internal method가 담겨있는 arguments 객체로 초기화합니다.
  • 2 & 5. SameValue(args, Receiver)false 라면 args 객체에 키가 P이고 값이 V인 프로퍼티를 설정하고 그 결과를 반환합니다.
  • 3 & 4. args.[[ParameterMap]] 객체에 P를 키로 갖는 프로퍼티가 있으면 그 프로퍼티에 V를 설정합니다(여기서 ArgSetter가 호출됩니다!)
  • 5. args 객체에 키가 P이고 값이 V인 프로퍼티를 설정하고 그 결과를 반환합니다.

Time to explain!

자, 지금까지 우리는 arguments 객체의 종류, arguments 객체가 어떻게 생성되는 과정, 어떻게 양방향 데이터 바인딩을 구현했는지를 보았습니다.
이 정도면 여러분들 스스로를 arguments 학사 라고 칭하셔도 됩니다.(이 글에서 다루지 않은 디테일하지만 크게 중요하지는 않은 사항들이 남았거든요..ㅜㅜ)

자, 이제 마지막으로, 글의 서문에서 여러분들이 풀은 문제에 대한 해설을 지금까지 배운 지식들을 기반으로 해설해드리도록 하겠습니다.

1번 문제: Mapped arguments 객체는 자신의 프로퍼티의 변경사항을 매개변수에 반영시킵니다.

1
2
3
4
5
6
function problem1(a, b, c) {
arguments[0] = 100;
return [a, b, c];
}

problem1(1, 2, 3) // 반환값을 맞추어 보세요!

정답: [100, 2, 3]

간단한 문제입니다. problem1 에서의 arguments 객체는 Mapped Arguments 객체이기 때문에, arguments[0]a 사이의 바인딩이 생기게 됩니다.
그래서 arguments[0]을 수정했을 때, 그 변경사항이 a에도 반영된 것이지요.

2번 문제: [[ParameterMap]]가 공유한 프로퍼티는 arguments 객체의 own 프로퍼티가 아닙니다.

1
2
3
4
5
function problem2(a, b, c) {
return Object.hasOwnProperty(arguments, 0);
}

problem2(1, 2, 3) // 반환값을 맞추어 보세요!

정답: false

arguments 객체를 통해 arguments.[[ParameterMap]] 에 있는 프로퍼티를 접근할 수 있음에도 불구하고, 접근할 수 있는 프로퍼티들이 arguments 객체에 정의되있는 프로퍼티는 아니기에 false가 나옵니다.
여담으로, 프로토타입 객체의 프로퍼티가 공유되는 방식은 arguments.[[ParameterMap]]의 프로퍼티가 공유되는 방식과 같은 방식입니다.

3번 문제: Unmapped arguments 객체는 자신의 프로퍼티의 변경사항을 매개변수에 반영시키지 않습니다.

1
2
3
4
5
6
function problem3(a, b, c = 10) {
arguments[0] = 100;
return [a, b, c];
}

problem3(1, 2) // 반환값을 맞추어 보세요!

정답: [1, 2, 10]

problem3의 매개변수 중, c가 기본값 문법을 사용하고 있기 때문에 Mapped arguments 객체가 아닌 Unmapped arguments 객체가 생성됩니다.
그리고.. 아시다시피 Unmapped arguments 객체는 양방향 바인딩을 구현하고 있지 않지요. 그래서 arguments 객체의 프로퍼티를 수정하여도 그 변경사항이 problem3의 매개변수에게 반영되지 않습니다.

4번 문제: arguments.callee는 Unmapped arguments 객체에서 접근할 수 없습니다.

1
2
3
4
5
function problem4(a, b, c = 10) {
return arguments.callee;
}

problem4(1, 2) // 반환값을 맞추어 보세요!

정답: 반환값 없음(TypeError)

여기서도 문제 3과 같은 이유로 Unmapped arguments 객체가 생성됩니다. 아시다시피 Unmapped arguments 객체의 callee 프로퍼티는
get 이든 set 이든 TypeError를 일으키는 접근자로만 이루어진 접근자 프로퍼티입니다.

5번 문제: arguments 객체는 배열이 아닙니다.

1
2
3
4
5
6
function problem5() {
arguments[2] = 3;
return Array.prototype.slice.call(arguments);
}

problem5(1, 2); // 반환값을 맞추어 보세요!

정답: [1, 2]

아까 우리가 살펴 본 [[Set]] internal method에는 프로퍼티의 수가 변경되었다고 해서 arguments 객체의 length 프로퍼티를 수정하는 로직을 가지고 있지 않았습니다.
하지만 Array.prototype.slice 메서드는 주어진 객체의 length를 가지고 주어진 객체를 탐색하고 복사하지요.
그렇기에 Array.prototype.slicelength(2)만 보고 arguments 객체의 '2'을 키로 갖는 프로퍼티를 복사하지 않게 됩니다.

End

후우, 드디어 이 길고 긴 글이 끝을 맺게 되었네요.
부족한 필력에도 뒤로가기를 누르시지 않고 여기까지 읽어주셔서 감사합니다 :)
다음에는 더 좋은 자료로 돌아오도록 하겠습니다. 모두 즐거운 금요일 되세요!

공유하기