class-autobind-decorator
Advanced tools
Comparing version 2.0.0 to 2.1.0
@@ -26,2 +26,5 @@ 'use strict'; | ||
* @param {String[]} [options.methodsToIgnore] - names of methods to skip auto-binding | ||
* @param {boolean} [options.dontOptimize] - if truthy, turns off the decorator's default | ||
* optimization behavior, which is to define the bound method directly on the class instance | ||
* in order to prevent lookups and re-binding on every access | ||
* @returns {*} | ||
@@ -45,3 +48,6 @@ */ | ||
* @param {Object} [options] - optional options | ||
* @param {String[]} [options.methodsToIgnore] - names of methods to skip auto-binding | ||
* @param {string[]} [options.methodsToIgnore] - names of methods to skip auto-binding | ||
* @param {boolean} [options.dontOptimize] - if truthy, turns off the decorator's default | ||
* optimization behavior, which is to define the bound method directly on the class instance | ||
* in order to prevent lookups and re-binding on every access | ||
*/ | ||
@@ -57,4 +63,7 @@ function autoBindMethodsDecorator(target) { | ||
var _options$methodsToIgn = options.methodsToIgnore, | ||
methodsToIgnore = _options$methodsToIgn === undefined ? [] : _options$methodsToIgn; | ||
methodsToIgnore = _options$methodsToIgn === undefined ? [] : _options$methodsToIgn, | ||
_options$dontOptimize = options.dontOptimize, | ||
dontOptimize = _options$dontOptimize === undefined ? false : _options$dontOptimize; | ||
var ownProps = typeof Object.getOwnPropertySymbols === 'function' ? Object.getOwnPropertyNames(prototype).concat(Object.getOwnPropertySymbols(prototype)) : Object.getOwnPropertyNames(prototype); | ||
@@ -70,6 +79,7 @@ | ||
var propDescriptor = Object.getOwnPropertyDescriptor(prototype, ownPropIdentifier); | ||
var value = propDescriptor.value; | ||
var value = propDescriptor.value, | ||
configurable = propDescriptor.configurable; | ||
if (typeof value !== 'function' || !propDescriptor.configurable) { | ||
if (typeof value !== 'function' || !configurable) { | ||
// We can only do our work with configurable functions, so bail early here. | ||
@@ -79,16 +89,21 @@ return; | ||
var boundMethod = void 0; | ||
Object.defineProperty(prototype, ownPropIdentifier, { | ||
get: function get() { | ||
if (!boundMethod) { | ||
if (!(this instanceof target)) { | ||
// We don't want to bind to something that isn't an instance of the constructor in the rare | ||
// case where the property is read by some means other than an instance *before* it has been | ||
// bound (e.g., if something checks whether the method exists via the prototype, as in | ||
// `someConstructor.prototype.someProp`), so we just return the unbound method in that case. | ||
return value; | ||
} | ||
if (this.hasOwnProperty(ownPropIdentifier)) { | ||
// Don't bind the prototype's method to the prototype, or we can't re-bind it. | ||
return value; | ||
} | ||
boundMethod = value.bind(this); | ||
var boundMethod = value.bind(this); | ||
if (!dontOptimize) { | ||
var _configurable = propDescriptor.configurable, | ||
writable = propDescriptor.writable; | ||
Object.defineProperty(this, ownPropIdentifier, { | ||
value: boundMethod, | ||
configurable: _configurable, | ||
writable: writable | ||
}); | ||
} | ||
@@ -95,0 +110,0 @@ |
# CHANGELOG | ||
## v2.1.0 (11/18/2016) | ||
- Add optimizations (add methods to instances and unnecessary re-binding) | ||
- Add `dontOptimize` flag for overridding optimizations if necessary | ||
- Actually make this thing work for classes with more than one instance | ||
(whoops!). This is the *real* fix for issue #2. | ||
## v2.0.0 (11/16/2016) | ||
@@ -4,0 +11,0 @@ |
{ | ||
"name": "class-autobind-decorator", | ||
"version": "2.0.0", | ||
"version": "2.1.0", | ||
"description": "A small framework-agnostic utility for auto-binding \"class\" methods to instances (with customization options) using either \"legacy\" decorator syntax or plain old ES5 (without needing ES2015+ polyfills).", | ||
@@ -5,0 +5,0 @@ "main": "build/index.js", |
@@ -10,11 +10,12 @@ # class-autobind-decorator | ||
- Usable with ES6 [React](https://facebook.github.io/react/) classes, | ||
- Usable with ES6+ [React](https://facebook.github.io/react/) and [Preact](https://preactjs.com) classes, | ||
but also elsewhere | ||
- Built version is fully ES5-compatible, requiring no ES6+ polyfills | ||
- Supports class methods that have ES6 Symbols as keys | ||
- Accepts passed-in options (see examples below), allowing the user to | ||
- Accepts configuration options (see examples below), allowing the user to | ||
specify the methods that should or should not be bound (supporting both | ||
named methods and methods with Symbols as keys in this case, as well) | ||
named methods and methods with Symbols as keys in this case, as well), and | ||
to specify whether or not to perform certain optimizations | ||
- Does not attempt to redefine methods marked as non-configurable | ||
(actually checks for configurability first!) | ||
(checks for configurability first!) | ||
- Conforms to [the ES6+ "legacy" decorator pattern](https://babeljs.io/docs/plugins/transform-decorators/), | ||
@@ -24,3 +25,3 @@ and hence is usable as an ES6+ legacy decorator | ||
or as a configured decorator (`@autoBindMethods(options)`) | ||
- Extensively documented and tested | ||
- Documented and tested | ||
@@ -131,5 +132,15 @@ ## Installation | ||
## Configuration Options | ||
**methodsToIgnore**: An array of method names that should not be bound if found on the prototype. | ||
See the above examples for usage. | ||
**dontOptimize**: The default behavior of this decorator is to only bind methods to instances once, | ||
and, from that point onward, to store the bound method on the instance itself. You can override | ||
this behavior by setting `dontOptimize` to true. | ||
## Building | ||
Clone the repository, then, in the main (top-level) repo directory: | ||
Clone the repository, run `npm install`, then, in the main (top-level) repo | ||
directory: | ||
@@ -141,3 +152,3 @@ ```js | ||
Compiled code will be placed in the `./build` directory. You can also | ||
download it directly from this repository. | ||
download compiled code directly from this repository. | ||
@@ -162,7 +173,8 @@ ## Running Tests | ||
aware of either hard-code React-specific stuff, or don't check whether | ||
properties are configurable before trying to redefine them, or can't be | ||
used as both "bare" (unconfigured) decorators and configured decorators, | ||
and things like that -- no hate, though). I also just wanted an | ||
properties are configurable before trying to redefine them, or inadvertently | ||
bind to non-instance objects when methods are accessed first via the prototype, | ||
or can't be used as both "bare" (unconfigured) decorators and configured | ||
decorators, and things like that -- no hate, though!). I also just wanted an | ||
opportunity to work more directly with decorators, so I used it as a | ||
learning experience. | ||
learning experience. :) | ||
@@ -169,0 +181,0 @@ ## License |
@@ -17,2 +17,5 @@ /** | ||
* @param {String[]} [options.methodsToIgnore] - names of methods to skip auto-binding | ||
* @param {boolean} [options.dontOptimize] - if truthy, turns off the decorator's default | ||
* optimization behavior, which is to define the bound method directly on the class instance | ||
* in order to prevent lookups and re-binding on every access | ||
* @returns {*} | ||
@@ -36,3 +39,6 @@ */ | ||
* @param {Object} [options] - optional options | ||
* @param {String[]} [options.methodsToIgnore] - names of methods to skip auto-binding | ||
* @param {string[]} [options.methodsToIgnore] - names of methods to skip auto-binding | ||
* @param {boolean} [options.dontOptimize] - if truthy, turns off the decorator's default | ||
* optimization behavior, which is to define the bound method directly on the class instance | ||
* in order to prevent lookups and re-binding on every access | ||
*/ | ||
@@ -48,3 +54,4 @@ function autoBindMethodsDecorator(target, options = {}) { | ||
const { prototype } = target; | ||
const { methodsToIgnore = [] } = options; | ||
const { methodsToIgnore = [], dontOptimize = false } = options; | ||
let ownProps = typeof Object.getOwnPropertySymbols === 'function' ? | ||
@@ -60,5 +67,5 @@ Object.getOwnPropertyNames(prototype).concat(Object.getOwnPropertySymbols(prototype)) : | ||
const propDescriptor = Object.getOwnPropertyDescriptor(prototype, ownPropIdentifier); | ||
const { value } = propDescriptor; | ||
const { value, configurable } = propDescriptor; | ||
if (typeof value !== 'function' || !propDescriptor.configurable) { | ||
if (typeof value !== 'function' || !configurable) { | ||
// We can only do our work with configurable functions, so bail early here. | ||
@@ -68,16 +75,19 @@ return; | ||
let boundMethod; | ||
Object.defineProperty(prototype, ownPropIdentifier, { | ||
get() { | ||
if (!boundMethod) { | ||
if (!(this instanceof target)) { | ||
// We don't want to bind to something that isn't an instance of the constructor in the rare | ||
// case where the property is read by some means other than an instance *before* it has been | ||
// bound (e.g., if something checks whether the method exists via the prototype, as in | ||
// `someConstructor.prototype.someProp`), so we just return the unbound method in that case. | ||
return value; | ||
} | ||
if (this.hasOwnProperty(ownPropIdentifier)) { | ||
// Don't bind the prototype's method to the prototype, or we can't re-bind it. | ||
return value; | ||
} | ||
boundMethod = value.bind(this); | ||
const boundMethod = value.bind(this); | ||
if (!dontOptimize) { | ||
const { configurable, writable } = propDescriptor; | ||
Object.defineProperty(this, ownPropIdentifier, { | ||
value: boundMethod, | ||
configurable, | ||
writable | ||
}); | ||
} | ||
@@ -84,0 +94,0 @@ |
@@ -107,2 +107,29 @@ import 'babel-polyfill'; | ||
}); | ||
describe('when the options specify dontOptimize', function () { | ||
it('should not alter the instance and should re-bind on every access', function () { | ||
const [MyFirstClass] = getClasses(); | ||
const customAutoBinder = autoBindMethods({ dontOptimize: true }); | ||
customAutoBinder(MyFirstClass); | ||
const myFirstInstance = new MyFirstClass(); | ||
const a = myFirstInstance.testMethodOne; | ||
const b = myFirstInstance.testMethodOne; | ||
expect(a).not.to.equal(b); | ||
expect(myFirstInstance.hasOwnProperty('testMethodOne')).to.equal(false); | ||
}); | ||
}); | ||
describe('when the options do not specify dontOptimize', function () { | ||
it('should alter the instance and not re-bind on every access', function () { | ||
const [, MySecondClass] = getClasses(); | ||
const myFirstInstance = new MySecondClass(); | ||
const a = myFirstInstance.testMethodOne; | ||
const b = myFirstInstance.testMethodOne; | ||
expect(a).to.equal(b); | ||
expect(myFirstInstance.hasOwnProperty('testMethodOne')).to.equal(true); | ||
}) | ||
}) | ||
}); | ||
@@ -131,5 +158,6 @@ | ||
describe('when a method is accessed by something other than the instance', function () { | ||
it('should not bind the method, but should only bind when access *is* by the instance', function () { | ||
const [ , MySecondClass, ] = getClasses(); | ||
describe('when a method is accessed via the prototype', function () { | ||
it('should not bind the method to the prototype', function () { | ||
const [ , MySecondClass] = getClasses(); | ||
MySecondClass.prototype.testMethodOne; // `get` via prototype, not instance, should not bind | ||
@@ -143,2 +171,19 @@ const myInstance = new MySecondClass(); | ||
}); | ||
describe('when applied to a class that has multiple instances', function () { | ||
it('should bind the method to each individual instance', function () { | ||
const [ , MySecondClass] = getClasses(); | ||
const myFirstInstance = new MySecondClass(); | ||
const mySecondInstance = new MySecondClass(); | ||
let { testMethodOne } = myFirstInstance; | ||
expect(testMethodOne(myFirstInstance)).to.equal(true); | ||
expect(testMethodOne(mySecondInstance)).to.equal(false); | ||
testMethodOne = mySecondInstance.testMethodOne; | ||
expect(testMethodOne(mySecondInstance)).to.equal(true); | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
23588
323
179