1 몇 가지 기본 개념 더 이 말을 다시 하는 이유는 무엇입니까?
상속을 논할 때 이미 캡슐화와 밀접하게 관련된 몇 가지 기본 개념을 나열했습니다. 오늘 논의할 기본 개념은 주로 상속 및 다형성과 관련되어 있지만 캡슐화와도 밀접한 관련이 있습니다. 일부 연결.
1.1 정의 및 할당
변수 정의는
var a
형식으로 변수를 선언하는 것을 말합니다.
함수 정의란
function a(...) {...}
형식으로 함수를 선언하는 것을 말합니다.
var a = 1;
은 두 개의 프로세스입니다. 첫 번째 프로세스는 변수 a를 정의하는 것이고, 두 번째 프로세스는 변수 a에 값을 할당하는 것입니다.
마찬가지로
var a = function(...) {};
또한 두 가지 프로세스가 있습니다. 첫 번째 프로세스는 변수 a와 익명 함수를 정의하는 것이고 두 번째 프로세스는 익명을 할당하는 것입니다. 변수 a에 대한 함수입니다.
변수 정의와 함수 정의는 전체 스크립트가 실행되기 전에 완료되고, 변수 할당은 실행 단계에서 완료됩니다.
변수 정의의 기능은 선언된 변수에 범위를 표시하는 것뿐입니다. 변수 정의는 정의되지 않았지만 직접 사용되는 변수 또는 정의되었지만 변수에 초기 값을 제공하지 않습니다. 값이 할당되어 있으며 해당 값은 모두 정의되지 않았습니다.
함수 정의는 함수의 범위를 선언하는 것 외에도 함수 본문 구조도 정의합니다. 이 프로세스는 재귀적입니다. 즉, 함수 본문의 정의에는 함수 본문 내의 변수 및 함수 정의가 포함됩니다.
다음 예시를 통해 이를 더욱 명확하게 이해할 수 있습니다.
alert(a);
alert(c)
var a = "a"
function a()
function b ( ) {}
var b = "b";
var c = "c";
var c = function() {}
alert(a); ;
alert(c);
이 프로그램의 결과가 무엇인지 맞춰보세요. 그런 다음 실행해 보고 생각한 것과 같은지 확인하면 위에서 말한 내용을 이해했다는 뜻입니다.
이 프로그램의 결과는 매우 흥미롭습니다. 첫 번째 경고(a)가 앞에 있지만 출력되는 값은 함수 a() {}이며, 이는 함수 정의가 실제로 전체에서 실행됨을 나타냅니다. 프로그램은 이전에 완료되었습니다.
b를 다시 보면 함수 b가 변수 b보다 먼저 정의되어 있지만 첫 번째 경고(b)의 출력은 여전히 함수 b() {}입니다. 이는 변수 정의가 변수에 대해 아무 작업도 수행하지 않음을 보여줍니다. 단지 범위일 뿐이며 함수 정의를 덮어쓰지는 않습니다.
마지막으로 c를 살펴보세요. 첫 번째 경고(c)의 출력은 정의되지 않았습니다. 이는 var c = function() {}이 함수 c를 정의하지 않고 변수 c와 익명 함수만 정의한다는 의미입니다.
두 번째 경고(a)를 보면 출력이 실제로 a라는 것을 알 수 있습니다. 이는 할당 문이 실제로 실행 중에 완료되었음을 보여주므로 함수 a의 정의를 다루고 있습니다.
두 번째 경고(b)는 물론 동일하며 출력은 b입니다. 이는 대입문이 함수 정의 이전에 작성되든 이후에 작성되든 상관없이 동일한 이름의 변수에 값을 할당한다는 의미입니다. 함수는 항상 함수 정의를 덮어씁니다.
두 번째 경고(c)의 출력은 function() {}입니다. 이는 할당 문이 순차적으로 실행되고 할당된 값이 함수인지 다른 객체인지에 관계없이 후속 할당이 이전 할당을 덮어쓰는 것을 보여줍니다.
위의 내용을 이해하신 후에는 function x(..) {…}를 언제 사용해야 하는지, var x = function (…) {…}를 언제 사용해야 하는지 알아야 할 것 같습니다.
마지막으로 변수 정의와 함수 정의가 eval에 나타나면 실행 단계에서 완료된다는 점을 다시 한번 말씀드리고 싶습니다. 따라서 꼭 필요한 경우가 아니면 eval을 사용하지 마십시오! 또한 eval을 사용하려는 경우에도 로컬 변수 및 로컬 메서드를 사용하지 마세요!
1.2 this 및 실행 컨텍스트
이에 대해서는 이미 캡슐화를 논의할 때 다루었습니다. 캡슐화에 대한 논의에서 우리가 보는 this는 this가 위치한 클래스의 인스턴스화된 객체 자체를 나타냅니다. 이것이 정말로 사실입니까?
먼저 다음 예시를 살펴보겠습니다.
코드 복사 코드는 다음과 같습니다.
var x = "나는 전역 변수입니다!";
함수 메서드() {
경고(x);
경고(this.x);
}
function class1() {
// private field
var x = "나는 private 변수입니다!";
// 프라이빗 메소드
function method1() {
alert(x);
경고(this.x);
}
var method2 = 메소드;
// public field
this.x = "나는 객체변수입니다!";
// 공개 메소드
this.method1 = function() {
alert(x);
경고(this.x);
}
this.method2 = 메소드;
// 생성자
{
this.method1(); // 나는 개인 변수입니다!
// 나는 객체변수다!
this.method2(); // 저는 전역 변수입니다!
// 나는 객체변수다!
방법1(); // 나는 개인 변수입니다!
// 나는 전역변수다!
방법2(); // 저는 전역 변수입니다!
// 나는 전역변수다!
method1.call(this); // 나는 개인 변수입니다!
// 나는 객체변수다!
method2.call(this); // 저는 전역 변수입니다!
// 나는 객체변수다!
}
}
var o = new class1();
메서드(); // 저는 전역 변수입니다!
// 나는 전역변수다!
o.method1(); // 나는 개인 변수입니다!
// 나는 객체변수다!
o.method2(); // 저는 전역 변수입니다!
// 나는 객체변수다!
왜 이런 결과가 나온 걸까요?
먼저 실행 컨텍스트가 무엇인지 살펴보겠습니다. 그렇다면 실행 컨텍스트란 무엇일까요?
현재 메소드가 실행 중인 경우 실행 컨텍스트는 메소드에 연결된 객체입니다(new를 통해 생성됨). 현재 실행 중인 객체는 실행 컨텍스트입니다.
메서드가 실행될 때 개체에 명시적으로 연결되지 않은 경우 해당 실행 컨텍스트는 전역 개체(최상위 개체)이지만 반드시 전역 개체에 연결될 필요는 없습니다. 전역 개체는 현재 환경에 따라 결정됩니다. 브라우저 환경에서 전역 개체는 창 개체입니다.
모든 함수 외부에서 정의된 전역 변수, 전역 함수는 전역 객체에 붙고, 함수 내부에 정의된 지역 변수, 지역 함수는 아무 객체에도 붙지 않습니다.
실행 컨텍스트와 변수 범위 사이에 관계가 있나요?
실행 컨텍스트와 변수 범위가 다릅니다.
함수가 다른 변수에 할당되면 함수 내부에서 사용되는 변수의 범위는 변경되지 않지만, 실행 컨텍스트는 변수가 연결된 개체로 변경됩니다(변수에 연결된 개체가 있는 경우). .
함수 프로토타입의 호출 및 적용 메서드는 실행 컨텍스트를 변경할 수 있지만 변수 범위는 변경하지 않습니다.
위 단어를 이해하려면 한 가지만 기억하면 됩니다.
변수의 범위는 정의될 때 결정되며, 실행 컨텍스트는 실행될 때 결정됩니다. 언제든지 변경될 수 있습니다.
이렇게 하면 위의 예를 이해하는 것이 어렵지 않습니다. this.method1() 문(여기서 말한 내용은 아직 함수 본문에 입력되지 않았음에 유의)이 실행되면 객체가 생성되고 현재 실행 컨텍스트가 생성되는 객체이므로 this는 현재 생성 중인 객체를 가리킵니다. , this.method1() 메소드가 실행되면(여기서는 함수 본문을 입력하는 것을 의미함) 실행 메소드에 첨부된 객체도 생성되는 객체이므로 this.x의 this도 동일한 객체입니다. . 그래서 표시되는 출력은 I'm a object Variable!입니다.
함수 method1() 실행 시(함수 본문 진입 후) method1()은 class1에 정의되어 있지만 class1에는 붙어 있지 않습니다. class1에 의해 인스턴스화된 개체이지만 해당 범위는 class1로 제한됩니다. 따라서 해당 객체는 실제로 전역 객체입니다. 따라서 그 안에서 Alert(this.x)가 실행되면 this.x는 전역 환경에서 "나는 x의 전역 변수입니다!"라고 정의한 값이 됩니다.
method2()는 class1에 정의되어 있지만 method()는 class1 외부에 정의되어 있습니다. method2에 method가 할당되면 method2의 범위는 변경되지 않습니다. 메소드가 정의된 범위이므로 표시되는 것은 전역 변수 출력입니다. 마찬가지로, this.method2()가 호출되면 Alert(x)는 I'm 전역 변수를 출력합니다.
call은 실행 컨텍스트를 변경하기 때문에 method1.call(this) 및 method2.call(this)을 전달할 때 this.x는 I'm 객체 변수가 됩니다! 하지만 범위를 변경할 수는 없으므로 x는 호출 메서드가 없었던 것과 여전히 동일합니다.
나중에 o.method1()을 실행할 때, Alert(x)는 x의 실행 컨텍스트를 지적하기 위해 이를 사용하지 않습니다. 그러면 x는 현재 실행되는 함수 범위에서 가장 최근에 정의된 변수를 나타냅니다. 이때 출력되는 것은 I'm private 변수입니다!. 최종 출력은 내가 객체 변수라는 것입니다! 제가 말하지 않아도 모두가 그 이유를 알 것 같아요. 그렇죠?
2 상속과 다형성
2.1 캡슐화로 시작
앞서 말했듯이 캡슐화의 목적은 데이터 은닉을 달성하는 것입니다.
그러나 더 깊은 수준에서 자바스크립트의 캡슐화에는 다음과 같은 이점도 있습니다.
1. 비공개 부분의 구현이 완전히 다시 작성되면 호출자의 동작을 변경할 필요가 없습니다. 이는 캡슐화를 달성하려는 다른 객체지향 언어의 주요 목적이기도 합니다.
2. JavaScript에서는 로컬 변수와 로컬 함수에 더 빠르게 접근하므로 프라이빗 필드를 로컬 변수로 캡슐화하고 프라이빗 메서드를 로컬 메서드로 캡슐화하면 스크립트의 실행 효율성을 높일 수 있습니다.
3. 자바스크립트 압축 난독처리기(제가 아는 한 현재 최고의 자바스크립트 분석, 압축, 난독처리기는 JSA입니다)의 경우 로컬 변수와 로컬 함수 이름은 교체가 가능하지만, 글로벌 변수와 글로벌 함수 이름은 교체가 불가능합니다. (실제로 이는 JavaScript 파서의 경우에도 마찬가지입니다.) 따라서 오픈 소스 또는 비오픈 소스 JavaScript 프로그램의 경우 프라이빗 필드와 프라이빗 메서드가 캡슐화 기술을 사용하는 경우 코드를 작성할 때 충분히 긴 표의 문자 이름으로 정의하여 코드의 가독성을 높이고 게시할 때 완전히 압축되고 난독화될 수 있도록 매우 짧은 이름(일반적으로 단일 문자 이름)으로 대체됩니다. 또한 대역폭 사용량을 줄이고 세부 정보를 실제로 숨길 수 있습니다.
그래서 캡슐화는 자바스크립트에 매우 유용합니다!
그럼 자바스크립트에서 상속을 구현하는 목적은 무엇인가요?
2.2 상속이 필요한 이유
다른 객체지향 프로그래밍 언어에서는 반복되는 코드 작성을 줄이는 것 외에도 상속의 가장 큰 용도는 다형성을 달성하는 것입니다. 이는 강력한 유형의 언어에서 특히 그렇습니다.
1. 강력한 유형의 언어에서는 두 유형이 변수 유형과 호환되지 않는 한 변수에 서로 다른 유형의 두 값을 할당할 수 없습니다. 계승.
2. 강력한 형식의 언어에서는 기존 형식의 메서드를 직접 확장하거나 다시 작성할 수 없습니다. 형식을 확장하려면 형식을 상속하고 하위 클래스에서 다시 작성하는 것이 유일한 방법입니다.
따라서 강력한 형식의 객체 지향 언어의 경우 다형성 구현은 상속 구현에 따라 달라집니다.
JavaScript 언어의 경우 상속은 다형성을 달성하는 데 그다지 중요하지 않습니다.
1. JavaScript 언어에서는 변수에 모든 유형의 값을 할당할 수 있으며 모든 유형을 동일한 방식으로 호출할 수 있습니다. 개체에 동일한 이름이 있습니다.
2. 자바스크립트 언어에서는 기존 타입의 메소드를 프로토타입을 통해 직접 확장하고 다시 작성할 수 있습니다.
그래서 JavaScript에서 상속의 주요 역할은 중복 코드 작성을 줄이는 것입니다.
다음에 이야기할 두 가지 상속 방법은 모두에게 친숙할 수 있습니다. 하나는 프로토타입 상속 방법이고 다른 하나는 호출 상속 방법입니다. 이 두 가지 방법 중 어느 것도 부작용을 일으키지 않습니다. 우리는 주로 이 두 가지 방법의 본질과 주의해야 할 사항에 대해 논의합니다.
2.3 프로토타입 상속 방법
자바스크립트에서는 각 클래스(함수)에 프로토타입이 있고, 클래스가 인스턴스화될 때 프로토타입의 멤버가 클래스의 인스턴스화된 객체에 전달됩니다. 인스턴스화된 객체에는 프로토타입이 없지만, 다른 클래스(함수)의 프로토타입으로 사용될 수 있다. 이는 객체의 프로토타입입니다. 이것이 프로토타입 상속의 본질이다.
프로토타입 상속은 JavaScript의 많은 기본 개체에서 사용되는 상속 방법이기도 합니다.
function parentClass() {
/ / private field
var x = "나는 parentClass 필드입니다!"
// private method
function method1() {
alert(x)>alert("I' m은 parentClass 메소드입니다!");
}
// 공개 필드
this.x = "나는 parentClass 객체 필드입니다!";
// 공개 메소드
this.method1 = function( ) {
alert(x);
alert(this.x);
method1()
}
}
parentClass.prototype.method() {
alert("나는 parentClass 프로토타입 메서드입니다!");
}
parentClass.staticMethod = function () {
alert("나는 parentClass 정적 메서드입니다!");
}
function subClass() {
// private 필드
var x = "나는 하위 클래스 필드입니다!"
// private 메소드
function method2() {
Alert(x);
alert("나는 하위 클래스 메소드입니다!");
}
// public field
this.x = "나는 하위 클래스 객체 필드입니다. !";
// 공개 메소드
this.method2 = function() {
alert(x);
alert(this.x);
method2();
}
this.method3 = function() {
method1();
}
}
// 상속
subClass.prototype = new parentClass(); .constructor = subClass;
// 테스트
var o = new subClass();
alert(o 인스턴스of parentClass); // true
alert(o 인스턴스of subClass); >alert( o.constructor); // function subClass() {...}
o.method1(); // 나는 parentClass 필드입니다!
// 나는 subClass 객체 필드입니다!
// 나는 parentClass 필드입니다!
// 나는 parentClass 메소드입니다!
o.method2() // 나는 하위 클래스 필드입니다! subClass 객체 필드입니다!
// 저는 subClass 메소드입니다!
o.method() // 저는 parentClass 프로토타입 메소드입니다! >o.method3(); // 오류!!!
subClass.staticMethod(); // 오류!!!
위의 예는 프로토타입 상속을 사용하는 방법을 잘 보여줍니다. .