编写可维护面向对象的JavaScript代码[翻译]


Posted in Javascript onFebruary 12, 2011

Writing maintainable Object-Oriented (OO) JavaScript will save you money and make you popular. Don't believe me? Odds are that either you or someone else will come back and work with your code. Making that as painless an experience as possible will save time, which we all know equates to money. It will also win you the favor of those for whom you just saved a headache. But before we dive into writing maintainable OO JavaScript, let's just take a quick look at what OO is all about. If you know already about OO, feel free to skip the next section.

What is OO?
Object-oriented programming basically represents physical, real-world objects that you want to work with in code. In order to create objects, you need to first define them by writing what's called a class. Classes can represent pretty much anything: accounts, employees, navigation menus, vehicles, plants, advertisements, drinks, etc... Then, every time you want to create an object to work with, you instantiate one from a class. In other words, you create an instance of a class which gives you an object to work with. In fact, the best time to be using objects is when you'll be dealing with more than one of anything. Otherwise, a simple functional program will likely do just as well. Objects are essentially containers for data. So in the case of an employee object, you might store their employee number, name, start date, title, salary, seniority, etc... Objects also include functions (called methods) to handle that data. Methods are used as intermediaries to ensure data integrity. They're also used to transform data before storing it. For example, a method could receive a date in an arbitrary format then convert it to a standardized format before storing it. Finally, classes can also inherit from other classes. Inheritance allows you to reuse code across different types of classes. For example, both bank account and video store account classes could inherit from a base account class which would provide fields for profile information, account creation date, branch information, etc... Then, each would define its own transactions or rentals handling data structures and methods.

Warning: JavaScript OO is different
In the previous section I outlined the basics of classical object-oriented programming. I say classical because JavaScript doesn't quite follow those rules. Instead, JavaScript classes are actually written as functions and inheritance is prototypal. Prototypal inheritance basically means that rather than classes inheriting from classes, they inherit from objects using the prototype property.

Object Instantiation
Here's an example of object instantiation in JavaScript:

// Define the Employee class 

function Employee(num, fname, lname) { 

this.getFullName = function () { 

return fname + " " + lname; 

} 

}; 

// Instantiate an Employee object 

var john = new Employee("4815162342", "John", "Doe"); 

alert("The employee's full name is " + john.getFullName());

There are three important things to note here:

I uppercased the first letter of my "class" function. That important distinction lets people know that it's for instantiation and not to be called as a normal function.
I use the new operator when instantiating. Leaving it out would simply call the function and result in problems.
Though getFullName is publicly available because it's assigned to the this operator, fname and lname are not. The closure created by the Employee function gives getFullName access to fname and lname while allowing them to remain private from everyone else.
Prototypal Inheritance
Now, here's an example of prototypal inheritance in JavaScript:

// Define Human class 

function Human() { 

this.setName = function (fname, lname) { 

this.fname = fname; 

this.lname = lname; 

} 

this.getFullName = function () { 

return this.fname + " " + this.lname; 
} 

} 

// Define the Employee class 

function Employee(num) { 

this.getNum = function () { 

return num; 

} 

}; 

// Let Employee inherit from Human 

Employee.prototype = new Human(); 

// Instantiate an Employee object 

var john = new Employee("4815162342"); 

john.setName("John", "Doe"); 

alert(john.getFullName() + "'s employee number is " + john.getNum());

This time, I've created a Human class which contains properties common to humans--I moved fname and lname there since all humans, not just employees have names. I then extended the Employee class by assigning a Human object to its prototype property.

Code Reuse Through Inheritance
In the previous example, I split the original Employee class in two. I moved out all the properties common to all humans into a Human class, and then caused Employee to inherit from Human. That way, the properties laid out in Human could be used by other objects such as Student, Client, Citizen, Visitor, etc... As you may have noticed by now, this is a great way to compartmentalize and reuse code. Rather than recreating all of those properties for every single type of object where we're dealing with a human being, we can just use what's already available by inheriting from Human. What's more, if we ever wanted to add a property like say, middle name, we'd do it once and it would immediately be available to everyone inheriting from Human. Conversely, if we only wanted to add a middle name property to just one object, we could do it directly in that object instead of adding it to Human.

