观察者模式-发布订阅模式

观察者模式

有具体目标的发布者和观察者,微信订阅号便是观察者模式的一种实现。感兴趣的人订阅公众号,在公众号有新的文章时推送给所有订阅人。

发布订阅模式

在发布-订阅模式,消息的发送方,叫做发布者(publishers),消息不会直接发送给特定的接收者(订阅者)。
需要一个第三方组件,叫做信息中介,它将订阅者和发布者串联起来,它过滤和分配所有输入的消息。

MVC架构 (model、controller、view)

  • View: 是应用程序中处理数据显示的部分,通常视图是依据模型数据创建的。

检测用户输入、操作(键盘、鼠标)行为,传递调用Controller执行对应逻辑。View更新需要重新获取 Model的数据。

  • Model:数据层,用于处理应用程序数据逻辑的部分,通常模型对象负责在数据库中存取数据。
  • Controller:处理用户交互的部分,通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据。

优点:

  • 模块化:低耦合、可替换、可扩展性、可复用性强
  • 多视图更新:使用观察者模式可以做到 单方Model通知多视图 实现数据更新

缺点:

  • 依赖强:View和 Model 强依赖,很难抽离成 组件化

    MVVM框架核心原理

    3.框架篇 【高级】 - 图1
    图上可以看到,view通过viewmodel 的DOM Listeners 将事件绑定到 Model上,而Model则通过 Data Bindings来管理View中的数据,View-Model从中起到一个连接桥的作用。

    ViewModel 监听模型数据的改变和控制视图行为、处理用户交互,简单理解就是一个同步 View 和Model 的对象,连接 Model 和 View

更详细的查看 https://segmentfault.com/a/1190000015895017

双向数据绑定(响应式)原理

vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。

Vue实现这种数据双向绑定的效果,需要三大模块:

  • Observer:能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
  • Compile:对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
  • Watcher:作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图

    数组响应式的实现

    1、先获取原生 Array 的原型方法,push\pop\shift\unshift\sort\splice\reverse
    2、对 7个 原型方法使用 Object.defineProperty 做一些拦截操作
    3、把拦截的 Array 类型的数据原型指向改造后原型: 【 原生方法.apply(this, args)】

Vue 与 React 的主要区别

  • vue 是自动挡 照着 api 就能写,react 是手动挡,很多 js 原生写法
  • vue 响应式, react 是手动 setState

Vue 使用 web 开发者更熟悉的模板与特性,比如 单文件组件以 模板+javascript+css 组合而成,跟传统页面开发很像。更容易被前端工程师接受。
react 特色在于函数式编程的理念和丰富的技术选型,可以自由灵活的进行开发。

最大区别在于数据的 响应式。 Vue 提供响应式数据,当数据发生变化,界面就会自动更新,而React 里面需要调用方法 setState。可以分成 Push-basedPull-based。所谓Push-based就是说,改动数据之后,数据本身会把这个改动推送出去,告知渲染系统自动进行渲染。在React里面,它是一个Pull的形式,用户要给系统一个明确的信号说明现在需要重新渲染了,这个系统才会重新渲染。

Vue全家桶

网上都说操作真实 DOM 慢,但测试结果却比 React 更快,为什么? - 知乎

https://www.zhihu.com/question/31809713

一次关于 Vue 的自我模拟面试

https://juejin.cn/post/6921911974611664903#heading-22 高阶问题统一

https://zhuanlan.zhihu.com/p/328041831

https://mp.weixin.qq.com/s/_MFOv_uCVwHrEtU_InU06w

组件深入( 组件通信、生命周期顺序 )

组件通信:

  1. 父子之间:props ——- $emit
  2. this.$parent.event 调用父组件的方法、this.$refs.xxx.属性/方法:父组件里直接获取子组件 方法和属性
  3. eventBus模式:新建Vue事件bus对象,然后通过 bus.$emit 触发事件,bus.$on监听触发事件。

    mixin

    image.png

    Vue Router 路由原理

    更新视图而不用重新请求页面,根据 mode 参数采用哪一种方式:

    HashHistory:默认模式

  • hash 是用来指导浏览器动作的,对服务器端完全无用,因此改变 hash 不会重新加载页面。
  • 为 hash 的改变添加监听事件:window.addEventListener("onhashchange"``, funcRef, false)
  • 每次改变 hash(window.location.hash)都会在浏览器的访问历史中增加一个记录

