Edit in JSFiddle

console.clear();

/*
 * Commons
 */
var nivo = {};

/**
 * Retreive a deep node in an object by using the dot-syntax
 * @param  {object} obj     The Object being traversed
 * @param  {string} dotKeys The dotseparated keys
 * @return {mixed}          The value found at the given keys
 */
nivo.objRetreive = function(obj, dotKeys) {
		var keys = dotKeys.split('.');
		keys.forEach(function(key) {
				if (typeof obj[key] === 'undefined') return '';
				obj = obj[key];
		});
		return obj;
};

/**
 * Check whether or not a passed selector matches a given element
 * @param  {domNode} element  The element that will be checked against the selector
 * @param  {string} selector    The selector that needs to be checked
 * @return {Bool}               Indicates whether or not the selector was valid
 */
nivo.elementMatches = function(element, selector) {
		var matches = false;
		if (element instanceof HTMLElement) {
				if (element.matches) {
						matches = element.matches(selector);
				} else if (element.msMatchesSelector) {
						matches = element.msMatchesSelector(selector);
				} else if (element.webkitMatchesSelector) {
						matches = element.webkitMatchesSelector(selector);
				}
		}
		return matches;
};

/**
 * Event Delegation
 * @param  {domNode}   node      The root node that will added an eventlistener on
 * @param  {string}    evtName   The event name we will listen for
 * @param  {string}    selector  The selector for the target element
 * @param  {callable}  callback  The event callback
 * @return {void}
 */
nivo.on = function(node, evtName, selector, callback) {
		var args = [].slice.call(arguments);
		callback = args.length == 3 ? args[2] : callback;
		selector = args.length == 3 ? null : selector;

		node.addEventListener(evtName, function(e) {
				var target = e.target;

				if (selector === null) {
						callback.call(target, e);
						return;
				}

				while (target !== null) {
						if (nivo.elementMatches(target, selector)) {
								callback.call(target, e);
								break;
						}
						target = target.parentNode;
				}
		});
};

/**
 * Core initialization implicitly instantiated once by the Controller
 */
var App = function(){
	nivo.on(document, 'input', '[nv-model]', function() {
		var
			element = this,
			scope = null,
			model = element.getAttribute('nv-model'),
			value = element.value;
		
    // retreive the current scope by traversing up the dom
		while (element !== null && scope === null) {
			if (element.$scope)
				scope = element.$scope;
			else
				element = element.parentNode;
		}
		
    // sometimes a model was not set before
		if(scope[model] !== value){
			scope.set(model, value);
		}
	});
}

/**
 * Controller
 * @param {string} name  The Controllers Name
 * @param {[type]} init  The Controllers Main Function
 */
var Controller = function(name, init) {
	var 
  	element = document.querySelector('[nv-controller="' + name + '"]'),
		$scope =  new Scope();
    
	// init the app the first time
	if(window.nv_app === undefined){
		window.nv_app = new App();
	}
  
	element.$scope = $scope;
	init.call(this, $scope);
	$scope.bindView(element);
}

/**
 * The Scope That will be instantiated by the controller
 * managing the actual modelView binding
 */
var Scope = function(){
	this.htmlNodes = {};
}

/**
 * Set a model to the scope and update the view
 * @param {string} key
 * @param {mixed}  val
 */
Scope.prototype.set = function(key, val) {
	// set the model
	var _this = this;
	val = val !== undefined ? val : this[key]; // since val is optional..
	this[key] = val;
	
  // update the view
	this.htmlNodes[key].forEach(function(element){
		if(element.type === 'textNode'){
			// a textnode can host multiple bindings so we need to replace all of them
			var content = element.node.nv_tmpl;
			[].forEach.call(element.node.nv_bindings, function(binding){
				var value = _this[binding];
				content = content.replace(new RegExp('\{\{\s*'+binding+'\s*\}\}', 'g'), value);
			});
			element.node.nodeValue = content;
		}else{
			if(element.node.value !== val)
			element.node.value = val;
		}
	});
};

/**
 * Add a node found in the view to the model
 * @param {string}  key
 * @param {domNode} node  The node that will be updated in the event of a changing model
 */
