klass.js是一个oop风格的javascript库,提供Javascript传统面对对象的编程风格。

起因

由于部门是从事电商领域,需要全面兼容不同的客户端,这就造成了我们前架构的“繁杂”,以PC端为例:jQuery + jqoteplus + freemarker-Template + React + klass + sass的混搭风你可能真的很少见过(微笑脸)。为了SEO友好,PC端大部分的页面都是使用传统的模版语言来进行后端渲染,前端复用效率比较低,这也就造成了可能只是一个小的需求修改,我们不得不深入到每一个具体的模版页面去修改,降低了工作效率不说,也很繁琐。好像有点跑题了,关于团队的项目后期有机会再聊~

作为一个电商部门,web页面中有许多公共的部分,例如页面头部的类目列表,顶部的用户信息栏、mini购物车等,这些公共的部分其实我们可以统一抽出来作为公共的业务逻辑进行打包处理。前面可能说了,作为电商网站,可能没法使用当前最新的前端技术栈,我们需要良好的SEO,需要兼容IE8(是的,不要惊讶我们还在兼容IE8),ES6甜甜的语法糖也因为项目在构建中的问题暂时没法使用,回归到ES5中,如何实现公共页面的逻辑统一封装?终于到了本文的正题了-使用klass.js a utility for creating expressive classes in JavaScript 一段在javascript中创建动态类的实用程序。

使用klass

使用klass创建一个类十分简单。

1
2
3
4
5
6
7
8
9
10
11
var Person = klass({
initial: function(name, age){
this.name = name;
this.age = age;
},
sayHello: function(){
console.log("Hello " + this.name); // Hello simmer
}
});
var simmer = new Person("simmer", 23);
simmer.sayHello();

通过调用klass显式的传入一个包含键值对的对象进行,klass默认会寻找其中key为initial的属性作为构造函数借用的方法,这里可以设置实例对象的私有属性(包括静态属性和方法)。为什么说借用构造函数呢?因为真实的构造函数并不是initial,只是在真实的构造函数中通过this.initial.apply(this, arguments)来实现借用构造函数,这点后面会进行说明。除了initial方法之外,其他的属性都将会被添加到构造函数的原型对象中,作为原型的方法被所有的实例所共享。
当然,还有另外定义类的方式:

1
2
3
4
5
6
7
8
9
10
11
var Person = klass(function(name, age){
this.name = name;
this.age = age;
})
.method({
sayHello: function(){
console.log("Hello" + this.name + "who is " + this.age + " years old"); //Hello sunny who is 23 years old
}
});
var sunny = new Person("sunny", 23);
sunny.sayHello();

像上面这种定义类的方法可能更加接近原生的javascript中的构造函数定义类的方法, 构造函数中设置实例对象特有的属性/方法,在Constructor.prototype上添加实例共享方法/属性。
为了使得代码更加靠近传统的oop,组内现有的使用方式是第一种,这种写法也基本与ES6的class保持一致。抛开业务逻辑谈论是没有多少意义的,这里以一个简单的3个类为例子,BaseComponentBaseModuleUcenterModule分别代表一般控件类、页面基类、用户中心基类。

  • 控件类是类似弹窗、toast等基本控件的继承类,封装了基本的控件类方法,包括控件的确定/取消按钮事件等。
  • 页面基类是绝大部分页面的继承类,这里类中封装了页面的公共逻辑,比如显示头部导航栏/侧边栏/底部等,是几乎所有的页面都共享的逻辑,这里的逻辑,只要在特定页面中继承这个基类,可以做到公共业务逻辑的公用。
  • 用户中心类是用户中心的特定类,除了函括大部分页面的公共逻辑之外还有专属的用户信息显示栏,所以UcenterModule是继承自BaseModule并封装了用户中心的统一处理逻辑,用户中心如果要新添加页面就只需要去继承UcenterModule这个类就好了。

那么实际的代码大概是怎么样的呢?以下是简化的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var BaseComponent = klass({
initialize: function(options){
//this is top initial
var _data = options.data || {};
this.config && this.config(_data); // 配置config参数
this.baseComponentInit();
},
baseComponentInit: function(){
//init baseComponent here
}
});

var BaseModule = BaseComponent.extend({
initialize: function(options){
this.supr(options); // call super initial function
this.BaseModuleInit();
},
BaseModuleInit: function(){
// init baseModele here
}
});

var UcenterModule = BaseComponent.extend({
initialize:function(options){
this.supr(options);
this.UcenterModuleInit();
},
UcenterModuleInit:function(){
//init you UcentModule here
}
})

当我们要实现一个弹窗组件的时候,只需要

1
2
3
4
5
6
var Dialog = BaseComponent.extend({
initialize:function(){
this.supr();
// initialize you code from here
}
});

新键一个页面,只需要

1
2
3
4
5
6
var Page = BaseModule.extend({
initialize: function(){
this.supr();
// initialize you code from here
}
})

新建一个用户中心页面,只需要

1
2
3
4
5
6
var UcenterPage = UcentModule.extend({
initialize: function(){
this.supr();
// initialize you code from here
}
})

看起来,基于oop(Object-oriented programming 面对对象程序设计)编程能够让我们将业务逻辑进行分层,通过抽象出可以复用的公共逻辑,并进行合理的分层就可以大大的提高我们的编码效率。