最近在做ipad相关的单页应用研究,被一个问题困住了思维,晚上看了两集布袋戏,完了在纸上画着画着,突然对半年多之前的一道面试题很有点思路
于是,今天晚上,抽时间记录之,此文只是个人见解,不一定正确,有误请指正,时间紧时间晚,行文不清晰勿怪
首先,当时我简历是抄的,里面有一句“对表现与数据分离有一定理解”,然后面试官就针对这点开问了
PS:尼玛,那是抄的,我哪里知道他是干神马的......
为了帮助我展开思维,面试官出了一道题:
他有一个国家列表,现在要将国家列表放到A中,然后B可以由A选择,也可以有总列表选择 但是B中添加后,若是A中没有要动态为A增加这项。
问题出来了,我现在一看就知道面试官想问观察者相关的知识点,当时没有理解到啦......
现在来看,说这道题与表现与数据分离有关是没有问题的,却不一定能让人很好的产生联想,所以这个问题的答案,还是需要继续挖掘
针对这个问题其实有几个实现方案,就算是初级入门的朋友也是能做的,但是要好好的回答这个题我们需要看到后面隐藏的意图
比如,现在是操作B时候需要A与之联动,如果现在出了一个C或者D呢???
解决方案
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml">
3 <head>
4 <title>
5 </title>
6 <style type="text/css">
7 .country { margin: 20px; padding: 30px; border: 1px solid black;} .item
8 { margin: 10px; padding: 10px; border: 1px solid black; } .list { margin:
9 10px; padding: 10px; border: 1px solid black; min-height: 50px;} #pop {
10 border: 1px solid black; padding: 0; position: absolute; display: none;
11 background-color: White; } #pop li { list-style: none; padding: 10px; border-bottom:
12 1px solid black; } #pop li:hover { background-color: Gray; }
13 </style>
14
15 <script id="jquery_183" type="text/javascript" class="library" src="/js/sandbox/jquery/jquery-1.8.3.min.js"></script>
16 </head>
17 <body>
18 <div class="country">
19 <h2>
20 全部国家
21 </h2>
22 <div id="all" class="list">
23 </div>
24 </div>
25 <div class="country">
26 <h2>
27 A国家
28 </h2>
29 <div id="a" class="list">
30 </div>
31 </div>
32 <div class="country">
33 <h2>
34 B国家
35 </h2>
36 <div id="b" class="list">
37 </div>
38 </div>
39 <ul id="pop">
40 <li attr="a">
41 移动到A
42 </li>
43 <li attr="b">
44 移动到B
45 </li>
46 <li attr="remove">
47 删除
48 </li>
49 </ul>
50
51 <script type="text/javascript">
52 var array = [];
53 var slice = array.slice;
54 var Pop = function() {
55 this.el = $('#pop');
56 var scope = this;
57 this.el.on('click',
58 function(e) {
59 var item = $(e.target).attr('attr');
60 scope.click(item);
61 scope.el.hide();
62 })
63 };
64 Pop.prototype = {
65 show: function(e, callback) {
66 this.el.css('top', e.pageY);
67 this.el.css('left', e.pageX);
68 this.el.show();
69 this.click = callback;
70 }
71 };
72 var pop = new Pop();
73 var CountryList = function(opts) {
74 this.list = opts.list || [];
75 this._events = {};
76 this.root = opts.el || $('body');
77 if (opts.events) {
78 for (var k in opts.events) this.on(k, opts.events[k]);
79 }
80 this.render();
81 this.bindEvent();
82 this.exec('onLoad')
83 };
84 CountryList.prototype = {
85 render: function() {
86 var item;
87 for (var i = 0,
88 len = this.list.length; i < len; i++) {
89 item = $('<span class="item" id="' + i + '">' + this.list[i] + '</span>');
90 this.root.append(item);
91 }
92 },
93 bindEvent: function() {
94 },
95 add: function(name) {
96 var item = $('<span class="item" id="' + this.list.length + '">' + name + '</span>');
97 this.list.push(name);
98 this.root.append(item);
99 this.exec('onAdd', name, item);
100 },
101 remove: function(name) {
102 var index = this.list.indexOf(name);
103 if (index != -1) {
104 this.list.splice(index, 1);
105 var item = this.root.find('#' + index);
106 item.remove();
107 }
108 this.exec('onRemove', name, item);
109 },
110 on: function(type, fn) {
111 if (!this._events[type]) {
112 this._events[type] = [];
113 }
114 this._events[type].push(fn);
115 },
116 exec: function(type) {
117 if (!this._events[type]) {
118 return;
119 }
120 var i = 0,
121 l = this._events[type].length;
122 if (!l) {
123 return;
124 }
125 var args = slice.call(arguments, 1);
126 for (; i < l; i++) {
127 this._events[type][i].apply(this, args);
128 }
129 },
130 exist: function (name) {
131 return this.list.indexOf(name) != -1;
132 }
133 };
134
135
136 var all = new CountryList({
137 list: ['中国', '美国', '英国', '法国', '朝鲜', '日本'],
138 el: $('#all'),
139 events: {
140 onLoad: function () {
141 this.root.on('click', function(e) {
142 var el = $(e.target);
143 if(el.attr('class') != 'item') return;
144 pop.show(e, function(item) {
145 if(item == 'a') {
146 a.add(el.html())
147 } else if(item == 'b') {
148 b.add(el.html())
149 }
150 });
151 });
152 }
153
154
155 }
156 });
157 var a = new CountryList({
158 el: $('#a'),
159 events: {
160 onLoad: function () {
161 this.root.on('click', function(e) {
162 var el = $(e.target);
163 if(el.attr('class') != 'item') return;
164 pop.show(e, function(item) {
165 if(item == 'b') {
166 b.add(el.html())
167 }
168 });
169 });
170 }
171 }
172 });
173 var b = new CountryList({
174 el: $('#b'),
175 events: {
176 onLoad: function () {
177 this.root.on('click', function(e) {
178 var el = $(e.target);
179 if(el.attr('class') != 'item') return;
180 pop.show(e, function(item) {
181 if(item == 'remove') {
182 b.remove(el.html())
183 }
184 });
185 });
186 },
187 onAdd: function(name) {
188 if(a.exist(name) == false) a.add(name)
189 },
190 onRemove: function(name) {
191 console.log('remove');
192 }
193 }
194 });
195 </script>
196 </body>
197 </html> 1 var CountryList = function(opts) {
2 this.list = opts.list || [];
3 this._events = {};
4 this.root = opts.el || $('body');
5 if (opts.events) {
6 for (var k in opts.events) this.on(k, opts.events[k]);
7 }
8 this.render();
9 this.bindEvent();
10 this.exec('onLoad')
11 };
12 CountryList.prototype = {
13 render: function() {
14 var item;
15 for (var i = 0,
16 len = this.list.length; i < len; i++) {
17 item = $('<span class="item" id="' + i + '">' + this.list[i] + '</span>');
18 this.root.append(item);
19 }
20 },
21 bindEvent: function() {
22 },
23 add: function(name) {
24 var item = $('<span class="item" id="' + this.list.length + '">' + name + '</span>');
25 this.list.push(name);
26 this.root.append(item);
27 this.exec('onAdd', name, item);
28 },
29 remove: function(name) {
30 var index = this.list.indexOf(name);
31 if (index != -1) {
32 this.list.splice(index, 1);
33 var item = this.root.find('#' + index);
34 item.remove();
35 }
36 this.exec('onRemove', name, item);
37 },
38 on: function(type, fn) {
39 if (!this._events[type]) {
40 this._events[type] = [];
41 }
42 this._events[type].push(fn);
43 },
44 exec: function(type) {
45 if (!this._events[type]) {
46 return;
47 }
48 var i = 0,
49 l = this._events[type].length;
50 if (!l) {
51 return;
52 }
53 var args = slice.call(arguments, 1);
54 for (; i < l; i++) {
55 this._events[type][i].apply(this, args);
56 }
57 },
58 exist: function (name) {
59 return this.list.indexOf(name) != -1;
60 }
61 };代码说明
因为本身比较晚了,加之今天工作量比较大,现在头脑有点浆糊,所以写的代码各位看看就好,不必认真,这个代码有何意义,有与表现与数据分离有什么关系呢?
其实这个代码本身就是实现了一个简单的数据集合类,在其内部有自建的事件机制,分别在三个点可以注册事件:
① 加载时
② 增加一项时
③ 删除一项时
于是,我就在几个点插入了自己的逻辑,我们特别来说一下b,我们为b注册了onload事件,并且onadd事件,在增加一项时会看看b中是否有,没有就增加了
这样做的话,我们就可以只关注b的数据了,而不必关心数据是拖过来,还是飞过来,还是爬过来了,我们这里的表现是什么呢?
什么是表现(行为)
我认为,表现就是增加数据的方式,这个题来说,是增加b项目的方式,我们题目中要求是“拖”,我嫌马上变成了“选”,这个增加数据的“表现”是多种多样的,
这里说是表现,我看行为更贴切,表现容易往模板上挂钩
比如说:
可以看到,我们在console环境下,b中新增一项也会往a国家新增,这个也是一种“表现”
PS:不知道我这个样子理解表现是否正确
表现各种各样,但是各种各样的表现并不应该影响数据的维护,比如b国家居然增加了一项all国家没有的数据,难道all国家列表中不应该增加吗?
反过来说,我们a列表,b列表数据都最终来源于all,如果all的国家都被删除了那么ab国家的数据也是不应该存在了,而现在是ab,实际情况可能a-z
并且,数据消失的方法五花八门......
如果我们不是在数据上注册了事件的话,我们可能遇到以下情况:
① 拖的时候我们需要做相关逻辑准备
② 选的时候我们需要做相关逻辑
③ 增加b国家项目的方法变成使用select或者其他什么方法了,这个时候我们可能又要单独写逻辑了
所以我们现在要将各种“表现”给分离出来,只要操作了“数据”,比如增删,我就会执行不变的逻辑,数据处理的逻辑不以表现而变化
以上便是我对变现与数据分离的理解,理解若是有偏差,请各位指出
项目中可能
这种模式的编码可能用于哪些地方呢?
场景一:
主要用于一对多的变化,特别是多个dom共享一个data,这个时候data变了,我们不同的dom也需要得到变化
比如,我现在ipad项目上有一个城市列表数据(在localstarage中),左右两边同时会有针对该数据的显示,这个时候我们就需要对该data做一次封装
并且在其改变时候注册事件,让哥哥dom(view)注册事件,在data改变时候注册了事件的dom就会得到变化,比如左边导致数据变化
数据一旦发生变化,会触发相关事件,然后右边dom的数据就变了,这样做非常好,通过数据联系左右两边,因为左右两边可能根本无法通讯
却因为数据联系起来了
场景二:
其实关于这个的场景大同小异,另外一个常见的场景不那么明显,比如我们现在有一个商品,我们选择的数量不同,我们需要显示的折扣点不同,
同样我们的价钱当然会发生变化,这个变化看上去只是小区域的,但是总会有些莫名其妙的dom在页面的各个地方显示的相关的数据,一个不小心就会漏了
然后会有bug,这个情况下,就可以为个数相关的创建相关事件,当个数变化时候,其折扣相关的dom,价格相关的dom,或者其他会有一个联动关系
当然实际的业务可能比较复杂,是否应该这么干,值得这么干还得看情况。
热门评论
-
Lionad_Guiro2020-03-23 0
查看全部评论面试官出的这道题没看很懂。
表现与数据分离指的应该是题目看起来有三个列表,但是你在JS中只需要维护一个总的国家数组和A、B 两个序号数组