OOP in Javascript
注:这篇文章是分享给公司的同事的,考虑到一直使用nodejs可能会缺乏一些面向对象概念基础,因此在这篇文章里讲了下。
首先是几个术语
Name | description |
---|---|
Object | 对象,用于描述一类,一种或者一个具体事物,该事物有属性与方法 |
Function | 方法,通常可以执行 |
Attribute | 属性,属性会有各种类型,也可以是对象 |
Class | 类,表述一类事物,可以是抽象的 |
Instance | 实例,指一个具体的事物 |
Javascript OOP way (with ES6 syntax support)
class User {
constructor (name, email) {
this.name = name; // 实例属性
this.email = email;
// 类的构造方法,初始化的时候会调用,默认return this;当然也可以返回其它的
}
getName () { // 实例方法,等同于下面的prototype的写法
return this.name; // this是这个类的实例
}
static getUser() { // 写法同等于在外面写类方法
}
}
User.prototype.getEmail = function () { // 这种写法与上面的写法一样,其实这个是ES6->ES5的原理
return this.email;
}
User._cachedUser = {} // 类属性,表示这一类事物共有的属性
User.getUser = function (name) { // 类方法
if (name in this._cachedUser) {
return this._cachedUser[name];
} else {
var tempUser = new User(name, "");
this._cachedUser[name] = tempUser;
return tempUser;
}
}
上面的例子,应该会比较清晰的看到有几种不同的概念:
- 类属性
- 类方法
- 实例属性
- 实例方法
上面的例子中,类共用了一个_cachedUser
,用作存储User实例的字典。
然后类提供了getUser
的方法,从上面的属性中取到用户,以达到不用每次都从数据库中取用户的目的。
这里体现的是享元模式
,一种类似于单例模式
的设计模式。
实例的属性和方法这个应该很好理解。
但是原来JS里是并没有class关键字的,以上是如何实现的呢?
function User(name, email) {
this.name = name;
this.email = email;
this.getName = function () {
return this.name;
}
}
let name = "anyone";
let email = "fake@no.com"
var empty = User(name, email); // undefined
var user = new User(name, email); // User {name: "anyone", email: "fake@no.com"}
为什么有不同的区别呢?
关键就是在于new
,new
其实就是执行了User
方法,并且返回了这个方法this本身;
除此之外,上面的getName
跟上面class的写法其实又不一样。
这里每个getName
方法都是重新声明的,JS里为了节约这个开销,引入了prototype
的概念,prototype
的数据挂在方法下面,通过new出来的实例,会通过prototype拿到实例的属性或者方法。
来点实例吧
上面讲了JS的OOP的写法,并简单介绍了一下基本原理。还是讲点例子来演示如何以OO的写法来改善代码吧。
在此之前,我们先来讲讲ORM吧。
要讲清楚ORM之前,我们还是先讲一下没有ORM的使用下,我们如何使用数据库。
- 建立数据库连接
- 拼装准备SQL
- 执行SQL
- 返回数据
ORM的实现原理略微有点复杂,这里略过不讲,nodejs里有现成的sqeuelize
可以使用。
即定义好模型与数据库中的表一一映射起来,以下是一些伪代码。
class Order {
constructor(id, productId, status) {
this.id = id;
this.productId = productId;
this.stauts = status;
}
}
Order.findAll = function (query) {
return sequelize.executeSql("select id, item from core_order").then(queryResult=>{
let objList = [];
for (var row of queryResult) {
objList.push(new Order(row));
}
});
}
Order.findOne = function (query) {
// 将查询字段映射成SQL
return sequelize.executeSql("select id, item from core_order limit 1").then(queryResult=>{
return queryResult.length >0 ? new Order(queryResult[0]) : null;
});;
}
findAll
与findOne
均是绑定到Order上的类方法,会返回Order
的实例列表或单个实例。
以核心的订单、保单对外的查询、详情为例:
如果以restful风格来提供API,那么,url映射与功能大概如下:
url | method | 说明 |
---|---|---|
orders/ |
GET | 查询订单 |
orders/:id |
GET | 获取订单详情 |
那么后台的功能其实也就非常简单:
router.get("orders/", function(req, res) {
Order.findAll().then(orderList=>{
res.json({orderList});
});
});
router.get("orders/:id", function(req, res) {
Order.findOne({where: {id: req.params.id}}).then(order=>{
if (order) {
res.json({order});
} else {
res.status(404).json({});
}
});
});
可以看到,上面用这些代码,实际开发的量较少,就可以达到较实用的功能了。 然后我们还可以进一步增加保单的接口:
router.get("policy/", function(req, res) {
Policy.findAll().then(policyList=>{
res.json({policyList});
});
});
router.get("policys/:id", function(req, res) {
Policy.findOne({where: {id: req.params.id}}).then(policy=>{
if (policy) {
res.json({policy});
} else {
res.status(404).json({});
}
});
});
可以看到,其实上面的代码非常接近,在这个基础上,当然可以造一个基础类:
class BaseRESTViewSet {
constructor(model) {
this.model = model;
}
getList(req, res) {
this.model.findAll().then(objList=>{
res.json({objList});
});
}
getOne(req, res) {
this.model.findOne({where: {id: req.params.id}}).then(obj=>{
if (obj) {
res.json({obj})
} else{
res.status(404).json({});
};
});
}
}
var orderViewSet = new BaseRESTViewSet(Order);
var policyViewSet = new BaseRESTViewSet(Policy);
// 需要bind,否则express里的middware异步调用的时候,会丢失掉该就方法的this对象
router.get("orders/", orderViewSet.getList.bind(orderViewSet));
router.get("orders/:id", orderViewSet.getOne.bind(OrderViewSet));
router.get("policys/", policyViewSet.getList.bind(policyViewSet));
router.get("policys/:id", policyViewSet.getOne.bind(policyViewSet));
当然,上面的代码也只是因为业务简单,实际中会遇到不同的表的查询条件不一样或者需要关联查询等,逻辑就会更复杂。
其它应用:
- 自定义异常,用于返回不同的消息提示
深入的话题?
- 异步回调时的对象绑定(使用bind可以提前将对象绑定到方法上去)
- 元类(metaclass类似的机制,即动态生成一个类对象,JS里的constructor可以修改后return,也可以使用proxy)
- 装饰器(decorators)
- mixins(multi inherit,其实跟单继承原理一样,多继承时会有继承顺序的问题)
- Design Patterns