赞
踩
1、建立虚拟DOM Tree,通过document.createDocumentFragment(),遍历指定根节点内部节点,根据{{ prop }}、v-model等规则进行compile(编译);
2、通过ES5 Object.defineProperty()进行数据变化拦截;
3、截取到的数据变化,通过发布者-订阅者模式,触发Watcher,从而改变虚拟DOM中的具体数据;
4、通过改变虚拟DOM元素值,从而改变最后渲染dom树的值,完成双向绑定
完成数据的双向绑定在于Object.defineProperty()
1.事件修饰符
.stop 阻止事件继续传播
.prevent 阻止标签默认行为
.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
.self 只当在 event.target 是当前元素自身时触发处理函数
.once 事件将只会触发一次
.passive 告诉浏览器你不想阻止事件的默认行为
2.v-model 的修饰符
lazy 通过这个修饰符,转变为在 change 事件再同步
number 自动将用户的输入值转化为数值类型
trim 自动过滤用户输入的首尾空格
3.键盘事件的修饰符
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
4.系统修饰键
.ctrl
.alt
.shift
.meta
5.鼠标按钮修饰符
.left
.right
.middle
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)
第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)
export function compileToFunctions(template) { // 我们需要把html字符串变成render函数 // 1.把html代码转成ast语法树 ast用来描述代码本身形成树结构 不仅可以描述html 也能描述css以及js语法 // 很多库都运用到了ast 比如 webpack babel eslint等等 let ast = parse(template); // 2.优化静态节点 // 这个有兴趣的可以去看源码 不影响核心功能就不实现了 // if (options.optimize !== false) { // optimize(ast, options); // } // 3.通过ast 重新生成代码 // 我们最后生成的代码需要和render函数一样 // 类似_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world")))) // _c代表创建元素 _v代表创建文本 _s代表文Json.stringify--把对象解析成文本 let code = generate(ast); // 使用with语法改变作用域为this 之后调用render函数可以使用call改变this 方便code里面的变量取值 let renderFn = new Function(`with(this){return ${code}}`); return renderFn; }
指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素增加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。
自定义指令有五个生命周期(也叫钩子函数),分别是 bind、inserted、update、componentUpdated、unbind
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:被绑定于元素所在的模板更新时调用,而无论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
unbind:只调用一次,指令与元素解绑时调用。
原理
1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
2.通过 genDirectives 生成指令代码
3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
4.当执行指令对应钩子函数时,调用对应指令定义的方法
在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立,可以通过 Vue 的 mixin功能抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用mergeOptions方法进行合并,采用策略模式针对不同的属性进行合并。当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
keep-alive 是 Vue 内置的一个组件,可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
*补充:LRU 的核心思想是如果数据最近被访问过,那么将来被访问的几率也更高,所以我们将命中缓存的组件 key 重新插入到 this.keys 的尾部,这样一来,this.keys 中越往头部的数据即将来被访问几率越低,所以当缓存数量达到最大值时,我们就删除将来被访问几率最低的数据,即 this.keys 中第一个缓存的组件。
Vue 响应式原理 在两种情况下修改数据 Vue 是不会触发视图更新的
Vue.set 或者说是$set 原理如下
因为响应式数据 我们给对象和数组本身都增加了__ob__属性,代表的是 Observer 实例。当给对象新增不存在的属性 首先会把新的属性进行响应式跟踪 然后会触发对象__ob__的 dep 收集到的 watcher 去更新,当修改数组索引时我们调用数组本身的 splice 方法去更新数组
官方解释:Vue.extend 使用基础 Vue 构造器,创建一个“子类”。参数是一个包含组件选项的对象。
其实就是一个子类构造器 是 Vue 组件的核心 api 实现思路就是使用原型继承的方法返回了 Vue 的子类 并且利用 mergeOptions 把传入组件的 options 和父类的 options 进行了合并
函数式组件与普通组件的区别
1.函数式组件需要在声明组件是指定 functional:true
2.不需要实例化,所以没有this,this通过render函数的第二个参数context来代替
3.没有生命周期钩子函数,不能使用计算属性,watch
4.不能通过 $emit 对外暴露事件,调用事件只能通过context.listeners.click的方式
调用外部传入的事件
5.因为函数式组件是没有实例化的,所以在外部通过ref去引用组件时,实际引用的是HTMLElement
6.函数式组件的props可以不用显示声明,所以没有在props里面声明的属性都会被自动隐式解析为prop,
而普通组件所有未声明的属性都解析到$attrs里面,并自动挂载到组件根元素上面(可以通过inheritAttrs属性禁止)
优点 1.由于函数式组件不需要实例化,无状态,没有生命周期,所以渲染性能要好于普通组件 2.函数式组件结构比较简单,代码结构更清晰
一个简单的展示组件,作为容器组件使用 比如 router-view 就是一个函数式组件
watch: {
'$store.getters.permissionMenus': {
// 立即监听
immediate: true,
// 深度监听
deep: true,
handler (val) {
this.topMenuList = this.$store.getters.permissionMenus
}
}
}
computed 适用计算一些属性,内存消耗较小依赖值不变,这个也不会变。
computed : {
changeMes: {
get: () => {
return this.mes.split('').reverse().join('')
},
set: () => {
this.mes = '123'
}
}
}
调用自定义事件(发送消息) ==> 绑定自定义事件(接收消息)
**及时销毁自定义事件:否则会造成内存泄漏
this.$route: 表示当前正在用于跳转的路由器对象,可以调用其name、path、query、params等方法
this.$router: 是VueRouter的一个对象,表示全局路由器对象,项目中通过router路由参数注入路由之后,在任何一个页面都可以通过此方法获取到路由器对象,并调用其push(), go()等方法
$router.push({path:'home'});本质是向history栈中添加一个路由,在我们看来是切换路由,但本质是在添加一个history记录
$router.replace({path:'home'});替换路由,没有历史记录,点击返回,会跳转到上上一个页面
$router.addRoutes(routes: Array<RouteConfig>) 动态添加更多的路由规则,可实现用户权限
调用 this.$router.push进行连接跳转:传参的两种方式:
this.$router.push({name:xx, params:{a:xx, b:xx}})//a, b是我们要传递给另一个页面的参数
目标页面通过this.$route.params.a或this.$route.params.b来获取参数
this.$router.push({path:xx, query:{a:xx, b:xx}}) //a, b是我们要传递给另一个页面的参数
目标页面通过this.$route.query.a或this.$route.query.b来获取参数
不同点:
1、获取参数的方式不同
2、使用name-params的方式传递,参数不会显示到地址栏,而path-query则会,这个区别类似于post与get。
前置全局守卫beforeEach的使用
使用场景: 一般用在跳转前需要做校验的地方,如:进入首页前做登录校验,如果已登录则跳转首页,否则跳转登录页
使用的地方:
如果是做跳转首页前做登录校验,需要写在main.js文件中,表示在所有路由被访问之前做校验;
import Vue from 'vue' import App from './App' import router from './router' import config from './util/config' import axios from 'axios' import Cookies from 'js-cookie' import iView from 'iview'; import 'iview/dist/styles/iview.css'; import Home from './components/Home' import Login from './components/login/login' Vue.use(iView); Vue.config.productionTip = false; Vue.prototype.baseUrl = config.baseUrl; Vue.prototype.$http = axios; Vue.prototype.Cookies = Cookies; //路由跳转前做判断 router.beforeEach((to, from, next) => { let hasLogin = Cookies.get('hasLogin'); //从cookies中获取是否已登陆过的信息 if(hasLogin){ //如果已经登录,则直接跳转 next() } else if(to.path == '/Home'){ //如果未跳转,且访问的是首页,则重定向到登录页 next({ replace:true, name:'login', }) }else{ next() } } }) /* eslint-disable no-new */ new Vue({ el: '#app', router, components: { App }, template: '<App/>' })
vue异步组件:vue-router配置路由 , 使用vue的异步组件技术 , 可以实现按需加载 。但是,这种情况下一个组件生成一个js文件
/* vue异步组件技术 */
{
path: '/home',
name: 'home',
component: resolve => require(['@/components/home'],resolve)
},{
path: '/index',
name: 'Index',
component: resolve => require(['@/components/index'],resolve)
},{
path: '/about',
name: 'about',
component: resolve => require(['@/components/about'],resolve)
}
es提案的import()
// 下面2行代码,没有指定webpackChunkName,每个组件打包成一个js文件。 /* const Home = () => import('@/components/home') const Index = () => import('@/components/index') const About = () => import('@/components/about') */ // 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。 把组件按组分块 const Home = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/home') const Index = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/index') const About = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/about') { path: '/about', component: About }, { path: '/index', component: Index }, { path: '/home', component: Home }
webpack的require.ensure()
vue-router配置路由,使用webpack的require.ensure技术,也可以实现按需加载。这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。
/* 组件懒加载方案三: webpack提供的require.ensure() */
{
path: '/home',
name: 'home',
component: r => require.ensure([], () => r(require('@/components/home')), 'demo')
}, {
path: '/index',
name: 'Index',
component: r => require.ensure([], () => r(require('@/components/index')), 'demo')
}, {
path: '/about',
name: 'about',
component: r => require.ensure([], () => r(require('@/components/about')), 'demo-01')
}
父组件:beforeCreate
父组件:created
父组件:beforeMount
子组件:beforeCreate
子组件:created
子组件:beforeMount
子组件:mounted
父组件:mounted
父子组件 props和emits
跨级访问父组件的数据,多层组件嵌套 inject和provide
自定义事件 event.$on event.$emit event.$off
所有组件都可以使用,可持久化存储数据 vuex
全局路由守卫(全局前置守卫、全局后置守卫),组件内路由守卫,路由独享守卫(是在路由配置页面单独给路由配置的一个守卫)
一.全局守卫
router.beforeEach((to,from,next)=>{}) 回调函数中的参数,to:进入到哪个路由去,from:从哪个路由离开,next:函数,决定是否展示你要看到的路由页面
如下例:main.js中设置全局守卫
router.beforeEach((to,from,next)=>{
if(to.path == '/login' || to.path == '/register'){
next();
}else{
alert('您还没有登录,请先登录');
next('/login');
}
})
router.afterEach((to,from)=>{}) 全局后置钩子,只有两个参数,to:进入到哪个路由去,from:从哪个路由离。
如下,每次切换路由时,都会弹出alert,点击确定后,展示当前页面
router.afterEach((to,from)=>{
alert("after each");
})
二.组件内的守卫
到达这个组件时,beforeRouteEnter:(to,from,next)=>{}
如下例,data 组件内守卫有特殊情况,如果我们直接以
beforeRouteEnter:(to,from,next)=>{ alert(“hello” + this.name);}进行访问admin页面,会发现alert输出hello undefined。这是因为,现在访问不到我们的data属性,执行顺序是不一致,这与的声明周期有关。在执行完之前,data数据还未渲染。所以这里,next()会给一个对应的回调,帮助完成。
<script>
export default {
data(){
return{
name:"Arya"
}
},
beforeRouteEnter:(to,from,next)=>{
next(vm=>{
alert("hello" + vm.name);
})
}
}
</script>
离开这个组件时,beforeRouteLeave:(to,from,next)=>{}
点击其他组件时,判断是否确认离开。确认执行next();取消执行next(false),留在当前页面。
beforeRouteLeave:(to,from,next)=>{
if(confirm("确定离开此页面吗?") == true){
next();
}else{
next(false);
}
}
三、路由独享的守卫
beforeEnter:(to,from,next)=>{},用法与全局守卫一致。只是,将其写进其中一个路由对象中,只在这个路由下起作用。
方案一:
1,去掉编译文件中map文件。在编译好后,我们会看到文件夹下有特别多的.map文件,这些文件主要是帮助我们线上调试代码,查看样式。所以为了避免部署包过大,通常都不生成这些文件。
在 config/index.js 文件中将productionSourceMap 的值设置为false. 再次打包就可以看到项目文件中已经没有map文件 (文件大小 35MB–>10.5MB)
2,vue-router 路由懒加载
/* vue异步组件技术 */
{
path: '/index',
name: 'index',
component: resolve => require(['@/views/index'],resolve)
}
// 下面2行代码,没有指定webpackChunkName,每个组件打包成一个js文件。
const Home = () => import('@/components/home')
const Index = () => import('@/components/index')
// 下面2行代码,指定了相同的webpackChunkName,会合并打包成一个js文件。 把组件按组分块
const Home = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/home')
const Index = () => import(/* webpackChunkName: 'ImportFuncDemo' */ '@/components/index')
// router
{
path: '/about',
component: About
}, {
path: '/index',
component: Index
}
/* 组件懒加载方案三: webpack提供的require.ensure() */
{
path: '/home',
name: 'home',
component: r => require.ensure([], () => r(require('@/components/home')), 'demo')
}, {
path: '/index',
name: 'Index',
component: r => require.ensure([], () => r(require('@/components/index')), 'demo')
}
方案二: 使用CDN减小代码体积加快请求速度
在性能方面,对比Vue2.x,性能提升了1.3~2倍左右;打包后的体积也更小了,如果单单写一个HelloWorld进行打包,只有13.5kb;加上所有运行时特性,也不过22.5kb
可替代beforeCreate,created生命周期
同时为了命名的统一,将beforeDestroy改名为beforeUnmount,destroyed改名为unmounted
vue3新增了生命周期钩子,我们可以通过在生命周期函数前加on来访问组件的生命周期,我们可以使用以下生命周期钩子:
onBeforeMount
onMounted
onBeforeUpdate
onUpdated
onBeforeUnmount
onUnmounted
onErrorCaptured
onRenderTracked
onRenderTriggered
import { onBeforeMount, onMounted } from "vue";
export default {
setup() {
console.log("----setup----");
onBeforeMount(() => {
// beforeMount代码执行
});
onMounted(() => {
// mounted代码执行
});
},
}
Tree-Shaking带来的bundle体积更小
在Vue3中,所有的API都通过ES6模块化的方式引入,这样就能让webpack或rollup等打包工具在打包时对没有用到API进行剔除,最小化bundle体积
export default {
setup() {
const { networkState } = useNetworkState();
const { user } = userDeatil();
const { list } = tableData();
return {
networkState,
user,
list,
};
},
};
function useNetworkState() {}
function userDeatil() {}
function tableData() {}
灵活选择
在开发大型开发项目时,使用TypeScript更加合适。如果有一个相对较小的编码项目,似乎没有必要使用TypeScript,只需使用JavaScript。
写法区别 代码
<template> <div class="home"> </div> </template> <script lang="ts"> import { Component, Vue, Prop, PropSync, Model, Watch, Provide, Inject, Emit, Ref } from 'vue-property-decorator'; import HelloWorld from '@/components/HelloWorld.vue'; // @ is an alias to /src const symbol = Symbol('baz'); @Component({ components: { HelloWorld, }, }) export default class Home extends Vue { @Prop(Number) readonly propA: number | undefined; @Prop({ default: 'default value' }) readonly propB!: string; @Prop([String, Boolean]) readonly propC: string | boolean | undefined; @PropSync('name', { type: String }) syncedName!: string; @Model('change', { type: Boolean }) readonly checked!: boolean; @Watch('child') onChildChanged(val: string, oldVal: string) {} @Watch('person', { immediate: true, deep: true }) onPersonChanged1(val, oldVal) {} @Watch('person') onPersonChanged2(val, oldVal) {} @Inject() readonly foo!: string; @Inject('bar') readonly bar!: string; @Inject({ from: 'optional', default: 'default' }) readonly optional!: string; @Inject(symbol) readonly baz!: string; @Provide() foo1 = 'foo'; @Provide('bar') baz1 = 'bar'; count = 0; @Emit() addToCount(n: number) { this.count += n; } @Emit('reset') resetCount() { this.count = 0; } @Emit() returnValue() { return 10; } @Emit() onInputChange(e) { return e.target.value; } @Emit() promise() { return new Promise(resolve => { setTimeout(() => { resolve(20) }, 0) }) } @Ref() readonly anotherComponent; @Ref('aButton') readonly button!: HTMLButtonElement; mounted() { console.log('mounted'); } } </script>
<template> <div class="home"> </div> </template> <script lang="js"> import HelloWorld from '@/components/HelloWorld.vue'; const symbol = Symbol('baz'); export default { components: { HelloWorld, }, props: { propA: { type: Number, }, propB: { default: 'default value', }, propC: { type: [String, Boolean], }, // @PropSync name: { type: String, }, // @Model checked: { type: Boolean, }, }, // @Model model: { prop: 'checked', event: 'change', }, inject: { foo: 'foo', bar: 'bar', optional: { from: 'optional', default: 'default' }, [symbol]: symbol }, data() { return { foo: 'foo', baz: 'bar' } }, provide() { return { foo1: this.foo, bar1: this.baz } }, computed: { // @PropSync syncedName: { get() { return this.name; }, set(value) { this.$emit('update:name', value); } }, // @Ref anotherComponent: { cache: false, get() { return this.$refs.anotherComponent; } }, button: { cache: false, get() { return this.$refs.aButton as HTMLButtonElement; } }, }, watch: { child: [ { handler: 'onChildChanged', immediate: false, deep: false } ], person: [ { handler: 'onPersonChanged1', immediate: true, deep: true }, { handler: 'onPersonChanged2', immediate: false, deep: false } ] }, methods: { // @Watch onChildChanged(val, oldVal) {}, onPersonChanged1(val, oldVal) {}, onPersonChanged2(val, oldVal) {}, // @Emit addToCount(n) { this.count += n; this.$emit('add-to-count', n) }, resetCount() { this.count = 0; this.$emit('reset') }, returnValue() { this.$emit('return-value', 10) }, onInputChange(e) { this.$emit('on-input-change', e.target.value, e) }, promise() { const promise = new Promise(resolve => { setTimeout(() => { resolve(20) }, 0) }); promise.then(value => { this.$emit('promise', value) }) } }, mounted() { console.log('mounted'); } } </script>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。