Public and Private
I'd like to touch on the subject of public and private variables in classes. Depending on what you're doing with the data in an object, you'll want to either make it private or public. A private property doesn't necessarily mean that people won't have access to it. You may just want them to go through one of your methods first.

Read-only
Sometimes, you only want a value defined once at the moment the object is created. Once created, you don't want anyone changing that value. In order to do this, you create a private variable and have its value set on instantiation.

function Animal(type) { 

var data = []; 

data['type'] = type; 

this.getType = function () { 

return data['type']; 

} 

} 
var fluffy = new Animal('dog'); 

fluffy.getType(); // returns 'dog'

In this example, I create a local array called data within the Animal class. When an Animal object is instantiated, a value for type is passed and set in the data array. This value can't be overwritten as it's private (the Animal function defines its scope). The only way to read the type value once an object is instantiated is to call the getType method that I've explicitly exposed. Since getType is defined inside Animal, it has access to data by virtue of the closure created by Animal. This way, people can read the object's type but not change it.

It's important to note that the "read-only" technique will not work when an object is inherited from. Every object instantiated after the inheritance is performed will share those read-only variables and overwrite each other's values. The simplest solution is to convert the read-only variables in the class to public ones. If however, you must keep them private, you can employ the technique pointed out by Philippe in the comments.

Public
There are times however, that you'll want to be able to read and write a property's value at will. To do that, you need to expose the property using the this operator.

function Animal() { 

this.mood = ''; 

} 
var fluffy = new Animal(); 

fluffy.mood = 'happy'; 

fluffy.mood; // returns 'happy'

This time our Animal class exposes a property named mood which can be written to and read at will. You can equally assign a function to public properties like the getType function in the previous example. Just be careful not to assign a value to a property like getType or you'll destroy it with your value.

Completely Private
Finally, you might find yourself in scenarios where you need a local variable that's completely private. In this case, you can use the same pattern as the first example and just not create a public method for people to access it.

function Animal() { 

var secret = "You'll never know!" 

} 

var fluffy = new Animal();

Writing a Flexible API
Now that we've covered class creation, we need to future proof them in order to keep up with product requirement changes. If you've done any project work, or maintained a product long-term, you know that requirements change. It's a fact of life. To think otherwise is to doom your code to obsolescence before it's even written. You may suddenly have to animate your tab content, or have it fetch data via an Ajax call. Though it's impossible to truly predict the future, it is possible to write code that's flexible enough to be reasonably future proof.

Saner Parameter Lists
One place where future-proofing can be done is when designing parameter lists. They're the main point of contact for people implementing your code and can be problematic if not well designed. What you want to avoid are parameter lists like this:

function Person(employeeId, fname, lname, tel, fax, email, email2, dob) { 

};

This class is very fragile. If you want to add a middle name parameter once you've released the code, you're going to have to do it at the very end of the list since order matters. That makes it awkward to work with. It also makes it difficult to work with if you don't have values for each parameter. For example:
var ara = new Person(1234, "Ara", "Pehlivanian", "514-555-1234", null, null, null, "1976-05-17");

A cleaner, more flexible way of approaching parameter lists is to use this pattern:
function Person(employeeId, data) { 

};

The first parameter is there because it's required. The rest is lumped together in an object which can be very flexible to work with.
var ara = new Person(1234, { 

fname: "Ara", 

lname: "Pehlivanian", 

tel: "514-555-1234", 

dob: "1976-05-17" 

});

The beauty of this pattern is that it's both easy to read and highly flexible. Note how fax, email and email2 were completely left out. Also, since objects aren't order specific, adding a middle name parameter is as easy as throwing it in wherever it's convenient:
var ara = new Person(1234, { 

fname: "Ara", 

mname: "Chris", 

lname: "Pehlivanian", 

tel: "514-555-1234", 

dob: "1976-05-17" 

});

