>웹 프론트엔드 >JS 튜토리얼 >객체지향 JavaScript(ibm 제공)에 대한 포괄적인 이해_javascript 기술

객체지향 JavaScript(ibm 제공)에 대한 포괄적인 이해_javascript 기술

WBOY
WBOY원래의
2016-05-16 17:16:22983검색

当今 JavaScript 大行其道,各种应用对其依赖日深。web 程序员已逐渐习惯使用各种优秀的 JavaScript 框架快速开发 Web 应用,从而忽略了对原生 JavaScript 的学习和深入理解。所以,经常出现的情况是,很多做了多年 JS 开发的程序员对闭包、函数式编程、原型总是说不清道不明,即使使用了框架,其代码组织也非常糟糕。这都是对原生 JavaScript 语言特性理解不够的表现。要掌握好 JavaScript,首先一点是必须摒弃一些其他高级语言如 Java、C# 等类式面向对象思维的干扰,全面地从函数式语言的角度理解 JavaScript 原型式面向对象的特点。把握好这一点之后,才有可能进一步使用好这门语言。本文适合群体:使用过 JS 框架但对 JS 语言本质缺乏理解的程序员,具有 Java、C++ 等语言开发经验,准备学习并使用 JavaScript 的程序员,以及一直对 JavaScript 是否面向对象模棱两可,但希望知道真相的 JS 爱好者。

重新认识面向对象

为了说明 JavaScript 是一门彻底的面向对象的语言,首先有必要从面向对象的概念着手 , 探讨一下面向对象中的几个概念:

一切事物皆对象

对象具有封装和继承特性
对象与对象之间使用消息通信,各自存在信息隐藏
以这三点做为依据,C++ 是半面向对象半面向过程语言,因为,虽然他实现了类的封装、继承和多态,但存在非对象性质的全局函数和变量。Java、C# 是完全的面向对象语言,它们通过类的形式组织函数和变量,使之不能脱离对象存在。但这里函数本身是一个过程,只是依附在某个类上。

然而,面向对象仅仅是一个概念或者编程思想而已,它不应该依赖于某个语言存在。比如 Java 采用面向对象思想构造其语言,它实现了类、继承、派生、多态、接口等机制。但是这些机制,只是实现面向对象编程的一种手段,而非必须。换言之,一门语言可以根据其自身特性选择合适的方式来实现面向对象。所以,由于大多数程序员首先学习或者使用的是类似 Java、C++ 等高级编译型语言(Java 虽然是半编译半解释,但一般做为编译型来讲解),因而先入为主地接受了“类”这个面向对象实现方式,从而在学习脚本语言的时候,习惯性地用类式面向对象语言中的概念来判断该语言是否是面向对象语言,或者是否具备面向对象特性。这也是阻碍程序员深入学习并掌握 JavaScript 的重要原因之一。

实际上,JavaScript 语言是通过一种叫做 原型(prototype)的方式来实现面向对象编程的。下面就来讨论 基于类的(class-based)面向对象和 基于原型的 (prototype-based) 面向对象这两种方式在构造客观世界的方式上的差别。

基于类的面向对象和基于原型的面向对象方式比较

