文章内容输出来源:拉勾教育大前端高薪训练营
const history = require('connect-history-api-fall-back')
app.use(history())
try_files $uri $uri/ /index.html
server{
location / {
try_files $uri $uri/ /index.html
}
}
let _Vue = null;
export default class VueRouter{
static install(Vue){
// 1 判断当前插件是否已安装
if(VueRouter.install.installed) return;
VueRouter.install.installed = true;
// 2 把Vue构造函数记录到全局变量
_Vue = Vue
// 3 把创建vue实例时候传入的router对象注入到Vue实例上
// 混入
_Vue.mixin({
beforeCreate(){
if(this.$options.router){ // 如果是组件的话 不存在router
_Vue.prototype.$router = this.$options.router
}
}
})
}
}
constructor(options){
this.options = options;
this.routerMap = {};// key 路由地址 value 路由组件
this.data = _Vue.observable({
current:'/'
})
}
createRouteMap(){
//把构造函数中传过来的routes转换为key-value形式,存放在routerMap中
this.options.routes.forEach(route=>{
this.routerMap[route.path] = route.component
})
}
initComponents(Vue){
Vue.component('router-link',{
props:{
to:String
},
template:` `
})
}
完整版Vue
module.exports = {
runtimeCompiler:true
}
Vue.component('router-link', {
props: {
to: String
},
// template: ' '
render (h) {
return h('a', {
attrs: {
href: this.to
}
}, [this.$slots.default])
}
})
router-view
Vue.component('router-view', {
render (h) {
const component = _this.routerMap[_this.data.current]
return h(component)
}
})
// router-link配合点击事件
Vue.component('router-link', {
props: {
to: String
},
// template: ' '
render (h) {
return h('a', {
attrs: {
href: this.to
},
on: {
click: this.clickHandler
}
}, [this.$slots.default])
},
methods: {
clickHandler (e) {
history.pushState({}, '', this.to)
this.$router.data.current = this.to
e.preventDefault()
}
}
})
initEvent () {
window.addEventListener('popstate', () => {
this.data.current = window.location.pathname
})
}
数据驱动
vue2响应式原理
<body>
<div id="app">
hello
</div>
<script>
let data = {
msg : 'hello'
}
let vm = {};
Object.defineProperty(vm,'msg',{
enumerable:true,
configurable:true,
get(){
console.log('get');
return data.msg
},
set(newVal){
console.log('set',newVal);
if(newVal ===data.msg){
return
}
data.msg = newVal
document.querySelector('#app').textContent = data.msg;
}
})
vm.msg = 'niahdas';
console.log(vm.msg);
</script>
</body>
vue3响应式原理
发布订阅模式
观察者模式
观察者VS发布订阅
模拟vue实现(学习版)
功能
结构
class Vue{
constructor(options){
// 1 通过属性保存 选项的数据
this.$options = options || {}
this.$data = options.data|| {}
this.$el = typeof options.el === 'string'?document.querySelector(options.el):options.el
// 2 把data中的数据转换成getter和setter,注入到vue实例中
this._proxyData(this.$data)
// 3 调用observer对象,监听数据变化
new Observer(this.$data)
// 4 调用compuler对象,解析指令和差值表达式
new Compiler(this)
}
_proxyData(data){
// 遍历data中所有的属性
Object.keys(data).forEach(key=>{
// 把data的属性注入到vue实例中
Object.defineProperty(this,key,{
enumerable:true,
configurable:true,
get(){
return data[key]
},
set(newValue){
if(newValue === data[key]){
return
}
data[key] = newValue
}
})
})
}
}
功能
结构
class Observer{
constructor(data){
this.walk(data)
}
walk(data){
// 判断data是否是对象
if(!data||typeof data!=='object') return
// 遍历data对象所有属性
Object.keys(data).forEach(key=>{
this.defineReactive(data,key,data[key])
})
}
// 传第三个参数val 不使用obj[key] 原因:会发生死递归
defineReactive(obj,key,val){
let _this = this;
let dep = new Dep();
this.walk(val);//如果val是对象 会将val内部的数据转为响应式
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get(){
// 收集依赖
Dep.target && dep.addSub(Dep.target)
return val
},
set(newVal){
if(newVal === val) return;
val = newVal;
_this.walk(newVal)
// 发送通知
dep.notify()
}
})
}
}
功能
结构
class Compiler{
constructor(vm){
this.el = vm.$el;
this.vm = vm;
this.compile(this.el)
}
// 编译模块,处理文本节点和元素节点
compile(el){
let childNodes = el.childNodes;
Array.from(childNodes).forEach(node=>{
if(this.isTextNode(node)){
this.compileText(node)
}else if(this.isElementNode(node)){
this.compileElement(node)
}
// 判断node是否有子节点
if(node.childNodes&&node.childNodes.length){
this.compile(node)
}
})
}
// 处理元素节点
compileElement(node){
// 遍历属性节点
Array.from(node.attributes).forEach(attr=>{
let attrName = attr.name;
if(this.isDirective(attrName)){
attrName = attrName.substr(2)
let key = attr.value;
if (attrName.indexOf(':')!==-1) {
let eventType = attrName.split(':')[1]
this.handleEvent(this,node,eventType,key)
}
this.update(node, key, attrName)
}
})
}
update(node,key, attrName){
let updateFn = this[attrName+'Updater']
updateFn && updateFn.call(this,node,this.vm[key],key)
}
handleEvent(vm,node,eventType,eventName){
console.log(vm,node,eventType);
node.addEventListener(eventType,()=>{
vm.vm.$options.methods[eventName]()
})
}
htmlUpdater(node,value,key){
node.innerHTML = value;
new Watcher(this.vm, key, (newValue)=>{
node.innerHTML = newValue
})
}
onUpdater(node,value,key){
console.log(node,value,key);
}
textUpdater(node,value,key){
node.textContent = value;
new Watcher(this.vm, key, (newValue)=>{
node.textContent = newValue
})
}
modelUpdater(node,value,key){
node.value = value;
new Watcher(this.vm, key, (newValue)=>{
node.value = newValue
})
// 双向绑定
node.addEventListener('input',()=>{
this.vm[key] = node.value
})
}
// 处理文本节点
compileText(node){
// console.log(node);
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if(reg.test(value)){
let key = RegExp.$1.trim();
node.textContent = value.replace(reg,this.vm[key])
new Watcher(this.vm, key, (newValue)=>{
node.textContent = newValue
})
}
}
// 判断元素属性是否是指令
isDirective(attrName){
return attrName.startsWith('v-')
}
// 判断节点是否是文本节点
isTextNode(node){
return node.nodeType === 3
}
// 判断节点是否是元素节点
isElementNode(node){
return node.nodeType === 1
}
}
功能
结构
class Dep{
constructor(){
this.subs = []
}
addSub(sub){
if(sub&&sub.update){
this.subs.push(sub)
}
}
notify(){
this.subs.forEach(sub=>{
sub.update()
})
}
}
class Watcher{
constructor(vm, key, cb){
this.vm = vm;
this.key = key//data中属性的名称
this.cb = cb// 回调函数负责更新视图
// 把watcher对象记录到Dep类的静态属性target上
Dep.target = this;
// 触发get方法 在get方法中调用addSub
this.oldValue = vm[key]
Dep.target = null;
}
update(){
let newValue = this.vm[this.key]
if(this.oldValue === newValue){
return
}
this.cb(newValue)
}
}
VDOM
VDOM库 - Snabbdom/virtual-dom
使用parcel打包
"scripts": {
"dev":"npx parcel index.html --open",
"build":"npm parcel build index.html"
},
npm i snabbdom -D
// import snabbdom from 'snabbdom' //错误引入
// const snabbdom = require('snabbdom')
// console.log(snabbdom);
// import { h, thunk, init } from 'snabbdom'
import { h, init } from 'snabbdom'
// hello world
let patch = init([])
// 参数1 标签+选择器
// 参数2 如果是字符串的话 就是标签中的内容
let vnode = h('div#container.cls',{
hook:{
init(vnode){
console.log(vnode.elm);
},
create(emptyVnode,vnode){
console.log(vnode.elm);
}
}
},'hello world')
let app = document.querySelector('#app')
// 参数1 可以是DOM元素,内部会把DOM元素转换为VNode 参数2 VNode
let oldVnode = patch(app,vnode)
vnode = h('div','hello snabbdom')
patch(oldVnode,vnode)
import {init, h} from 'snabbdom'
import style from 'snabbdom/modules/style'
import eventlisteners from 'snabbdom/modules/eventlisteners'
let patch = init([
style,
eventlisteners
])
let vnode = h('div',{
style:{
backgroundColor:'#f60'
},
on:{
click:eventHandler
}
},[h('h1','this is h1'),h('p','this is a p')])
function eventHandler(){
console.log('click event');
}
let app = document.querySelector('#app')
patch(app,vnode)