Browser pattern and call context in Javascript

Although about the pattern "Observer (Observer, Observer)" has been said enough, including on the Habré, I will briefly repeat it. The essence of the pattern is in monitoring the state of certain subjects of the system and the corresponding reaction of observers to changes in these states. Several observers can follow one subject, moreover, he himself does not know about it (weak binding), but regularly notifies everyone about a state change.

It is convenient to use the Browser on sites and in web applications, so it is logical to implement it using the most popular language for the web environment - Javascript.

Observable = function() {
	this.observers = [];	
}
Observable.prototype.deliver = function(data) {
	for (var i in this.observers) {
		this.observers[i](data);
	}
}
Function.prototype.subscribe = function(observable) {
	observable.observers.push(this);
	return this;
}

Everything is simple. The Observable class is the subject. Array of observers - subscribers, observers. The deliver method notifies observers of a state change. Subscribe signs the observer. In practice, it looks like this:

myClass = function() {
	this.value = 0; //некое значение
	this.onChange = new Observable(); //наблюдаемое состояние
}
myClass.prototype.change = function(new_value) {
	this.value = new_value;
	this.onChange.deliver(this.value); //изменилось значение — сообщили наблюдателям
}
var c = new myClass(); 
var write_log = function(value) { 
	console.log(value);
}
write_log.subscribe(c.onChange);

A wonderful working example: with every c.value change, the onChange observable will notify all observers of the change and inform them of a new state, which the observers will manage in their own way. So, for example, write_log () will print the new state in the console. But this example is remarkable only until it becomes necessary to operate this in the function executed after mailing.

So, for example, the following construction will not work as it should:

myClass = function() {
	this.value = 0;
	this.onChange = new Observable();
}
myClass.prototype.change = function(new_value) {
	this.value = new_value;
	this.onChange.deliver(this.value);
}
Logger = function(logtype) {
	this.type = (!!logtype) ? logtype : "alert";
}
Logger.prototype.write = function(value) {
	if (this.type == "console") { console.log(value); return; }
	alert(value);
}
var c = new myClass();
var logger = new Logger("console");
logger.write.subscribe(c.onChange);

The problem arises when accessing this while executing logger.write. Let me remind you that this is a context, and in this case, the context is not an instance of the Logger class, but an anonymous function () function, called when deliver is executed.

The wonderful call function helps to solve the problem, which not only executes a certain method, but also allows you to specify the execution context. Therefore, I rewrote the observable method - deliver a little and, accordingly, changed the subscription mechanism.

Observable = function() { //без изменений
	this.observers = [];	
}
Observable.prototype.deliver =function(data) {
	for (var i in this.observers) {
		this.observers[i].func.call(this.observers[i].context, data); //функция теперь вызывается в нужном контексте
	}
}
Function.prototype.subscribe = function(observable, context) {
	var ctx = context || this; //если контекст вызова не задан, то контекстом считается this «по-умолчанию», то есть текущая функция
	var observer = { //теперь наблюдатель будет сообщать, в каком контексте нужно вызвать функцию
		context: ctx,
		func: this
	}
	observable.observers.push(observer);
	return this;
}

Thus, in order for the extreme example to work, you just need to specify the call context in the subscription - an instance of Logger:

var logger = new Logger("console");
logger.write.subscribe(c.onChange, logger);

It works! And I hope this little trick is useful to someone.

Also popular now: