思维1.最小知识原则
- 不需要知道别人要做什么内容,只需要提供接口即可
- 当:引入一个模块需要引入 html、css、js
创建四个模块:
1.需要在index.html中link引入CSS
2.需要script引入JS
3.需要分别写入四个模块的HTML标签 - 当:引入一个模块需要引入 html、js
将app.css独立文件,引入app.js中,由JS引入CSS,省掉link引入
import "./app1.css";
- 当:引入一个模块只需要引入 js
将index.html中的每个app的HTML标签模块放入app.js中,然后插入到HTML文件里
这样index.html中就只需要script引入一个JS文件
const html =`
HTML app 标签内容
`
const $element = $(html).appendTo($('body>.page'))
- 经过上面的简化,别人在写代码的时候只需要:
1.写一个空的div.page,然后引入一个main.js
2.然后在main.js中引入四个模块的JS文件
3.不需要担心这四个模块的内容,因为这4个模块会自己照顾好自己 - 模块化为这一点奠定了基础
之前的代码就是一团糟,什么代码都在一个文件里面,每次找起来都非常麻烦
1.2 代价
- 这样做会使得页面一开始是空白的,没内容没样式
当网速慢的时候:什么都没有,因为JS加载引入都需要时间 - 解决方法很多,比如加菊花、加骨架、加占位内容等
1.菊花:gif loading(加载中)
操作:在main.js文件最后加一句img.remove()
,只要前面的模块JS文件都加载好了,就把图片删掉
优点:不需要做任何优化,有这个gif图片用户就会感到安心,空白页面用户等不了
2.骨架:页面的框架结构(没有内容)
3.占位内容:加载中…、文章标题 - 也有人选择用 SSR技术来解决,现在没必要学习
1.3 最小知识原则的优点
- 如果把所有的代码都写在一个文件里,过了几天之后就完全不记得自己写的什么了
- 模块化封装很好的时候,看一眼就知道大概结构内容是什么
思维2.以不变应万变
- 既然每个模块都可以用 m + v + c 搞定
- 那么每个模块我就都这样写就好啦
- 不用再思考类似的需求该怎么做了
- 代价:
1.有时候会有一些多余用不到的代码
2.有时候遇到特殊情况不知道怎么变通,比如没有 html 的模块怎么做 mvc
2.1 理解MVC的思维
- 写个m对象,v对象,c对象
- 给app.js文件中的代码加上大概框架注释
如:app1.js文件
1.引入(import)css文件和jQuery
2.初始化html
3.寻找重要元素
4.初始化数据
5.将数据渲染到页面
6.绑定鼠标事件
主要分为两个方向:绑定事件+渲染数据 - 将app1.js中的内容按照MVC的逻辑来理一下
1.所有与数据相关的都放到对象M
2.所有跟视图相关(用户看得见的)的都放到对象V
3.其他的都放到对象C
const m = {}
const v = {}
const c = {}
2.2 对象V
- 引入的html标签可以放到v中
注意:不能用变量了,就变成属性,key是html,value是字符串 - 把HTML渲染到page,appendTo()
放到对象V中作为函数render(),其中的html变量就要改为 v.html
后面初始化就是:v.render() - 将数据渲染到页面
函数updata
2.3 对象M
- 初始化数据
2.4 对象C
- 绑定事件和寻找重要元素都属于其他操作,放到C中
- 找到的重要元素作为一个新对象ui放到对象C中,key前面就不需要$了
- 绑定事件 bindEvent()函数
2.5 Bug
- 用MVC的方式修改了代码之后,会出现很多问题
- 需要修改变量名,修改函数的调用逻辑
- 因此需要不断调试修改
2.6 传参(app1中的bug)
- app1中将元素添加到 body .page中
- 我们可以用户自定义添加的位置,标签名设定为:container,则需要传入参数
render(container)、appendTo($(container)) - 这样才初始化 c.init() 的时候就必须要得到一个东西
- 如何得到参数?
答:只能把c暴露出去:export default c
不再需要c.init() - 此时app1.js就不是自己运行自己,而是需要别人传参才能运行起来
- 在main.js中修改为:
import x from "./app1.js"
引入这个x就是c的地址 - 在main.js中给一个container
x.innit('#app1')
意思是:把页面中的app1传给模块去初始化
2.7 绑定事件
- 问题
1.就是app1.js中html字符串中的内容,在进行一次运算后重新渲染一次
2.html重新渲染替换这也导致绑定在上面的事件不见了,也就是无法进行第二次操作 - 解决
1.需要这个容器 container 里有东西是不可变的,是不被替换掉的
2.将section不放在字符串中,而是index.html中
3.这样每次渲染替换也只是替换section里面的内容,而section是不变的 - 如此
1.索性把事件绑定在section标签上,进行事件委托
2.然后在onclick里面判断点击的是不是 #add1 等子元素
v.container.on('click','#add1',()=>{
m.data.n += 1
v.render()
})
思维3 表驱动编程
- 当你看到大批类似但不重复的代码
- 眯起眼睛,看看到底哪些才是重要的数据
- 把重要的数据做成哈希表,你的代码就简单了
- 这是数据结构知识给我们的红利
思维4 事不过三
- 同样的代码写三遍,就应该抽成一个函数
- 同样的属性写三遍,就应该做成共用属性(原型或类)
- 同样的原型写三遍,就应该用继承
- 代价:
1.有的时候会造成继承层级太深,无法一下看懂代码
2.可以通过写文档、画类图解决
思维5 俯瞰全局
- 把所有的对象看成点
- 一个点和一个点怎么通信
- 一个点和多个点怎么通信
- 多个点和多个点怎么通信
- 最终我们找出一个专用的点负责通信
- 这个点就是 event bus(事件总线)
5.1 eventBus(思维6中会使用)
1.声明
const eventBus = $(window)
- eventBus里面是一个我们传过去的window,全局变量
- 因为我们根本不需要对象,而是需要对象的两个API
1.on 方法——监听事件
2.trigger 方法——触发事件 - 也就是说eventBus有一个on函数,还有一个trigger函数
2.对m进行增删改查
- 由于操作是对于m中的n进行操作
- 则在m对象中添加一个改函数 :updata()
也就是给我一个什么值,我就把n改成对应的值(所有属性都修改) - m更新了之后就会触发一个更新事件
update(data){
//把data的所有属性全部给m.data
Object.assign(m.data,data)
//大吼一声,我更新了
eventBus.trigger('m:updated')//字符串事件不能加空格
}
- 监听这个事件,在c对象的init中添加一个监听
一旦m更新了,就render()
eventBus.on('m:data',()=>{
v.render(m.data.n)
})
- 操作后,再调用add、minus等函数的时候就不需要每次都render
注意:由于只有update才能监听事件,因此n的操作需要updata来进行
add(){
m.update(data{n: m.data.n + 1})
}
思维6 view = render(data)
- 所有的视图就是把数据给渲染一下
- 这个思维造成了react的诞生
- 比起操作 DOM 对象,直接 render 简单多了
- 只要改变 data,就可以得到对应的 view
- 代价
1.render 粗犷的渲染肯定比 DOM 操作浪费性能
(只要有改变就重新渲染页面和找到DOM对应元素然后添加/删除class,肯定是后者更加节省性能)
2.还好我们后面会用到虚拟 DOM
3.虚拟 DOM 能让 render 只更新该更新的地方 (不会整个页面全部渲染)
1.理解
- 在JS中写代码,但操作的是DOM(也就是页面)
1.2 以操作 +1 功能为例子
- 获取页面中的span的text
- 然后 n+=1
- 然后将 n 放到 text 里面
- 数据流向示意图(DOM --> JS --> DOM)
1.3 react思想
- 每次更新都要进行这个操作非常的麻烦
- 简化数据流向(JS --> DOM)
1.首先声明 n = 100
2.然后渲染,页面上得到100的span
3.如果点击一下,就 n+=1
4.然后再渲染一次,页面上就得到 101的span - 特点:数据流向一直是左(JS)到右(DOM)
没有右到左
1.4 以app1为例
- 从本地数据库中拿到n
- 初始化的时候进行第一次渲染
v.render(m.data.n)
——视图等于渲染数据 - 当用户点击 +1 的时候更新这个n,进行第二次渲染
- 往后点击每一个操作,都会进行一次渲染,然后JS把n传给DOM
1.4.1 问题1:把一堆事件绑定改成哈希表
- 当进行上面的修改之后,出现了一个问题——重复!
- 除了个别参数和运算不同,代码结构都是一样的
v.el.on('click','#add1',()=>{
m.data.n += 1
v.render(m.data.n)
})
- 简化思想:把一样的东西都隐藏掉,只留下需要的东西
- 解决:
1.声明一个对象events,把操作的思想写在里面
(点击 #add1,就调用add函数,然后声明函数,其他操作同理)
2.只写不同的,重复的都不抄
events:{
'click #add1': 'add'
'click #minus1': 'minus'
略
}
add(){
m.data.n += 1
}
minus(){
m.data.n -= 1
}
略
- 写好对象和函数后,声明一个 autoBindEvents 函数
1.获取到c中的events,然后进行遍历:for(let key in events)
2.遍历获取到了所有的key,那么使用空格来分割,得到用户想要的操作click 和 add等
autoBindEvents(){
for(let key in events){
//得到events里面对应的value,然后去c对象里找对象的函数
const value = c[c.events[key]]
const spaceIndex = key.indexOf(' ')
const part1 = key.slice(0,spaceIndex)
const part2 = key.slice(spaceIndex+1)//不要空格
v.el.on(part1,part2,value)
}
}
1.4.2 问题2:如何简化在每次操作1次之后就渲染
- 真实需求:n一变,就自动渲染视图
- 解决:监听n的变化
1.vue的API
2.eventBus 对象间通信