├── .gitignore
├── .spmignore
├── .travis.yml
├── HISTORY.md
├── README.md
├── class.js
├── docs
└── competitors.md
├── package.json
└── tests
├── class-spec.js
└── data
├── animal.js
└── dog.js
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .idea/
3 | .ipr
4 | .iws
5 | *~
6 | ~*
7 | *.diff
8 | *.patch
9 | *.bak
10 | .DS_Store
11 | Thumbs.db
12 | .project
13 | .*proj
14 | .svn/
15 | *.swp
16 | *.pyc
17 | *.pyo
18 | _site
19 | _theme
20 | sea-modules
21 | node_modules
22 | dist/
23 |
--------------------------------------------------------------------------------
/.spmignore:
--------------------------------------------------------------------------------
1 | tests/
2 | docs/
3 | dist/
4 | .travis.yml
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "0.10"
5 |
6 | install:
7 | - npm install spm@ninja coveralls
8 |
9 | before_script:
10 | - node_modules/spm/bin/spm-install
11 |
12 | script:
13 | - node_modules/spm/bin/spm-test
14 |
15 | after_success:
16 | - node_modules/spm/bin/spm-test --coveralls | node_modules/.bin/coveralls
17 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | # History
2 |
3 | ---
4 |
5 | ## 1.2.0
6 |
7 | `improved` 按照 spm@3.x 规范升级。
8 |
9 | ## 1.1.0
10 |
11 | `changed` 去除 module 信息,seajs 也已经去掉这个 API 了 [#1](https://github.com/aralejs/class/issues/1)
12 |
13 | ## 1.0.0
14 |
15 | 正式版本
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Class
2 |
3 | ---
4 |
5 | [](http://spmjs.io/package/arale-class)
6 | [](https://travis-ci.org/aralejs/class)
7 | [](https://coveralls.io/r/aralejs/class)
8 |
9 |
10 | 提供简洁的 OO 实现。
11 |
12 | ---
13 |
14 |
15 | ## 使用说明
16 |
17 |
18 | ### create `Class.create([parent], [properties])`
19 |
20 | 创建一个新类。参数 `parent` 是继承的父类,`properties` 是要混入的实例属性。
21 |
22 | 来看一个简单的例子(线上演示):
23 |
24 | ```js
25 | /* pig.js */
26 | define(function(require, exports, module) {
27 | var Class = require('class');
28 |
29 | var Pig = Class.create({
30 | initialize: function(name) {
31 | this.name = name;
32 | },
33 |
34 | talk: function() {
35 | alert('我是' + this.name);
36 | }
37 | });
38 |
39 | module.exports = Pig;
40 | });
41 | ```
42 |
43 | `initialize` 属性,标明初始化方法,会在构建实例时调用。
44 |
45 | 使用 `create` 方法创建的类,拥有 `extend` 方法,可以继续创建子类:
46 |
47 | ```js
48 | /* red-pig.js */
49 | define(function(require, exports, module) {
50 | var Pig = require('./pig');
51 |
52 | var RedPig = Pig.extend({
53 | initialize: function(name) {
54 | RedPig.superclass.initialize.call(this, name);
55 | },
56 |
57 | color: '红色'
58 | });
59 |
60 | module.exports = RedPig;
61 | });
62 | ```
63 |
64 | **注意**:需要在子类方法中,调用父类中的同名方法时,JavaScript 语言自身并没有提供类似 `super`
65 | 的方式来轻松实现。用 `create` 或 `extend` 方法创建类时,可以使用 `superclass.methodName`
66 | 来显式调用父类方法。之所以不提供 `super` 方法,原因有二:
67 |
68 | 1. 实现起来较麻烦。现有类库的实现方案,都不完美。
69 | 2. 在 JavaScript 编程中,调用 `super` 的需求并不多。简单通过 `superclass.methodName`
70 | 来调用已经能够满足需求,并很灵活、清晰。
71 |
72 | `properties` 参数中,除了支持用 `initialize` 来标明初始化方法,还可以用 `Implements`
73 | 来标明所创建的类需要从哪些类中混入属性:
74 |
75 | ```js
76 | /* flyable.js */
77 | define(function(require, exports, module) {
78 | exports.fly = function() {
79 | alert('我飞起来了');
80 | };
81 | });
82 | ```
83 |
84 | ```js
85 | /* flyable-red-pig.js */
86 | define(function(require, exports, module) {
87 | var RedPig = require('./red-pig');
88 | var Flyable = require('./flyable');
89 |
90 | var FlyableRedPig = RedPig.extend({
91 | Implements: Flyable,
92 |
93 | initialize: function(name) {
94 | FlyableRedPig.superclass.initialize.call(this, name);
95 | }
96 | });
97 |
98 | module.exports = FlyableRedPig;
99 | });
100 | ```
101 |
102 | **注意**:`Implements` 采用首字母大写,是因为小写的 `implements` 是 JavaScript
103 | 保留字。大写也表示其特殊性,与 MooTools 的方式一致。
104 |
105 | 除了 `Implements`, 还有一个特殊属性:
106 |
107 | - `Extends` - 用来指定继承的父类,注意只能有一个父类,不支持多继承。
108 |
109 |
110 | ### implement `SomeClass.implement(properties)`
111 |
112 | 该方法与 `Implements` 属性的功能类似。当某个类已存在,需要动态修改时,用 `implement`
113 | 方法更便捷。
114 |
115 |
116 | ```js
117 | /* flyable-red-pig-extension.js */
118 | define(function(require, exports, module) {
119 | var FlyableRedPig = require('./flyable-red-pig');
120 |
121 | FlyableRedPig.implement({
122 | swim: function() {
123 | alert('我还会游泳');
124 | }
125 | });
126 | });
127 | ```
128 |
129 | 这样,我们得到了会说话、会飞、还会游泳的飞天红猪侠:
130 |
131 | ```js
132 | /* test.js */
133 | define(function(require, exports, module) {
134 | var FlyableRedPig = require('./flyable-red-pig');
135 | require('./flyable-red-pig-extension');
136 |
137 | var pig = new FlyableRedPig('飞天红猪侠');
138 | pig.talk(); // alerts '我是飞天红猪侠'
139 | pig.fly(); // alerts '我飞起来了'
140 | pig.swim(); // alerts '我还会游泳'
141 | });
142 | ```
143 |
144 |
145 | ### extend `SomeClass.extend(properties)`
146 |
147 | 由 `Class.create` 创建的类,自动具有 `extend` 方法,功能与 `Class.create`
148 | 完全一样,只是继承的父类是 `SomeClass` 自身,前面的例子中已说明,不赘述。
149 |
150 |
151 | ### Class `Class(fn)`
152 |
153 | 将已经存在的 function 函数转换为 Class 类:
154 |
155 | ```js
156 | function Animal() {
157 | }
158 | Animal.prototype.talk = function() {};
159 |
160 | var Dog = Class(Animal).extend({
161 | swim: function() {}
162 | });
163 | ```
164 |
165 |
166 | ## 性能对比
167 |
168 | -
169 |
170 |
--------------------------------------------------------------------------------
/class.js:
--------------------------------------------------------------------------------
1 | // Class
2 | // -----------------
3 | // Thanks to:
4 | // - http://mootools.net/docs/core/Class/Class
5 | // - http://ejohn.org/blog/simple-javascript-inheritance/
6 | // - https://github.com/ded/klass
7 | // - http://documentcloud.github.com/backbone/#Model-extend
8 | // - https://github.com/joyent/node/blob/master/lib/util.js
9 | // - https://github.com/kissyteam/kissy/blob/master/src/seed/src/kissy.js
10 |
11 |
12 | // The base Class implementation.
13 | function Class(o) {
14 | // Convert existed function to Class.
15 | if (!(this instanceof Class) && isFunction(o)) {
16 | return classify(o)
17 | }
18 | }
19 |
20 | module.exports = Class
21 |
22 |
23 | // Create a new Class.
24 | //
25 | // var SuperPig = Class.create({
26 | // Extends: Animal,
27 | // Implements: Flyable,
28 | // initialize: function() {
29 | // SuperPig.superclass.initialize.apply(this, arguments)
30 | // },
31 | // Statics: {
32 | // COLOR: 'red'
33 | // }
34 | // })
35 | //
36 | Class.create = function(parent, properties) {
37 | if (!isFunction(parent)) {
38 | properties = parent
39 | parent = null
40 | }
41 |
42 | properties || (properties = {})
43 | parent || (parent = properties.Extends || Class)
44 | properties.Extends = parent
45 |
46 | // The created class constructor
47 | function SubClass() {
48 | // Call the parent constructor.
49 | parent.apply(this, arguments)
50 |
51 | // Only call initialize in self constructor.
52 | if (this.constructor === SubClass && this.initialize) {
53 | this.initialize.apply(this, arguments)
54 | }
55 | }
56 |
57 | // Inherit class (static) properties from parent.
58 | if (parent !== Class) {
59 | mix(SubClass, parent, parent.StaticsWhiteList)
60 | }
61 |
62 | // Add instance properties to the subclass.
63 | implement.call(SubClass, properties)
64 |
65 | // Make subclass extendable.
66 | return classify(SubClass)
67 | }
68 |
69 |
70 | function implement(properties) {
71 | var key, value
72 |
73 | for (key in properties) {
74 | value = properties[key]
75 |
76 | if (Class.Mutators.hasOwnProperty(key)) {
77 | Class.Mutators[key].call(this, value)
78 | } else {
79 | this.prototype[key] = value
80 | }
81 | }
82 | }
83 |
84 |
85 | // Create a sub Class based on `Class`.
86 | Class.extend = function(properties) {
87 | properties || (properties = {})
88 | properties.Extends = this
89 |
90 | return Class.create(properties)
91 | }
92 |
93 |
94 | function classify(cls) {
95 | cls.extend = Class.extend
96 | cls.implement = implement
97 | return cls
98 | }
99 |
100 |
101 | // Mutators define special properties.
102 | Class.Mutators = {
103 |
104 | 'Extends': function(parent) {
105 | var existed = this.prototype
106 | var proto = createProto(parent.prototype)
107 |
108 | // Keep existed properties.
109 | mix(proto, existed)
110 |
111 | // Enforce the constructor to be what we expect.
112 | proto.constructor = this
113 |
114 | // Set the prototype chain to inherit from `parent`.
115 | this.prototype = proto
116 |
117 | // Set a convenience property in case the parent's prototype is
118 | // needed later.
119 | this.superclass = parent.prototype
120 | },
121 |
122 | 'Implements': function(items) {
123 | isArray(items) || (items = [items])
124 | var proto = this.prototype, item
125 |
126 | while (item = items.shift()) {
127 | mix(proto, item.prototype || item)
128 | }
129 | },
130 |
131 | 'Statics': function(staticProperties) {
132 | mix(this, staticProperties)
133 | }
134 | }
135 |
136 |
137 | // Shared empty constructor function to aid in prototype-chain creation.
138 | function Ctor() {
139 | }
140 |
141 | // See: http://jsperf.com/object-create-vs-new-ctor
142 | var createProto = Object.__proto__ ?
143 | function(proto) {
144 | return { __proto__: proto }
145 | } :
146 | function(proto) {
147 | Ctor.prototype = proto
148 | return new Ctor()
149 | }
150 |
151 |
152 | // Helpers
153 | // ------------
154 |
155 | function mix(r, s, wl) {
156 | // Copy "all" properties including inherited ones.
157 | for (var p in s) {
158 | if (s.hasOwnProperty(p)) {
159 | if (wl && indexOf(wl, p) === -1) continue
160 |
161 | // 在 iPhone 1 代等设备的 Safari 中,prototype 也会被枚举出来,需排除
162 | if (p !== 'prototype') {
163 | r[p] = s[p]
164 | }
165 | }
166 | }
167 | }
168 |
169 |
170 | var toString = Object.prototype.toString
171 |
172 | var isArray = Array.isArray || function(val) {
173 | return toString.call(val) === '[object Array]'
174 | }
175 |
176 | var isFunction = function(val) {
177 | return toString.call(val) === '[object Function]'
178 | }
179 |
180 | var indexOf = Array.prototype.indexOf ?
181 | function(arr, item) {
182 | return arr.indexOf(item)
183 | } :
184 | function(arr, item) {
185 | for (var i = 0, len = arr.length; i < len; i++) {
186 | if (arr[i] === item) {
187 | return i
188 | }
189 | }
190 | return -1
191 | }
192 |
--------------------------------------------------------------------------------
/docs/competitors.md:
--------------------------------------------------------------------------------
1 | # OO 模拟那些事儿
2 |
3 | ---
4 |
5 | > 感谢所有老前辈们,感谢所有同类代码。因为有了你们,世界才丰富多采。
6 |
7 |
8 | ## Douglas Crockford 的尝试与悟道
9 |
10 | 关于类继承,Douglas 有一篇经典文章:
11 |
12 | - [Classical Inheritance in JavaScript](http://javascript.crockford.com/inheritance.html)
13 |
14 | 这篇文章里,老道分析了为什么要在 JavaScript 里模拟类继承:主要目的是复用。老道的实现方式是给
15 | `Function.prototype` 增加 `method` 和 `inherits` 两个方法,并提供了 `uber` 语法糖。
16 |
17 | 悲催的是,大神实现的 `inherits` 和 `uber` 存在不少缺陷,国内和国外都有不少人剖析过,可以参考
18 | 2008 年时的一篇讨论帖子:
19 |
20 | - [Crockford uber 方法中的陷阱](http://www.iteye.com/topic/248933)
21 |
22 | 老道最后悟出了一段经常被引用的话:
23 |
24 | > 我编写 JavaScript 已经 8 个年头了,从来没有一次觉得需要使用 uber 方法。在类模式中,super
25 | 的概念相当重要;但是在原型和函数式模式中,super 的概念看起来是不必要的。现在回顾起来,我早期在
26 | JavaScript 中支持类模型的尝试是一个错误。
27 |
28 | 由此,老道又写了一篇经典文章,推崇在 JavaScript 里,直接使用原型继承:
29 |
30 | - [Prototypal Inheritance in JavaScript](http://javascript.crockford.com/prototypal.html)
31 |
32 | 继续悲催的是,老道的实现,依旧有不足之处。可以参考两篇博文:
33 |
34 | - [Debunking object\(\)](http://www.nczonline.net/blog/2006/10/14/debunking-object/)
35 | - [Javascript – How Prototypal Inheritance really works](http://blog.vjeux.com/2011/javascript/how-prototypal-inheritance-really-works.html)
36 |
37 | 老道的代码虽然不尽完美,但老道的尝试和对原型继承的呼吁依旧非常值得我们尊敬和思考。
38 |
39 | 研究到此,如果大家都能理解原型继承,问题其实已经终结,特别是在 webkit 等支持 `__proto__` 的运行环境下。比如:
40 |
41 | ```js
42 | function Animal() {}
43 | function Dog() {}
44 |
45 | // 要让 Dog 继承 Animal, 只需:
46 | Dog.prototype.__proto__ == Animal.prototype;
47 |
48 | // 实例化后
49 | var dog = new Dog();
50 | // dog.__proto__ 指向 Dog.prototype
51 | // dog.__proto__.__proto__ 指向 Animal.prototype
52 | // 原型链已成功建立起来,而且很清晰
53 | ```
54 |
55 | 老道的 `inherits` 和 NCZ 的 `inherit` 本质上都是设置好 `__proto__`
56 | 属性。看清楚这一点,一切都很简单。
57 |
58 | 原型继承的确已经够用,但这需要大家都能深入理解原型继承,对 `__proto__`, `prototype` 和 `new`
59 | 等关键点有清晰的认识。通过 `inherits` 等方法,可以简化部分细节。但用户在使用时,依旧需要面对
60 | `prototype` 等属性,并且很容易写出有隐患的代码,比如:
61 |
62 | ```js
63 | function Animal() {}
64 | function Dog() {}
65 |
66 | util.inherits(Dog, Animal);
67 |
68 | Dog.prototype = {
69 | talk: function() {},
70 | run: function() {}
71 | };
72 | ```
73 |
74 | 上面的代码,你知道问题在哪吗?请继续阅读。
75 |
76 |
77 | ## YUI 之路
78 |
79 | YUI 团队是 Douglas 的铁杆粉丝团。从 YUI2 到 YUI3, 都高度贯彻了 Douglas 的精神。在 YUI
80 | 里,提供了 `extend` 方法:
81 |
82 | ```js
83 | function Animal() {}
84 | function Dog() {}
85 |
86 | Y.extend(Dog, Animal, {
87 | talk: fn,
88 | run: fn
89 | });
90 | ```
91 |
92 | YUI 还提供了 `augment`, `mix` 等方法来混入原型和静态方法。理论上足够用了,但对普通使用者来说,依旧存在陷阱:
93 |
94 | ```js
95 | function Animal() {}
96 | function Dog() {}
97 |
98 | Y.extend(Dog, Animal);
99 |
100 | Dog.prototype = {
101 | talk: fn,
102 | run: fn
103 | };
104 |
105 | var dog = new Dog();
106 | alert(dog instanceof Dog); // false
107 | ```
108 |
109 | 上面的写法,破坏了 `Dog.prototype.constructor`, 导致 `instanceof` 不能正常工作。正确的写法是:
110 |
111 | ```js
112 | Dog.prototype = {
113 | constructor: Dog,
114 | talk: fn,
115 | run: fn
116 | };
117 | ```
118 |
119 | 或
120 |
121 | ```js
122 | Dog.prototype.talk = fn;
123 | Dog.prototype.run = fn;
124 | ```
125 |
126 | 通过 `extend` 等方式来实现原型继承,写法上很灵活。`constructor` 是个不小不大的问题,
127 | 但对于类库来说,任何小问题,都有可能成为大问题。
128 |
129 | `extend` 的方式,仅仅是对 JavaScript 语言中原型继承的简单封装,需要有一定 JavaScript
130 | 编程经验后才能娴熟使用。(我个人其实蛮喜欢简简单单的 `extend`)。
131 |
132 | 此外,`extend` 的灵活性也是一种“伤害”。定义一个类时,我们更希望能有一种比较固定的书写模式,
133 | 什么东西写在什么地方,都能更简单,更一目了然。
134 |
135 | JavaScript 是一门大众语言,在类继承模式当道的今天,直接让用户去面对灵活的原型继承,未必是最好的选择。
136 |
137 | 世界的进步在于人类的不满足。作为前端的我们,只是想用更简单更舒适的方式来书写代码。JavaScript
138 | 新的语言规范里,已经提出了 `class` 概念。但在规范确定和浏览器原生支持前,故事还得继续。
139 |
140 | **注**:YUI3 里,除了 `extend` 方式,也提供了 `Base.create`
141 | 来创建新类,但是该方法比较重量级了,用起来不轻便。
142 |
143 |
144 | ## Dean Edwards 的 Base.js
145 |
146 | Dean Edwards 是前端界的一位老前辈。老前辈做过一个当时很著名的 JavaScript 类库:
147 | Base.js, 其中有一套非常不错的 OO 实现:
148 |
149 | - [A Base Class for JavaScript Inheritance](http://dean.edwards.name/weblog/2006/03/base/)
150 |
151 | 这个方案开辟了一条阳光大道:通过精心构造的 `Base` 基类来实现类继承。同一时期,JavaScript
152 | 界 OO 模拟蔚然成风,万马奔腾。让我们继续考考古。
153 |
154 |
155 | ## Prototype 的 Class
156 |
157 | 作为一名前端,如果没用过 Prototype, 那么恭喜你,说明你还年轻,潜力无限。来看一名老前端的吐槽:
158 |
159 | - [Prototype 1.6 的超级符咒](http://hax.iteye.com/blog/167131)
160 |
161 | Prototype 目前已经 v1.7 了。从官方文档来看,Class 继承已经很成熟:
162 |
163 | - [Defining classes and inheritance](http://prototypejs.org/learn/class-inheritance)
164 |
165 | `Class.create` 的写法已经比较优美。然而悲催的是,`$super` 的约定真让人无语。`super`
166 | 虽然很难实现,但也不要这样实现呀:代码一压缩就都浮云了。
167 |
168 |
169 | ## John Resig 的实现
170 |
171 | jQuery 专注于 DOM 操作,因此无论现在还是以后,应该都不会去模拟类继承。但在风云变幻的年代里,jQuery
172 | 作者 John Resig 也忍不住掺合一脚:
173 |
174 | - [Simple JavaScript Inheritance](http://ejohn.org/blog/simple-javascript-inheritance/)
175 |
176 | 与 Base2 和 Prototype 相比,John Resig 的实现无疑更漂亮。`_super`
177 | 的实现方案也简单有效,不过在 JavaScript 实现原生的 `class` 之前,所有 `super`
178 | 方案都很难完美。比如:
179 |
180 | ```js
181 | var Animal = Class.extend({
182 | talk: function() {
183 | alert('I am talking.');
184 | },
185 | sleep: function() {
186 | alert('I am sleeping.')
187 | }
188 | });
189 |
190 | var Dog = Animal.extend({
191 | talk: function() {
192 | this._super();
193 | }
194 | });
195 |
196 | // 在另一个文件里,扩展 Dog 对象:
197 | Dog.prototype.sleep = function() {
198 | this._super(); // 会报错
199 | };
200 | ```
201 |
202 | 很明显,要使用 `_super`, 必须严格按照固定模式来写。面对灵活的 JavaScript, 所有 `super`
203 | 都是美丽的谎言。
204 |
205 |
206 | ## MooTools Class
207 |
208 | MooTools 的全称是 My OO Tools, 有一套口碑很不错的 Class 机制:
209 |
210 | - [Class](http://mootools.net/docs/core/Class/Class)
211 |
212 | `new Class` 的方式很优美,`Extends` 和 `Implements` 的首字母大写,看习惯了也觉得挺好。
213 |
214 | `Class` 和所创建的类上,也都有 `extend` 方法,与 John Resig 版本相同。
215 |
216 | `super` 语法糖,MooTools 采用了 `this.parent()` 的形式。原理与 John Resig
217 | 的差不多,都是采用 `wrap` 的方式,但 MooTools 利用了非标准属性 `caller` 来实现。
218 |
219 | 所有 `wrap` 的实现方式,都要求使用者彻底忘记 `prototype`. 以下代码在 MooTools 里也会出问题:
220 |
221 | ```js
222 | Dog.prototype.sleep = function() {
223 | this.parent(); // 会报错
224 | };
225 | ```
226 |
227 | 在 MooTools 里,需要这样写:
228 |
229 | ```js
230 | Dog.implement({
231 | sleep: function() {
232 | this.parent();
233 | }
234 | });
235 | ```
236 |
237 |
238 | ## 还有很多很多
239 |
240 | JavaScript 的世界里,OO 的实现还有很多很多,比较有名气的还有:
241 |
242 | - [Joose](http://joose.it/)
243 | - [JS Class](http://jsclass.jcoglan.com/inheritance.html)
244 | - [Dojo](http://dojotoolkit.org/reference-guide/1.7/dojo/declare.html#dojo-declare)
245 | - [Klass](https://github.com/ded/klass)
246 | - [Backbone.Model.extend](http://documentcloud.github.com/backbone/#Model-extend)
247 |
248 | 还有一个很有意思、崇尚组合的:[Traits.js](http://soft.vub.ac.be/~tvcutsem/traitsjs/)
249 |
250 | 实现方式上都大同小异,有兴趣的可以逐一看看。
251 |
252 | 写文档比写代码还累呀,终于快接近尾声了 —— 最重要的尾声部分。如果你在家里的话,强烈建议去洗把冷水脸,清爽一下后再来看。
253 |
254 |
255 | ## 我们的选择
256 |
257 | Arale 2.0 的核心设计原则是 KISS:
258 |
259 | 1. 如无必要,勿增实体 —— Simple
260 | 2. 一目了然,容易学习 —— Stupid
261 |
262 | 这两个原则是我们选择的权衡点。从 Simple 原则出发,`Y.extend` 就很好了。但从 Stupid
263 | 的原则考虑,明显 `Class.create` 的形式更一目了然,同时在功能上也具有 `Y.extend` 的简洁实用性。
264 |
265 | 权衡考虑后,我们选择 `Class.create` 风格,部分细节考虑如下:
266 |
267 | 1. 主要 API 与 MooTools 保持一致,但不用 `new Class`, 而用 `Class.create` 和 `SomeClass.extend`。
268 | 1. `Implements` 接收的参数就是普通对象,与 `implement` 方法保持一致。MooTools 中 `Implements` 属性需要是类。
269 | 1. 去除 `this.parent()` 语法糖,需要调用时,和 Backbone 类似,推荐直接使用 `SuperClass.prototype.methodName` 来调用。
270 | 1. 为了方便调用父类中的方法,提供 `superclass` 语法糖,与 YUI 类似。
271 |
272 |
273 | 欢迎交流反馈。
274 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "arale-class",
3 | "version": "1.2.0",
4 | "keywords": ["infrastructure"],
5 | "description": "提供简洁的 OO 实现。",
6 | "homepage": "http://aralejs.org/class/",
7 | "author": "玉伯 ",
8 | "maintainers": [
9 | "玉伯 ",
10 | "贯高 "
11 | ],
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/aralejs/class.git"
15 | },
16 | "bugs": {
17 | "url": "https://github.com/aralejs/class/issues"
18 | },
19 | "spm": {
20 | "main": "class.js",
21 | "devDependencies": {
22 | "expect": "0.3.1"
23 | },
24 | "engines": {
25 | "seajs": "2.2.1"
26 | },
27 | "tests": "tests/*-spec.js"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tests/class-spec.js:
--------------------------------------------------------------------------------
1 | var Class = require('../class')
2 | var expect = require('expect')
3 |
4 | describe('Class', function() {
5 |
6 | it('Class.create(parent)', function() {
7 | function Animal(name) {
8 | this.name = name
9 | }
10 |
11 | Animal.prototype.getName = function() {
12 | return this.name
13 | }
14 |
15 | var Dog = Class.create(Animal)
16 | var dog = new Dog('Jack')
17 |
18 | expect(dog.constructor).to.equal(Dog)
19 | expect(dog.name).to.equal('Jack')
20 | expect(dog.getName()).to.equal('Jack')
21 | })
22 |
23 | it('Class.create(null)', function() {
24 | var Dog = Class.create(null)
25 | var dog = new Dog()
26 | expect(dog.constructor).to.equal(Dog)
27 | expect(Dog.superclass.constructor).to.equal(Class)
28 |
29 | Dog = Class.create()
30 | new Dog()
31 | expect(Dog.superclass.constructor).to.equal(Class)
32 | })
33 |
34 | it('Class.create(parent, properties)', function() {
35 | function Animal(name) {
36 | this.name = name
37 | }
38 |
39 | Animal.prototype.getName = function() {
40 | return this.name
41 | }
42 |
43 | var Dog = Class.create(Animal, {
44 | talk: function() {
45 | return 'I am ' + this.name
46 | }
47 | })
48 | var dog = new Dog('Jack')
49 |
50 | expect(dog.name).to.equal('Jack')
51 | expect(dog.talk()).to.equal('I am Jack')
52 | })
53 |
54 | it('call initialize method properly', function() {
55 | var counter = 0
56 |
57 | var Animal = Class.create({
58 | initialize: function() {
59 | counter++
60 | }
61 | })
62 |
63 | var Dog = Class.create(Animal, {
64 | initialize: function() {
65 | counter++
66 | }
67 | })
68 |
69 | new Dog()
70 |
71 | // Dog 有 initialize 时,只调用 Dog 的 initialize
72 | expect(counter).to.equal(1)
73 |
74 | counter = 0
75 | Dog = Class.create(Animal)
76 |
77 | new Dog()
78 |
79 | // Dog 没有 initialize 时,会自动调用父类中最近的 initialize
80 | expect(counter).to.equal(1)
81 | })
82 |
83 | it('pass arguments to initialize method properly', function() {
84 |
85 | var Animal = Class.create({
86 | initialize: function(firstName, lastName) {
87 | this.fullName = firstName + ' ' + lastName
88 | }
89 | })
90 |
91 | var Bird = Animal.extend({
92 | fly: function() {
93 | }
94 | })
95 |
96 | var bird = new Bird('Frank', 'Wang')
97 |
98 | expect(bird.fullName).to.equal('Frank Wang')
99 | })
100 |
101 | it('superclass', function() {
102 | var counter = 0
103 |
104 | var Animal = Class.create({
105 | initialize: function() {
106 | counter++
107 | },
108 | talk: function() {
109 | return 'I am an animal'
110 | }
111 | })
112 |
113 | var Dog = Class.create(Animal, {
114 | initialize: function() {
115 | Dog.superclass.initialize()
116 | },
117 | talk: function() {
118 | return Dog.superclass.talk()
119 | }
120 | })
121 |
122 | var dog = new Dog()
123 |
124 | expect(counter).to.equal(1)
125 | expect(dog.talk()).to.equal('I am an animal')
126 | })
127 |
128 | it('Extends', function() {
129 | function Animal(name) {
130 | this.name = name
131 | }
132 |
133 | Animal.prototype.getName = function() {
134 | return this.name
135 | }
136 |
137 | var Dog = Class.create({
138 | Extends: Animal,
139 | talk: function() {
140 | return 'I am ' + this.name
141 | }
142 | })
143 |
144 | var dog = new Dog('Jack')
145 |
146 | expect(dog.name).to.equal('Jack')
147 | expect(dog.getName()).to.equal('Jack')
148 | expect(dog.talk()).to.equal('I am Jack')
149 | })
150 |
151 | it('Implements', function() {
152 | var Animal = Class.create(function(name) {
153 | this.name = name
154 | }, {
155 | getName: function() {
156 | return this.name
157 | }
158 |
159 | })
160 |
161 | var Flyable = {
162 | fly: function() {
163 | return 'I am flying'
164 | }
165 | }
166 |
167 | var Talkable = function() {
168 | }
169 | Talkable.prototype.talk = function() {
170 | return 'I am ' + this.name
171 | }
172 |
173 | var Dog = Class.create({
174 | Extends: Animal,
175 | Implements: [Flyable, Talkable]
176 | })
177 |
178 | var dog = new Dog('Jack')
179 |
180 | expect(dog.name).to.equal('Jack')
181 | expect(dog.getName()).to.equal('Jack')
182 | expect(dog.fly()).to.equal('I am flying')
183 | expect(dog.talk()).to.equal('I am Jack')
184 | })
185 |
186 | it('Statics', function() {
187 | var Dog = Class.create({
188 | initialize: function(name) {
189 | this.name = name
190 | },
191 | Statics: {
192 | COLOR: 'red'
193 | }
194 | })
195 |
196 | var dog = new Dog('Jack')
197 |
198 | expect(dog.name).to.equal('Jack')
199 | expect(Dog.COLOR).to.equal('red')
200 | })
201 |
202 | it('statics inherited from parent', function() {
203 | var Animal = Class.create()
204 | Animal.LEGS = 4
205 |
206 | var Dog = Class.create({
207 | Extends: Animal,
208 |
209 | Statics: {
210 | COLOR: 'red'
211 | },
212 |
213 | initialize: function(name) {
214 | this.name = name
215 | }
216 | })
217 |
218 | expect(Dog.LEGS).to.equal(4)
219 | expect(Dog.COLOR).to.equal('red')
220 |
221 | var Pig = Class.create(Class)
222 |
223 | expect(typeof Pig.implement).to.equal('function')
224 | expect(typeof Pig.extend).to.equal('function')
225 | expect(typeof Pig.Mutators).to.equal('undefined')
226 | expect(typeof Pig.create).to.equal('undefined')
227 | })
228 |
229 | it('Class.extend', function() {
230 | var Dog = Class.extend({
231 | initialize: function(name) {
232 | this.name = name
233 | }
234 | })
235 |
236 | var dog = new Dog('Jack')
237 |
238 | expect(dog.name).to.equal('Jack')
239 | expect(Dog.superclass.constructor).to.equal(Class)
240 | })
241 |
242 | it('SubClass.extend', function() {
243 | var Animal = Class.create(function(name) {
244 | this.name = name
245 | })
246 |
247 | var Dog = Animal.extend()
248 | var dog = new Dog('Jack')
249 |
250 | expect(dog.name).to.equal('Jack')
251 | expect(Dog.superclass.constructor).to.equal(Animal)
252 | })
253 |
254 | it('SubClass.implement', function() {
255 | var Animal = Class.create(function(name) {
256 | this.name = name
257 | })
258 |
259 | var Dog = Animal.extend()
260 | Dog.implement({
261 | talk: function() {
262 | return 'I am ' + this.name
263 | }
264 | })
265 |
266 | var dog = new Dog('Jack')
267 |
268 | expect(dog.name).to.equal('Jack')
269 | expect(dog.talk()).to.equal('I am Jack')
270 | expect(Dog.superclass.constructor).to.equal(Animal)
271 | })
272 |
273 | it('convert existed function to Class', function() {
274 | function Dog(name) {
275 | this.name = name
276 | }
277 |
278 | Class(Dog).implement({
279 | getName: function() {
280 | return this.name
281 | }
282 | })
283 |
284 | var dog = new Dog('Jack')
285 |
286 | expect(dog.name).to.equal('Jack')
287 | expect(dog.getName()).to.equal('Jack')
288 |
289 | var MyDog = Dog.extend({
290 | talk: function() {
291 | return 'I am ' + this.name
292 | }
293 | })
294 |
295 | var myDog = new MyDog('Frank')
296 | expect(myDog.name).to.equal('Frank')
297 | })
298 |
299 | it('new AnotherClass() in initialize', function() {
300 | var called = []
301 |
302 | var Animal = Class.create({
303 | initialize: function() {
304 | called.push('Animal')
305 | }
306 | })
307 |
308 | var Pig = Class.create(Animal, {
309 | initialize: function() {
310 | called.push('Pig')
311 | }
312 | })
313 |
314 | var Dog = Class.create(Animal, {
315 | initialize: function() {
316 | new Pig()
317 | called.push('Dog')
318 | }
319 | })
320 |
321 | new Dog()
322 | expect(called.join(' ')).to.equal('Pig Dog')
323 |
324 | })
325 |
326 | it('StaticsWhiteList', function() {
327 |
328 | var A = Class.create()
329 | A.a = 1
330 | A.b = 1
331 | A.StaticsWhiteList = ['a']
332 | var B = A.extend(A)
333 |
334 | expect(B.a).to.equal(1)
335 | expect(B.b).to.equal(undefined)
336 | expect(B.StaticsWhiteList).to.equal(undefined)
337 |
338 | })
339 |
340 | it('Meta information', function() {
341 |
342 | var Dog = require('./data/dog')
343 | var dog = new Dog()
344 | //seajs.log(dog, 'dir')
345 |
346 | expect(dog.isAnimal).to.equal(true)
347 | expect(dog.isDog).to.equal(true)
348 | })
349 |
350 | })
351 |
--------------------------------------------------------------------------------
/tests/data/animal.js:
--------------------------------------------------------------------------------
1 | var Class = require('../../class')
2 |
3 | module.exports = Class.create({
4 | isAnimal: true
5 | })
6 |
--------------------------------------------------------------------------------
/tests/data/dog.js:
--------------------------------------------------------------------------------
1 | var Animal = require('./animal')
2 |
3 | module.exports = Animal.extend({
4 | isDog: true
5 | })
6 |
--------------------------------------------------------------------------------