├── .npmrc ├── test ├── mocha.opts ├── .eslintrc ├── index.js ├── index.html └── test.js ├── .gitignore ├── .eslintignore ├── examples ├── test-pet.js └── pet.js ├── .travis.yml ├── .eslintrc ├── .editorconfig ├── umd.js ├── package.json ├── README.md ├── src └── class.js └── dist └── class.js /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | -u tdd 2 | -R spec -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | example 5 | examples 6 | sandbox 7 | umd.js 8 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | env: 3 | mocha: true 4 | globals: 5 | assert: false 6 | Class: false 7 | -------------------------------------------------------------------------------- /examples/test-pet.js: -------------------------------------------------------------------------------- 1 | var my = require('my') 2 | my.global.console = console 3 | my.global.setTimeout = setTimeout 4 | my.load('pet.js') -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | cache: 3 | directories: 4 | - "node_modules" 5 | node_js: 6 | - "9" 7 | - "8" 8 | - "6" 9 | - "4" 10 | after_success: 11 | - npm run coveralls 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | semi: [2, never] 4 | quotes: [2, single, avoid-escape] 5 | strict: [2, global] 6 | global-strict: 0 7 | no-use-before-define: [2, nofunc] 8 | curly: [2, multi-line] 9 | no-underscore-dangle: 0 10 | no-proto: 0 11 | no-constant-condition: 1 12 | new-cap: [2, capIsNew: false] 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | tab_width = 3 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | max_line_length = 80 11 | 12 | [{package.json, *.yml, *.yaml}] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.{markdown, md}] 17 | indent_size = 4 18 | -------------------------------------------------------------------------------- /umd.js: -------------------------------------------------------------------------------- 1 | /* $package_name$ -- $package_description$ 2 | * v$package_version$ $date$ 3 | * Author: $package_author$ 4 | */ 5 | void function(root, factory){ 6 | if (typeof require === 'function' && typeof exports === 'object' && exports) { 7 | // CommonJS Module/1.0+ 8 | factory(require, exports) 9 | } else if (typeof define === 'function' && (define.amd || define.cmd)) { 10 | // AMD or CMD 11 | define(factory) 12 | } else { 13 | factory(null, root) 14 | } 15 | }(this, function(_, exports){ 16 | 17 | //$content$ 18 | 19 | }) 20 | -------------------------------------------------------------------------------- /examples/pet.js: -------------------------------------------------------------------------------- 1 | imports: {Class} from: '../src/class.js' 2 | 3 | var Pet = Class({ 4 | constructor: function(name) { 5 | this._name = name 6 | }, 7 | speak: function() { 8 | console.log(this._name + ' says...') 9 | } 10 | }) 11 | 12 | var Dog = Class.extend(Pet)({ 13 | woof: function() { 14 | return 'Woof, woof!' 15 | }, 16 | speak: function($super) { 17 | $super() 18 | console.log(this.woof() + " I'm a dog, pet me!") 19 | } 20 | }) 21 | 22 | var Cat = Class.extend(Pet)({ 23 | meow: function() { 24 | return 'Meow ~~' 25 | }, 26 | speak: function($super) { 27 | $super() 28 | console.log(this.meow() + " I'm a cat, go away!") 29 | } 30 | }) 31 | 32 | 33 | var dog = new Dog('Odie') 34 | dog.speak() 35 | 36 | var cat = new Cat('Garfield') 37 | cat.speak() 38 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | /* eslint strict: 0 */ 3 | /* global assert:true */ 4 | assert = require('assert') 5 | assert.egal = function(actual, expect, message) { 6 | message = (message ? message + ': ' : '') + 7 | 'expect ' + expect + ' but actual ' + actual 8 | if (actual === expect) { 9 | assert.ok(actual !== 0 || 1 / actual === 1 / expect, message) 10 | } else { 11 | assert.ok(actual !== actual && expect !== expect, message) 12 | } 13 | } 14 | assert.isFunction = function(f, message) { 15 | message = (message ? message + ': ' : '') + 16 | typeof f + ' is not a function' 17 | assert.ok(typeof f === 'function', message) 18 | } 19 | assert.instanceOf = function(x, y, message) { 20 | message = (message ? message + ': ' : '') + 21 | {}.toString.call(x) + ' is not a instance of ' + y 22 | assert.ok(x instanceof y, message) 23 | } 24 | 25 | /* global Class:true */ 26 | Class = require('../dist/class').Class 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmclass", 3 | "version": "0.4.2", 4 | "main": "dist/class.js", 5 | "scripts": { 6 | "dist": "node build", 7 | "test": "mocha", 8 | "coveralls": "istanbul cover _mocha && istanbul-coveralls" 9 | }, 10 | "description": "ES5 Class util which follow the semantics of ES6 max-min class", 11 | "keywords": [ 12 | "class", 13 | "es6", 14 | "OO", 15 | "extends", 16 | "super" 17 | ], 18 | "author": "Hax (http://johnhax.net/)", 19 | "homepage": "http://github.com/hax/mmclass", 20 | "bugs": "http://github.com/hax/mmclass/issues", 21 | "repository": { 22 | "type": "git", 23 | "url": "http://github.com/hax/mmclass.git" 24 | }, 25 | "devDependencies": { 26 | "better-es5-shim": "^0.1.0", 27 | "es5-shim": "^4.5.9", 28 | "istanbul": "^0.4.5", 29 | "istanbul-coveralls": "^1.0.3", 30 | "mocha": "~6.0.0", 31 | "my": "~0.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Testing mmclass 6 | 7 | 12 | 13 | 14 | 36 | 37 | 38 |
39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | test('class {}', function(){ 4 | var Test = Class() 5 | 6 | assert.isFunction(Test) 7 | assert.egal(Object.getPrototypeOf(Test), Function.prototype) 8 | assert.egal(Object.getPrototypeOf(Test.prototype), Object.prototype) 9 | assert.egal(Test.prototype.constructor, Test) 10 | var x = new Test() 11 | assert.instanceOf(x, Test) 12 | }) 13 | 14 | test('class { constructor(){}; method(){}; }', function(){ 15 | 16 | var Test = Class({ 17 | constructor: function(name) { 18 | this._name = name 19 | }, 20 | getName: function() { 21 | return this._name 22 | } 23 | }) 24 | 25 | assert.isFunction(Test) 26 | assert.egal(Object.getPrototypeOf(Test), Function.prototype) 27 | assert.egal(Object.getPrototypeOf(Test.prototype), Object.prototype) 28 | assert.egal(Test.prototype.constructor, Test) 29 | var hax = new Test('hax') 30 | assert.instanceOf(hax, Test) 31 | assert.egal(hax.getName(), 'hax') 32 | assert.egal(Object.getPrototypeOf(hax), Test.prototype) 33 | //assert.egal(Object.getOwnPropertyDescriptor(Test.prototype, 'getName').enumerable, false) 34 | //assert.throws(function(){ new hax.getName() }, TypeError) 35 | }) 36 | 37 | test('class extends null {}', function(){ 38 | var Test = Class.extend(null)() 39 | 40 | assert.isFunction(Test) 41 | assert.egal(Object.getPrototypeOf(Test), Function.prototype) 42 | assert.egal(Object.getPrototypeOf(Test.prototype), null) 43 | assert.egal(Test.prototype.constructor, Test) 44 | assert.instanceOf(new Test(), Test) 45 | }) 46 | 47 | test('class extends Super {}', function(){ 48 | function Super() {} 49 | Super.className = 'Super' 50 | Super.prototype.getClassName = function() { 51 | return this.constructor.className 52 | } 53 | var Test = Class.extend(Super)() 54 | 55 | assert.isFunction(Test) 56 | if ('__proto__' in function(){}) { 57 | assert.egal(Object.getPrototypeOf(Test), Super) 58 | } else { 59 | assert.egal(Test.className, 'Super') 60 | } 61 | 62 | assert.egal(Object.getPrototypeOf(Test.prototype), Super.prototype) 63 | assert.egal(Test.prototype.constructor, Test) 64 | assert.instanceOf(new Test(), Test) 65 | assert.instanceOf(new Test(), Super) 66 | assert.egal(new Test().getClassName(), 'Super') 67 | Test.className = 'Test' 68 | assert.egal(new Test().getClassName(), 'Test') 69 | }) 70 | 71 | test('class extends proto {}', function(){ 72 | var proto = { 73 | _name: 'hax', 74 | getName: function() { 75 | return this._name 76 | } 77 | } 78 | var Test = Class.extend(proto)() 79 | 80 | assert.isFunction(Test) 81 | assert.egal(Object.getPrototypeOf(Test), Function.prototype) 82 | assert.egal(Object.getPrototypeOf(Test.prototype), proto) 83 | assert.egal(Test.prototype.constructor, Test) 84 | assert.instanceOf(new Test(), Test) 85 | assert.egal(new Test().getName(), 'hax') 86 | }) 87 | 88 | test('class method override', function(){ 89 | var A = Class({ 90 | toString: function() { return 'a' } 91 | }) 92 | var B = Class.extend(A)({ 93 | toString: function() { return 'b' } 94 | }) 95 | 96 | assert.instanceOf(new B(), B) 97 | assert.instanceOf(new B(), A) 98 | assert.egal(new A().toString(), 'a') 99 | assert.egal(new B().toString(), 'b') 100 | }) 101 | 102 | test('super call', function(){ 103 | var A = Class({ 104 | constructor: function(name) { 105 | this._name = name 106 | }, 107 | getName: function() { 108 | return this._name 109 | }, 110 | toString: function() { 111 | return this.getName() 112 | } 113 | }) 114 | var B = Class.extend(A)({ 115 | constructor: function($super, name, desc) { 116 | $super(name) 117 | this._desc = desc 118 | }, 119 | getDesc: function() { 120 | return this._desc 121 | }, 122 | toString: function($super) { 123 | return $super() + ': ' + this.getDesc() 124 | } 125 | }) 126 | var C = Class.extend(B)({ 127 | getName: function($super) { 128 | return '[' + $super() + ']' 129 | } 130 | }) 131 | 132 | var b = new B('test', 'Hello world!') 133 | assert.instanceOf(b, B) 134 | assert.instanceOf(b, A) 135 | assert.egal(b.getName(), 'test') 136 | assert.egal(b.getDesc(), 'Hello world!') 137 | assert.egal(b.toString(), 'test: Hello world!') 138 | 139 | var c = new C('test', 'Hello world!') 140 | assert.instanceOf(c, C) 141 | assert.instanceOf(c, B) 142 | assert.instanceOf(c, A) 143 | assert.egal(c.getName(), '[test]') 144 | assert.egal(c.getDesc(), 'Hello world!') 145 | assert.egal(c.toString(), '[test]: Hello world!') 146 | }) 147 | 148 | /*test('class { static method() }', function(){ 149 | var A = Class.statics 150 | })*/ 151 | 152 | test('#1 multi-line constructor/method parameters', function () { 153 | 154 | var Test = Class({ 155 | constructor: function ( a, 156 | b, 157 | c) { 158 | return [a, b, c] 159 | }, 160 | test: function 161 | ( a, 162 | b, 163 | c) { 164 | return [a, b, c] 165 | } 166 | }) 167 | 168 | assert.egal(Test.length, 3) 169 | assert.egal(Test.prototype.test.length, 3) 170 | 171 | }) 172 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM version][npm-image]][npm-url] 2 | [![Build status][travis-image]][travis-url] 3 | [![Test coverage][coveralls-image]][coveralls-url] 4 | [![Codacy badge][codacy-image]][codacy-url] 5 | [![Downloads][downloads-image]][npm-url] 6 | [![Greenkeeper badge](https://badges.greenkeeper.io/hax/mmclass.svg)](https://greenkeeper.io/) 7 | 8 | ## What is MMClass ## 9 | 10 | MMClass is just another JavaScript Class factory, 11 | like Class in [Prototype](http://prototypejs.org/learn/class-inheritance), 12 | [MooTools](http://mootools.net/docs/core/Class/Class) or 13 | [Base2](http://base2.googlecode.com/svn/version/1.0.2/doc/base2.html#/doc/!base2.Base). 14 | But it follow the semantics of ES6 max-min class draft, 15 | so all codes written with MMClass are expected to be compatible with ES6. 16 | 17 | ## Example ## 18 | 19 | ### ES 6 syntax: ### 20 | 21 | ```js 22 | Class Pet { 23 | constructor(name) { 24 | this._name = name 25 | } 26 | speak() { 27 | console.log(this._name + ' says...') 28 | } 29 | } 30 | Class Dog extends Pet { 31 | constructor(name) { 32 | super(name) 33 | } 34 | woof() { 35 | return 'Woof, woof!' 36 | } 37 | speak() { 38 | super.speak() 39 | console.log(this.woof() + " I'm a dog, pet me!") 40 | } 41 | } 42 | Class Cat extends Pet { 43 | meow() { 44 | return 'Meow ~~' 45 | } 46 | speak() { 47 | super.speak() 48 | console.log(this.meow() + " I'm a cat, go away!") 49 | } 50 | } 51 | ``` 52 | 53 | ### ES5 version with MMClass: ### 54 | 55 | ```js 56 | var Pet = Class({ 57 | constructor: function(name) { 58 | this._name = name 59 | }, 60 | speak: function() { 61 | console.log(this._name + ' says...') 62 | } 63 | }) 64 | 65 | var Dog = Class.extend(Pet)({ 66 | constructor: function($super, name) { 67 | $super(name) 68 | }, 69 | woof: function() { 70 | return 'Woof, woof!' 71 | }, 72 | speak: function($super) { 73 | $super.speak() 74 | console.log(this.woof() + " I'm a dog, pet me!") 75 | } 76 | }) 77 | 78 | var Cat = Class.extend(Pet)({ 79 | // if no constructor provided, 80 | // default to constructor(...args) { super(...args) } 81 | meow: function() { 82 | return 'Meow ~~' 83 | }, 84 | speak: function($super) { 85 | $super.speak() 86 | console.log(this.meow() + " I'm a cat, go away!") 87 | } 88 | }) 89 | 90 | 91 | var dog = new Dog('Odie') 92 | dog.speak() // Output: Odie says... Woof, woof! I'm a dog, pet me! 93 | 94 | var cat = new Cat('Garfield') 95 | cat.speak() // Output: Garfield says... Meow ~~ I'm a cat, go away! 96 | ``` 97 | 98 | ## More info about ES6 Class/ES5 MMClass ## 99 | 100 | ### You can extend a traditional ES5 constructor function ### 101 | 102 | ```js 103 | function Pet(name) { 104 | this._name = name 105 | } 106 | Pet.prototype.speak = function() { 107 | console.log(this._name + ' says...') 108 | } 109 | ``` 110 | 111 | ES6: 112 | 113 | ```js 114 | class Dog extends Pet {...} 115 | ``` 116 | 117 | MMClass: 118 | 119 | ```js 120 | var Dog = Class.extend(Pet)({...}) 121 | ``` 122 | 123 | Note: As ES6, Dog.[[prototype]] should be Pet so that Dog class can inherit 124 | all "static" properties on Pet. MMClass follow this semantic via setting 125 | \_\_proto\_\_ . Most engines support this pseudo property, if it's not supported, 126 | MMClass will copy all properties. 127 | 128 | ### You also can extend an object directly (aka. prototype-based inheritance) ### 129 | 130 | ```js 131 | var pet = { 132 | constructor: function(name) { 133 | this._name = name 134 | }, 135 | speak: function() { 136 | console.log(this._name + ' says...') 137 | } 138 | } 139 | ``` 140 | 141 | ES6: 142 | 143 | ```js 144 | class Dog extends pet {...} 145 | ``` 146 | 147 | MMClass: 148 | 149 | ```js 150 | var Dog = Class.extend(pet)({...}) 151 | ``` 152 | 153 | ### You even can extend null to avoid inheriting the properties from Object.prototype (such as isPrototypeOf/hasOwnProperty/propertyIsEnumerable) ### 154 | 155 | ES6: 156 | 157 | ```js 158 | class NakedObject extends null {} 159 | ``` 160 | 161 | MMClass: 162 | 163 | ```js 164 | var NakedObject = Class.extend(null)() 165 | ``` 166 | 167 | ### 168 | 169 | ## Usage ## 170 | 171 | **Note:** MMClass requires ES5 environment. You can try _es5-shim_ for legacy browsers (though untested yet). 172 | 173 | ### my.js ### 174 | 175 | ```js 176 | 'use strict' 177 | 'import Class from "$PATH_TO/mmclass/src/class.js"' 178 | ... 179 | ``` 180 | See [my.js](http://github.com/hax/my.js/) for more info 181 | 182 | ### NodeJS ### 183 | 184 | Installation: 185 | ``` 186 | npm i mmclass 187 | ``` 188 | 189 | Source: 190 | ```js 191 | var Class = require('mmclass').Class 192 | ... 193 | ``` 194 | 195 | ### AMD / CMD ### 196 | 197 | ```js 198 | define(function(require, exports) { 199 | var Class = require('$PATH_TO/mmclass/dist/mmclass').Class 200 | ... 201 | } 202 | ``` 203 | 204 | ### Browser ### 205 | 206 | ```html 207 | 208 | ``` 209 | 210 | ## TODO ## 211 | 212 | * Check the recent released ES6 draft to follow the changes 213 | (the standard draft is changing dramatically...) 214 | 215 | 216 | [npm-image]: https://img.shields.io/npm/v/mmclass.svg?style=flat-square 217 | [npm-url]: https://npmjs.org/package/mmclass 218 | [travis-image]: https://img.shields.io/travis/hax/mmclass/v0.4.x.svg?style=flat-square 219 | [travis-url]: https://travis-ci.org/hax/mmclass 220 | [coveralls-image]: https://img.shields.io/coveralls/hax/mmclass/v0.4.x.svg?style=flat-square 221 | [coveralls-url]: https://coveralls.io/r/hax/mmclass?branch=v0.4.x 222 | [downloads-image]: http://img.shields.io/npm/dm/mmclass.svg?style=flat-square 223 | [codacy-image]: https://img.shields.io/codacy/4ce5706252da43709594735f4728ad83.svg?style=flat-square 224 | [codacy-url]: https://www.codacy.com/public/hax/mmclass 225 | -------------------------------------------------------------------------------- /src/class.js: -------------------------------------------------------------------------------- 1 | // ES5 Class util as ES6 max-min class semantics 2 | // See: http://wiki.ecmascript.org/doku.php?id=strawman:maximally_minimal_classes 3 | 4 | 'use strict' 5 | 'export {Class}' 6 | 7 | function Class(methods) { 8 | if (arguments.length === 0) return function(){} 9 | return createClass(methods) 10 | } 11 | Class.extend = function(superclass, props) { 12 | var protoParent, constructorParent 13 | if (superclass === null) { 14 | protoParent = null 15 | } else { 16 | switch (typeof superclass) { 17 | case 'function': // TODO: should be constructor 18 | if (typeof superclass.prototype === 'object' || superclass.prototype === null) { 19 | protoParent = superclass.prototype 20 | constructorParent = superclass 21 | } else throw new TypeError('superclass.prototype should be an object or null') 22 | break 23 | case 'object': 24 | protoParent = superclass 25 | break 26 | default: throw new TypeError('superclass should be an object or null') 27 | } 28 | } 29 | var pds = {} 30 | if (props !== undefined) { 31 | for (var names = Object.getOwnPropertyNames(props), i = 0; i < names.length; i++) { 32 | var name = names[i] 33 | pds[name] = Object.getOwnPropertyDescriptor(props, name) 34 | } 35 | } 36 | return function(methods) { 37 | return createClass(methods, protoParent, pds, constructorParent) 38 | } 39 | } 40 | 41 | var _isPrototypeOf = {}.isPrototypeOf 42 | var inherit = supportSetProto() ? 43 | function (obj, proto) { obj.__proto__ = proto } : 44 | function (obj, proto) { 45 | // copy all properties from proto 46 | while (proto !== null && !_isPrototypeOf.call(proto, obj)) { // stop if obj has the same prototype 47 | for (var names = Object.getOwnPropertyNames(proto), i = 0; i < names.length; i++) { 48 | var name = names[i] 49 | if (!obj.hasOwnProperty(name)) { 50 | Object.defineProperty(obj, name, Object.getOwnPropertyDescriptor(proto, name)) 51 | } 52 | } 53 | proto = Object.getPrototypeOf(proto) 54 | } 55 | } 56 | 57 | function createClass(methods, protoParent, protoPDs, constructorParent) { 58 | var methodDescriptors = {} 59 | if (arguments.length <= 1) protoParent = Object.prototype 60 | if (methods !== undefined) { 61 | for (var names = Object.getOwnPropertyNames(methods), i = 0; i < names.length; i++) { 62 | var name = names[i] 63 | var pd = Object.getOwnPropertyDescriptor(methods, name) 64 | if ('value' in pd) { 65 | if (typeof pd.value === 'function') { 66 | pd.value = createMethod(name, pd.value, protoParent) 67 | } else throw new TypeError('[' + name + '] is not a function') 68 | } else { 69 | if (pd.get) pd.get = createMethod('get ' + name, pd.get, protoParent) 70 | if (pd.set) pd.set = createMethod('set ' + name, pd.set, protoParent) 71 | } 72 | pd.enumerable = false 73 | methodDescriptors[name] = pd 74 | } 75 | } 76 | var Ctor 77 | if (methodDescriptors.hasOwnProperty('constructor')) { 78 | Ctor = methodDescriptors.constructor.value 79 | if (Ctor === undefined) throw new TypeError('constructor is not a function') 80 | } else { 81 | Ctor = function constructor() { 82 | if (protoParent !== null) protoParent.constructor.apply(this, arguments) 83 | } 84 | methodDescriptors.constructor = {value: Ctor, writable: true, configurable: true} 85 | } 86 | if (arguments.length > 1) Ctor.prototype = Object.create(protoParent, protoPDs) 87 | Object.defineProperties(Ctor.prototype, methodDescriptors) 88 | Object.defineProperties(Ctor, { 89 | prototype: {writable: false, configurable: false, enumerable: false} 90 | }) 91 | if (constructorParent) inherit(Ctor, constructorParent) 92 | return Ctor 93 | } 94 | 95 | function supportSetProto() { 96 | var x = {} 97 | x.__proto__ = C.prototype 98 | return x instanceof C 99 | 100 | function C() {} 101 | } 102 | 103 | var f = function(){} 104 | var toFunctionSource = f.toSource || f.toString 105 | 106 | function createMethod(name, func, base) { 107 | var params = /\(([\s\S]*?)\)/.exec(toFunctionSource.call(func))[1].split(/\s*,\s*/) 108 | var method 109 | if (params[0] === '$super') { 110 | method = function() { 111 | var args = [].slice.call(arguments) 112 | args.unshift(createSuper(this, base, name)) 113 | return func.apply(this, args) 114 | } 115 | } else method = func 116 | return method 117 | } 118 | 119 | function createSuper(actualThis, base, methodName) { 120 | if (base === null) return null 121 | 122 | // TODO: via Proxy? 123 | var Super = function() { 124 | return base[methodName].apply(actualThis, arguments) 125 | } 126 | Super.call = function(name) { 127 | [].shift.call(arguments) 128 | return base[name].apply(actualThis, arguments) 129 | } 130 | Super.get = function(name) { 131 | var p = base, get 132 | while (!(get = Object.getOwnPropertyDescriptor(p, name).get)) { 133 | p = Object.getPrototypeOf(p) 134 | } 135 | return get.apply(actualThis) 136 | } 137 | Super.set = function(name, value) { 138 | var p = base, set 139 | while (!(set = Object.getOwnPropertyDescriptor(p, name).set)) { 140 | p = Object.getPrototypeOf(p) 141 | } 142 | return set.apply(actualThis, [value]) 143 | } 144 | inherit(Super, createSuperProto(actualThis, base)) 145 | return Super 146 | } 147 | 148 | function createSuperProto(actualThis, base) { 149 | if (base === null) return null 150 | var pds = {} 151 | for (var names = Object.getOwnPropertyNames(base), i = 0; i < names.length; i++) { 152 | var name = names[i] 153 | var pd = Object.getOwnPropertyDescriptor(base, name) 154 | if ('value' in pd) { 155 | pd.value = typeof pd.value === 'function' ? 156 | pd.value.bind(actualThis) : pd.value 157 | } else { 158 | if (pd.get) pd.get = pd.get.bind(actualThis) 159 | if (pd.set) pd.set = pd.set.bind(actualThis) 160 | } 161 | pds[name] = pd 162 | } 163 | var proto = createSuperProto(actualThis, Object.getPrototypeOf(base)) 164 | return Object.create(proto, pds) 165 | } 166 | 167 | /* 168 | extra API? 169 | 170 | Class#defineMethod 171 | 172 | support $this 173 | 174 | Object.augment 175 | */ 176 | -------------------------------------------------------------------------------- /dist/class.js: -------------------------------------------------------------------------------- 1 | /* mmclass -- ES5 Class util which follow the semantics of ES6 max-min class 2 | * v0.4.2 2015-06-29T05:59:17.494Z 3 | * Author: Hax (http://johnhax.net/) 4 | */ 5 | void function(root, factory){ 6 | if (typeof require === 'function' && typeof exports === 'object' && exports) { 7 | // CommonJS Module/1.0+ 8 | factory(require, exports) 9 | } else if (typeof define === 'function' && (define.amd || define.cmd)) { 10 | // AMD or CMD 11 | define(factory) 12 | } else { 13 | factory(null, root) 14 | } 15 | }(this, function(_, exports){ 16 | 17 | // ES5 Class util as ES6 max-min class semantics 18 | // See: http://wiki.ecmascript.org/doku.php?id=strawman:maximally_minimal_classes 19 | 20 | 'use strict' 21 | exports.Class = Class 22 | 23 | function Class(methods) { 24 | if (arguments.length === 0) return function(){} 25 | return createClass(methods) 26 | } 27 | Class.extend = function(superclass, props) { 28 | var protoParent, constructorParent 29 | if (superclass === null) { 30 | protoParent = null 31 | } else { 32 | switch (typeof superclass) { 33 | case 'function': // TODO: should be constructor 34 | if (typeof superclass.prototype === 'object' || superclass.prototype === null) { 35 | protoParent = superclass.prototype 36 | constructorParent = superclass 37 | } else throw new TypeError('superclass.prototype should be an object or null') 38 | break 39 | case 'object': 40 | protoParent = superclass 41 | break 42 | default: throw new TypeError('superclass should be an object or null') 43 | } 44 | } 45 | var pds = {} 46 | if (props !== undefined) { 47 | for (var names = Object.getOwnPropertyNames(props), i = 0; i < names.length; i++) { 48 | var name = names[i] 49 | pds[name] = Object.getOwnPropertyDescriptor(props, name) 50 | } 51 | } 52 | return function(methods) { 53 | return createClass(methods, protoParent, pds, constructorParent) 54 | } 55 | } 56 | 57 | var _isPrototypeOf = {}.isPrototypeOf 58 | var inherit = supportSetProto() ? 59 | function (obj, proto) { obj.__proto__ = proto } : 60 | function (obj, proto) { 61 | // copy all properties from proto 62 | while (proto !== null && !_isPrototypeOf.call(proto, obj)) { // stop if obj has the same prototype 63 | for (var names = Object.getOwnPropertyNames(proto), i = 0; i < names.length; i++) { 64 | var name = names[i] 65 | if (!obj.hasOwnProperty(name)) { 66 | Object.defineProperty(obj, name, Object.getOwnPropertyDescriptor(proto, name)) 67 | } 68 | } 69 | proto = Object.getPrototypeOf(proto) 70 | } 71 | } 72 | 73 | function createClass(methods, protoParent, protoPDs, constructorParent) { 74 | var methodDescriptors = {} 75 | if (arguments.length <= 1) protoParent = Object.prototype 76 | if (methods !== undefined) { 77 | for (var names = Object.getOwnPropertyNames(methods), i = 0; i < names.length; i++) { 78 | var name = names[i] 79 | var pd = Object.getOwnPropertyDescriptor(methods, name) 80 | if ('value' in pd) { 81 | if (typeof pd.value === 'function') { 82 | pd.value = createMethod(name, pd.value, protoParent) 83 | } else throw new TypeError('[' + name + '] is not a function') 84 | } else { 85 | if (pd.get) pd.get = createMethod('get ' + name, pd.get, protoParent) 86 | if (pd.set) pd.set = createMethod('set ' + name, pd.set, protoParent) 87 | } 88 | pd.enumerable = false 89 | methodDescriptors[name] = pd 90 | } 91 | } 92 | var Ctor 93 | if (methodDescriptors.hasOwnProperty('constructor')) { 94 | Ctor = methodDescriptors.constructor.value 95 | if (Ctor === undefined) throw new TypeError('constructor is not a function') 96 | } else { 97 | Ctor = function constructor() { 98 | if (protoParent !== null) protoParent.constructor.apply(this, arguments) 99 | } 100 | methodDescriptors.constructor = {value: Ctor, writable: true, configurable: true} 101 | } 102 | if (arguments.length > 1) Ctor.prototype = Object.create(protoParent, protoPDs) 103 | Object.defineProperties(Ctor.prototype, methodDescriptors) 104 | Object.defineProperties(Ctor, { 105 | prototype: {writable: false, configurable: false, enumerable: false} 106 | }) 107 | if (constructorParent) inherit(Ctor, constructorParent) 108 | return Ctor 109 | } 110 | 111 | function supportSetProto() { 112 | var x = {} 113 | x.__proto__ = C.prototype 114 | return x instanceof C 115 | 116 | function C() {} 117 | } 118 | 119 | var f = function(){} 120 | var toFunctionSource = f.toSource || f.toString 121 | 122 | function createMethod(name, func, base) { 123 | var params = /\(([\s\S]*?)\)/.exec(toFunctionSource.call(func))[1].split(/\s*,\s*/) 124 | var method 125 | if (params[0] === '$super') { 126 | method = function() { 127 | var args = [].slice.call(arguments) 128 | args.unshift(createSuper(this, base, name)) 129 | return func.apply(this, args) 130 | } 131 | } else method = func 132 | return method 133 | } 134 | 135 | function createSuper(actualThis, base, methodName) { 136 | if (base === null) return null 137 | 138 | // TODO: via Proxy? 139 | var Super = function() { 140 | return base[methodName].apply(actualThis, arguments) 141 | } 142 | Super.call = function(name) { 143 | [].shift.call(arguments) 144 | return base[name].apply(actualThis, arguments) 145 | } 146 | Super.get = function(name) { 147 | var p = base, get 148 | while (!(get = Object.getOwnPropertyDescriptor(p, name).get)) { 149 | p = Object.getPrototypeOf(p) 150 | } 151 | return get.apply(actualThis) 152 | } 153 | Super.set = function(name, value) { 154 | var p = base, set 155 | while (!(set = Object.getOwnPropertyDescriptor(p, name).set)) { 156 | p = Object.getPrototypeOf(p) 157 | } 158 | return set.apply(actualThis, [value]) 159 | } 160 | inherit(Super, createSuperProto(actualThis, base)) 161 | return Super 162 | } 163 | 164 | function createSuperProto(actualThis, base) { 165 | if (base === null) return null 166 | var pds = {} 167 | for (var names = Object.getOwnPropertyNames(base), i = 0; i < names.length; i++) { 168 | var name = names[i] 169 | var pd = Object.getOwnPropertyDescriptor(base, name) 170 | if ('value' in pd) { 171 | pd.value = typeof pd.value === 'function' ? 172 | pd.value.bind(actualThis) : pd.value 173 | } else { 174 | if (pd.get) pd.get = pd.get.bind(actualThis) 175 | if (pd.set) pd.set = pd.set.bind(actualThis) 176 | } 177 | pds[name] = pd 178 | } 179 | var proto = createSuperProto(actualThis, Object.getPrototypeOf(base)) 180 | return Object.create(proto, pds) 181 | } 182 | 183 | /* 184 | extra API? 185 | 186 | Class#defineMethod 187 | 188 | support $this 189 | 190 | Object.augment 191 | */ 192 | 193 | 194 | }) 195 | --------------------------------------------------------------------------------