利用这几点特性,就可以实现前端路由 【更新视图但不重新请求页面】的功能了

HashHistory.push() 将新路由添加到浏览器访问历史的栈顶

从设置路由改变到视图更新的流程如下:

  1. $router.push() --> HashHistory.push() --> History.transitionTo() --> History.updateRoute() --> {app._route = route} --> vm.render()
  2. 1 $router.push() //调用方法
  3. 2 HashHistory.push() //根据hash模式调用,设置hash并添加到浏览器历史记录(添加到栈顶)(window.location.hash= XXX)
  4. 3 History.transitionTo() //监测更新,更新则调用History.updateRoute()
  5. 4 History.updateRoute() //更新路由
  6. 5 {app._route= route} //替换当前app路由
  7. 6 vm.render() //更新视图

HashHistory.replace()

其实和push()类似,就是替换掉当前路由,调用的是 window.location.replace 方法。

  1. function replaceHash (path) {
  2. const i = window.location.href.indexOf('#')
  3. window.location.replace(
  4. window.location.href.slice(0, i >= 0 ? i : 0) + '#' + path
  5. )
  6. }

HTML5History 历史记录栈

  • 之前通过 back()、forward()、go()等方法读取浏览器历史记录栈信息
  • H5新增 pushState() 、replaceState() 使得我们可以对浏览器历史记录栈进行修改
  1. // stateObject: 当浏览器跳转到新的状态时,将触发popState事件,该事件将携带这个stateObject参数的副本
  2. // title: 所添加记录的标题
  3. // URL: 所添加记录的URL
  4. window.history.pushState(stateObject, title, URL)
  5. window.history.replaceState(stateObject, title, URL)

实现原理:代码结构以及更新视图的逻辑与 hash 模式基本类似
1.push
与hash模式类似,只是将window.hash改为history.pushState
2.replace
与hash模式类似,只是将window.replace改为history.replaceState
3.监听地址变化 popState
在HTML5History的构造函数中监听popState(window.onpopstate)

路由按需加载

  1. // vue异步组件和webpack的【代码分块点】功能结合,实现了按需加载
  2. const App = () => import('../component/Login.vue');
  3. // webpack3提供了Magic Comments(魔法注释)
  4. const App = () => import(/* webpackChunkName:'login'*/ '../component/Login.vue');
  5. // 这样我们就为打包出来的chunk指定一个名字,最终生成login.js的chunk包。

如果您使用的是 Babel,你将需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正确地解析语法。