Scope.prototype._addNode = function(key, node) {
	if(typeof this.htmlNodes[key] === 'object'){
		this.htmlNodes[key].push(node)
	}else{
		this.htmlNodes[key] = [node];
	}
}

/**
 * Find all textnodes with a valid placeholder in their contents
 * @param  {domNode} el  The element we are looking for the nodes
 * @return {array}       All found textelements
 */
Scope.prototype.textNodes = function(el){
	var
		node,
		textNodes = [],
		walk = document.createTreeWalker(el,NodeFilter.SHOW_TEXT,function(node){
			if (node.nodeValue.match(/\{\{[^\}]+\}\}/)) // we are only interested in nodes containing a model reference
				return NodeFilter.FILTER_ACCEPT
			else
				return NodeFilter.FILTER_SKIP
		},false);

	while(node=walk.nextNode()){
		node.nv_tmpl = node.nodeValue;
		textNodes.push(node);
	}

	return textNodes;
}

/**
 * Called once by the controller after initialization. Find all bindings in the view and assignes accorging models to the scope
 * @param  {domNode} element  The actual view
 * @return void
 */
Scope.prototype.bindView = function(element){
	var _this = this;

	// add all textnodes to the htmlNodesasdf
	[].forEach.call(this.textNodes(element), function(textNode){
		var keys = {};
		[].forEach.call(textNode.nodeValue.match(/\{\{\s*([^\{\s]+)\s*\}\}/g), function(match){
			// match: {{modelA}} lorem {{modelB}}
			var model = match.replace(/[\{\}]/g, '');
			keys[model] = null; // assign only the key to end up with unique keys assigned to this node

			if(typeof textNode.nv_bindings === 'object'){
				textNode.nv_bindings.push(model);
			}else{
				textNode.nv_bindings = [model];
			}
		});

		for(var key in keys){
			_this._addNode(key, {node: textNode, type: 'textNode'})
			_this.set(key, nivo.objRetreive(_this, key));
		}
	});
	// add all inputs to the htmlNodes
	[].forEach.call(element.querySelectorAll('[nv-model]'), function(element) {
		var model = element.getAttribute('nv-model');
		element.value = nivo.objRetreive(_this, model);
		var node = {node: element, type: 'domNode'};
		_this._addNode(model, node);
	});
}

/**
 * Add a bound node to the nodeslist
 * @param {strig} model
 * @param {domNode} node
 */
Scope.prototype._addNode = function(model, node) {
	if(typeof this.htmlNodes[model] === 'object'){
		this.htmlNodes[model].push(node)
	}else{
		this.htmlNodes[model] = [node];
	}
}

/* ------------ */
// EXAMPLE
/* ------------ */

// Create the controller
new Controller('MainCtrl', function($scope) {
	// Assign some defaults to the scope
	$scope.title = "Implementation Of a Model View Binding";
	$scope.contents = {
		copy: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam'
	}

	return;

	// Alter some models to see some changes reflected in the view invoked by the controller
	setTimeout(function(){
		// whenever you want a model to be reflected in the view you need to call the set-method
		$scope.title += ' ...got extended'; // alter the model
		$scope.set('title'); // and trigger the update
	}, 1000	);

	setTimeout(function(){
		$scope.set('title', 'Some totally new title'); // or just pass a new value directly to the model
	}, 2000);
});
<div nv-controller="MainCtrl">
	<h1>{{title}}</h1>

	<p>
		{{contents.copy}}
	</p>

	<div>
		<label>Title</label>
		<input type="text" nv-model="title" />
	</div>

	<div>
		<label>Copy</label>
		<textarea nv-model="contents.copy"></textarea>
	</div>

	<p>
    {{title}} : {{contents.copy}}
  </p>
</div>
body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 15px; }
label { display: block;  margin-bottom: 5px; font-weight: 700; }
textarea,
input[type=text] { display: block; width: 100%; height: 34px; padding: 6px 12px; font-size: 14px; line-height: 1.4; color: #555; background-color: #fff; border: 1px solid #ccc; border-radius: 4px; }
textarea { height: 100px; }
div { margin: 0 0 40px; }
p { font-size: 12px; color: #333; }