@jsongao98
2021-04-24T12:17:00.000000Z
字数 13204
阅读 51
JavaScript
vue实例在初始化时候会使用Object.defineProperty()遍历data中的所有property,设置getter/setter(访问器属性)
初始化完之后才是生命周期钩子函数beforeCreated()
对于对象,数组,因为存的是引用,所以无法检测变化。对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。
已存在的对象动态添加响应式属性:
单个值的属性 : vm.$set(this.someObject,key,value)
想添加的属性是对象? : this.someObject = Object.assign( {},this.someObject,{......} )
数组
想通过数组索引添加响应值:还是用vm.$set(this.someArray,index,value)
改变了数组长度?:通过splice方法
通过new Vue创建的根实例,是Vue类,组件是通过Vue.extends继承自Vue类:在渲染组件的时候三步走,初始化子类(组件)构造函数,安装组件钩子函数和实例化 vnode。
$
开头的值Vue.prototype.$globalValue = 'Global Scope!';
可以在任何组件上使用它,通过this.$globalValue。 provide/inject: 不同于props只能获取上一级父组件,只要父组件provide的变量,子组件无论嵌套多深多可以inject这个变量。
event bus: 非父子组件的通信,虽然简便但是项目一旦数据多了就难以维护。
复杂情况: vuex
1.在表单控件中使用:
v-model 在内部为不同的输入元素使用不同的 property 并抛出不同的事件:
text 和 textarea 元素使用 value property 和 input 事件;
checkbox 和 radio 使用 checked property 和 change 事件;
select 字段将 value 作为 prop 并将 change 作为事件。
value + input 以及 checked || selected + change的语法糖
语法糖: v-model="x" 相当于 :value = "x" @input = (value) => "x"= value
将model层的变量与表单控件的value||checked||selected绑定
并监听控件上的input||change事件,事件接受一个参数(value),赋值给model层变量
2.在组件中使用:
组件内this.$emit('input',this.value),组件上v-model 监听我们自定义的input事件
自定义组件model选项:
Vue.component('base-checkbox', {
model: {
prop: '2checked',//组件上默认使用value的prop名和input事件,自定义我们要绑定的prop名
event: 'change'//自定义要emit的事件
},
props: {
1checked: Boolean//从组件上传入的值
},
template: `
<input
type="checkbox"
v-bind:2checked="1checked"
v-on:change="$emit('change', $event.target.2checked)"
>
`
})
<base-checkbox v-model="lovingVue"></base-checkbox>//
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 <base-checkbox> 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的 property 将会被更新。
注意你仍然需要在组件的 props 选项里声明 checked 这个 prop。
新增.sync修饰符:(提供一种双向绑定)
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
<text-document v-bind:title.sync="doc.title"></text-document> //上边例子的语法糖
<text-document v-bind.sync="doc"></text-document>
这样会把 doc 对象中的每一个 property (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。注意不能使用对象字面量语法。
Vue采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter(或通过遍历对象和数组),在数据变动时发布消息给订阅者,触发相应的监听回调。
参考:https://ustbhuangyi.github.io/vue-analysis/v2/reactive/
View其实可以理解为Vnode,我们不是直接操作DOM。视图的更新一般我们通过事件监听来更新data。双向绑定的原理主要体现在data更新到Vnode更新,也就是所谓响应式,数据驱动。
实现一个Observer监听器
非Vnode对象类型
添加Observer,Observer是一个类,类中我们使用了Object.defineProperty()
为每个属性添加getter(依赖收集)/setter(派发更新)。defineReactive函数
,采用遍历+递归
(假如有嵌套的对象)的方式为每个(子)对象的属性添加Oberver。遍历数组
,跟普通属性一样调用observe函数为每一项添加Observer。 let dep = new Dep()
1 自身subs属性为一个数组
,在数据变动的时候发布通知。 dep.addsub(Dep.target)
) 1全局唯一一个Watcher
)1dep.notify()
方法 2 Dep.target = this
1dep.addsub(Dep.target)
,把当前Watcher添加进订阅者数组中。1Dep.target = null
释放自己1dep.notify()
方法被调用后,把所有订阅者(subs数组
)添加到一个队列,先排序(父组件子组件update顺序,等等...)后遍历队列,在nextTick中一一调用Watcher的update方法。然后再进行Vnode的diff,patch。所以数据的变化到DOM渲染是个异步过程。2 实现一个Compiler
computed
watch
https://github.com/aooy/blog/issues/2
生命周期钩子 | 组件状态 | 最佳实践 |
---|---|---|
beforeCreated | 实例初始化之后,this指向创建的实例,不能访问到data、computed、watch、methods上的方法和数据 | 常用于初始化非响应式变量 |
created | 实例创建完成,可访问data、computed、watch、methods上的方法和数据,未挂载到DOM,不能访问到$el属性,$ref属性内容为空数组 | 常用于简单的ajax请求,页面的初始化 |
beforeMount | 在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数 | -- |
mounted | 实例挂载到DOM上,此时可以通过DOM API获取到DOM节点,$ref属性可以访问 | 常用于获取VNode信息和操作,ajax请求 |
beforeUpdate | 响应式数据更新时调用,发生在虚拟DOM打补丁之前 | 适合在更新之前访问现有的DOM,比如手动移除已添加的事件监听器 |
updated | 虚拟 DOM 重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作 | 避免在这个钩子函数中操作数据,可能陷入死循环 |
beforedDestory | 实例销毁之前调用。这一步,实例仍然完全可用,this仍能获取到实例 | 常用于销毁定时器、解绑全局事件、销毁插件对象等操作 |
destoryed | 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 | -- |
vm.$nextTick | 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。 | -- |
注意:
1. created阶段的ajax请求与mounted请求的区别:前者页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态
2.mounted 不会承诺所有的子组件也都一起被挂载(如果子组件包含异步组件)。如果你希望等到整个视图都渲染完毕,可以用 vm.$nextTick,但异步组件(通过() => import(...))也是和nextTick中的callback函数一样存在于微任务队列中,执行分先后顺序,有时会导致nextTick被执行时候,异步组件的加载任务还停留在微任务队列中,导致callback无法获取异步组件中的内容。
3.vue2.0之后主动调用$destroy()不会移除dom节点,作者不推荐直接destroy这种做法,如果实在需要这样用可以在这个生命周期钩子中手动移除dom节点
有关组件生命周期参考:https://juejin.cn/post/6844903602356502542#heading-3
一方面是出于性能方面的考量:
- 创建真实DOM的代价高:真实的 DOM 节点 node 实现的属性很多,而 vnode 仅仅实现一些必要的属性,相比起来,创建一个 vnode 的成本比较低。
- 触发多次浏览器重绘及回流:使用 vnode ,相当于加了一个缓冲,让一次数据变动所带来的所有 node 变化,先在 vnode 中进行修改,然后 diff 之后对所有产生差异的节点集中一次对 DOM tree 进行修改,以减少浏览器的重绘及回流
但是性能受场景的影响是非常大的,不同的场景可能造成不同实现方案之间成倍的性能差距,所以依赖细粒度绑定及 Virtual DOM哪个的性能更好不是一个容易下定论的问题。
更重要的原因是为了解耦HTML依赖,这带来两个非常重要的好处是:
- 不再依赖 HTML 解析器进行模版解析,可以进行更多的 AOT 工作提高运行时效率:通过模版 AOT 编译,Vue 的运行时体积可以进一步压缩,运行时效率可以进一步提升;
- 可以渲染到 DOM 以外的平台,实现 SSR、同构渲染这些高级特性,Weex 等框架应用的就是这一特性。
综上,Virtual DOM 在性能上的收益并不是最主要的,更重要的是它使得 Vue 具备了现代框架应有的高级特性。
- Virtual DOM 除了它的数据结构的定义,映射到真实的 DOM 实际上要经历 VNode 的 create、diff、patch 等过程。那么在 Vue.js 中,VNode 的 create 是通过之前提到的 createElement 方法创建的,
window.onhashchange = function(event){
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
document.body.style.color = hash;
}
//在es6模块化语法下
import router from "vue-router"
Vue.use(router)//模块机制下需要使用Vue.use(..)
export default new router(
mode:history/hash/abstract,
routes:[
{
path:"...",
component: () => import('....'),//动态传入组件,异步加载
children://嵌套路由
name://命名路由
},//路由route1
{....}//路由route2
]
)
import router from "..."//引入创建router实例,配置路由的js脚本
new Vue = {
router //注入
}.$mount('#app')
<router-view/>
渲染最高级路由匹配到的组件/同样地,一个被渲染组件同样可以包含自己的嵌套 <router-view/>
或者<router-link>
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,//没有name的router-view为default
a: Bar,
b: Baz
}
}
]
})
https://www.codenong.com/cs106288371/
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
不过,你可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。
beforeRouteEnter (to, from, next) {
next(vm => {
// 通过 `vm` 访问组件实例
})
}
注意 beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations([
'increment', // 将 `this.increment()` 映射为 `this.$store.commit('increment')`
// `mapMutations` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.commit('incrementBy', amount)`
]),
...mapMutations({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.commit('increment')`
})
}
}
state.obj = { ...state.obj, newProp: 123 }
// 假设 getData() 和 getOtherData() 返回的是 Promise
actions: {
async actionA ({ commit }) {
commit('gotData', await getData())
},
async actionB ({ dispatch, commit }) {
await dispatch('actionA') // 等待 actionA 完成
commit('gotOtherData', await getOtherData())
}
}
import { mapActions } from 'vuex'
export default {
// ...
methods: {
...mapActions([
'increment', // 将 `this.increment()` 映射为 `this.$store.dispatch('increment')`
// `mapActions` 也支持载荷:
'incrementBy' // 将 `this.incrementBy(amount)` 映射为 `this.$store.dispatch('incrementBy', amount)`
]),
...mapActions({
add: 'increment' // 将 `this.add()` 映射为 `this.$store.dispatch('increment')`
})
}
}
vue-loader 是一个 webpack 的 loader,可以将单文件组件SFC转换为 JavaScript 模块
- 默认支持 ES2015;
- 允许对 Vue 组件的组成部分使用其它 webpack loader,比如对
<style>
使用 Sass 和对<template>
使用 Jade;
.vue 文件中允许自定义节点,然后使用自定义的 loader 进行处理;- 把
<style>
和<template>
中的静态资源当作模块来对待,并使用 webpack loader 进行处理;- 对每个组件模拟出 CSS 作用域;
- 支持开发期组件的热重载。
v-on 缩写@
在内联语句处理器中访问原始DOM事件,可以用特殊变量
$event传入方法内
<input v-on:keyup.page-down="onPageDown">//
在上述示例中,处理函数只会在 $event.key 等于 PageDown 时被调用。
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
.ctrl
.alt
.shift
<!-- 即使 Alt 或 Shift 被一同按下时也会触发 -->
<button v-on:click.ctrl="onClick">A</button>
<!-- 有且只有 Ctrl 被按下的时候才触发 -->
<button v-on:click.ctrl.exact="onCtrlClick">A</button>
<!-- 没有任何系统修饰符被按下的时候才触发 -->
<button v-on:click.exact="onClick">A</button>
新增$listener对象:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners"传入内部组件——在创建更高层次的组件时非常有用
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
computed: {
inputListeners: function () {
var vm = this
// `Object.assign` 将所有的对象合并为一个新对象
return Object.assign({},
// 我们从父级添加所有的监听器
this.$listeners,
// 然后我们添加自定义监听器,
// 或覆写一些监听器的行为
{
// 这里确保组件配合 `v-model` 的工作
input: function (event) {
vm.$emit('input', event.target.value)
}
}
)
}
},
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on="inputListeners"
>
</label>
`
})
具名插槽:template里的内容代替带有该name属性的slot
作用域插槽:
在子组件模板中,<slot>
绑定attribute,在父组件模板中,插槽template中通过插槽slotProps.attribute
来访问子组件作用域的变量。
父组件模板中
<子组件>
<template v-slot:header = "slotProps">
slotProps.attribute1/2/...//来访问子组件作用域的变量的内容
</template>
<子组件>
子组件模板中
<slot name="header" :attribute1="变量1" :attribute2="变量2" ....>默认内容</slot>
作用域插槽的内部工作原理是将你的插槽内容包裹在一个拥有单个参数的函数里:
function (slotProps) {
// 插槽内容
}
这意味着 v-slot 的值实际上可以是任何能够作为函数定义中的参数的 JavaScript 表达式。所以在支持的环境下 (单文件组件或现代浏览器),你也可以使用 ES2015 解构来传入具体的插槽 prop,
slotProps:{
attr1:{},
attr2:{},
...
}