The code inside the class doesn't care because the values get accessed by index like so:
function Person(employeeId, data) { 

this.fname = data['fname']; 

};

If data['fname'] returns a value, it gets set. Otherwise, it doesn't, and nothing breaks.

Make It Pluggable
As time goes on, product requirements might demand additional behavior from your classes. Yet often times that behavior has absolutely nothing to do with your class's core functionality. It's also likely that it's a requirement for only one implementation of the class, like fading in the contents of one tab's panel while fetching external data for another. You may be tempted to put those abilities inside your class, but they don't belong there. A tab strip's responsibility is to manage tabs. Animation and data fetching are two completely separate things and should be kept outside the tab strip's code. The only way to future proof your tab strip and keep all that extra functionality external, is to allow people to plug behaviors into your code. In other words, allow people to hook into key moments in your code by creating events like onTabChange, afterTabChange, onShowPanel, afterShowPanel and so on. That way, they can easily hook into your onShowPanel event, write a handler that fades in the contents of that panel and everyone is happy. JavaScript libraries allow you to do this easily enough, but it's not too hard to work it out on your own. Here's a simple example using 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'); 

// Code to show the panel 

this.fire('afterShowPanel'); 

}; 

}; 

// Give TabStrip the ability to fire custom events 

Y.augment(TabStrip, Y.EventTarget); 

var ts = new TabStrip(); 

// Set up custom event handlers for this instance of TabStrip 

ts.on('onShowPanel', function () { 

//Do something before showing panel 

}); 

ts.on('onShowPanel', function () { 

//Do something else before showing panel 

}); 

ts.on('afterShowPanel', function () { 

//Do something after showing panel 

}); 

ts.showPanel(); 

}); 

</script>

This example has a simple TabStrip class with a showPanel method. The method fires two events, onShowPanel and afterShowPanel. It's given the ability to do so by augmenting the class with Y.EventTarget. Once that's done, we instantiate a TabStrip object and assign a bunch of event handlers to it. This is our custom code for handling this instance's unique behavior without messing with the actual class.

Summary
If you plan on reusing code either on the same page, website or across multiple projects, consider packaging and organizing it in classes. Object-oriented JavaScript lends itself naturally to better code organization and reuse. What's more, with a little forethought, you can make sure that your code is flexible enough to stay in use long after you've written it. Writing reusable, future-proof JavaScript will save you, your team and your company time and money. That's sure to make you popular.

这本书比较适合有一定javascipt代码基础,但是需要提高代码规范性的开发人员看

个人的收获:

1.书写代码 的规范性

@书写要合理缩进

@行太长时,要记得换行,并注意下一行缩进

@空行,对每一个逻辑分支,以模块进行注释

@变量的命名以驼峰形式,统一 规范命名

@===与==的用法要注意,能用===时一定不要用==,异步的我们要与开发确认好数据的类型

@双括号要有适当空格,提高阅读性

@ switch case要用缩进一下,并用break结束

2.css与html,js分离

在html里最好不要内联,要使用外联形式,display内联是可以使用的

标签里不要内联事件,做好JS事件和标签分离

在脚本里面用css,最好用classname.不要直接写一些样式。做好css和js的分离

3.try catch

捕获错误,好处可以看到哪个地方出现了问题,能及时发现,减少调试的时间,要充分利用组件中的报错机制

4.配置文件config的新建和分离

配置一些常量及值,或者是一些message文本信息

