Skip to content

Instantly share code, notes, and snippets.

@ackvf
Last active November 16, 2023 17:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ackvf/68f992660a5eda645c4671d3599b2acf to your computer and use it in GitHub Desktop.
Save ackvf/68f992660a5eda645c4671d3599b2acf to your computer and use it in GitHub Desktop.
TS/JS utility functions

TS/JS utility functions

other gists
🔗 TypeScript type toolbelt
🔗 React utils


  • accessTrap - Object and Array Proxy to count number of property accesses to map used/unused properties. It maintains referential stability using a caching mechanism to not disrupt React.js render flow.

    image

  • colorizer - Primitive console coloring library.

    const dyeRed = str =>colors.fg.black +  colors.bg.red + str + colors.reset
    console.log(colors.fg.black + colors.bg.red + 'Is this thing on?' + colors.reset)
    console.log(dyeRed(error))
    console.log(red`nope`)
  • proxy colorizer - Advanced console coloring library using proxy with cleaner syntax.

    const dyeRed = colorProxy.bg.red.fg.black 
    console.log(colorProxy.fg.black.bg.red + 'Is this thing on?' + colors.reset + ' ...')
    console.log(colorProxy.fg.black.bg.red('Is this thing on?') + ' ...')
    console.log(colorProxy.fg.black.bg.red`Is this thing on?` + ' ...')
  • sleep / wait - Promises to create a delay.

  • debounce

  • poll

  • slugify / getNodeText (React) - Create URL#anchor-friendly text from a JSX component or any text.

 

 

