vue的data是函数的原因:
1.Object
是引用数据类型,指向地址为同一个,造成多个实例的data混乱
2.函数作用域独立每个实例的data数据。
3.根本在于js特性导致的
Object.defineProperty()
的作用就是直接在一个对象上定义一个新属性
,或者修改
一个已经存在的属性
Object.defineProperty(obj, prop, desc)
let Person = {}
Object.defineProperty(Person, 'name', {
value: 'jack',
writable: true // 是否可以改变
})
let Person = {}
let temp = null
Object.defineProperty(Person, 'name', {
get: function () {
return temp
},
set: function (val) {
temp = val
}
})
属性定义,通过Object.defineProperty()形式
如果Obj没有名为Prop的自身属性的话:如果Obj是可扩展的话,则创建Prop这个自身属性,否则拒绝
如果Obj已经有了名为Prop的自身属性:则按照下面的步骤重新配置这个属性
如果这个已有的属性是不可配置的,则进行下面的操作会被拒绝
defineProperty
不能检测到数组长度
的变化,监听数组所有索引
的的代价也比较高,可通过修改数组
的一些方法(push、pop、shift、unshift、splice、sort、reverse
)去监听,但避免直接修改原型:
第一步:先获取原生 Array 的原型方法,因为拦截后还是需要原生的方法帮我们实现数组的变化。
第二步:对 Array 的原型方法使用 Object.defineProperty 做一些拦截操作。
第三步:把需要被拦截的 Array 类型的数据原型指向改造后原型。
// 将data中我们定义的每个属性进行响应式绑定
export function observe (data) {
const keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {
// 如果是数组
if (Array.isArray(keys[i])) {
observeArray(keys[i]);
} else {
// 如果是对象
defineReactive(obj, keys[i]);
}
}
}
// 数组的处理
export function observeArray () {
// ...省略
}
function def (obj, key) {
Object.defineProperty(obj, key, {
writable: true,
enumerable: true,
configurable: true,
value: function(...args) {
console.log('key', key);
console.log('args', args);
}
});
}
// 重写的数组方法
let obj = {
push() {}
}
// 数组方法的绑定
def(obj, 'push');
vue响应式原理: https://cn.vuejs.org/v2/guide/reactivity.html
众所周知,vue2.0 的核心原理就是利用Object.defineProperty()
; 可以重新定义数据的属性,给属性增加 getter
和 setter
// 首先先定义一个 对象数据
let data = {name :'yolin'};
// 那么我们需要对这个数据进行观察;
// 定义一个 observer() 函数
observer(data);
- observer函数
function observer(target) {
// 首先我们要判断下传递进来的参数是否是对象,所以就有了下面的if判断
if(typeof target !== 'object' && target === null){
// 如果传递进来的参数不是对象或者是null,则直接滚粗,return出去
return target;
}
// 如果传递进来的参数是对象数据结构, 那就循环,抡它!
for (let key in target) {
// 在调用一个 下面说到的一个 核心函数了
// 三个参数 键值对, 属性, 值
defineReactive(target,key,target[key]);
}
}
- defineReactive函数
// 开始重写数据的属性,比如get 获取属性值,以及set 设置属性值
function defineReactive(target, key, value) {
// 如果是多层数据,比如 let person = {name: 'bryant',age:{n:24}};
//这种数据类型 在data中的age属性还是一个对象,即为多层嵌套,所以我们要对这样的value 再次进行观察
if (typeof value === 'object' && value !== null) {
observer(value);
}
// 核心来了
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
if (typeof value === 'object' && value !== null) {
observer(value);
}
updateView();
value = newValue();
}
},
})
- updateView 函数(视图更新触发的函数)
function updateView () {
console.log("更新视图");
}
- vue2.0缺陷,如果数据层级过多,则会出现大量的递归,造成内存性能损耗
vue-router有router-link
链接、router-view
试图 组件
$route
为当前router
跳转对象里面可以获取name、path、query、params
等
$router
为VueRouter
实例,想要导航到不同URL
,则使用$router.push
方法
keep-alive
用于缓存组件 <transition name="bounce">
过渡
路由懒加载
const Foo = () => import('./Foo.vue')
const router = new VueRouter({
routes: [
{ path: '/foo', component: Foo }
]
})
vue-router传参:
传一个值时,用params
配置路由格式:/router/:id
// $routes耦合
const User = {
template: '<div>User {{ $route.params.id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User }
]
})
// props 解耦
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
//函数模式
const router = new VueRouter({
routes: [
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
]
})
如果传入数据较多时,用query
,因为query传入的是一个对象
配置路由格式:/router,也就是普通配置
<router-link :to="{path:'/profile',query={name:'why',age=18,height=1.88}}">档案</router-link>
获取参数:this.$route.query.name
const router = new VueRouter({
mode: 'history',// 防止404
routes: [
{ path: '*', component: NotFoundComponent }
]
})
组件之间传参:
八种通信方式
1. props 、$emit 子组件中书写
props: ['message']
<child :message="msg"></child>
2. children 、children、parent
3. ref
<child ref="msg"></child>
this.$refs.msg
4. provide 、reject
5. Vuex
state:页面状态管理容器对象
commit:状态改变提交操作方法。对mutation进行提交,是唯一能执行mutation的方法。
mutations:存改变状态的操作方法
getters:读取state中的数据
6. $attrs 与listenters
7. eventBus
8. localStorage 、sessionStorage
父子组件通信: props; $parent / $children; provide / inject ; ref ; $attrs / $listeners
兄弟组件通信: eventBus ; vuex
跨级通信: eventBus;Vuex;provide / inject 、$attrs / $listeners
组件复用时,路由传参a的变化可以通过beforeRouteUpdate或者watch (监测变化) $route 对象
const router = new VueRouter({ ... })
vue-router有哪几种导航钩子( 导航守卫 )?
1、全局守卫: router.beforeEach next() 必须
2、全局解析守卫: router.beforeResolve
3、全局后置钩子: router.afterEach
4、路由独享的守卫: beforeEnter
5、组件内的守卫: beforeRouteEnter next()、beforeRouteUpdate (2.2 新增)、beforeRouteLeave
vue-router响应路由参数的变化:
一是用watch监听
在一个就是在父组件的router-view上加个key <router-view :key="$route.fullPath"></router-view>
vue生命周期.png
beforeCreate:function(){}
//组件实例化之前执行的函数
created:function(){}
//组件实例化完毕,但是页面没有显示
beforeMount:function(){}
//组件挂载前,页面还没有展示,但是虚拟的DOM已经配置
mounted:function(){}
//组件挂载后,这个方法执行后,页面显示
beforeUpdate:function(){}
//当页面操作后,组件更新前,页面没有显示,此时虚拟DOM已经挂载
updated:function(){}
//组件更新完毕,页面已经显示
beforeDestroy:function(){}
//组件销毁之前
destroyed:function(){}
//组件销毁之后
vue数据双向绑定
是通过数据劫持
结合发布者-订阅者模式
的方式来实现的。
1.使用Object.defineProperty
进行数据劫持
,把data对象
,computed
等里的所有属性进行数据劫持。
2.使用观察者模式
,完成发布订阅
。
模板
里使用data对象属性
的dom对象
都订阅
。
当data对象
里的属性的值
发生变化
时,就会发布
,发布时,就改变了dom
里的内容。
原理对data的getter/setter
方法进行拦截(Object.defineProperty
或者Proxy
),利用发布订阅
的设计模式,在getter
方法中进行订阅
,在setter
方法中发布
通知,让所有订阅者
完成响应
监听器watch
、计算属性computed
、视图渲染template/render
三个角色同时作为订阅者.前者直接操作属性,后两者是操作属性getter
vue怎么操作dom:(在mounted钩子进行挂载后)
1.ref="idRef" =>this.$refs.idRef.style.color='red';(子组件ref报错)
[2.id=](http://2.id)"id". => document.querySelector("#id").innerHTML="xxxx"
如果一个数据需要经过复杂计算就用 computed
如果一个数据需要被监听并且对数据做一些操作就用 watch
methods: 存放的方法是一些内部方法、事件的回调、命令是调用的方法。
watch: 用于监听数据的实时的变化。在数据变化的回调中执行异步操作或者开销很大的时候使用。
computed: 也是实时监听数据变化,做出相应的变化,跟watch不同的是他可以被看成一个data里面的属性值来使用。所以当我们需要监听一个值并且需要生成一个新的属性时就可以使用computed。
计算属性computed和监听器watch都可以观察属性的变化从而做出响应,不同的是:
计算属性computed更多是作为缓存功能的观察者,它可以将一个或者多个data的属性进行复杂的计算生成一个新的值,提供给渲染函数使用,当依赖的属性变化时,computed不会立即重新计算生成新的值,而是先标记为脏数据,当下次computed被获取时候,才会进行重新计算并返回。
而监听器watch并不具备缓存性,监听器watch提供一个监听函数,当监听的属性发生变化时,会立即执行该函数。
computed:适用单一值依赖多值进行变化的场景 具有缓存性 必须用return返回 为了进行不必要的资源消耗选择用计算属性 变量在computed中定义
watch:自动执行 newVal和oldVal参数且并不一定需要return 适用多值依赖单一值进行变化的场景 属性监听在data中定义
vif与vshow的区别: if操作dom是否渲染 show修改css display
登陆前验证计算 beforeRouteUpdate
路由守卫~监听路由 全局的beforeEach(next) afterEach, 单个路由独享的beforeEnter , 或者组件级的beforeRouteEnter(next) beforeRouteUpdate beforeRouteLeave
数据初始化通常在mounted 因为create无法不能操作dom
组件通信:props、$emit/$on、vuex、$parent / $children、$attrs/$listeners和provide/inject
<meta charset="utf-8">
父组件 -> 子组件:prop
子组件 -> 父组件:on/emit
兄弟组件通信:
Event Bus:每一个Vue实例都是一个Event Bus,都支持
emit,可以为兄弟组件的实例之间new一个Vue实例,作为Event Bus进行通信。
跨级组件通信:使用provide/inject
1.父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
vuex:
Vuex实现了一个单向数据流,在全局拥有一个State存放数据,当组件要更改State中的数据时,必须通过Mutation进行,Mutation同时提供了订阅者模式供外部插件调用获取State数据的更新。而当所有异步操作(常见于调用后端接口异步获取更新数据)或批量的同步操作需要走Action,但Action也是无法直接修改State的,还是需要通过Mutation来修改State的数据。最后,根据State的变化,渲染到视图上。
$attrs , $listeners 来传递数据与事件,跨级组件之间的通讯
$attrs与$listeners 是两个对象,$attrs 里存放的是父组件中绑定的非 Props 属性,$listeners里存放的是父组件中绑定的非原生事件。
父子通信:
父向子传递数据是通过 props,子向父是通过 events($emit);通过父链 / 子链也可以通信($parent / $children);ref 也可以访问组件实例;provide / inject API;$attrs/$listeners
兄弟通信:Bus;Vuex
跨级通信:
Bus;Vuex;provide / inject API、$attrs/$listeners
Vues.png
vue 渲染原理:
template
无法被浏览器解析并渲染,所以需要先编译成js函数render
:
解析parse
,优化optimize
,生成generate
,最终生成可执行函数render
。
template
和jsx
的都是render
的一种表现形式
Virtual DOM
是 DOM 节点在JavaScript
中的一种抽象数据结构
虚拟DOM
的作用是在每一次响应式数据
发生变化
引起页面重渲染时,Vue对比更新前后
的虚拟DOM
,匹配找出尽可能少的需要更新的真实DOM
,从而达到提升性能
的目的。
在对节点进行diff
的过程中,判断是否为相同节点
的一个很重要的条件是key是否相等
,如果是相同节点,则会尽可能的复用原有的DOM节点
。所以key属性
是提供给框架在diff
的时候使用的
网友评论