});
});
};
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前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:前端)
最后
由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处免费获取。
rc7-1713439964489)]
由于文档内容过多,为了避免影响到大家的阅读体验,在此只以截图展示部分内容,详细完整版的JavaScript面试题文档,或更多前端资料可以点此处免费获取。