Olá, meu nome é Dmitry Karlovsky e (desde que me lembro) estou lutando com o que me rodeia. Afinal, é tão osso, carvalho, e nunca entende o que quero dele. Mas em algum momento percebi que era o suficiente para agüentar e algo tinha que ser mudado. Portanto, agora não é o ambiente que me dita o que posso e não posso fazer, mas eu dito ao ambiente o que deve ser.
Como você já entendeu, ainda falaremos sobre a inversão de controle por meio do “contexto do ambiente”. Muitas pessoas já estão familiarizadas com essa abordagem das "variáveis de ambiente" - elas são definidas quando o programa é iniciado e geralmente são herdadas por todos os programas que ele inicia. Usaremos esse conceito para organizar nosso código TypeScript.
Então, o que queremos obter:
As funções, quando chamadas, herdam o contexto da função de chamada.
Os objetos herdam o contexto de seu objeto proprietário
Um sistema pode ter muitas opções de contexto ao mesmo tempo
Mudanças em contextos derivados não afetam o original
Mudanças no contexto original são refletidas nos derivados
Os testes podem ser executados em contexto isolado e não isolado
Boilerplate mínimo
Performance máxima
A verificação de tipo de tudo
, - :
namespace $ {
export let $user_name: string = 'Anonymous'
}
- . , :
namespace $ {
export function $log( this: $, ... params: unknown[] ) {
console.log( ... params )
}
}
this
. , :
$log( 123 ) // Error
- . , :
$.$log( 123 ) // OK
, $
- , . :
namespace $ {
export type $ = typeof $
}
this
, . , , :
namespace $ {
export function $hello( this: $ ) {
this.$log( 'Hello ' + this.$user_name )
}
}
. , . , , , :
namespace $ {
export function $ambient(
this: $,
over = {} as Partial< $ >,
): $ {
const context = Object.create( this )
for( const field of Object.getOwnPropertyNames( over ) ) {
const descr = Object.getOwnPropertyDescriptor( over, field )!
Object.defineProperty( context, field, descr )
}
return context
}
}
Object.create
, , . Object.assign
, , , . , :
namespace $.test {
export function $hello_greets_anon_by_default( this: $ ) {
const logs = [] as unknown[]
this.$log = logs.push.bind( logs )
this.$hello()
this.$assert( logs, [ 'Hello Anonymous' ] )
}
}
, - $log
, . , , , , . :
namespace $ {
export function $assert< Value >( a: Value, b: Value ) {
const sa = JSON.stringify( a, null, '\t' )
const sb = JSON.stringify( b, null, '\t' )
if( sa === sb ) return
throw new Error( `Not equal\n${sa}\n${sb}`)
}
}
, $.$test
. , :
namespace $ {
export async function $test_run( this: $ ) {
for( const test of Object.values( this.$test ) ) {
await test.call( this.$isolated() )
}
this.$log( 'All tests passed' )
}
}
, . , , ( , , , , ..). , :
namespace $ {
export function $isolated( this: $ ) {
return this.$ambient({})
}
}
$log
, - . , $isolated
, $log
:
namespace $ {
const base = $isolated
$.$isolated = function( this: $ ) {
return base.call( this ).$ambient({
$log: ()=> {}
})
}
}
, $log
.
, :
namespace $.test {
export function $hello_greets_overrided_name( this: $ ) {
const logs = [] as unknown[]
this.$log = logs.push.bind( logs )
const context = this.$ambient({ $user_name: 'Jin' })
context.$hello()
this.$hello()
this.$assert( logs, [ 'Hello Jin', 'Hello Anonymous' ] )
}
}
. :
namespace $ {
export class $thing {
constructor( private _$: $ ) {}
get $() { return this._$ }
}
}
. , . , . , , , :
namespace $ {
export class $hello_card extends $thing {
get $() {
return super.$.$ambient({
$user_name: super.$.$user_name + '!'
})
}
get user_name() {
return this.$.$user_name
}
set user_name( next: string ) {
this.$.$user_name = next
}
run() {
this.$.$hello()
}
}
}
, , :
namespace $.test {
export function $hello_card_greets_anon_with_suffix( this: $ ) {
const logs = [] as unknown[]
this.$log = logs.push.bind( logs )
const card = new $hello_card( this )
card.run()
this.$assert( logs, [ 'Hello Anonymous!' ] )
}
}
, , . , , . , :
namespace $ {
export class $hello_page extends $thing {
get $() {
return super.$.$ambient({
$user_name: 'Jin'
})
}
@ $mem
get Card() {
return new this.$.$hello_card( this.$ )
}
get user_name() {
return this.Card.user_name
}
set user_name( next: string ) {
this.Card.user_name = next
}
run() {
this.Card.run()
}
}
}
. . $mem
. :
namespace $ {
export function $mem(
host: object,
field: string,
descr: PropertyDescriptor,
) {
const store = new WeakMap< object, any >()
return {
... descr,
get() {
let val = store.get( this )
if( val !== undefined ) return val
val = descr.get!.call( this )
store.set( this, val )
return val
}
}
}
}
WeakMap
, . , , , :
namespace $.test {
export function $hello_page_greets_overrided_name_with_suffix( this: $ ) {
const logs = [] as unknown[]
this.$log = logs.push.bind( logs )
const page = new $hello_page( this )
page.run()
this.$assert( logs, [ 'Hello Jin!' ] )
}
}
, . - . , , .
namespace $ {
export class $app_card extends $.$hello_card {
get $() {
const form = this
return super.$.$ambient({
get $user_name() { return form.user_name },
set $user_name( next: string ) { form.user_name = next }
})
}
get user_name() {
return super.$.$storage_local.getItem( 'user_name' ) ?? super.$.$user_name
}
set user_name( next: string ) {
super.$.$storage_local.setItem( 'user_name', next )
}
}
}
- :
namespace $ {
export const $storage_local: Storage = window.localStorage
}
, , , :
namespace $ {
const base = $isolated
$.$isolated = function( this: $ ) {
const state = new Map< string, string >()
return base.call( this ).$ambient({
$storage_local: {
getItem( key: string ){ return state.get( key ) ?? null },
setItem( key: string, val: string ) { state.set( key, val ) },
removeItem( key: string ) { state.delete( key ) },
key( index: number ) { return [ ... state.keys() ][ index ] ?? null },
get length() { return state.size },
clear() { state.clear() },
}
})
}
}
, , , $hello_card
$app_card
, .
namespace $ {
export class $app extends $thing {
get $() {
return super.$.$ambient({
$hello_card: $app_card,
})
}
@ $mem
get Hello() {
return new this.$.$hello_page( this.$ )
}
get user_name() {
return this.Hello.user_name
}
rename() {
this.Hello.user_name = 'John'
}
}
}
, , , , , , , , :
namespace $.$test {
export function $changable_user_name_in_object_tree( this: $ ) {
const name_old = this.$storage_local.getItem( 'user_name' )
this.$storage_local.removeItem( 'user_name' )
const app1 = new $app( this )
this.$assert( app1.user_name, 'Jin!' )
app1.rename()
this.$assert( app1.user_name, 'John' )
const app2 = new $app( this )
this.$assert( app2.user_name, 'John' )
this.$storage_local.removeItem( 'user_name' )
this.$assert( app2.user_name, 'Jin!' )
if( name_old !== null ) {
this.$storage_local.setItem( 'user_name', name_old )
}
}
}
, , . .
, , :
namespace $ {
$.$test_run()
}
, , , . , $isolated
, - :
namespace $ {
$.$ambient({
$isolated: $.$ambient
}).$test_run()
}
, , , localStorage, $storage_local
.
, , , , .
$mol, . …
c import/export, : Fully Qualified Names vs Imports. , : PascalCase vs camelCase vs kebab case vs snake_case.