>웹 프론트엔드 >JS 튜토리얼 >JavaScript의 언어 기능에 대한 간략한 토론

JavaScript의 언어 기능에 대한 간략한 토론

高洛峰
高洛峰원래의
2016-11-28 09:56:35866검색

먼저 코드 조각을 살펴보세요.

var f = function foo(){
return typeof foo; // foo는 내부 범위에서 유효합니다.
};
// foo는 외부 사용이 보이지 않습니다
typeof foo; // "undefine"
f(); // "function"

여기서 말하고 싶은 것은 함수 표현식에서 foo라는 것입니다. 함수 외부에서는 참조할 수 없고 함수 내부에서만 참조할 수 있습니다.

json

많은 JavaScript 개발자는 JavaScript 개체 리터럴을 JSON 개체로 잘못 참조합니다. JSON은 데이터 교환 형식을 설명하도록 설계되었으며 JavaScript의 하위 집합인 자체 구문도 있습니다.

{ “prop”: “val” } 이러한 선언은 사용되는 컨텍스트에 따라 JavaScript 개체 리터럴 또는 JSON 문자열일 수 있습니다. 문자열 컨텍스트에서 사용되는 경우(작은따옴표나 큰따옴표로 묶이거나 텍스트 파일에서 읽음) 객체 리터럴 컨텍스트에서 사용되는 경우 객체 리터럴입니다.

// JSON 문자열입니다.
var foo = '{ "prop": "val" }';
// 객체 리터럴입니다.
var bar = { "prop ": "val" };

또 알아야 할 점은 JSON.parse는 JSON 문자열을 객체로 역직렬화하는 데 사용되고, JSON.stringify는 객체를 JSON 문자열로 직렬화하는 데 사용된다는 것입니다. 이전 버전의 브라우저는 이 객체를 지원하지 않지만 json2.js를 통해 동일한 기능을 얻을 수 있습니다.

프로토타입

function Animal (){
// ...
}
function cat (){
// ...
}
cat.prototype = new Animal();//이 메서드는 생성자의 내용을 상속합니다.
cat.prototype = Animal.prototype;//이 메서드는 생성자를 상속하지 않습니다.
//주의해야 할 또 다른 중요한 세부 사항은 자신만의 프로토타입 체인을 유지해야 한다는 것입니다. 초보자는 이것을 항상 잊어버릴 것입니다!
cat.prototype.constructor = cat;

새 객체를 할당하여 함수의 프로토타입 속성을 완전히 변경하면 원래 생성자에 대한 참조가 손실됩니다. 생성자 속성 제외:


function A() {}
A.prototype = {
x: 10
};
var a = new A();
alert(a.x); // 10
alert(a.constructor === A); // false!

MDN의 생성자에 대한 설명을 살펴보겠습니다. 인스턴스의 프로토타입을 생성한 Object 함수에 대한 참조입니다. 따라서 함수에 대한 프로토타입 참조를 수동으로 복원해야 합니다.


function A() {}
A.prototype = {
생성자: A,
x: 10
};
var a = new A();
alert(a.x) // 10
alert(a.constructor === A) ; // true

그러나 프로토타입 속성을 제출하면 생성된 객체의 프로토타입에는 영향을 미치지 않습니다(생성자의 프로토타입 속성이 변경될 때만 영향을 받습니다). 생성된 객체는 새로운 프로토타입을 갖게 되며, 생성된 객체는 여전히 원래의 이전 프로토타입을 참조합니다(이 프로토타입은 더 이상 수정할 수 없습니다).


function A() {}
A.prototype.x = 10;
var a = new A();
alert(a.x); >A.prototype = {
생성자: A,
x: 20
y: 30
};
// 객체 a는 암시적 [[Prototype]]을 통해 원유에서 참조됩니다. 프로토타입에서 얻은 값
alert(a.x); // 10
alert(a.y) // 정의되지 않음
var b = new A();
// 하지만 새 개체는 새 개체에서 가져온 것입니다. 프로토타입 획득 값
alert(b.x); // 20
alert(b.y) // 30

