├── .npmrc
├── cjs
├── package.json
└── index.js
├── test
├── package.json
├── index.js
└── index.html
├── .gitignore
├── .npmignore
├── .travis.yml
├── min.js
├── rollup
└── babel.config.js
├── esm
└── index.js
├── index.js
├── LICENSE
├── package.json
└── README.md
/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
2 |
--------------------------------------------------------------------------------
/cjs/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/test/package.json:
--------------------------------------------------------------------------------
1 | {"type":"commonjs"}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .nyc_output
3 | .travis.yml
4 | node_modules/
5 | rollup/
6 | test/
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - stable
4 | git:
5 | depth: 1
6 | branches:
7 | only:
8 | - master
9 | - /^greenkeeper/.*$/
10 | after_success:
11 | - "npm run coveralls"
12 |
--------------------------------------------------------------------------------
/min.js:
--------------------------------------------------------------------------------
1 | self.boundOnce=function(n){"use strict";var t=new WeakMap;return function(n,e){var r="function"==typeof e?e:n[e],u=t.get(n)||function(n,e){return t.set(n,e),e}(n,new Map);return u.get(r)||function(n,t,e){return n.set(t,e),e}(u,r,r.bind(n))}}();
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | const bound = require('../cjs');
2 |
3 | const object = {};
4 | const fn = bound(object, method);
5 |
6 | console.assert(fn() === object, 'unexpected return');
7 |
8 | const own = {method};
9 | console.assert(bound(own, 'method') === bound(own, 'method'), 'unexpected duplication');
10 |
11 | function method() {
12 | return this;
13 | }
14 |
--------------------------------------------------------------------------------
/rollup/babel.config.js:
--------------------------------------------------------------------------------
1 | import {nodeResolve} from '@rollup/plugin-node-resolve';
2 | import babel from '@rollup/plugin-babel';
3 |
4 | export default {
5 | input: './esm/index.js',
6 | plugins: [
7 |
8 | nodeResolve(),
9 | babel({
10 | presets: ['@babel/preset-env'],
11 | babelHelpers: 'bundled'
12 | })
13 | ],
14 |
15 | output: {
16 | exports: 'named',
17 | file: './index.js',
18 | format: 'iife',
19 | name: 'boundOnce'
20 | }
21 | };
22 |
--------------------------------------------------------------------------------
/esm/index.js:
--------------------------------------------------------------------------------
1 | const _ = new WeakMap;
2 | const add = (context, methods) => {
3 | _.set(context, methods);
4 | return methods;
5 | };
6 | const set = (methods, method, bound) => {
7 | methods.set(method, bound);
8 | return bound;
9 | };
10 | export default (context, fn) => {
11 | const method = typeof fn === 'function' ? fn : context[fn];
12 | const methods = _.get(context) || add(context, new Map);
13 | return methods.get(method) || set(methods, method, method.bind(context));
14 | };
15 |
--------------------------------------------------------------------------------
/cjs/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 | const _ = new WeakMap;
3 | const add = (context, methods) => {
4 | _.set(context, methods);
5 | return methods;
6 | };
7 | const set = (methods, method, bound) => {
8 | methods.set(method, bound);
9 | return bound;
10 | };
11 | module.exports = (context, fn) => {
12 | const method = typeof fn === 'function' ? fn : context[fn];
13 | const methods = _.get(context) || add(context, new Map);
14 | return methods.get(method) || set(methods, method, method.bind(context));
15 | };
16 |
--------------------------------------------------------------------------------
/test/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | bound-once
7 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | self.boundOnce = (function (exports) {
2 | 'use strict';
3 |
4 | var _ = new WeakMap();
5 |
6 | var add = function add(context, methods) {
7 | _.set(context, methods);
8 |
9 | return methods;
10 | };
11 |
12 | var set = function set(methods, method, bound) {
13 | methods.set(method, bound);
14 | return bound;
15 | };
16 |
17 | var index = (function (context, fn) {
18 | var method = typeof fn === 'function' ? fn : context[fn];
19 | var methods = _.get(context) || add(context, new Map());
20 | return methods.get(method) || set(methods, method, method.bind(context));
21 | });
22 |
23 |
24 |
25 | return index;
26 |
27 | }({}));
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | ISC License
2 |
3 | Copyright (c) 2020, Andrea Giammarchi, @WebReflection
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted, provided that the above
7 | copyright notice and this permission notice appear in all copies.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15 | PERFORMANCE OF THIS SOFTWARE.
16 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "bound-once",
3 | "version": "0.1.3",
4 | "description": "A fast, memory efficient, and tiny solution to an evergreen problem",
5 | "main": "./cjs/index.js",
6 | "scripts": {
7 | "build": "npm run cjs && npm run rollup:babel && npm run fix:default && npm run min && npm run test",
8 | "cjs": "ascjs --no-default esm cjs",
9 | "rollup:babel": "rollup --config rollup/babel.config.js && sed -i.bck 's/^var /self./' index.js && rm -rf index.js.bck",
10 | "min": "terser index.js --comments='/^!/' -c -m -o min.js",
11 | "fix:default": "sed -i 's/exports.default = index;//' index.js && sed -i 's/return exports/return index/' index.js && sed -i 's/({})/({}).default/' min.js",
12 | "coveralls": "nyc report --reporter=text-lcov | coveralls",
13 | "test": "nyc node test/index.js"
14 | },
15 | "keywords": [
16 | "bind",
17 | "bound",
18 | "once"
19 | ],
20 | "author": "Andrea Giammarchi",
21 | "license": "ISC",
22 | "devDependencies": {
23 | "@babel/core": "^7.11.6",
24 | "@babel/preset-env": "^7.11.5",
25 | "@rollup/plugin-babel": "^5.2.1",
26 | "@rollup/plugin-node-resolve": "^9.0.0",
27 | "ascjs": "^4.0.1",
28 | "coveralls": "^3.1.0",
29 | "nyc": "^15.1.0",
30 | "rollup": "^2.27.1",
31 | "rollup-plugin-terser": "^7.0.2",
32 | "terser": "^5.3.1"
33 | },
34 | "module": "./esm/index.js",
35 | "type": "module",
36 | "exports": {
37 | ".": {
38 | "import": "./esm/index.js",
39 | "default": "./cjs/index.js"
40 | },
41 | "./package.json": "./package.json"
42 | },
43 | "unpkg": "min.js",
44 | "repository": {
45 | "type": "git",
46 | "url": "git+https://github.com/WebReflection/bound-once.git"
47 | },
48 | "bugs": {
49 | "url": "https://github.com/WebReflection/bound-once/issues"
50 | },
51 | "homepage": "https://github.com/WebReflection/bound-once#readme"
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bound-once
2 |
3 | [](https://travis-ci.com/WebReflection/bound-once) [](https://coveralls.io/github/WebReflection/bound-once?branch=master)
4 |
5 | **Social Media Photo by [Mattias Olsson](https://unsplash.com/@mattiaswolsson) on [Unsplash](https://unsplash.com/)**
6 |
7 | A fast, memory efficient, and tiny solution to an evergreen problem.
8 |
9 | Alternatively, you can check [bind.for](https://github.com/WebReflection/bind.for#readme) utility, which is definitively faster, but it has not ideal ergonomics.
10 |
11 | ## API
12 |
13 | ```js
14 | const once = bound(
15 | context, // the context to bind
16 | method // a string, resolved as context[method]
17 | // or a function/method/callback to bind
18 | );
19 |
20 | once(); // will have the right context
21 | ```
22 |
23 |
24 | ### Usage Example
25 |
26 | ```js
27 | import bound from 'bound-once';
28 | // const bound = require('bound-once');
29 | //
30 |
31 | class Counter {
32 | constructor() {
33 | this.count = 0;
34 | }
35 | increment() {
36 | this.count++;
37 | console.log(this.count);
38 | }
39 | }
40 |
41 | const counter = new Counter;
42 |
43 | // bound(counter, 'increment') always returns the same bound method
44 | // so it is safe to use it for listeners, as it'll be added only once
45 | document.body.addEventListener('click', bound(counter, 'increment'));
46 | document.body.addEventListener('click', bound(counter, 'increment'));
47 | document.body.addEventListener('click', bound(counter, 'increment'));
48 |
49 | // example by passing a callback instead of a string
50 | const {increment} = Counter.prototype;
51 | bound(counter, 'increment') === bound(counter, increment);
52 | ```
53 |
54 | ### Partial Application
55 |
56 | ```js
57 | class Component extends HTMLElement {
58 | // instead of assigning N methods bound
59 | // just assign once and happily bind it after
60 | #bound = bound.bind(null, this);
61 | connectedCallback() {
62 | this.addEventListener('click', this.#bound('onClick'));
63 | this.addEventListener('change', this.#bound('onChange'));
64 | this.addEventListener('input', this.#bound(this.onInput));
65 | }
66 | disconnectedCallback() {
67 | this.removeEventListener('click', this.#bound('onClick'));
68 | this.removeEventListener('change', this.#bound('onChange'));
69 | this.removeEventListener('input', this.#bound(this.onInput));
70 | }
71 | // any method
72 | onClick() {}
73 | onChange() {}
74 | onInput() {}
75 | }
76 | ```
77 |
78 | This [Custom Element with closed ShadowDOM](https://codepen.io/WebReflection/pen/qBZMRxy?editors=0010) is also another practical application, avoiding constant listeners changes per each render call.
79 |
--------------------------------------------------------------------------------