30行代码实现Javascript中的MVC

本文介绍了如何在JavaScript中利用观察者模式实现Model与View的简单绑定,随后引入Controller,将绑定逻辑从业务代码中解耦,通过HTML标签属性标记视图与模型的关联。作者探讨了MVC模式在实际应用中的简化实现,以及如何扩展到更复杂的场景,如双向绑定和虚拟DOM等。
摘要由CSDN通过智能技术生成

});

});

};

Model.prototype.watch =function (listener) {

// 注册监听的回调函数

this._listeners.push(listener);

};

1
2
3
4
5
6
7
8
9
10
11
// 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' );
})();

借助观察者模式,我们已经实现了在调用model的set方法改变其值的时候,模板也同步更新,但这样的实现却很别扭,因为我们需要手动监听model值的改变(通过watch方法)并传入一个回调函数,有没有办法让view(一个或多个dom node)和model更简单的绑定呢?

2. 实现bind方法,绑定model和view

1
2
3
4
5
6
Model.prototype.bind = function (node) {
     // 将watch的逻辑和通用的回调函数放到这里
     this .watch( function (value) {
         node.innerHTML = value;
     });
};
1
2
3
4
5
6
7
8
9
10
// 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' );
})();

通过一个简单的封装,view和model之间的绑定已经初见雏形,即使需要在一个model上绑定多个view,实现起来也很轻松。注意bind是Function类prototype上的一个原生方法,不过它和MVC的关系并不紧密,笔者又实在太喜欢bind这个单词,一语中的,言简意赅,所以索性在这里把原生方法覆盖了,大家可以忽略。言归正传,虽然绑定的复杂度降低了,这一步依然要依赖我们手动完成,有没有可能把绑定的逻辑从业务代码中彻底解耦呢?

3. 实现controller,将绑定从逻辑代码中解耦

细心的朋友可能已经注意到,虽然讲的是MVC,但是上文中却只出现了Model类,View类不出现可以理解,毕竟HTML就是现成的View(事实上本文中从始至终也只是利用HTML作为View,javascript代码中并没有出现过View类),那Controller类为何也隐身了呢?别急,其实所谓的"逻辑代码"就是一个框架逻辑(姑且将本文的原型玩具称之为框架)和业务逻辑耦合度很高的代码段,现在我们就来将它分解一下。

如果要将绑定的逻辑交给框架完成,那么就需要告诉框架如何来完成绑定。由于JS中较难完成annotation(注解),我们可以在view中做这层标记——使用html的标签属性就是一个简单有效的办法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
1
2
3
4
5
6
7
8
// 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' );
});

就这么简单吗?就这么简单。MVC的本质就是在controller中完成业务逻辑,并对model进行修改,同时model的改变引起view的自动更新,这些逻辑在上面的代码中都有所体现,并且支持多个view、多个model。虽然不足以用于生产项目,但是希望对大家的MVC学习多少有些帮助。

整理后去掉注释的"框架"代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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()).bind(view);
     });
     callback.call( this , models);
}

4. 一个简单的例子

下面请大家看一个简单例子,如何实现电子表

1
2
3
4
5
6
7
8
9
10
11
12
13
// html:
<span bind= "hour" ></span> : <span bind= "minute" ></span> : <span bind= "second" ></span>
// controller:
new Controller( function (models) {
     function setTime() {
         var date = new Date();
         models.hour.set(date.getHours());
         models.minute.set(date.getMinutes());
         models.second.set(date.getSeconds());
     }
     setTime();
     setInterval(setTime, 1000);
});

可以看出,controller中只负责更新model的逻辑,和view完全解耦;而view和model的绑定是通过view中的属性和框架中controller的初始化代码完成的,也没有出现在业务逻辑中;至于view的更新,也是通过框架中的观察者模式实现的。

后记:

笔者在学习flux和redux的过程中,虽然掌握了工具的使用方法,但只是知其然而不知其所以然,对ReactJS官方文档中一直强调的 “Flux eschews MVC in favor of a unidirectional data flow” 不甚理解,始终觉得单向数据流和MVC并不冲突,不明白为什么在ReactJS的文档中这二者会被对立起来,有他无我,有我无他(eschew,避开)。终于下定决心,回到MVC的定义上重新研究,虽然平日工作里大大咧咧复制粘贴,但是咱们偶尔也得任性一把,咬文嚼字一番,对吧?这样的方式也的确帮助了我对于这句话的理解,这里可以把自己的思考分享给大家:之所以觉得MVC和flux中的单向数据流相似,可能是因为没有区分清楚MVC和观察者模式的关系造成的——MVC是基于观察者模式的,flux也是,因此这种相似性的由来是观察者模式,而不是MVC和flux本身。这样的理解也在四人组的设计模式原著中得到了印证:"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. "。

如果读者有兴趣在这样一个原型玩具的基础上继续拓展,可以参考下面的一些方向:

1. 实现对input类标签的双向绑定

2. 实现对controller所控制的scope的精准控制,这里一个controller就控制了整个dom树

3. 实现view层有关dom node隐藏/显示、创建/销毁的逻辑

4. 集成virtual dom,增加dom diff的功能,提高渲染效率

5. 提供依赖注入功能,实现控制反转

6. 对innerHTML的赋值内容进行安全检查,防止恶意注入
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)

最后

前15.PNG

前16.PNG

由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处免费获取

rc7-1713439964489)]

由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处免费获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值