Home > Article > Web Front-end > Only 30 lines of code to implement MVC_javascript techniques in Javascript
Since around 2009, MVC has gradually shined in the front-end field, and finally ushered in a big explosion with the launch of React Native in 2015: AngularJS, EmberJS, Backbone, ReactJS, RiotJS, VueJS... ... A series of names have appeared and changed in a flashy way. Some of them have gradually faded out of everyone's sight, some are still growing rapidly, and some have already taken on their own role in a specific ecological environment. But no matter what, MVC has and will continue to profoundly affect the way of thinking and working methods of front-end engineers.
Many examples of explaining MVC start from a certain concept of a specific framework, such as Backbone's collection or the model in AngularJS. This is certainly a good approach. But the reason why a framework is a framework, not a class library (jQuery) or a tool set (Underscore), is because there are many excellent design concepts and best practices behind them. These design essences complement each other, are interlocked, and are indispensable. , it is not easy to see the essence of a certain design pattern through a complex framework in a short period of time.
This is the origin of this essay - the prototype code created to help everyone understand the concept should be as simple as possible, just simple enough for everyone to understand the concept.
1. The basis of MVC is the observer pattern, which is the key to achieving synchronization between model and view
For simplicity, each model instance contains only one primitive value.
function Model(value) { this._value = typeof value === 'undefined' ? '' : value; this._listeners = []; } Model.prototype.set = function (value) { var self = this; self._value = value; // model中的值改变时,应通知注册过的回调函数 // 按照Javascript事件处理的一般机制,我们异步地调用回调函数 // 如果觉得setTimeout影响性能,也可以采用requestAnimationFrame setTimeout(function () { self._listeners.forEach(function (listener) { listener.call(self, value); }); }); }; Model.prototype.watch = function (listener) { // 注册监听的回调函数 this._listeners.push(listener); };
// html代码: <div id="div1"></div> // 逻辑代码: (function () { var model = new Model(); var div1 = document.getElementById('div1'); model.watch(function (value) { div1.innerHTML = value; }); model.set('hello, this is a div'); })();
With the help of the observer pattern, we have realized that when the set method of the model is called to change its value, the template is also updated synchronously, but this implementation is very awkward because we need to manually monitor the change of the model value (through the watch method ) and pass in a callback function. Is there a way to make it easier to bind the view (one or more dom nodes) to the model?
2. Implement the bind method and bind the model and view
Model.prototype.bind = function (node) { // 将watch的逻辑和通用的回调函数放到这里 this.watch(function (value) { node.innerHTML = value; }); };
// html代码: <div id="div1"></div> <div id="div2"></div> // 逻辑代码: (function () { var model = new Model(); model.bind(document.getElementById('div1')); model.bind(document.getElementById('div2')); model.set('this is a div'); })();
Through a simple encapsulation, the binding between view and model has taken shape. Even if multiple views need to be bound, it is easy to implement. Note that bind is a native method on the Function class prototype, but it is not closely related to MVC. The author really likes the word bind. It is to the point and concise, so I simply cover the native method here. You can neglect. Closer to home, although the complexity of binding has been reduced, this step still requires us to complete it manually. Is it possible to completely decouple the binding logic from the business code?
3. Implement controller to decouple binding from logic code
Careful friends may have noticed that although we are talking about MVC, only the Model class appears in the above article. It is understandable that the View class does not appear. After all, HTML is a ready-made View (in fact, this article also mentions it from beginning to end. Just using HTML as View, the View class does not appear in the javascript code), then why is the Controller class invisible? Don't worry, in fact, the so-called "logic code" is a code segment with a high degree of coupling between framework logic (let's call this article's prototype toy a framework) and business logic. Let's break it down now.
If you want to leave the binding logic to the framework, you need to tell the framework how to complete the binding. Since it is difficult to complete annotation in JS, we can do this layer of markup in the view - using the tag attribute of html is a simple and effective way.
function Controller(callback) { var models = {}; // 找到所有有bind属性的元素 var views = document.querySelectorAll('[bind]'); // 将views处理为普通数组 views = Array.prototype.slice.call(views, 0); views.forEach(function (view) { var modelName = view.getAttribute('bind'); // 取出或新建该元素所绑定的model models[modelName] = models[modelName] || new Model(); // 完成该元素和指定model的绑定 models[modelName].bind(view); }); // 调用controller的具体逻辑,将models传入,方便业务处理 callback.call(this, models); }
// html: <div id="div1" bind="model1"></div> <div id="div2" bind="model1"></div> // 逻辑代码: new Controller(function (models) { var model1 = models.model1; model1.set('this is a div'); });
Is it that simple? It's that simple. The essence of MVC is to complete business logic in the controller and modify the model. At the same time, changes in the model cause automatic updates of the view. These logics are reflected in the above code and support multiple views and multiple models. Although it is not enough for production projects, I hope it will be somewhat helpful to everyone's MVC learning.
The organized "framework" code with comments removed:
function Model(value) { this._value = typeof value === 'undefined' ? '' : value; this._listeners = []; } Model.prototype.set = function (value) { var self = this; self._value = value; setTimeout(function () { self._listeners.forEach(function (listener) { listener.call(self, value); }); }); }; Model.prototype.watch = function (listener) { this._listeners.push(listener); }; Model.prototype.bind = function (node) { this.watch(function (value) { node.innerHTML = value; }); }; function Controller(callback) { var models = {}; var views = Array.prototype.slice.call(document.querySelectorAll('[bind]'), 0); views.forEach(function (view) { var modelName = view.getAttribute('bind'); models[modelName] = models[modelName] || new Model(); models[modelName].bind(view); }); callback.call(this, models); }
Postscript:
In the process of learning flux and redux, although the author has mastered how to use the tools, I only know it but don’t know why. I have always emphasized "Flux eschews MVC in favor of a unidirectional data flow" in the official ReactJS documentation. I don’t quite understand. I always feel that one-way data flow and MVC do not conflict. I don’t understand why the two are opposed in the ReactJS document. There is one without me, there is one without him (eschew, avoid). Finally, I made up my mind to go back to the definition of MVC and study it again. Although I copy and paste carelessly in my daily work, we still have to be willful and chew on the words occasionally, right? This method really helped me understand this sentence. Here I can share my thoughts with you: The reason why I feel that the one-way data flow in MVC and flux is similar may be because there is no clear distinction between MVC and the observer pattern. Caused by the relationship - MVC is based on the observer pattern, and so is flux, so the source of this similarity is the observer pattern, not MVC and flux themselves. This understanding is also confirmed in the original design pattern book of the foursome: "The first and perhaps best-known example of the Observer pattern appears in Smalltalk Model/View/Controller (MVC), the user interface framework in the Smalltalk environment [KP88]. MVC's Model class plays the role of Subject, while View is the base class for observers. ".
If readers are interested in continuing to expand on such a prototype toy, you can refer to the following directions:
A complete framework needs to go through countless refinements and modifications. This is just the first step. The road is still long. I hope everyone will continue to work hard.