Javascript 相关文章推荐
javascript 一个自定义长度的文本自动换行的函数
Aug 19 Javascript
JavaScript Event学习第九章 鼠标事件
Feb 08 Javascript
JS加jquery简单实现标签元素的显示或隐藏
Sep 23 Javascript
js 遍历json返回的map内容示例代码
Oct 29 Javascript
jQuery 浮动导航菜单适合购物商品类型的网站
Sep 09 Javascript
js中的json对象详细介绍
Oct 29 Javascript
javascript获取当前的时间戳的方法汇总
Jul 26 Javascript
Angularjs中$http以post请求通过消息体传递参数的实现方法
Aug 05 Javascript
Angular2 多级注入器详解及实例
Oct 30 Javascript
微信小程序 简单DEMO布局,逻辑,样式的练习
Nov 30 Javascript
JSON与js对象序列化实例详解
Mar 16 Javascript
微信小程序自定义navigationBar顶部导航栏适配所有机型(附完整案例)
Apr 26 Javascript
URL地址中的#符号使用说明
Feb 12 #Javascript
基于Jquery制作的幻灯片图集效果打包下载
Feb 12 #Javascript
基于jquery的jqDnR拖拽溢出的修改
Feb 12 #Javascript
jQuery1.4.2与老版本json格式兼容的解决方法
Feb 12 #Javascript
在vs2010中调试javascript代码方法
Feb 11 #Javascript
juqery 学习之六 CSS--css、位置、宽高
Feb 11 #Javascript
juqery 学习之五 文档处理 包裹、替换、删除、复制
Feb 11 #Javascript
You might like
超神学院:天使彦公认最美的三个视角,网友:我的天使快下凡吧!
2020/03/02 国漫
php递归列出所有文件和目录的代码
2008/09/10 PHP
PHP ? EasyUI DataGrid 资料取的方式介绍
2012/11/07 PHP
Yii2表单事件之Ajax提交实现方法
2017/05/04 PHP
PhpStorm2020.1 安装 debug - Postman 调用的详细教程
2020/08/17 PHP
用JavaScript 处理 URL 的两个函数代码
2007/08/13 Javascript
传智播客学习之JavaScript基础篇
2009/11/13 Javascript
jQuery html()等方法介绍
2009/11/18 Javascript
javascript 进阶篇2 CSS XML学习
2012/03/14 Javascript
利用JQuery和JS实现奇偶行背景颜色自定义效果
2012/11/19 Javascript
简易js代码实现计算器操作
2013/04/15 Javascript
IE6-IE9中tbody的innerHTML不能赋值的解决方法
2014/09/26 Javascript
Javascript中的五种数据类型详解
2014/12/26 Javascript
用JS写的一个Ajax库(实例代码)
2016/08/06 Javascript
浅谈Vue.js 1.x 和 2.x 实例的生命周期
2017/07/25 Javascript
详解Chai.js断言库API中文文档
2018/01/31 Javascript
Vue拖拽组件开发实例详解
2018/05/11 Javascript
NodeJS 将文件夹按照存放路径变成一个对应的JSON的方法
2018/10/17 NodeJs
微信小程序实现比较功能的方法汇总(五种方法)
2020/03/07 Javascript
Element-ui树形控件el-tree自定义增删改和局部刷新及懒加载操作
2020/08/31 Javascript
python函数装饰器用法实例详解
2015/06/04 Python
Python导入模块时遇到的错误分析
2017/08/30 Python
Python实现按照指定要求逆序输出一个数字的方法
2018/04/19 Python
python如何爬取个性签名
2018/06/19 Python
pygame实现非图片按钮效果
2019/10/29 Python
python opencv图像处理(素描、怀旧、光照、流年、滤镜 原理及实现)
2020/12/10 Python
详解webapp页面滚动卡顿的解决办法
2018/12/26 HTML / CSS
美国一家专业的太阳镜网上零售商:Solstice太阳镜
2016/07/25 全球购物
法律专业个人实习自我鉴定
2013/09/23 职场文书
主管职责范文
2013/11/09 职场文书
医学专业五年以上个人求职信
2013/12/03 职场文书
《临死前的严监生》教学反思
2014/02/13 职场文书
夜不归宿检讨书
2014/02/25 职场文书
五年后的职业生涯规划
2014/03/04 职场文书
出国留学经济担保书
2014/04/01 职场文书
MySQL串行化隔离级别(间隙锁实现)
2022/06/16 MySQL