导航守卫流程

  1. 导航被触发。
  2. 在失活(离开)的组件里调用 beforeRouteLeave 守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
  5. 在路由配置里调用 beforeEnter。
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter。
  8. 调用全局的 beforeResolve 守卫 (2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。

    vuex 原理

    为什么使用 vuex

    主要解决两大问题:
    1、多个视图依赖于同一个状态
    2、来自不同视图的行为需要变更同一状态
    对于问题一,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力。

对于问题二,我们经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式非常脆弱,通常会导致无法维护的代码。

因此,我们为什么不把组件的共享状态抽取出来,以一个全局单例模式管理.

优势

1、vuex 的状态存储是响应式的。当 vue 组件从 store中读取的时候,如果 store 中的状态发生变化,那么相应的组件也会得到高效的更新。
2、不能直接修改 store 的状态。唯一办法就是通过提交 action (commit) mutation 里对应的函数。可以方便的跟踪每一个状态的变化,从而通过工具帮我们更好的了解应用。

state: 存储数据(相当于 data)

getter:获取store属性方法 (相当于 computed)

mutations: 更改store中状态的唯一方法就是 提交 mutation,类似于调用事件(methods)

视图通过点击事件,触发 mutattions中的方法,可以更改state中的数据,此时 getters把数据反映到视图。

action: 提交 mutation 去变更状态

那么action 可以理解是 为了处理异步,单纯多加的一层。

dispatch、commit

在vue中 我们触发 click事件,就能触发 methods中的方法。但是 vuex就不行,一定要有个东西来触发才行,就相当于自定义事件on\emit。

关系就是,通过dispatch来触发actions中得方法,action中commi去触发mutions中方法。

Proxy 与 Object.defineProperty

defineProperty 的缺点:

  • 通过数组索引修改数据,视图不会更新。
  • 不能检测到数组长度的变化

数组:通过重写原生方法来监听数组。

  1. push shift unshift pop reverse sort splice如果用户调用的是以上七个方法,会调用 vue 重写的,否则用原来的数组方法。
    为什么只有这七个呢?
    因为这七个方法会改变数组方法,而其他像concat这种就不会改变原数组。
  2. 数组没有监控索引的变化,但是如果数组中的数据是对象类型,会对对象则需要observe对象变化.所以如果arr:[{name:"chibaozi"}],那么像这样**vm.arr[0].name="早上吃包子"**通过下标修改 是可以被监听到的。
  3. 数组新增的数据是**对象**类型时,也需要observe新增的对象。

    Proxy 的优势:

  • 直接监听对象而非属性
  • 可以直接监听数组的变化
  • Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty只能遍历对象属性直接修改
  • 作为新标准会收到浏览器厂商重点持续的性能优化。

    虚拟dom原理

    Virtual DOM 是对DOM的抽象,本质上是 JavaScript 对象,这个对象就是更加轻量级的对 DOM 的描述.

主流

现代前端框架的一个基本要求就是无须手动操作 DOM,
一方面是因为手动操作 DOM 无法保证程序性能,多人协作的项目中如果 review 不严格,可能会有开发者写出性能较低的代码,
另一方面更重要的是省略手动 DOM 操作可以大大提高开发效率.

实现流程

1、需要一个函数创建单个 Virtual DOM ,这个函数很简单,接受一定的参数,再根据这些参数返回一个对象,这个对象就是 DOM 的抽象.

  1. /**
  2. * 生成 vnode
  3. * @param {String} type 类型,如 'div'
  4. * @param {String} key key vnode的唯一id
  5. * @param {Object} data data,包括属性,事件等等
  6. * @param {Array} children 子 vnode
  7. * @param {String} text 文本
  8. * @param {Element} elm 对应的 dom
  9. * @return {Object} vnode
  10. */
  11. function vnode(type, key, data, children, text, elm) {
  12. const element = {
  13. __type: VNODE_TYPE,
  14. type, key, data, children, text, elm
  15. }
  16. return element
  17. }

2、DOM 其实是一个 Tree ,我们接下来要做的就是声明一个函数用于创建 DOM Tree 的抽象 — Virtual DOM Tree.

3、Virtual DOM 归根到底是 JavaScript 对象,我们得想办法将 Virtual DOM 与真实的 DOM 对应起来,也就是说,需要我们声明一个函数,此函数可以将 vnode 转化为真实 DOM.

4、Virtual DOM 的 diff 才是整个 Virtual DOM 中最难理解也最核心的部分, diff 的目的就是比较新旧 Virtual DOM Tree 找出差异并更新.

Virtual DOM的优化

snabbdom.js 已经是社区内主流的 Virtual DOM 实现了,vue 2.0 阶段与 snabbdom.js 一样都采用了上面讲解的
「双端比较算法」,那么有没有一些优化方案可以使其更快?

其实,社区内有更快的算法,例如 inferno.js 就号称最快 react-like 框架(虽然 inferno.js 性能强悍的原因不仅仅是算法,但是其 diff 算法的确是目前最快的),而 vue 3.0 就会借鉴 inferno.js 的算法进行优化.

Vue中的key到底有什么用?

diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异.

  • v-for 需要使用key
  • 触发过渡
  • 完整的触发组件的生命周期钩子

因为默认情况 Vue 为了高效的渲染元素,通常会复用已有元素,而不是从头开始渲染。但是这会导致一些数据的混乱问题,并不符合我们实际开发需求。 所以 提供了一个方式来添加具有唯一值的 key 属性告诉”这两个元素是完全独立的,不要复用它们“

$nextTick 的原理 详细版

将回调延迟到下次DOM更新循环之后执行。在修改数据之后立即使用它,然后等待DOM更新。

  1. let microTimerFunc
  2. let macroTimerFunc
  3. let useMacroTask = false // 默认是 false 选用 微任务函数
  4. 但是要根据浏览器的 API 兼容性做判断,
  5. 1macroTask优先去使用setImmediate 其次是MessageChannel,最次是setTimeout
  6. 2microTask只有Promise,如果浏览器不支持promise,就只能降级为上面判断的macroTimerFunc了。
  7. if (!pending) {
  8. pending = true
  9. if (useMacroTask) {
  10. macroTimerFunc()
  11. } else {
  12. microTimerFunc()
  13. }
  14. }

队列控制的最佳选择是microtask,而microtask的最佳选择是Promise.但如果当前环境不支持Promise,vue就不得不降级为macrotask来做队列控制了。

在vue2.5的源码中,macrotask降级的方案依次是:setImmediate、MessageChannel、setTimeout.
setImmediate是最理想的方案了,可惜的是只有IE和nodejs支持。
MessageChannel的onmessage回调也是microtask,但也是个新API,面临兼容性的尴尬…
所以最后的兜底方案就是setTimeout了,尽管它有执行延迟,可能造成多次渲染,算是没有办法的办法了。

数据为什么频繁变化但只会更新一次

  1. 检测到数据变化
  2. 开启一个队列
  3. 在同一事件循环中缓冲所有数据改变
  4. 如果同一个 watcher (watcherId相同)被多次触发,只会被推入到队列中一次

不优化,每一个数据变化都会执行: setter->Dep->Watcher->update->run
优化后:执行顺序update -> queueWatcher -> 维护观察者队列(重复id的Watcher处理) -> waiting标志位处理 -> 处理$nextTick(在为微任务或者宏任务中异步更新DOM)

首屏加载性能优化

此点其实在说的是 白屏问题,白屏时间就是 当用户地址栏按下确认键开始到首次内容绘制(即看到第一个内容)。
所以 解决 白屏问题 才是关键 优化点。

我们先梳理下白屏时间内发生了什么:

  1. 回车按下,浏览器解析网址,进行 DNS 查询,查询返回 IP,通过 IP 发出 HTTP(S) 请求
  2. 服务器返回HTML,浏览器开始解析 HTML,此时触发请求 js 和 css 资源
  3. js 被加载,开始执行 js,调用各种函数创建 DOM 并渲染到根节点,直到第一个可见元素产生

主要是因为首次加载资源太多、太大请求慢导致的,所以从这几点入手解决。

1、因为 解析HTML 时遇到 script 会阻塞 DOM 的构建,所以 后置或者 异步 defer:异步加载,按照顺序执行, async 异步不按照顺序执行
2、静态资源可以使用 cdn
3、路由懒加载 import(‘….’)
4、DNS 缓存、预加载策略 preload: 预先加载资源 prefetch:预判加载资源

开启 HTTP2

  1. http2 的通信效率更高
  2. 可以进行多路复用
  3. http2 可以头部压缩,能够节省消息头占用的网络的流量

使用骨架屏


React全家桶

(React)专注于提供一个非常基础的 UI 模型,它专注于提供更底层的原生实现,基于此你可以构建出一套属于自己的抽象。

组件、数据流、事件、表单

生命周期

路由 react-router

react-hooks

redux


TypeScript

而TS是一门强类型静态的语言,强大的类型系统,不仅能开发阶段推导类型,带来开发的便利,同时为每一个变量函数声明类型,有助于代码的维护和重构。
TS的ROI(投入回报率)是勾型的。小型且不长久的项目慎入,越是需要多人合作和生命周期越长的项目,回报率越高。

ts环境配置

ts 类型深入、函数、类

类型 新增:void、any、never、元组、枚举enum、高级类型

类:多态、封装、继承

接口之间可以相互继承、类之间也可以并实现复用。接口可以通过类实现,但是接口只能约束类的共有成员。

ts 泛型、装饰器、模块系统

泛型:类型也是动态传入的,实现类型的灵活。也可以理解为 不预先确定的数据类型,具体类型只有在使用的时候才能确定。

高级类型:


小程序

注意事项

  • 不需要 视图使用的对象,不需要使用 this.setData(),直接 this.a = xxx 绑定到实例就行了。
  • onLoad(options) //获取小程序页面参数 必须在这里获取
  • 登录权限授权、和用户信息获取 时刻(什么时候保证可以获取、什么时候获取不到)
  • 分享微信功能是 生命周期,里面的异步函数赋值 是不起作用的。
  • 生成小程序码的scene 长度不能超过32(长度)
  • 小程序 跳转方式 url: ‘/pages/home’ 【开头必须是/】

    项目结构、语法

    这些基础直接看官网就知道了

    核心组件

    核心API

  • 存储

  • 跳转

  • 相关权限、打包上传

mpvue框架使用