따라서 "프로토타입을 동적으로 수정하면 모든 객체에 영향을 미치고 새로운 프로토타입을 갖게 됩니다"는 잘못된 것입니다. 새 프로토타입은 프로토타입이 수정된 후에 새로 생성된 개체에만 적용됩니다. 여기서 주요 규칙은 객체가 생성될 때 객체의 프로토타입이 생성되고 이후에 새 객체로 수정될 수 없다는 것입니다. 여전히 동일한 객체를 참조하는 경우 생성자의 명시적인 프로토타입을 통해 참조할 수 있습니다. 객체가 생성된 후에는 프로토타입의 속성만 추가하거나 수정할 수 있습니다.

변수 객체 함수 실행 컨텍스트에서는 VO(변수 객체)에 직접 접근할 수 없습니다. 이때 활성화 객체(activation object)가 VO 역할을 합니다. 활성 객체는 함수 컨텍스트에 들어갈 때 생성되고 함수의 인수 속성을 통해 초기화됩니다. Arguments 속성의 값은 Arguments 객체입니다:

function foo(x, y, z) {

// 선언된 함수 매개변수 인수의 수(x, y, z)
Alert(foo.length); // 3
// 실제로 전달된 매개변수 수(x, y만 해당)
Alert(arguments.length) // 2
/ / 매개변수의 피호출자는 함수 그 자체이다
Alert(arguments.callee === foo); // true
}

실행 컨텍스트에 들어갈 때(코드 실행 전) 이미 VO 다음 속성을 포함합니다: 1 . 함수의 모든 형식 매개변수(함수 실행 컨텍스트에 있는 경우)

모든 함수 선언(FunctionDeclaration, FD)

모든 변수 선언(var, VariableDeclaration) ;
또 다른 전형적인 예:

alert(x); // function

var x = 10;
alert(x);

x = 20 ;

function x() {};
alert(x) // 20

사양에 따르면 컨텍스트에 들어갈 때 함수 선언이 채워집니다. 컨텍스트에 들어갈 때 변수 선언 "x"도 있는데 위에서 말했듯이 변수 선언은 함수 선언과 형식을 순서대로 따릅니다. 매개변수 선언, 그리고 이 컨텍스트 입력 단계에서 변수 선언은 VO에 이미 존재하는 동일한 이름의 함수 선언이나 형식적 매개변수 선언을 방해하지 않습니다. 단순 속성과 비교하여 변수에는 {DontDelete} 속성이 있습니다. 이 속성의 의미는 변수 속성을 삭제 연산자를 사용하여 직접 삭제할 수 없다는 것입니다.


a = 10;
alert(window.a); // 10
alert(delete a); // true
alert(window.a); 정의되지 않음
var b = 20;
alert(window.b); // 20
alert(delete b); // false
alert(window.b); b는 속성이 아니라 변수입니다!
var a = 10; // 전역 컨텍스트의 변수
(function () {
var b = 20; // 함수 컨텍스트의 지역 변수
} )( );
alert(a); // 10
alert(b); // 전역 변수 "b"가 선언되지 않았습니다.

이것은 함수 컨텍스트에 있습니다. 함수가 호출되는 방식에 따라 결정되는 호출자 제공. 호출 대괄호()의 왼쪽이 참조 유형 값인 경우 이는 참조 유형 값의 기본 개체로 설정됩니다(참조 유형과 다른 모든 속성). 이 값은 null이 됩니다. . 그러나 this 값이 null인 실제 상황은 없습니다. 왜냐하면 this 값이 null이면 해당 값이 암시적으로 전역 개체로 변환되기 때문입니다.


(function () {
Alert(this); // null => 전역
})();


in 이 예에서는 함수 객체가 있지만 참조 유형 객체는 없습니다(식별자나 속성 접근자가 아님). 따라서 this 값은 결국 전역 객체로 설정됩니다.


var foo = {
bar: function () {
Alert(this);
}
};
foo.bar() // 참조, OK => foo
(foo.bar)(); // 참조, OK => foo
(foo.bar = foo.bar)() // 전역
(false || foo.bar)(); // global
(foo.bar, foo.bar)(); // global

의 문제는 다음 세 가지 호출이 이후에 특정 작업을 적용한다는 것입니다. , 호출 대괄호 왼쪽의 값은 더 이상 참조 유형이 아닙니다.

