En este capítulo, te explicaré las técnicas de comprobación sucia y de link de datos de accesores y señalaré sus fortalezas y debilidades.
An introduction to data binding
Data binding is a general technique that joins the provider and consumer data sources and synchronizes them.
This is a general definition, describing the common building blocks of data binding techniques.
- A syntax to define the provider and the consumer.
- A syntax for defining what changes should trigger the sync.
- One way to hear about these changes in the provider.
- A synchronization function that runs when these changes occur. From now on I will call this function hhandler () andler ().
The above steps are implemented in different ways using different data binding techniques. The next sections will cover two such techniques, namely error checking and access method. Both have their strengths and weaknesses, which I will discuss briefly after presenting them.
Dirty control
Dirty checking is probably the most popular data binding method. It is simple in concept, and does not require complex language features, making it a good candidate for legacy use.
Syntax
Definir el proveedor y el consumidor no requiere ninguna sintaxis especial, solo objetos simples de Javascript.
const provider = {message: 'Hello World'} const consumer = document.createElement ('p')
Synchronization is generally triggered by property mutations in the provider. The properties to be observed for changes must be explicitly mapped with their handler ().
observe (provider, 'message', message => {consumer.innerHTML = message})
The function I watched() just save the assignment (provider, property) -> handler for later use.
function observe (provider, prop, handler) {provider._handlers [prop] = handler}
With this, we have a syntax to define provider and consumer, and a way to register functions handler () para los cambios de propiedad. La API pública de nuestra biblioteca está lista, ahora viene la implementación interna.
Hearing about the changes
The dirty check is called dirty for a reason. Run periodic checks instead of listening directly to property changes. Let's call this check a digestion cycle from now on. A summary loop iterates through each entry (provider, property) -> handler added to I watched() and check if the property value changed since the last iteration. If it changed, run the function handler (). A simple implementation would be seen below.
function digest () {providers.forEach (digestProvider)} function digestProvider (provider) {for (let prop in provider._handlers) {if (provider._prevValues [prop]! == provider [prop]) {provider._prevValues [prop ] = provider [prop] handler (provider [prop])}}}
The function digest () it must be run from time to time to ensure a synchronized state.
The Accessory Technique.
The access technique is the current trend. It is a little less widely supported as it requires the get / set functionality of ES5, but this elegantly makes up for it.
Syntax
Defining the provider requires special syntax. The flat provider object must be passed to the function observer (), that transforms it into an observable object.
const provider = observer ({greeting: 'Hello', subject: 'World'}) const consumer = document.createElement ('p')
This little inconvenience is more than made up for by the simple mapping syntax handler (). With dirty check, we would have to define each observed property explicitly as shown below.
observer (provider, 'greeting', greeting => {consumer.innerHTML = greeting + '' + provider.subject}) observe (provider, 'subject', subject => {consumer.innerHTML = provider.greeting + '' + subject })
This is detailed and clunky. The access technique can automatically detect the properties of the provider used within the function handler (), which allows us to simplify the previous code.
observer (() => {consumer.innerHTML = provider.greeting + '' + provider.subject})
The implementation of I watched() it is different from dirty checking. Just run the function handler () passed and marks it as currently active while it is running.
let activeHandler function observe (handler) {activeHandler = handler handler () activeHandler = undefined}
Note that we exploit the single threaded nature of JavaScript here by using the single variable activeHandler to keep track of handler () function currently running.
Hearing about the changes
This is where the name of 'accessory technique'. The provider is complemented with getters / setters, they do the heavy lifting in the background. The idea is to intercept the operations of obtaining / adjusting from the provider properties as follows.
- get: if there is an execution activeHandler, save assignment (provider, property) -> activeHandler for later use.
- set: Run all functions handler (), that are assigned with the pair (provide, property).
The access data link technique.
The following code shows a simple implementation of this for a single provider property.
function observableProp (provider, prop) {const value = provider [prop] Object.defineProperty (provider, prop, {get () {if (activeHandler) {provider._handlers [prop] = activeHandler} return value}, set (newValue) {value = newValue const handler = obj._handlers [prop] if (handler) {activeHandler = handler handler () activeHandler = undefined}}})}
The function observable() mentioned in the previous section loops through the provider properties and makes them observable with the above function observableProp ().
[/ php]
function observable (provider) {
for (let prop in provider) {
observableProp (provider, prop)
if (typeof provider [prop] === 'object') {
observable (provider [prop])
}
}
}
[/ php]
This is a very simple implementation, but it is sufficient for a comparison between the two techniques.
Comparison of techniques.
In this section, I will briefly describe the strengths and weaknesses of the dirty verification and access technique.
Syntax
The dirty check does not require a syntax to define the provider and the consumer, but assign the pair (provider, property) handler () it is clumsy and not flexible.
Access technique requires provider to fit role observable(), but automatic assignment handler () makes up for it. For large projects with data binding, it is a must-have feature.
Performance
The dirty check is notorious for its poor performance. You have to verify each entry (provider, property) -> handler possibly multiple times during each summary cycle. Also, you have to grind even when the app is idle as it can't tell when property changes happen.
The access method is faster, but performance could be unnecessarily degraded for large observable objects. Replacing each provider property with accessors is generally overkill. One solution would be to build the tree getter / setter dinámicamente cuando be necesario, en lugar de hacerlo por adelantado en un lote. Alternativamente, una solución más simple es envolver las propiedades innecesarias con una función noObserve (), that tells observable() leave that part untouched. This unfortunately introduces some extra syntax.
Flexibility
Dirty check naturally works with both expando properties (dynamically added) and access properties.
The access technique has a weak point here. Expando properties are not supported because they fall outside the initial get / set tree. This causes problems with arrays, for example, but can be fixed by running manually observableProp () after adding a new property. Function properties are not supported since accessors cannot be enveloped by accessors again. A common solution for this is to use a function computed () instead of a getter. This introduces even more custom syntax.
Time alternatives
The dirty check doesn't give us much latitude here as we have no way of knowing when the actual property changes happen. The functions handler () can only be run asynchronously, running the digest cycle () occasionally.
Triggers / setters added using the accessor technique are fired synchronously, so we have freedom of choice. We can decide to run the file handler () immediately or save it to a batch that runs asynchronously later. The first approach gives us the advantage of predictability, while the second allows performance improvements by eliminating duplicates.