유지 관리 가능한 객체 지향 JavaScript 코드를 작성할 수 있으면 비용을 절약할 수 있을 뿐만 아니라 인기도 높아질 것입니다. 믿을 수 없나요? 귀하 또는 다른 사람이 언젠가 다시 방문하여 귀하의 코드를 재사용할 가능성이 있습니다. 이 경험을 가능한 한 덜 고통스럽게 만들 수 있다면 많은 시간을 절약할 수 있습니다. 지구상의 모든 사람들은 시간이 돈이라는 것을 알고 있습니다. 마찬가지로, 당신은 누군가의 골치 아픈 일을 덜어줌으로써 누군가의 호의를 얻을 수도 있습니다. 하지만 유지 관리 가능한 객체 지향 JavaScript 코드를 작성하는 방법을 살펴보기 전에 객체 지향이 무엇인지 간단히 살펴보겠습니다. 객체지향 개념을 이미 이해하고 있다면 다음 섹션을 바로 건너뛰어도 됩니다.
객체지향이란 무엇인가요?
객체 지향 프로그래밍은 주로 코드를 통해 현실 세계의 물리적 객체를 표현합니다. 객체를 생성하려면 먼저 객체를 정의하는 "클래스"를 작성해야 합니다. 클래스는 계정, 직원, 탐색 메뉴, 자동차, 식물, 광고, 음료 등 거의 모든 것을 나타낼 수 있습니다. 그리고 객체를 만들고 싶을 때마다 클래스에서 객체를 인스턴스화하세요. 즉, 클래스의 인스턴스가 객체로 생성됩니다. 실제로 객체는 일반적으로 동일한 유형의 두 개 이상의 항목을 처리할 때 사용됩니다. 게다가 단순한 기능적 프로그램만으로도 훌륭한 작업을 수행할 수 있습니다. 객체는 본질적으로 데이터의 컨테이너입니다. 따라서 직원 개체에는 직원 번호, 이름, 입사 날짜, 직위, 급여, 직위 등을 저장하고 싶을 수 있습니다.
객체에는 데이터를 처리하는 함수("메서드"라고도 함)도 포함되어 있습니다. 방법은 데이터 무결성을 보장하고 저장하기 전에 데이터를 변환하는 중개자로 사용됩니다. 예를 들어, 메서드는 모든 형식의 날짜를 허용하고 이를 저장하기 전에 표준화된 형식으로 변환할 수 있습니다. 마지막으로 클래스는 다른 클래스로부터 상속받을 수도 있습니다. 상속을 사용하면 다른 클래스에서 동일한 코드를 재사용할 수 있습니다. 예를 들어, 은행계좌와 비디오가게 계좌 모두 개인정보, 계좌 개설일, 지점 정보 등이 포함된 기본 계정 클래스를 상속받을 수 있습니다. 그런 다음 각각은 거래 또는 대출 처리를 위한 자체 데이터 구조와 방법을 정의할 수 있습니다.
경고: JavaScript 객체 지향은 동일하지 않습니다.
이전 섹션에서는 클래식 객체 지향의 개요를 객체지향 객체 프로그래밍에 대한 기본 지식. JavaScript가 이러한 규칙을 따르지 않기 때문에 내가 고전적이라고 말하는 것입니다. 이에 비해 JavaScript 클래스는 함수로 작성되고 프로토타입을 통해 상속이 구현됩니다. 프로토타입 상속은 기본적으로 클래스에서 클래스를 상속하는 대신 프로토타입 속성을 사용하여 객체의 상속을 구현하는 것을 의미합니다.
객체 인스턴스화
다음은 JavaScript의 객체 인스턴스화 예입니다.
// 定义Employee类 function Employee(num, fname, lname) { this.getFullName = function () { return fname + " " + lname; } }; // 实例化Employee对象 var john = new Employee("4815162342", "John", "Doe"); alert("The employee's full name is " + john.getFullName());
여기서 주목해야 할 세 가지 중요한 사항이 있습니다.
1 "클래스" 함수 이름의 첫 글자는 대문자로 시작해야 합니다. 이는 함수가 일반 함수처럼 호출되지 않고 인스턴스화되도록 의도되었음을 나타냅니다.
2는 인스턴스화 중에 new 연산자를 사용합니다. new를 생략하고 함수만 호출하면 많은 문제가 발생합니다.
3 이 연산자에는 getFullName이 할당되어 있으므로 공개적으로 사용할 수 있지만 fname 및 lname은 사용할 수 없습니다. Employee 함수에 의해 생성된 클로저는 fname 및 lname에 대한 getFullName 액세스를 제공하지만 다른 클래스에는 비공개로 유지됩니다.
프로토타입 상속
다음은 JavaScript의 프로토타입 상속의 예입니다.
// 定义Human类 function Human() { this.setName = function (fname, lname) { this.fname = fname; this.lname = lname; } this.getFullName = function () { return this.fname + " " + this.lname; } } // 定义Employee类 function Employee(num) { this.getNum = function () { return num; } }; //让Employee继承Human类 Employee.prototype = new Human(); // 实例化Employee对象 var john = new Employee("4815162342"); john.setName("John", "Doe"); alert(john.getFullName() + "'s employee number is " + john.getNum());
이번에는 인간의 공통 속성을 모두 포함하는 Human 클래스를 만들었습니다. 직원에게만 이름이 있는 것이 아니라 모든 사람에게 이름이 있기 때문에 fname과 lname도 입력했습니다. 그런 다음 Human 개체를 프로토타입 속성에 할당합니다.
상속을 통한 코드 재사용
이전 예에서는 원래 Employee 클래스가 두 부분으로 나누어졌습니다. 모든 일반적인 인간 속성은 Human 클래스로 이동되었으며 Employee는 Human을 상속 받았습니다. 이 경우 Human의 속성은 Student, Client, Citizen, Visitor 등과 같은 다른 개체에서 사용될 수 있습니다. 이제 이것이 코드를 분할하고 재사용하는 좋은 방법이라는 것을 눈치챘을 것입니다. Human 개체를 다룰 때 각각의 다른 개체를 하나씩 다시 만드는 대신 Human 개체를 상속하여 기존 속성을 사용하기만 하면 됩니다. 또한 "중간 이름" 속성을 추가하려는 경우 한 번만 추가하면 되며 Human 클래스를 상속받은 사용자는 즉시 사용할 수 있습니다. 반대로 "중간 이름" 속성을 개체에 추가하려는 경우 Human 클래스에 추가하지 않고 해당 개체에 직접 추가할 수 있습니다.
1. Public과 Private
다음 주제는 클래스의 public과 private 변수에 대해 이야기해보겠습니다. . 객체에서 데이터가 처리되는 방식에 따라 데이터는 비공개 또는 공개로 처리됩니다. 사유 재산이 반드시 다른 사람이 해당 재산에 접근할 수 없다는 것을 의미하지는 않습니다. 어쩌면 특정 방법만 사용해야 할 수도 있습니다.
읽기 전용
객체를 생성할 때 값만 원하는 경우가 있습니다. 일단 생성되면 다른 사람이 이 값을 변경하는 것을 원하지 않습니다. 이렇게 하려면 개인 변수를 만들고 인스턴스화 중에 값을 할당합니다.
function Animal(type) { var data = []; data['type'] = type; this.getType = function () { return data['type']; } } var fluffy = new Animal('dog'); fluffy.getType(); // 返回 'dog'
在这个例子中,Animal类中创建了一个本地数组data。当 Animal对象被实例化时,传递了一个type的值并将该值放置在data数组中。因为它是私有的,所以该值无法被覆盖(Animal函数定义了它的范围)。一旦对象被实例化了,读取type值的唯一方式是调用getType方法。因为getType是在Animal中定义的,因此凭借Animal产生的闭包,getType可以进到data中。这样的话,虽可以读到对象的类型却无法改变。
有一点非常重要,就是当对象被继承时,“只读”技术就无法运用。在执行继承后,每个实例化的对象都会共享那些只读变量并覆盖其值。最简单的解决办法是将类中的只读变量转换成公共变量。但是你必须保持它们是私有的,你可以使用Philippe在评论中提到的技术。
Public(公有)
当然也有些时候你想要任意读写某个属性的值。要实现这一点,需要使用this操作符。
function Animal() { this.mood = ''; } var fluffy = new Animal(); fluffy.mood = 'happy'; fluffy.mood; // 返回 'happy'
这次Animal类公开了一个叫mood的属性,可以被随意读写。同样地,你还可以将函数指定给公有的属性,例如之前例子中的getType函数。只是要注意不要给getType赋值,不然的话你会毁了它的。
完全私有
最后,可能你发现你需要一个完全私有化的本地变量。这样的话,你可以使用与第一个例子中一样的模式而不需要创建公有方法。
function Animal() { var secret = "You'll never know!" } var fluffy = new Animal();
2. 写灵活的API
既然我们已经谈到类的创建,为了保持与产品需求变化同步,我们需要保持代码不过时。如果你已经做过某些项目或者是长期维护过某个产品,那么你就应该知道需求是变化的。这是一个不争的事实。如果你不是这么想的话,那么你的代码在还没有写之前就将注定荒废。可能你突然就需要将选项卡中的内容弄成动画形式,或是需要通过Ajax调用来获取数据。尽管准确预测未来是不大可能,但是却完全可以将代码写灵活以备将来不时之需。
Saner参数列表
在设计参数列表的时候可以让代码有前瞻性。参数列表是让别人实现你代码的主要接触点,如果没有设计好的话,是会很有问题的。你应该避免下面这样的参数列表:
function Person(employeeId, fname, lname, tel, fax, email, email2, dob) { };
这个类十分脆弱。如果在你发布代码后想要添加一个中间名参数,因为顺序问题,你不得不在列表的最后往上加。这让工作变得尴尬。如果你没有为每个参数赋值的话,将会十分困难。例如:
var ara = new Person(1234, "Ara", "Pehlivanian", "514-555-1234", null, null, null, "1976-05-17");
操作参数列表更整洁也更灵活的方式是使用这个模式:
function Person(employeeId, data) { };
有第一个参数因为这是必需的。剩下的就混在对象的里面,这样才可以灵活运用。
var ara = new Person(1234, { fname: "Ara", lname: "Pehlivanian", tel: "514-555-1234", dob: "1976-05-17" });
这个模式的漂亮之处在于它即方便阅读又高度灵活。注意到fax, email和email2完全被忽略了。不仅如此,对象是没有特定顺序的,因此哪里方便就在哪里添加一个中间名参数是非常容易的:
var ara = new Person(1234, { fname: "Ara", mname: "Chris", lname: "Pehlivanian", tel: "514-555-1234", dob: "1976-05-17" });
类里面的代码不重要,因为里面的值可以通过索引来访问:
function Person(employeeId, data) { this.fname = data['fname']; };
如果data['fname'] 返回一个值,那么他就被设定好了。否则的话,没被设定好,也没有什么损失。
让代码可嵌入
随着时间流逝,产品需求可能对你类的行为有更多的要求。而该行为却与你类的核心功能没有半毛钱关系。也有可能是类的唯一一种实现,好比在一个选项卡的面板获取另一个选项卡的外部数据时,将这个选项卡面板中的内容变灰。你可能想把这些功能放在类的里面,但是它们不属于那里。选项卡条的责任在于管理选项卡。动画和获取数据是完全不同的两码事,也必须与选项卡条的代码分开。唯一一个让你的选项卡条不过时而又将那些额外的功能排除在外的方法是,允许将行为嵌入到代码当中。换句话说,通过创建事件,让它们在你的代码中与关键时刻挂钩,例如onTabChange, afterTabChange, onShowPanel, afterShowPanel等等。那样的话,他们可以轻易地与你的onShowPanel事件挂钩,写一个将面板内容变灰的处理器,这样就皆大欢喜了。JavaScript库让你可以足够容易地做到这一点,但是你自己写也不那么难。下面是使用YUI 3的一个例子。
<script type="text/javascript" src="http://yui.yahooapis.com/combo?3.2.0/build/yui/yui-min.js"></script> <script type="text/javascript"> YUI().use('event', function (Y) { function TabStrip() { this.showPanel = function () { this.fire('onShowPanel'); // 展现面板的代码 this.fire('afterShowPanel'); }; }; // 让TabStrip有能力激发常用事件 Y.augment(TabStrip, Y.EventTarget); var ts = new TabStrip(); // 给TabStrip的这个实例创建常用时间处理器 ts.on('onShowPanel', function () { //在展示面板之前要做的事 }); ts.on('onShowPanel', function () { //在展示面板之前要做的其他事 }); ts.on('afterShowPanel', function () { //在展示面板之后要做的事 }); ts.showPanel(); }); </script>
这个例子有一个简单的 TabStrip 类,其中有个showPanel方法。这个方法激发两个事件,onShowPanel和afterShowPanel。这个能力是通过用Y.EventTarget扩大类来实现的。一旦做成,我们就实例化了一个TabStrip对象,并将一堆处理器都分配给它。这是用来处理实例的唯一行为而又能避免混乱当前类的常用代码。
总结
如果你打算重用代码,无论是在同一网页,同一网站还是跨项目操作,考虑一下在类里面将其打包和组织起来。面向对象JavaScript很自然地帮助实现更好的代码组织以及代码重用。除此以外,有点远见的你可以确保代码具有足够的灵活性,可以在你写完代码后持续使用很长时间。编写可重用的不过时JavaScript代码可以节省你,你的团队还有你公司的时间和金钱。这绝对能让你大受欢迎。