첫 번째 예는 명백합니다. 결과적으로 이것이 기본 객체인 foo입니다.
두 번째 예에서는 그룹 연산자가 적용되지 않습니다. 위에서 언급한 GetValue와 같은 참조 유형에서 개체의 실제 값을 가져오는 메서드를 생각해 보세요. 이에 따라 그룹 작업이 반환될 때 여전히 참조 유형을 얻습니다. 이것이 바로 this 값이 기본 개체인 foo로 다시 설정되는 이유입니다.
세 번째 예에서는 그룹 연산자와 달리 할당 연산자가 GetValue 메서드를 호출합니다. 반환된 결과는 함수 객체입니다(참조 유형은 아님). 이는 null로 설정되고 결과가 전역 객체임을 의미합니다.
네 번째와 다섯 번째도 마찬가지입니다. 쉼표 연산자와 논리 연산자(OR)가 GetValue 메서드를 호출하고 이에 따라 참조를 잃고 함수를 가져옵니다. 그리고 다시 전역으로 설정하세요.
아시다시피 지역 변수, 내부 함수, 형식 매개변수는 해당 함수의 활성화 개체에 저장됩니다.

function foo() {
function bar() {
Alert(this); // 전역
}
bar() // AO.bar( )
}
활성 객체는 항상 다음과 같이 반환되며 값은 null입니다. 즉, 의사 코드의 AO.bar()는 null.bar()와 동일합니다. 여기서는 전역 객체로 설정된 이 예제를 다시 위에서 설명한 예제로 돌아갑니다.

스코프 체인

함수 생성자를 통해 생성된 함수의 범위 속성은 항상 유일한 전역 개체입니다.

한 가지 중요한 예외는 함수 생성자를 통해 생성된 함수와 관련됩니다.

var x = 10;
function foo() {
var y = 20;
function barFD() { // 함수 선언
Alert(x);
Alert(y);
}
var barFn = Function('alert(x); Alert(y);');
barFD() // 10, 20
barFn(); // 10, "y"는 정의되지 않았습니다
}
foo();

또한:


var x = 10, y = 10;
with ({x: 20}) {
var x = 30, y = 30;
//x = 30 여기서는 x = 20을 다룹니다.
경고(x) // 30
경고 (y); // 30
}
alert(x); // 10
alert(y); // 30

컨텍스트에 들어가면 어떻게 되나요? 식별자 "x"와 "y"가 변수 개체에 추가되었습니다. 또한 코드 실행 단계에서 다음과 같이 수정하세요.

x = 10, y = 10;
스코프 앞에 {x:20} 객체가 추가됩니다.
with 내부에서 var 선언이 발견되고 당연히 아무것도 생성되지 않습니다. 왜냐하면 context를 입력할 때 모든 변수가 구문 분석되어 추가되었기 때문입니다.
두 번째 단계에서는 변수 "x"만 수정되고 실제로는 개체의 "x"가 구문 분석되어 범위 앞에 추가됩니다. 체인 "x"는 20이고 30으로 변경되었습니다.
또한 구문 분석 후 해당 값이 10에서 30으로 변경됩니다.
또한, 명령문이 완료되면 해당 특정 객체가 범위 체인에서 제거됨에서 변경됩니다(변경된 변수 "x" - 30도 해당 객체에서 제거됨). 즉, 범위 체인의 구조가 with가 강화되기 전 상태로 복원됩니다.
마지막 두 개의 경고에서 현재 변수 개체의 "x"는 동일하게 유지되고 "y"의 값은 이제 30과 동일하며 with 문 실행 중에 변경되었습니다.
함수

괄호에 관한 질문

'함수를 생성한 후 바로 호출할 때 왜 함수를 괄호로 묶어야 하나요?'라는 질문을 살펴보겠습니다. ’라고 대답하면, 표현문의 제한은 이렇습니다.