在基于类的面向对象方式中,对象(object依靠 类(class来产生。而在基于原型的面向对象方式中,对象(object则是依靠 构造器(constructor利用 原型(prototype构造出来的。举个客观世界的例子来说明二种方式认知的差异。例如工厂造一辆车,一方面,工人必须参照一张工程图纸,设计规定这辆车应该如何制造。这里的工程图纸就好比是语言中的 类 (class),而车就是按照这个 类(class制造出来的;另一方面,工人和机器 ( 相当于 constructor) 利用各种零部件如发动机,轮胎,方向盘 ( 相当于 prototype 的各个属性 ) 将汽车构造出来。

事实上关于这两种方式谁更为彻底地表达了面向对象的思想,目前尚有争论。但笔者认为原型式面向对象是一种更为彻底的面向对象方式,理由如下:

首先,客观世界中的对象的产生都是其它实物对象构造的结果,而抽象的“图纸”是不能产生“汽车”的,也就是说,类是一个抽象概念而并非实体,而对象的产生是一个实体的产生;

其次,按照一切事物皆对象这个最基本的面向对象的法则来看,类 (class) 本身并不是一个对象,然而原型方式中的构造器 (constructor) 和原型 (prototype) 本身也是其他对象通过原型方式构造出来的对象。

또, 클래스 기반 객체지향 언어에서는 객체의 상태는 객체 인스턴스가 갖고, 객체의 행위 방식은 객체를 선언한 클래스가 갖고, 객체만이 가지는 구조 메소드는 상속될 수 있습니다. 프로토타입 객체 지향 언어에서는 객체의 동작과 상태가 객체 자체에 속하며 함께 상속될 수 있습니다(참조 리소스). 이는 또한 객관적인 현실에 더 가깝습니다.

마지막으로 Java와 같은 클래스 기반 객체지향 언어에서는 절차적 언어에서 전역 함수와 변수를 사용할 수 없는 불편함을 보완하기 위해 정적 속성과 정적 메서드를 선언할 수 있도록 허용합니다. 수업. 사실, 객관적 세계에는 소위 정적인 개념이 없습니다. 왜냐하면 모든 것이 대상이기 때문입니다! 프로토타입 객체지향 언어에서는 내장 객체를 제외하고 전역 객체, 메소드, 속성이 존재할 수 없으며 정적 개념도 없습니다. 모든 언어 요소(원시 요소)는 존재하기 위해 객체에 의존해야 합니다. 그러나 함수형 언어의 특성으로 인해 언어 요소가 의존하는 개체는 런타임 컨텍스트의 변경에 따라 변경되며 이는 특히 this 포인터의 변경에 반영됩니다. “만물은 무엇인가에 속하며, 우주는 만물이 생존하는 토대이다”라는 자연관에 더 가까운 것이 바로 이 특징이다. Listing 1에서 window는 우주의 개념과 유사하다.

목록 1. 객체의 상황별 종속성

코드 복사 코드는 다음과 같습니다.

<script> = "나는 String 객체입니다. 여기서 선언하지만 독립적으로 존재하지 않습니다! "<br> var obj = { des: "나는 Object 객체입니다. 여기서 선언합니다. 독립적으로 존재하지 않습니다. " }; <br> var fun = function() { <br> console.log( "나는 함수 객체입니다! 나를 부르는 사람, 나는 누구에게 속합니까:", this ); <br> }; <br> obj.fun = 재미 <p> </p> console.log( this === window ); // true를 인쇄합니다. <p> console.log( window.str === str ) // true를 인쇄합니다. <br> console.log( window.obj == = obj ); // true를 인쇄합니다. <br> console.log( window.fun === fun ); // true를 인쇄합니다. <br> fun() // 인쇄합니다. 나를 부르는 사람, 나는 누구에 속합니까: window <br> obj.fun(); 나를 부르는 사람, 나는 누구에게 속합니까: obj <br> fun.apply(str); // 인쇄 나는 함수 객체입니다! 나를 부르는 사람, 나는 누구에게 속해 있는가: str <br> </script>


프로토타입 기반 구현이라는 객체 지향 구현 방법이 있다는 사실을 받아들인 후, ECMAScript가 이 방법을 기반으로 자체 언어를 구성하는 방법을 더 자세히 살펴볼 수 있습니다.



가장 기본적인 객체지향

ECMAScript는 완전한 객체 지향 프로그래밍 언어(참조 리소스)이며 JavaScript는 그 변형입니다. Boolean, Number, String, Null, Undefine, Object 등 6가지 기본 데이터 유형을 제공합니다. 객체 지향을 달성하기 위해 ECMAScript는 매우 성공적인 데이터 구조인 JSON(JavaScript Object Notation)을 설계했습니다. 이 고전적인 구조는 언어에서 분리되어 널리 사용되는 데이터 상호 작용 형식이 됩니다( 참고자료).

기본 데이터 유형과 JSON 구성 구문을 갖춘 ECMAScript는 기본적으로 객체지향 프로그래밍을 구현할 수 있다고 해야 할까요. 개발자는

리터럴 표기법(리터럴 표기법)을 자유롭게 사용하여 객체를 구성하고 존재하지 않는 속성에 직접 값을 할당하거나 삭제 삭제 속성을 사용할 수 있습니다. (참고: JS의 delete 키워드는 객체 속성을 삭제하는 데 사용되며 C에서는 더 이상 사용되지 않는 객체를 해제하는 데 사용되는 삭제로 오해되는 경우가 많습니다.) 프로그램 목록 2 .

목록 2. 리터럴 표기법 개체 선언

코드 복사 코드는 다음과 같습니다.

var person = {
이름: "张三",
연령: 26,
성별: "남성",
먹는: 기능(물건) {
경고 ( "나는 먹고 있어요" );
person.height = 176> delete person[ "age" ];
실제 개발 과정에서 JS 애플리케이션에 대한 높은 요구 사항이 없는 대부분의 초보자나 개발자는 기본적으로 기본적인 개발 요구 사항을 충족하기 위해 ECMAScript 정의의 이 부분만 사용합니다. 그러나 이러한 코드 재사용성은 상속, 파생, 다형성 등을 구현하는 다른 클래스 기반 객체 지향 강력한 형식의 언어에 비해 다소 건조해 보이고 복잡한 JS 애플리케이션 개발 요구 사항을 충족할 수 없습니다. 그래서 ECMAScript는 객체 상속 문제를 해결하기 위해 프로토타입을 도입했습니다.


함수 생성자를 사용하여 객체 생성

리터럴 표기법(리터럴 표기법

)

외에도 ECMAScript에서는 생성자(constructor). 각 생성자는 실제로 프로토타입 기반 프로토타입 기반 상속 함수(function) 객체 입니다. >공유 속성 . 개체는 프로그램 목록 3과 같은 "새 키워드 생성자 호출"로 생성할 수 있습니다. 목록 3. 생성자를 사용하여 객체 생성 코드 복사 코드는 다음과 같습니다.// 생성자 Person 자체가 함수입니다. object function Person() {

// 여기에서 일부 초기화 작업을 수행할 수 있습니다

}

// 프로토타입이라는 속성이 있습니다.

Person.prototype = {

// new 키워드를 사용하여 객체 생성
var p = new Person()



초기 JavaScript의 발명가는 언어를 유명한 Java와 연결하기 위해 new 키워드를 사용하여 생성자 호출을 제한하고 객체를 생성했기 때문입니다(이제 두 가지가 Lei Feng 및 Lei Feng Tower와 관련이 있다는 것은 모두가 알고 있지만). 구문은 Java가 객체를 생성하는 방법과 비슷해 보입니다. 그러나 이 두 언어에서 new의 의미는 객체 구성 메커니즘이 완전히 다르기 때문에 그것과 아무 관련이 없다는 점을 지적해야 합니다. 클래스 기반 객체지향 언어의 객체 생성 방식에 익숙한 많은 프로그래머들이 항상 이해하지 못하기 때문에 JS 객체 프로토타입 구축 방식을 철저하게 이해하는 데 어려움을 겪는 것은 바로 여기서 구문의 유사성 때문입니다. JS 언어에서 "함수"를 사용하는 이유 이름을 클래스 이름으로 사용할 수 있는 현상. 본질적으로 JS는 여기서 new 키워드만 차용합니다. 즉, ECMAScript는 생성자를 호출하여 객체를 생성하기 위해 new가 아닌 ​​다른 표현식을 사용할 수 있습니다.




프로토타입 체인의 완전한 이해


ECMAScript에서 생성자가 생성한 각 객체에는 생성자의 프로토타입 속성 값을 가리키는
암시적 참조(

암시적 참조
)가 있습니다. 참조를

프로토타입(

프로토타입)이라고 합니다. 게다가 각 프로토타입은 자신의 프로토타입(즉, 프로토타입의 프로토타입)을 가리키는 암시적 참조

를 가질 수 있습니다. 이것이 계속되면 소위

프로토타입 체인(프로토타입 체인)(참조 리소스). 특정 언어 구현에서 각 객체에는 프로토타입에 대한 암시적 참조를 구현하기 위한 __proto__ 속성이 있습니다. 목록 4는 이 점을 보여줍니다. 목록 4. 객체의 __proto__ 속성 ​​및 암시적 참조 코드 복사 코드는 다음과 같습니다.


function Person( name ) {
this.name = name;
}
var p = new Person()
// 객체의 암시적 참조는 생성자 프로토타입 속성이므로 여기에 true가 인쇄됩니다.
console.log( p.__proto__ === Person.prototype ); // 프로토타입 자체는 Object 객체이므로 암시적 참조는

을 가리킵니다. // Object 생성자의 프로토타입 속성이므로 true가 인쇄됩니다.
console.log( Person.prototype.__proto__ == = 객체.프로토타입 )

// 생성자 Person 자체는 함수 객체이므로 여기에 true가 인쇄됩니다.

console.log( Person.__proto__ === Function.prototype )

프로토타입 체인을 사용하면 소위 속성 숨기기 메커니즘을 정의하고 이 메커니즘을 통해 상속을 얻을 수 있습니다. ECMAScript는 객체의 속성에 값을 할당할 때 인터프리터가 객체의 프로토타입 체인에서 속성을 포함하는 첫 번째 객체를 검색하도록 규정합니다(참고: 프로토타입 자체는 객체이고 프로토타입 체인은 집합의 체인입니다). 객체 프로토타입 체인의 첫 번째 객체는 객체 자체입니다. 반대로 객체 속성의 값을 얻으려는 경우 인터프리터는 자연스럽게 객체의 프로토타입 체인에서 해당 속성을 처음 가진 객체 속성 값을 반환합니다. 그림 1숨겨진 메커니즘을 설명합니다.


그림 1. 프로토타입 체인의 속성 숨기기 메커니즘
图 1. 原型链中的属性隐藏机制

그림 1에서 object1->prototype1->prototype2는 object1의 프로토타입 체인을 구성합니다. 위의 속성 숨기기 메커니즘에 따르면 프로토타입1 개체의 property4 속성과 prototype1 개체의 property3 속성을 명확하게 볼 수 있습니다. 프로토타입2 객체는 둘 다 숨겨져 있습니다. 프로토타입 체인을 이해하면 JS에서 프로토타입 기반 상속의 구현 원리를 매우 쉽게 이해할 수 있습니다.

프로그램 목록 5는 프로토타입 체인을 사용하여 상속을 구현하는 간단한 예입니다.

목록 5. 프로토타입 체인 Horse->Mammal->Animal을 사용하여 상속 구현

코드 복사 코드는 다음과 같습니다.
// Animal 객체 생성자 선언
function Animal () {
}
// Animal의 프로토타입 속성을 객체를 가리킨다,
// Animal 객체의 프로토타입을 지정하는 것으로 직접적으로 이해할 수도 있다.
Animal.prototype = {
이름: 동물 ",
무게: 0,
eat: function() {
Alert( "동물이 먹고 있어요!" );
}
}
/ / Mammal 객체 생성자 선언
function Mammal() {
this.name = "mammal";
}
// Mammal 객체의 프로토타입을 Animal 객체로 지정
// 실제로 Mammal 객체와 Animal 객체 사이의 프로토타입 체인을 생성합니다.
Mammal.prototype = new Animal();
// Horse 객체 생성자 선언 function Horse( height, Weight) {

this.name = "horse";
this.height = height; 🎜> }
// Horse 객체의 프로토타입을 Mammal 객체로 지정하고 Horse와 Mammal 사이의 프로토타입 체인을 계속 구축합니다.
Horse.prototype = new Mammal();
// eat 메소드를 다시 지정합니다. 이 메소드는 Animal 프로토타입에서 상속된 eat 메소드를 재정의합니다.
Horse.prototype.eat = function() {
Alert( "Horse is eating grass!" ) ;
}

// 프로토타입 체인 확인 및 이해

var Horse = new Horse( 100, 300 )
console.log( Horse.__proto__ === Horse.prototype ); > console.log( Horse.prototype.__proto__ === Mammal.prototype );
console.log( Mammal.prototype.__proto__ === Animal.prototype );

목록 5의 객체 프로토타입 상속 논리 구현을 이해하는 열쇠는 Horse.prototype = new Mammal() 및 Mammal.prototype = new Animal() 두 줄의 코드에 있습니다. 먼저, 방정식 우변의 결과는 임시 객체를 생성한 후, 이 객체를 방정식 좌변 객체의 프로토타입 속성에 할당하는 것입니다. 즉, 새로 생성된 오른쪽 개체가 왼쪽 개체의 프로토타입으로 사용됩니다. 독자는 이 두 방정식을 목록 5의 마지막 두 줄의 코드에 있는 해당 방정식으로 대체하여 스스로 이해할 수 있습니다.

JavaScript 클래스 상속 구현 방법

코드 목록 5에서 볼 수 있듯이 프로토타입 기반 상속 방법은 코드 재사용을 달성하지만 작성이 느슨하고 유창하지 않으며 가독성이 좋지 않아 확장 및 효과적인 구성 및 관리에 도움이 되지 않습니다. 소스 코드. 클래스 상속은 언어 구현에서 더욱 강력하며 재사용 가능한 코드를 작성하고 프로그램을 구성하는 데 분명한 이점이 있다는 점을 인정해야 합니다. 이로 인해 프로그래머는 JavaScript에서 클래스 기반 상속 스타일로 코딩하는 방법을 찾게 되었습니다. 추상적인 관점에서 볼 때, 클래스 상속과 프로토타입 상속은 모두 객체 지향을 구현하도록 설계되었으며, 이들이 구현한 캐리어 언어는 컴퓨팅 성능 측면에서 동일하므로(튜링 머신과 람다 미적분학의 컴퓨팅 성능이 power는 동등함), 그렇다면 프로토타입 상속 언어가 이 변환을 통해 클래스 기반 상속 코딩 스타일을 구현할 수 있게 하는 변환을 찾을 수 있을까요?

현재 일부 주류 JS 프레임워크는 이러한 변환 메커니즘, 즉 Dojo.declare(), Ext.entend() 등과 같은 클래스 선언 메서드를 제공합니다. 이러한 프레임워크를 사용하면 사용자는 JS 코드를 쉽고 친숙하게 구성할 수 있습니다. 실제로 많은 프레임워크가 등장하기 전에 JavaScript 마스터 Douglas Crockford는 세 가지 함수를 사용하여 이러한 변환을 달성하기 위해 Function 객체를 확장한 최초의 사람이었습니다. 구현에 대한 자세한 내용은 (참조) 리소스 ). Dean Edwards(참조 리소스)가 구현한 유명한 Base.js도 있습니다. jQuery의 아버지 John Resig가 다른 사람들의 장점을 활용해 30줄도 안 되는 코드로 자신만의 단순 상속을 구현했다는 점은 언급할 가치가 있습니다. 제공되는 확장 메소드를 사용하여 클래스를 선언하는 것은 매우 간단합니다. 프로그램 목록 6단순 상속 라이브러리 구현 클래스 선언을 사용한 예입니다. 마지막 print 출력문은 클래스 상속을 구현하기 위한 단순 상속을 가장 잘 설명하는 문장입니다.

목록 6. 단순 상속을 사용하여 클래스 상속 구현

코드 복사 코드는 다음과 같습니다.

// Person 클래스 선언
var Person = Class .extend( {
_issleeping: true,
init: function( name ) {
this._name = name;
},
isSleeping: function() {
return this.
}
} );
// Programmer 클래스를 선언하고 Person
var Programmer = Person.extend( {
init: function( name, issleeping ) {
// 상위 클래스 생성자 호출
this._super( name );
// 자신만의 상태 설정
this._issleeping = issleeping;
}
} ); > var person = new Person( "Zhang San" );
var diors = new Programmer( "Zhang Jiangnan", false )
// true 인쇄
console.log( person.isSleeping() ) ;
//false 인쇄
console.log( diors.isSleeping() )
// 모두 true이므로 true를 인쇄합니다.
console.log( person instanceof Person && person instanceof Class
&& diors 인스턴스of 프로그래머 &&
  diors 인스턴스of Person && diors 인스턴스of 클래스 );

이미 프로토타입, 함수 생성자, 클로저, 컨텍스트 기반에 대해 잘 이해하고 있다면 단순 상속이 어떻게 작동하는지 이해하는 것은 그리 어렵지 않습니다. 본질적으로 var Person = Class.extend(...) 문에서 왼쪽의 Person은 실제로 확장 메서드를 호출하는 Class에 의해 반환된 생성자, 즉 함수 객체에 대한 참조를 얻습니다. 이 아이디어에 따라 우리는 단순 상속이 이를 수행하는 방법을 계속 소개하고 프로토타입 상속에서 클래스 상속으로의 전환을 실현합니다. 그림 2는 단순 상속의 소스 코드와 그에 따른 주석입니다. 이해를 돕기 위해 코드를 한줄씩 중국어로 설명하였습니다.

그림 2. 단순 상속 소스 코드 분석

图 2.Simple Inheritance 源码解析

코드의 두 번째 부분을 제쳐두고 첫 번째와 세 번째 부분을 전체적으로 일관되게 살펴봅니다. 확장 함수의 기본 목적은 새로운 프로토타입 속성을 사용하여 새 생성자를 생성하는 것입니다. John Resig의 능숙한 필체와 JS 언어의 본질을 섬세하게 이해하는 그의 손길에 감탄하지 않을 수 없습니다. John Resig가 어떻게 이런 절묘한 구현 방법을 고안했는지에 대해 관심 있는 독자는 단순 상속의 초기 설계 사고 과정을 자세히 설명하는 이 기사(참고 자료)를 읽어 볼 수 있습니다.

JavaScript 비공개 멤버 구현

지금까지 객체지향 JavaScript에 대해 여전히 회의적이라면 JavaScript는 객체지향, 즉 프라이빗과 퍼블릭에 숨어 있는 정보를 구현하지 않는다는 의심을 가져야 합니다. 비공개 및 공개 멤버를 명시적으로 선언하는 다른 클래스 기반 객체 지향 방법과 달리 JavaScript의 정보 숨기기는 클로저를 통해 이루어집니다. 목록 7 참조:

목록 7. 클로저를 사용하여 정보 숨기기 구현

코드 복사 코드는 다음과 같습니다.

// 사용자 생성자 선언
함수 User( pwd ) {
// 개인 속성 정의
varpassword = pwd;
// 개인 메서드 정의
function getPassword() {
// 클로저에 비밀번호 반환
return 비밀번호
}
// 권한 있는 함수 선언, 이 권한 있는 메서드를 통해 비공개 멤버에 액세스하기 위해 객체의 다른 공개 메서드에 사용됩니다.
this.passwordService = function() {
return getPassword( ) ;
}
}
// 공개 멤버 선언
User.prototype.checkPassword = function( pwd ) {
return this.passwordService() === pwd
} ;
// 숨김 확인
var u = new User( "123456" ); // true 인쇄
console.log( u.checkPassword( "123456" ) ); / 정의되지 않음 인쇄
console.log( u.password ); // true 인쇄
console.log( typeof u.gePassword === "undefine" );
JavaScript는 기능적 언어 특성에 따라 결정되는 정보 숨기기를 달성하기 위해 클로저에 의존해야 합니다. 위의 내용은 JavaScript에서 컨텍스트 기반을 이해한다고 가정하므로 이 기사에서는 기능적 언어 및 클로저에 대한 주제를 논의하지 않습니다. JavaScript에 숨겨진 정보에 대해서는
Douglas Crockford
의 "JavaScript의 개인 멤버"(
참조 자료
) 기사에 더 권위 있고 자세한 소개가 있습니다.

결론

JavaScript는 C 언어 계열의 망토를 입고 있지만 클래스가 없지만 객체를 완벽하게 구현하기 때문에 LISP 스타일의 기능적 언어 기능을 표현하기 때문에 세계에서 가장 오해받는 프로그래밍 언어로 간주됩니다. 지향. 이 언어를 철저하게 이해하려면 C 언어의 외투를 벗고 함수형 프로그래밍의 관점으로 돌아가야 하며, 동시에 원래 클래스의 객체 지향 개념을 버리고 배우고 이해해야 합니다. 최근 웹 애플리케이션의 인기와 JS 언어 자체의 급속한 발전, 특히 백엔드 JS 엔진(예: V8 기반 NodeJS 등)의 등장으로 인해 원래는 그저 JS에 불과했던 JS가 페이지 효과를 쓰는 장난감은 더 넓은 세계로 발전할 것입니다. 이러한 개발 추세는 또한 JS 프로그래머에게 더 높은 요구 사항을 제시합니다. 이 언어를 철저히 이해해야만 대규모 JS 프로젝트에서 그 힘을 사용할 수 있습니다.

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