设计模式
MVC
开发体系分为 View Controller Model
Model:data 核心数据结构
View:HTML + CSS 完成视图展示、样式区分
Controller:JS 负责页面逻辑,数据再处理,动态响应、交互
这个体系中,user 操作view 和 controller,下沉到model层,model 层再去反应到view层
特点:架构为一个闭环
MVVM
VM: ViewModel 数据逻辑层
view 层和 viewModel层双向绑定
这个体系中,user 只操作view 层
面试题:MVVM的理解? 首先就是双向绑定。
- 区别上理解: 编程思想上抛弃了对DOM的严格感知,不再需要严格知道具体操作哪一个DOM,只需要知道模块 无需对流程的过程化操作。只需要改变data, 写法上,代码可维护性上聊聊
- 实现上
vuex
面试:vue组件传值? 兄弟节点传值:
- 通过父节点
- eventBus
- provide inject 通过注入,跨模块,跨父子层级
- vuex
SonSon2 给 son1 传值:
状态机中的数据都是响应式
vuex 总线机制,单例实现 -> 全局只能有一个实例 否则不知道son2弄在哪个状态机
vuex 组成和过程
component 提交 dispatch,触发actions 内部的方法,actions 调用 mutations 方法,mutations 再去修改states 中的值。 states 值可以被外部监听,响应到 component
面试题:为何要兜一圈?
为了异步操作。所有的异步操作都在 actions 中,保证到mutations 中都保持同步,states 渲染时候需要稳定。
面试题:异步如何转成同步?
例子: 如何将promise 转成同步?
.then esNext async await
场景:
method: {
asyncFunc() {
return new Promise(resolve => {
setTimeout(() => {
resolve(true)
console.log('async');
}, 4000)
})
},
asyncFunc2() {
return new Promise(resolve => {
setTimeout(() => {
resolve(true)
console.log('async');
}, 5000)
})
}
},
mounted () {
// promise 解决
this.asyncFunc.then(res => {
console.log('mounted')
})
}
缺点:写在回调里,多了回调地狱 async
mounted () {
// promise 解决
this.asyncFunc.then(res => {
console.log('mounted')
})
// es语法
await this.asyncFunc()
console.log('mounted')
}
追问:多个异步函数?
// mounted里
// 全部 & 竞争
Promise.all(this.asyncFunc(), this.asyncFunc1().then(_=> {
console.log('all done')
}))
Promise.race(this.asyncFunc(), this.asyncFunc1().then(_=> {
console.log('all done')
}))
// 多个异步依赖执行取巧
await this.asyncFunc()
await this.asyncFunc2()
// 多个异步依赖执行co库 - 简单实现 - 迭代器模式
// koa
const pipeLine = [this.asyncFunc, this.asyncFunc2]
// 需要迭代器
setIterator(pipeLine)
// 通过下一个指针,做流水线
// next() function* + yield - 生成器
function* setGenerator(pipeLine) {
for (const fn of pipeLine) {
yield fn(); // 归化城迭代器的每一个步骤
}
}
// 配置迭代器
function setIterator(pipeLine) {
const generator = setGenerator(pipeLine)
GFC(generator)
}
// 流水线 - 区分异步同步,依次执行
function GFC(gen) {
const item = gen.next();
// 最后一个值为done
if (item.done) {
return item.values
}
const { value, done } = item
// value 内容,done 状态
if (value instanceof Promise) {
value.then(e => GFC(gen))
} else {
// 同步的直接执行
GFC(gen)
}
}
vuex 使用
引入
// main.js
import store from './store'
src 下面建立 store 文件夹 index.js 为入口
import Vue from 'vue'
import Vuex from 'vuex'
// 1. use 上
Vue.use(Vuex)
// 2. 创建store 实例
const store = new Vuex Store({
actions: {
async setNodeInfo({ commit }, info) {
// 这里可以异步转同步
// await xxx()
commit('SET_NODE_INFO'), {
info
}
}
}.
mutations: {
SET_NODE_INFO(state, { info }) {
state.nodeInfo = info
}
},
state: {
nodeInfo: {
name: '-',
age: 0,
words: 'hello world!'
}
}
})
export default store
App 为 hello world 父组件
// App.js
data () {
return {
nodeInfo: {
name: '-',
age: 0,
words: 'hello world!'
}
}
}
1:04
HelloWorld.vue
异步改变 store 的值
可以做一个computed 上
一些辅助函数:mapState
computed: {
// 辅助函数
...mapState(['nodeInfo'])
// 第二种写法
...mapState
}
vuex 源码
首先看3.x
git checkout 3.x
git pull
Vue.use() 默认调用 install
store.js
// use 的开始
function install (_Vue)
// 校验vue已经被挂载,区别传入和Vue 相同 => 已经use过了
Vuex 3 理论基础为mixin Vuex 4 provide inject
minxin.js
// 兼容下老版本
if (version >= 2) {
Vue.mixin({ beforeCreate: vuexInit })
} else {
// override init and inject vuex init procedure
// for 1.x backwards compatibility.
const _init = Vue.prototype._init
Vue.prototype._init = function (options = {}) {
options.init = options.init
? [vuexInit].concat(options.init)
: vuexInit
_init.call(this, options)
}
}
问题:vuex什么时候初始化? - beforeCreate
vuex 初始化
// mixin.js
function vuexInit () {
// 这里this 指向实例
const options = this.$options
// store injection
// store 注入到每个实例中
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store()
: options.store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
store 实例对象的描述
构造函数
// store.js
// cdn 直接引用方式,可以自动 install
if (!Vue && typeof window !== 'undefined' && window.Vue) {
install(window.Vue)
}
if (__DEV__) {
assert(Vue, `must call Vue.use(Vuex) before creating a store instance.`)
assert(typeof Promise !== 'undefined', `vuex requires a Promise polyfill in this browser.`)
// 必须要用new操作符 调用 store
assert(this instanceof Store, `store must be called with the new operator.`)
}
面试题:vuex 自己定义了告警,为什么不用console.assert?
主要的目的为了 throw Error ,对框架进行核心把控,提高稳定性
// 用来定义用户定义的getters - 响应式
this._wrappedGetters = Object.create(null)
vuex 实现响应式的方式:根据vue 为响应式
// 响应式$watch
this._watcherVM = new Vue()
面试题:Object.create(null) 和 {} 创建对象的区别?
原型链的区别
Object.create(null).__proto__为undefined
{}.__proto__是Object.prototype
Object.create(null)可以只保证自身的属性
// 写单例core,可以参考 确认this是当前store 的实例
const store = this
const { dispatch, commit } = this
this.dispatch = function boundDispatch (type, payload) {
return dispatch.call(store, type, payload)
}
this.commit = function boundCommit (type, payload, options) {
return commit.call(store, type, payload, options)
}
装载模块
installModule 遍历注册Mutation,Action,Getter,Children
核心:vue如何做响应式 wrapperGetters -> 拿到用户的值
Object.defineProperty 做数据劫持
// 遍历地将所有getters侨接上store,并配置成computed 属性
Object.defineProperty(store.getters, key, {
get: () => store._vm[key],
enumerable: true // for local getters
})
// 利用vue 做响应式
store._vm = new Vue({
data: {
$$state: state
},
computed
})
异步处理看看
SSR
传统CSR 静态文件拿到data -> 全部给到client (浏览器)样式和结构引擎解析,渲染
SSR 服务侧生成好一个配置页面,直接给到client
区别:浏览器端渲染和服务端渲染
优点:首屏快,SEO友好 -> 后台直接会有配置 缺点:所有东西都是后台传输,server压力很大
配置
建立serve服务
Mongo Angular Express Node
npm vue-server-render express -D
vue-server-render 前端渲染器
最外层建立server文件夹
index.js node 服务 0. 加载依赖
const express = require('express')
const Vue = require('vue')
const app = express()
const renderer =require('vue-server-render').createRenderer()
渲染器渲染page得到html内容
- page
const page = new Vue({
template: '<div>hello world</div>'
})
- 传递接口
app.get('/', async(req, res) => {
try {
// 转成字符串
const html = await renderer.renderToString(page)
res.send(html)
} catch {
res.status(500).send('serve inner error')
}
})
- 启动服务器监听
app.listen(3000, () => {
})
- 执行
ssr 路由 普通router:
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
}
]
})
SSR router
不可以返回路由实例,需要返回工厂函数
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default function createRouter() {
return new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
}
]
})
}
面试题:为什么不导出一个router实例 而是工厂函数?
因为用户每次操作都在服务端做的,连接服务端有无数个客户 -> 每一个请求都要创建一个实例,保证用户每次进入系统应用重新切换
例子:
import Vue from 'vue';
import App from './router'
import createRouter from './router'
export default function createApp () {
const router = createRouter()
const app = new Vue({
router,
render: h => h(App)
})
return { app, router }
}
金融板块中直接塞值不安全