표준에 따르면 표현식 문은 코드 블록과 구별하기 어렵기 때문에 중괄호 {로 시작할 수 없습니다. 마찬가지로 함수 키워드로도 시작할 수 없습니다. 함수 선언. 즉, 생성 직후 즉시 실행되는 함수를 정의하면 다음과 같은 방식으로 호출합니다.

function () {
...
}();
/ / 이름이
function foo() {
...
}();

있어도 우리는 함수 선언을 사용합니다. 위의 두 정의에 따라 인터프리터는 해석할 때 오류를 보고하지만 여러 가지 이유가 있을 수 있습니다. 전역 코드(즉, 프로그램 수준)에 정의된 경우 함수 키워드로 시작하므로 인터프리터는 이를 함수 선언으로 처리합니다. 첫 번째 예에서는 함수 선언에 이름이 없기 때문에 SyntaxError가 발생합니다. (앞서 함수 선언에는 이름이 있어야 한다고 언급했습니다). 두 번째 예에는 정상적으로 생성된 foo라는 함수 선언이 있지만 여전히 구문 오류가 발생합니다. 즉, 표현식이 없는 그룹화 연산자 오류입니다. 이는 실제로 함수 호출에 사용되는 괄호가 아니라 함수 선언 뒤의 그룹화 연산자입니다. 따라서 다음 코드를 선언하면:


// "foo"는 컨텍스트에 들어갈 때 생성되는 함수 선언입니다.
alert(foo) // function
function foo(x) ) {
Alert(x);
}(1); // 이것은 함수 호출이 아닌 그룹화 연산자입니다!
foo(10); // 이것은 실제 함수 호출이고 결과는 10입니다.

식을 만드는 가장 간단한 방법은 그룹화 연산자 대괄호를 사용하는 것이며, 그 안에 배치된 식은 항상 입니다. 통역사는 통역할 때 모호함을 갖지 않을 것입니다. 코드 실행 단계에서 이 함수는 생성되어 즉시 실행된 다음 자동으로 소멸됩니다(참조가 없는 경우)


(function foo(x) {
Alert(x);
})(1); // 그룹화 연산자가 아닌 호출입니다.

위 코드는 표현식을 괄호로 묶은 다음 (1)을 통해 호출한다고 합니다. 즉시 실행되는 다음 함수의 경우 함수가 이미 표현식의 위치에 있고 파서는 함수 실행 단계에서 생성되어야 하는 FE를 처리하고 있음을 알고 있기 때문에 주변 괄호가 필요하지 않습니다. , 함수가 생성된 직후에 호출되도록 합니다.

var foo = {
bar: function (x) {
return x % 2 != 0 ? 'yes' : 'no';
}( 1)
};
alert(foo.bar); // 'yes'

보시다시피 foo.bar는 함수가 아니라 문자열입니다. 조건부 매개변수를 기반으로 이 속성을 초기화하는 데 사용됩니다. 즉시 생성되고 호출됩니다.

따라서 "괄호에 대하여"라는 질문에 대한 완전한 대답은 다음과 같습니다.
그룹 연산자 괄호는 함수가 표현식 위치에 없을 때 필요합니다. 수동으로 FE로 기능합니다.
파서가 FE를 다루고 있다는 것을 알고 있다면 괄호를 사용할 필요가 없습니다.

자유 변수:

function testFn() {
var localVar = 10;//innerFn 함수의 경우 localVar는 자유 변수입니다.
function innerFn(innerParam) {
Alert(innerParam + localVar);
}
return innerFn;
}

정적 폐쇄 범위:

var z = 10;
function foo() {
Alert(z);
}
foo() // 10 – 정적 및 동적 범위 사용
(function () {
var z = 20;
foo(); // 10 – 정적 범위 사용, 20 – 동적 범위 사용
})();
// 동일합니다 foo를 매개변수로 사용하는 경우
(function (funArg) {
var z = 30;
funArg(); // 10 – 정적 범위, 30 – 동적 범위
})(foo);

이론: 범위 체인으로 인해 모든 함수는 클로저입니다(함수 유형에 관계없이 익명 함수, FE, NFE 및 FD는 모두 클로저입니다). 실용적인 관점에서 볼 때: 다음 함수는 클로저로 간주됩니다. * 생성된 컨텍스트가 파괴된 경우에도 여전히 존재합니다(예: 내부 함수가 상위 함수에서 반환됨)

* 무료 변수는 코드에서 참조됩니다.

마지막:

ECMAScript는 프로토타입 기반 위임 상속을 지원하는 객체 지향 언어입니다.


성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.