/**
* @author Qwerty <qwerty@qwerty.xyz>
*
* @description
* This helper wraps an object or an array with a Proxy allowing to reveal and count the number of accesses to each property.
*
* @example
* const counter = {}
* const x = accessTrap({address: { city: "" }}, counter, { verbose: true })
* x.address === x.address // true
*
* @example
* // • Restart counter for each render
* const DropMintWrapper: React.FC<DropMintWrapperProps> = ({ drop }) => {
* const c = window.d.c1 = {}
* drop = accessTrap(drop, c, { verbose: true })
* return <DropMintTemplate drop={drop} />
* }
*
* @example
* // • Global counter for all renders
* // • Map accessTrap in an array for cumulative counting (optional) or apply it to an array
* const c = window.d.c2 = {} // puts the collector object in the window
* const HomePage: NextPage<HomePageProps> = ({ drops }) => {
* drops = drops!.map(drop => accessTrap(drop, c))
* return <DiscoverPageGridFeatured drops={drops} />
* }
*/
export function accessTrap<T extends AnyObject>(target: T, collector: AnyObject): T
export function accessTrap<T extends AnyObject>(target: T, collector: AnyObject, settings?: Settings): T
export function accessTrap<T extends AnyObject>(this: Trap, target: T, __collector: AnyObject, { verbose = false }: Settings = {}): T {
const trap: Trap = {
get,
__collector,
__cache: {},
__previous: this?.__previous ?? '',
__settings: {
verbose,
},
}
if (typeof __collector !== 'object' || __collector === null) throw new Error('Collector must be an object.')
return new Proxy(target, trap) as T
}
function get(this: Trap, target: AnyObject, prop: string): any {
const __previous = this.__previous + (this.__previous && '.')
if (this.__settings.verbose) console.log(`👮‍♂️ ${Y}Accessing property ${this.__previous ? `${B}${__previous}` : ''}${C}${prop}${!isNaN(this.__collector[prop]) ? ` ${B}(${this.__collector[prop] + 1}x)` : ''}${this.__cache[prop] ? ` ${M}(cached)` : ''} %O🔍`, target)
const value = target[prop]
if (typeof value === 'object' && value !== null) {
this.__collector[prop] ??= {}
return this.__cache[prop] ??= accessTrap.bind({ __previous: __previous + prop })(value, this.__collector[prop], this.__settings)
}
this.__collector[prop] ??= 0
this.__collector[prop]++
return value
}
interface Trap {
/* list of available traps https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy/Proxy#handler_functions */
get(target: AnyObject, prop: string): any
/** Object that stores the number of accesses. */
__collector: AnyObject
/** Caches proxyfied props to avoid reference change. */
__cache: AnyObject
/** The previous access path. */
__previous: string
__settings: Settings
}
interface Settings {
/** Log each access to console. */
verbose?: boolean
}
const B = '\x1b[34m'
const C = '\x1b[36m'
const M = '\x1b[35m'
const Y = '\x1b[33m'
export const colorCodes = {
uncolorize: (str: string) => str.replace(/(\x1b|)\[[\d;]*m/gi, ''),
reset: '0',
bright: '1',
dim: '2',
underscore: '4',
blink: '5',
reverse: '7',
hidden: '8',
fg: {
black: '30',
red: '31',
green: '32',
yellow: '33',
blue: '34',
magenta: '35',
cyan: '36',
white: '37',
crimson: '38',
},
bg: {
black: '40',
red: '41',
green: '42',
yellow: '43',
blue: '44',
magenta: '45',
cyan: '46',
white: '47',
crimson: '48',
},
/** e.g. `wrap('0;30;41')` */
wrap(...codeString: Args) { return `\x1b[${_(codeString)}m` },
} as const
/* Proxy implementation */
const trap = {
get(target: ColorProxy, prop: keyof typeof colorCodes, receiver: typeof target) {
if (target.hasOwnProperty(prop)) {
let _code = target._code ?? '' as ColorProxy['_code']
let nextTarget: any = colorCodes[prop]
let callable = {}
if (!['bg', 'fg'].includes(prop)) {
nextTarget = colorCodes
_code += (_code ? ';' : '') + target[prop]
callable = function callableTemplateLiteralString(...message: Args): string {
return colorCodes.wrap(_code!) + _(message) + (message.length ? colorCodes.wrap(colorCodes.reset) : '')
}
}
const newTarget = Object.assign(callable, nextTarget, { _code })
return new Proxy(newTarget, trap)
}
if (['toString', 'valueOf', Symbol.toPrimitive].includes(prop))
return () => colorCodes.wrap(target._code!)
if (String.prototype[prop] !== undefined)
return String.prototype[prop]
// loop
return receiver
},
}
/**
* @author Qwerty <qwerty@qwerty.xyz>
*
* @description Advanced colorizing library with proxy, tagged template literal support & customizations.
*
* note:
* - `\x1b` = ``
* - joining supported: `\x1b[0;30;41m`
*
* @example
*
* const red = colorProxy.fg.red
* const dyeRed = colorProxy.fg.black.bg.red
*
* console.log(red`Hallo?`)
* console.log(dyeRed('ERROR:'))
*
* logger.log(`${colors.fg.yellow}API server is listening on http://localhost:${red(port)+colors.fg.yellow}/api/v1/...${colors.reset}`)
*
* @example // All these are equivalent.
*
* console.log(colorProxy.fg.black.bg.red + 'Is this thing on?' + colors.reset + ' ...')
* console.log(colorProxy.fg.black.bg.red('Is this thing on?') + ' ...')
* console.log(colorProxy.fg.black.bg.red`Is this thing on?` + ' ...')
*
* @example
* console.log('\x1b[32;41m green-red')
* console.log(' green-red')
* console.log(c.uncolorize(' default'))
*/
export const colorProxy = new Proxy<ColorProxy>(colorCodes as ColorProxy, trap)
export default colorProxy
/* Support for template literals */
export function interlace(strs: TemplateStringsArray | TemplateStringsArray['raw'], ...args: any[]): string {
return strs.reduce((prev, current, ix) => prev + (current ?? '') + (args[ix] ?? ''), '')
}
export function interlaceRaw(strs: TemplateStringsArray, ...args: any[]): string {
return interlace(strs.raw, ...args)
}
export const _ = extractMessage
export function extractMessage(args: Args): string {
if (!['string', 'number', 'boolean'].includes(typeof args[0]) && !Array.isArray(args[0])) return ''
if (isTemplate(args)) return interlace(...args)
return `${args.join(' ') ?? ''}`
}
function isTemplate(args: Args): args is TemplateArgs { return !!(args as TemplateArgs)?.[0]?.raw }
/** Called as tagged template: `` red`no` `` */
export type TemplateArgs = [template: TemplateStringsArray, ...values: any[]]
/** Called as function: `` green('yes') `` */
export type MsgArgs = [message: string | number | boolean]
export type Args =
| /** e.g. green('yes') */ MsgArgs
| /** e.g. red`no` */ TemplateArgs
/* Support for Proxy */
export type ColorProxy =
& typeof colorCodes
& Controls
& {
fg: { [key in keyof typeof colorCodes.fg]: CallableTaggedTemplateProxy }
bg: { [key in keyof typeof colorCodes.bg]: CallableTaggedTemplateProxy }
}
& { _code?: `${number}${`;${number}${`;${number}${string}` | ''}` | ''}` | '' }
type CallableTaggedTemplateProxy = string & ColorProxy & ((...args: Args) => string)
type ValueOf<T> = T[keyof T]
type ValidCodes = Extract<ValueOf<typeof colorCodes & (typeof colorCodes.bg | typeof colorCodes.fg)>, string>
type Controls = {
[key in Exclude<keyof typeof colorCodes, 'uncolorize' | 'fg' | 'bg' | 'wrap'>]: CallableTaggedTemplateProxy
}
// ---------------------------------------------------------------------------------------------------------------------
export const c = colorProxy
export const dyeRed = c.fg.black.bg.red
export const dyeGreen = c.fg.black.bg.green
export const dyeBlue = c.fg.black.bg.blue
export const dyeYellow = c.fg.black.bg.yellow
export const red = c.fg.red
export const green = c.fg.green
export const blue = c.fg.blue
export const yellow = c.fg.yellow
/**
* @author Qwerty <qwerty@qwerty.xyz>
*
* @description Primitive colorizing library with template literal support & customizations.
* `\x1b` = ``
*
* @example
* console.log(colors.bg.red + colors.fg.black + 'Is this thing on?' + colors.reset)
* console.log(dyeRed(error))
* console.log(red`nope`) // tagged template
*
* logger.log(`${colors.fg.yellow}API server is listening on http://localhost:${red(port)+colors.fg.yellow}/api/v1/...${colors.reset}`)
*
* @example
* console.log('\x1b[32;41m green-red')
* console.log(' green-red')
* console.log(c.uncolorize(' default'))
*/
export const colors = {
uncolorize: (str: string) => str.replace(/(\x1b|)\[[\d;]*m/gi, ''),
reset: '\x1b[0m',
bright: '\x1b[1m',
dim: '\x1b[2m',
underscore: '\x1b[4m',
blink: '\x1b[5m',
reverse: '\x1b[7m',
hidden: '\x1b[8m',
fg: {
black: '\x1b[30m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
magenta: '\x1b[35m',
cyan: '\x1b[36m',
white: '\x1b[37m',
crimson: '\x1b[38m',
},
bg: {
black: '\x1b[40m',
red: '\x1b[41m',
green: '\x1b[42m',
yellow: '\x1b[43m',
blue: '\x1b[44m',
magenta: '\x1b[45m',
cyan: '\x1b[46m',
white: '\x1b[47m',
crimson: '\x1b[48m',
},
}
/* Support for tagged templates */
function interlace(strs: TemplateStringsArray, ...args: any[]): string {
return strs.reduce((prev, current = '', ix) => prev + current + (args[ix] ?? ''), '')
}
const _ = extractMessage
function extractMessage(args: Args): string {
if (isTemplate(args)) return interlace(...args)
return `${args[0]}`
}
function isTemplate(args: Args): args is TemplateArgs { return !!args[0].raw }
type TemplateArgs = [template: TemplateStringsArray, ...values: any[]]
type Args =
| /* as function: red('hello') */ [message: any]
| /* as tagged template: r`hi` */ TemplateArgs
// --------------------------------------------------------------------------------------------------------------------
export const red = (...message: Args): string => colors.fg.red + m(message) + colors.reset
export const green = (...message: Args): string => colors.fg.green + m(message) + colors.reset
export const dyeRed = (...message: Args): string => colors.fg.black + colors.bg.red + m(message) + colors.reset
/* text color */
export const r = (...message: Args): string => colors.fg.red + _(message) + colors.reset
export const g = (...message: Args): string => colors.fg.green + _(message) + colors.reset
export const b = (...message: Args): string => colors.fg.blue + _(message) + colors.reset
export const c = (...message: Args): string => colors.fg.cyan + _(message) + colors.reset
export const m = (...message: Args): string => colors.fg.magenta + _(message) + colors.reset
export const y = (...message: Args): string => colors.fg.yellow + _(message) + colors.reset
/* background color */
export const R = (...message: Args): string => colors.fg.black + colors.bg.red + _(message) + colors.reset
export const G = (...message: Args): string => colors.fg.black + colors.bg.green + _(message) + colors.reset
export const B = (...message: Args): string => colors.fg.black + colors.bg.blue + _(message) + colors.reset
/**
* @author Qwerty <qwerty@qwerty.xyz>
*
* @description Returns a function, that, as long as it continues to be invoked, will not
* trigger the callback. The callback will be called after it stops being called for
* N milliseconds. If `immediate` is passed, trigger the callback on the
* leading edge, instead of the trailing.
*
* `.cancel()` can be called manually to cancel the scheduled trailing invocation.
*
* @param wait milliseconds
*/
export default function debounce<Callback extends AnyFunction>(cb: Callback, wait: number, immediate?: boolean) {
let timeout: NodeJS.Timeout | null
function debounced(this: any, ...args: Parameters<Callback>): void {
const context = this
const later = () => {
timeout = null
if (!immediate) cb.apply(context, args)
}
const callNow = immediate && !timeout
timeout && clearTimeout(timeout)
timeout = setTimeout(later, wait)
if (callNow) cb.apply(context, args)
}
debounced.cancel = () => {
timeout && clearTimeout(timeout)
}
return debounced
}
type RecursivelyReplaceNullWithUndefined<T> = T extends null
? undefined // Note: Add interfaces here of all GraphQL scalars that will be transformed into an object
: T extends Date
? T
: {
[K in keyof T]: T[K] extends (infer U)[]
? RecursivelyReplaceNullWithUndefined<U>[]
: RecursivelyReplaceNullWithUndefined<T[K]>;
};
/**
* Recursively replaces all nulls with undefineds.
* Skips object classes (that have a `.__proto__.constructor`).
*
* Unfortunately, until // https://github.com/apollographql/apollo-client/issues/2412#issuecomment-755449680
* gets solved at some point,
* this is the only workaround to prevent `null`s going into the codebase,
* if it's connected to a Apollo server/client.
*/
export function replaceNullsWithUndefineds<T>(
obj: T
): RecursivelyReplaceNullWithUndefined<T> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const newObj: any = {};
Object.keys(obj).forEach((k) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const v: any = (obj as any)[k];
newObj[k as keyof T] =
v === null
? undefined
: // eslint-disable-next-line no-proto
v && typeof v === "object" && v.__proto__.constructor === Object
? replaceNullsWithUndefineds(v)
: v;
});
return newObj;
}
import { sleepResolve } from './sleep'
/**
* @author Qwerty <qwerty@qwerty.xyz>
*
* @description Returns a promise that keeps calling a given function in set intervals
* until it returns `true` for the first time. If `timeout` is provided
* and the promise has not resolved yet, it will get rejected instead, when the time runs out.
*
* `.cancel()` can be called manually to reject the promise.
*
* @param interval milliseconds
* @param timeout milliseconds
*/
export default function poll(condition: () => boolean, interval: number, timeout?: number) {
interface Cancellable { cancel(): void }
let promise = new Promise(async (resolve, reject) => {
let cancelled = false
setTimeout(() =>
promise.cancel = () => {
ref && clearTimeout(ref)
cancelled = true
reject('Poll: Cancelled by the user.')
}
)
// We use setTimeout for the rejection, because simply counting down `timeLeft` could be not granular enough.
const ref = timeout && setTimeout(reject, timeout, 'Poll: Condition was not met in time.')
let timeLeft = timeout || Infinity
while (!condition()) {
if (timeLeft <= 0 || cancelled) return // Time is out, promise should reject by now. Let's bail out.
timeLeft -= interval
await sleepResolve(interval)
}
// Condition was met, clean up and resolve
ref && clearTimeout(ref)
return resolve()
}) as Promise<void> & Cancellable
return promise
}
// TS
export const sleepResolve = <T = void>(msec: number, retVal?: T) => new Promise<T>((resolve) => setTimeout(resolve, msec, retVal))
export const sleepReject = <T = void>(msec: number, retVal?: T) => new Promise<T>((_, reject) => setTimeout(reject, msec, retVal))
// JS
const sleepResolve = (msec, retVal) => new Promise((resolve) => setTimeout(resolve, msec, retVal))
const sleepReject = (msec, retVal) => new Promise((_, reject) => setTimeout(reject, msec, retVal))
/**
* @author Qwerty <qwerty@qwerty.xyz>
*
* @description Extracts text content from any React node and returns an URL-friendly representation of any text. Useful for page anchors.
*
* example https://codesandbox.io/s/react-getnodetext-h21ss?file=/src/App.js
*/
const slugifyNode = (node: JSX.Element) => slugify(getNodeText(node))
const slugify = (label: string = '') => label.toLowerCase().replace(/[’']/g, '').replace(/[^a-z0-9_]+/g, '-').replace(/^-+|-+$/g, '')
const getNodeText = (node: JSX.Element): string => {
if (['string', 'number'].includes(typeof node)) return node
if (node instanceof Array) return node.map(getNodeText).join('')
if (typeof node === 'object' && node) return getNodeText(node.props.children)
}
/* TypsScript Playground gives some useful error hints for this code
https://www.typescriptlang.org/play?#code/EQVwzgpgBGAuBOBLAxrYUD0GqF4NwEjtQICeUyA9gLYUQB2siNA5oQBaJgBQAbgIbyk8ANoIjQAvFADkfeAEkaAMRqTM2fKRY8m0WGzBQtAEyhkARpHhdoAWlbQAZiBqpEZGlBo9qhMlFPRkIRFDAC4oR3cAHwN4fmj7GgBGKHiaACYU8JoAZkyZeSUOKCgOcho4E1MAKygJAG8ijy8IMMkzKskAGkasKABRHmQWcKcXNyhECgAHQRREWEESQwh7Bgh9ef0AA112LagAd3nhniheQRBoZanaQwZmcZYyA4nYCf0AJScAOkaIgAoAJRQOqkNxgMgib6CMiMf6SCJdVjsYEAX26xV6AEFYs8Rs56OCoAARADyADlJAAVKDLVY0HQsCCIfjPdw7PRbToTcqwCA8YzAXQQEiBKawEDwRnQDl7cLwSh2FkklYMe4wcg3YC-YoyMJA2oAPjB5UhEGhsPhMiRuzAgIxWUSYQiY3cCQNoLKEKhMLhCKSNr0aIdCTSztGhLdNA9Ju95t98NDgZRUFR31MDEM-ycdLWhntfxy4YJrijMa9ZotfoS2WTdtT6cz-1t9tUhyZUdy7AMYDAiEYDOMhkl6q27X2yClPEj3NlYH2UvsEHg+lgvk2JgO7jAmugRg8ZDeY+qWx1bYSABZi66suXwZWE-6L3W0Y2aFn2q3eh8VsvaMgID6XF4DCdp3gPN5c0HRp8kUaNgQaYpMWwXh+FtNt+SGfEb3+GhDxiBUXhdSN6yg9YoDnfYeA2exXkOaiDDOPhEB4UwoUaZCkIkMCkN6WRaPQ6hdDIYxu3gJx5UVMDjyqb5YKUIEuQo20tgAAu7ai+wHCBjGFJiLmgMhaJkrkOLbYoJAAdUzPFekQWiGDgflRLeMSJKENwZXk+ClMoqBF2XVdfC2az32efZ-jIfgthzVVB32BgYAQFA3goEToH+UBICSpBUGAQFATPYozIrH1LWkWICmUbkWzbPyApXHw7EqKoIFQVhp38pxVyZbCZwmWjAmEHSexauTKrgg0-IkEyipKJDY1cmgGXgHF+AkA0xGNUr43K7yoGNBgVrWl86pU-zf0atdmuQGFIAqHcyBuDrILi8jKLmsziNLHkVoUJwPTMzjUORfQrJsl4oqgWL6RGuzplmZB5mUzkgdjB89ompQDt+5d-uq0HgV6PTbrIe6jxU9T9HsBUKAoo68acLY0a9V6ICmNbaigTbtvvMq-X2w7lsZ9xjWWdmTpqoM2xJu71gpzkqckumtgZ+B8eZhbinFjnYiBMzUTMsy1bW-WFrV-GzdTbpDdKcE3naDJ6kaTxqFaR3JA4W27dNfn-i2AAdAAPRJTAAbWyAA2CgxFjuOBGG4xxjA2xg9DiO0gAbgvAAGNLqm+AASOohpECBUSBNPw7zrZAQ4dow9L0QAF19Z9uMq39quI+juP46b4wPIZKBU5D8Psiz3OKGLpvy8rsew5ruuQYSLmG9n5uOHdOv24xuFA4XqOY77iQB78MgcxHqBu5z7O8-+buJ-z2SZ6CUQ0wzd9IuqQEb7vihf6HzSBQee6cl7cD4OfS+3FqiNzfhAZub4Pw-w4L0EGpgL7vi5gkJB38qhE2wOhbsekwDNA4BgnMbcdqdwPunI+J9T5vyTu4LQh4mSsmqG1N4o86GTzzo7IudR-SSAroAsBFBa712qGkb4q8YFVDgcNBBqDsACLkVkGWehwIkLIWo+CHBd5+1oePXuJ8E7BCgVg66rDhQcNau1Hh1d-4PyAc-QRs8P5NkdmIpxU8fEZxAf48B6DMGGCdi1RRZdEGf2QVUNIdc0GQIoe+cJOCYl4PiZo7sxDeqkOoOQ0JaQgRAA
*/
"use strict" // 👈 try commenting this
var callee = 'arrInFn' // 👈 change this and observe - the function name to be called: fun | arr | fn1 | fn2 | fn3 | arrInFn
const obj = {
name: 'obj',
// Each function implicitly defines its `this` with a value depending on how it is Run.
fun() { console.log('fun', this) },
// Arrow functions DON'T define their own `this`, instead "they capture the `this` from their Defining scope".
arr: () => console.log('arr', this),
fn1: function fn() { console.log('fn1', this) },
fn2: function fn() { console.log('fn2', this) }.bind(undefined),
fn3: function fn() { console.log('fn3', this) }.bind(this), // when fn3 is assigned during `obj` creation, `this` refers to its own scope and not `obj`.
// fn4: function fn() { console.log('fn4', this) }.bind(obj), // ReferenceError: obj is not defined
arrInFn() {
// var this // each function (not arrow functions) defines `this` as if it was a variable.
// = obj // If this method is run from obj `obj.arrInFn()`, `this` is assigned the value of `obj`,
// = Window // if instead it is run alone `arrInFn()`, `this` refers to `Window` (or `undefined` in strict mode ("use strict")).
console.log('arrInFn', this) // `this` refers to the object that runs the function, if called as obj.arrInFn() `this` = `obj`.
const innerArr = () => console.log('arrInFn > innerArr', this) // `this` refers to the closest scope that defines `this`.
function innerFun() {
// var this = Window or undefined // implicit `this`
console.log('arrInFn > innerFun', this) // the closest `this` is from `innerFun`
const deepArr = () => console.log('arrInFn > innerFun > deepArr', this) // the closest `this` is from `innerFun`
deepArr()
}
innerArr()
innerFun()
},
}
const obj2 = {
name: 'obj2'
}
console.log(`\x1b[36m====== called on obj - \x1b[32;40mobj.${callee}()\x1b[0m`)
obj[callee]()
console.log(`\x1b[36m====== called alone - \x1b[32;40m${callee}()\x1b[0m`)
var fn = obj[callee]
fn()
console.log(`\x1b[36m====== called bound - \x1b[0;40m(\x1b[32mobj.${callee}.bind(obj)\x1b[0;40m)\x1b[32m()\x1b[0m`)
var bound = obj[callee].bind(obj)
// var bound = fn.bind(obj) // this is the same
bound()
console.log(`\x1b[36m====== called on another object - \x1b[32;40mobj2.${'fn'}()\x1b[0m`)
obj2.fn = obj[callee]
// obj2.fn = fn // this is the same
obj2.fn()
console.log(`\x1b[36m====== called bound to another object - \x1b[0;40m(\x1b[32mobj.${callee}.bind(obj2)\x1b[0;40m)\x1b[32m()\x1b[0m`)
var bound2 = obj[callee].bind(obj2)
// var bound2 = fn.bind(obj2) // this is the same
bound2()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment