那么,它有什么缺点?
首先你得说说相同点,两个都是MVVM框架,数据驱动视图,无争议。如果说不同,那可能分为以下这么几点:
vue是完整一套由官方维护的框架,核心库主要有由尤雨溪大神独自维护,而react是不要脸的书维护(很多库由社区维护),曾经一段时间很多人质疑vue的后续维护性,似乎这并不是问题。
vue上手简单,进阶式框架,白话说你可以学一点,就可以在你项目中去用一点,你不一定需要一次性学习整个vue才能去使用它,而react,恐怕如果你这样会面对项目束手无策。
语法上vue并不限制你必须es6+完全js形式编写页面,可以视图和js逻辑尽可能分离,减少很多人看不惯react-jsx的恶心嵌套,毕竟都是作为前端开发者,还是更习惯于html干净。
很多人说react适合大型项目,适合什么什么,vue轻量级,适合移动端中小型项目,其实我想说,说这话的人是心里根本没点逼数,vue完全可以应对复杂的大型应用,甚至于说如果你react学的不是很好,写出来的东西或根本不如vue写的,毕竟vue跟着官方文档撸就行,自有人帮你规范,而react比较懒散自由,可以自由发挥
vue在国内人气明显胜过react,这很大程度上得益于它的很多语法包括编程思维更符合国人思想
MVVM的核心是数据驱动
即ViewModel,ViewModel
是View和Model的关系映射
。
MVVM本质就是基于操作数据
来操作视图
进而操作DOM
,借助于MVVM无需直接
操作DOM,开发者只需编写ViewModel
中有业务
,使得View完全实现自动化
。
SPA( single-page application )即一个web项目
就只有一个页面
(即一个HTML文件,HTML 内容的变换是利用路由机制实现的。
仅在 Web 页面初始化
时加载
相应的 HTML、JavaScript 和 CSS
。一旦页面加载完成
,SPA 不会
因为用户的操作
而进行页面的重新加载或跳转
;取而代之的是利用路由机制
实现 HTML 内容
的变换,UI 与用户的交互,避免页面的重新加载。
优点:
用户体验好、快
,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;服务器压力小
;职责分离
,架构清晰
,前端进行交互逻辑,后端负责数据处理;缺点:
初次加载耗时多
:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;前进后退路由管理
:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
SEO 难度较大
:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。
什么是vue生命周期?
Vue 实例
从创建
到销毁
的过程
,就是生命周期。
注意:浏览器有8个钩子,但是node
中做服务端渲染的时候只有beforeCreate
和created
beforeCreate是new Vue()
之后触发的第一个
钩子,在当前阶段data、methods、computed以及watch上的数据和方法都不能被访问。 可以做页面拦截。当进一个路由的时候我们可以判断是否有权限进去,是否安全进去,携带参数是否完整,参数是否安全。使用这个钩子好函数的时候就避免了让页面去判断,省掉了创建一个组建Vue实例。
created 发生在实例创建完成后
,当前阶段已经完成了数据观测
,也就是可以使用数据,更改数据,在这里更改
数据不会
触发updated
函数。可以做一些初始数据的获取,在当前阶段无法
与Dom
进行交互
(因为Dom还没有创建),如果非要想,可以通过vm.$nextTick
来访问Dom。
beforeMount发生在挂载之前
,在这之前template模板已导入渲染函数编译。而当前阶段虚拟Dom
已经创建完成
,即将开始渲染。在此时也可以对数据进行更改,不会触发updated。
mounted发生在挂载完成后
,在当前阶段,真实
的Dom
挂载完毕,数据完成双向绑定
,可以访问
到Dom节点
,使用$refs属性对Dom进行操作。
beforeUpdate发生在更新之前
,也就是响应式数据发生更新,虚拟dom重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
updated发生在更新完成之后
,当前阶段组件Dom已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。
beforeDestroy发生在实例销毁之前
,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器,销毁父组件对子组件的重复监听。beforeDestroy(){Bus.$off("saveTheme")}
destroyed发生在实例销毁之后,这个时候只剩下了dom空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
加载渲染过程 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount- >子mounted->父mounted
子组件更新过程 父beforeUpdate->子beforeUpdate->子updated->父updated
父组件更新过程 父 beforeUpdate -> 父 updated
销毁过程 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
它的生命周期中有多个事件钩子,让我们控制
Vue实例过程更加清晰
。
第一次页面加载时会触发 beforeCreate, created, beforeMount, mounted 这几个钩子
v-if
事件监听器
和子组件
适当地被销毁和重建
;惰性
的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。v-show
不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。
所以:
很少改变条件
,不需要
频繁切换条件的场景;非常频繁
切换条件的场景。背景:
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定
:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变
父级组件的状态,从而导致你的应用的数据流向变的混乱。
每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。子组件想修改时,只能通过 $emit
派发一个自定义事件
,父组件接收到后,由父组件修改
。
有两种常见的试图改变一个 prop 的情形 :
在第2情况下,最好定义一个本地的 data属性并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
return {
counter: this.initialCounter//定义本地的data属性接收prop初始值
}
}
这个 prop 以一种原始的值传入且需要进行转换。
在这种情况下,最好使用这个 prop 的值来定义一个计算属性
props: ['size'],
computed: {
normalizedSize: function () {
return this.size.trim().toLowerCase()
}
}
官方实例
的异步请求是在mounted
生命周期中调用的,而实际上也可以在created生命周期中调用。
本人推荐在 created 钩子函数中调用异步请求,有以下优点:
更快
获取到服务端数据
,减少
页面 loading 时间;ssr
不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于一致性;1. 父子props,on
// 子组件
{{title}}
//绑定一个点击事件
// 父组件
//与子组件titleChanged自定义事件保持一致
{{title}}
2. parent / $children与 ref
// A 子组件
export default {
data () {
return {
title: 'a组件'
}
},
methods: {
sayHello () {
alert('Hello');
}
}
}
// 父组件
3.attrs,listeners
attrs: 包含了父作用域
中不被 prop
所识别
(且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
listeners: :包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件
// index.vue
浪里行舟
// childCom1.vue
foo: {{ foo }}
childCom1的$attrs: {{ $attrs }}
// childCom2.vue
boo: {{ boo }}
childCom2: {{ $attrs }}
// childCom3.vue
childCom3: {{ $attrs }}
4. Provide、inject的使用:
父组件
子组件
-
{{ item.label }}
SSR也就是
服务端渲染
,也就是将Vue在客户端把标签
渲染成HTML
的工作放在服务端完成,然后再把html直接返回
给客户端。
服务端渲染 SSR 的优缺点如下:
(1)服务端渲染的优点:
更好的 SEO: 因为 SPA 页面的内容是通过 Ajax 获取,而搜索引擎爬取工具并不会等待 Ajax 异步完成后再抓取页面内容,所以在 SPA
中是抓取不到
页面通过 Ajax
获取到的内容
;而 SSR
是直接由服务端
返回已经渲染好
的页面(数据已经包含在页面中),所以搜索引擎爬取工具可以抓取渲染好的页面;
更快的内容到达时间(首屏加载更快): SPA
会等待
所有 Vue 编译后的 js
文件都下载完成后
,才开始
进行页面的渲染
,文件下载等需要一定的时间等,所以首屏渲染需要一定的时间;SSR 直接由服务端渲染好页面直接返回显示,无需等待下载 js 文件及再去渲染等,所以 SSR 有更快的内容到达时间;
(2) 服务端渲染的缺点:
更多的开发条件限制: 例如服务端渲染只支持 beforCreate
和 created
两个钩子函数,这会导致一些外部扩展库需要特殊处理,才能在服务端渲染应用程序中运行;并且与可以部署在任何静态文件服务器上的完全静态单页面应用程序 SPA 不同,服务端渲染应用程序,需要处于 Node.js server 运行环境;
更多的服务器负载: 在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 ( high traffic ) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。
vue-router 有 3 种路由模式:hash
、history
、abstract
,对应的源码如下所示:
switch (mode) {
case 'history':
this.history = new HTML5History(this, options.base)
break
case 'hash':
this.history = new HashHistory(this, options.base, this.fallback)
break
case 'abstract':
this.history = new AbstractHistory(this, options.base)
break
default:
if (process.env.NODE_ENV !== 'production') {
assert(false, `invalid mode: ${mode}`)
}
}
路由模式的说明如下:
hash: 使用 URL hash 值来作路由。支持所有浏览器
,包括不支持 HTML5 History Api 的浏览器;
history : 依赖 HTML5 History API 和服务器配置。具体可以查看 HTML5 History 模式;
abstract : 支持所有 JavaScript
运行环境,如 Node.js 服务器端。如果发现没有浏览器的 API,路由会自动强制进入这个模式.
(1)hash 模式的实现原理
早期的前端路由的实现就是基于 location.hash
来实现的。其实现原理很简单,location.hash 的值就是 URL 中 # 后面的内容。
比如下面这个网站,它的 location.hash 的值为 '#search'
:
https://www.word.com#search
hash 路由模式的实现主要是基于下面几个特性:
客户端
的一种状态
,也就是说当向服务器端发出请求时
,hash 部分不会被发送
;改变
,都会在浏览器的访问历史
中增加
一个记录
。因此我们能通过浏览器的回退、前进按钮控制hash 的切换;hashchange
事件来监听 hash 值的变化,从而对页面进行跳转(渲染)。(2)history 模式的实现原理
HTML5 提供了 History API
来实现 URL 的变化,其中做最主要的 API 有以下两个:
这两个 API 可以在不进行刷新
的情况下,操作
浏览器的历史纪录
。唯一不同的是,前者是新增一个历史记录,后者是直接替换当前的历史记录,如下所示:
window.history.pushState(null, null, path);
window.history.replaceState(null, null, path);
history 路由模式的实现主要基于存在下面几个特性:
popstate
事件来监听 url 的变化,从而对页面进行跳转(渲染);key 是为 Vue 中 vnode
的唯一标记
,通过这个 key,我们的 diff
操作可以更准确、更快速
。
Vue 的 diff 过程可以概括为:
oldCh
和 newCh
各有两个头尾
的变量 oldStartIndex、oldEndIndex
和 newStartIndex、newEndIndex
,它们会新节点和旧节点会进行两两对比
,即一共有4种比较方式:newStartIndex 和oldStartIndex 、newEndIndex 和 oldEndIndex 、newStartIndex 和 oldEndIndex 、newEndIndex 和 oldStartIndex,如果以上 4 种比较都没匹配
,如果设置了key
,就会用 key 再进行
比较,在比较的过程中,遍历会往中间靠
,一旦 StartIdx > EndIdx 表明 oldCh 和 newCh 至少有一个已经遍历完了,就会结束比较。
所以 Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速
更准确因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key对比中可以避免就地复用的情况。所以会更加准确。
更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快,源码如下:
function createKeyToOldIdx (children, beginIdx, endIdx) {
let i, key
const map = {}
for (i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if (isDef(key)) map[key] = i
}
return map
}
参考1:Vue2.0 v-for 中 :key 到底有什么用?
虚拟 DOM 的实现原理主要包括以下 3 部分:
详情点击这里
优点:
保证性能下限: 框架的虚拟 DOM 需要适配任何上层 API 可能产生的操作,它的一些 DOM 操作
的实现必须是普适
的,所以它的性能并不是最优的;但是比起粗暴的 DOM 操作性能要好很多,因此框架的虚拟 DOM 至少可以保证在你不需要手动优化
的情况下,依然可以提供还不错
的性能
,即保证性能的下限;
无需手动操作 DOM: 我们不再需要手动去操作 DOM,只需要写好 View-Model 的代码逻辑,框架会根据虚拟 DOM 和 数据双向绑定,帮我们以可预期的方式更新视图,极大提高我们的开发效率;
跨平台: 虚拟 DOM 本质上是 JavaScript 对象,而 DOM 与平台强相关,相比之下虚拟 DOM 可以进行更方便地跨平台操作,例如服务器渲染、weex 开发等等。
缺点:
Proxy 的优势如下:
Object.defineProperty 的优势如下:
Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。
let p = new Proxy(target, handler)
添加代理
的对象对象中
的操作
,比如可以用来自定义 set 或者 get 函数。下面来通过 Proxy 来实现一个数据响应式:
let onWatch = (obj, setBind, getLogger) => {
let handler = {
get(target, property, receiver) {
getLogger(target, property)
return Reflect.get(target, property, receiver)
},
set(target, property, value, receiver) {
setBind(value, property)
return Reflect.set(target, property, value)
}
}
return new Proxy(obj, handler)
}
let obj = { a: 1 }
let p = onWatch(
obj,
(v, property) => {
console.log(`监听到属性${property}改变为${v}`)
},
(target, property) => {
console.log(`'${property}' = ${target[property]}`)
}
)
p.a = 2 // 监听到属性a改变为2
p.a // 'a' = 2
在上述代码中,通过自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。
当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷就是浏览器的兼容性不好。
Vue 框架是通过遍历数组 和递归遍历对象,从而达到利用 Object.defineProperty() 也能对对象和数组(部分方法的操作)进行监听。
vue2:
数组
就是使用 object.defineProperty
重新定义数组的每一项
,能引起数组变化的方法为 pop 、 push 、 shift 、 unshift 、 splice 、 sort 、 reverse
这七种,只要这些方法执行改了数组内容,就更新内容
是用来函数劫持
的方式,重写
了数组方法,具体就是更改了数组的原型,更改成自己的,用户调数组的一些方法的时候,走的就是自己的方法,然后通知视图去更新(本质就是在原有的方法上又调用了更新数据的方法)。
数组项可能是对象
,那么就对数组的每一项进行观测
vue3:
改用 proxy ,可直接监听对象数组的变化。
参考
Vue 数据双向绑定主要是指:数据变化更新视图,视图变化更新数据
输入框内容变化时,Data 中的数据同步变化。即 View => Data 的变化。 Data 中的数据变化时,文本节点的内容同步变化。即 Data => View 的变化。
其中,View 变化更新 Data ,可以通过事件监听的方式来实现,所以 Vue 的数据双向绑定的工作主要是如何根据 Data 变化更新 View。
Vue 主要通过以下 4 个步骤来实现数据双向绑定的:
实现一个监听器 Observer 对数据对象进行遍历,包括子属性对象的属性,利用 Object.defineProperty()
对属性都加上 setter
和 getter
。这样的话,给这个对象的某个值赋值,就会触发 setter,那么就能监听到了数据变化。
实现一个解析器 Compile 解析 Vue 模板指令,将模板中的变量
都替换成数据
,然后初始化渲染页面视图,并将每个指令
对应的节点绑定更新函数
,添加监听数据的订阅者,一旦数据有变动,收到通知,调用更新函数进行数据更新。
实现一个订阅者 Watcher 订阅者是 Observer 和 Compile 之间通信的桥梁 ,主要的任务是订阅 Observer 中的属性值变化的消息,当收到属性值变化的消息时,触发解析器 Compile 中对应的更新函数。
实现一个订阅器 Dep 订阅器采用 发布-订阅 设计模式,用来收集订阅者 Watcher,对监听器 Observer 和 订阅者 Watcher 进行统一管理。
v-model 指令在表单 input、textarea、select 等元素上创建双向数据绑定,v-model 本质上是语法糖
,会在内部为不同的输入元素使用不同的属性并抛出不同的事件:
value
属性和 input
事件;checked
属性和 change
事件;prop
并将 change
作为事件。以 input 表单元素为例:
相当于
为什么组件中的 data 必须是一个函数,然后 return 一个对象,而 new Vue 实例里,data 可以直接是一个对象?
// data
data() {
return {
message: "子组件",
childName:this.name
}
}
// new Vue
new Vue({
el: '#app',
router,
template: ' ',
components: {App}
})
一个组件被复用多次的话,也就会创建多个实例,本质上,这些实例用的都是同一个构造函数。
如果data是对象的话,对象属于引用类型,会影响到所有的实例,所以为了保证组件不同的实例之间data不冲突,data必须是一个函数。
而 new Vue 的实例,是不会被复用的,因此不存在引用对象的问题。
keep-alive 是 Vue 内置
的一个组件
,可以使被包含
的组件保留状态
,避免重新渲染
,其有以下特性:
一般结合路由和动态组件一起使用,用于缓存组件;
提供 include
和 exclude
属性,两者都支持字符串或正则表达式, include 表示只有名称匹配
的组件会被缓存
,exclude 表示任何名称匹配
的组件都不会被缓存
,其中 exclude 的优先级
比 include 高
;
对应两个钩子函数 activated
和 deactivated
,当组件被激活
时,触发钩子函数 activated,当组件被移除
时,触发钩子函数 deactivated。
keep-alive的生命周期
比如有父组件 Parent 和子组件 Child,如果父组件监听到子组件挂载 mounted 就做一些逻辑处理,可以通过以下写法实现:
// Parent.vue
// Child.vue
mounted() {
this.$emit("mounted");
}
以上需要手动通过 $emit 触发父组件的事件,更简单的方式可以在父组件引用子组件时通过 @hook 来监听即可,如下所示:
// Parent.vue
doSomething() {
console.log('父组件监听到 mounted 钩子函数 ...');
},
// Child.vue
mounted(){
console.log('子组件触发 mounted 钩子函数 ...');
},
// 以上输出顺序为:
// 子组件触发 mounted 钩子函数 …
// 父组件监听到 mounted 钩子函数 …
当然 @hook 方法不仅仅是可以监听 mounted,其它的生命周期事件,例如:created,updated 等都可以监听。
由于 JavaScript 的限制,Vue 不能检测到以下数组的变动:
vm.items[indexOfItem] = newValue
vm.items.length = newLength
为了解决第一个问题,Vue 提供了以下操作方法:
// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// vm.$set(Vue.set的一个别名)
vm.$set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
为了解决第二个问题,Vue 提供了以下操作方法:
// Array.prototype.splice
vm.items.splice(newLength)
使用了函数劫持
的方式,重写
了数组的方法,Vue将data
中的数组
进行了原型链重写
,指向了自己定义
的数组原型方法
。这样当调用数组api时,可以通知依赖更新。如果数组中包含着引用类型,会对数组中的引用类型再次递归遍历进行监控。这样就实现了监测数组变化。
简单来说,diff算法有以下过程
同级比较, 再比较子节点,先判断一方有子节点一方没有子节点的情况(如果新的children没有子节点,将旧的子节点移除)
比较都有子节点的情况(核心diff)递归比较子节点
正常Diff两个树
的时间复杂度是O(n3),但实际情况下我们很少会进行跨层级的移动DOM,所以Vue将Diff进行了优化,从O(n3) -> O(n),只有当新旧children都为多个子节点时才需要用核心的Diff算法进行同层级比较。
Vue2的核心Diff算法采用了双端比较
的算法,同时从新旧children的两端开始进行比较,借助key值找到可复用的节点,再进行相关操作。相比React的Diff算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。
Vue3.x借鉴了 ivi算法和 inferno算法 在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。 该算法中还运用了动态规划的思想求解最长递归子序列。
简单说,Vue的编译过程就是将template转化为render函数的过程。会经历以下阶段:
首先解析模版
,生成AST
语法树(一种用JavaScript对象的形式来描述整个模板)。
使用大量的正则
表达式对模板
进行解析
,遇到标签
、文本
的时候都会执行
对应的钩子
进行相关处理。
Vue的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染
后就不会
再变化
,对应的DOM
也不会变化。那么优化过程就是深度遍历
AST树,按照相关条件对树节点
进行标记
。这些被标记的节点
(静态节点)我们就可以跳过
对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最后一步是将优化后的AST树转换
为可执行
的代码
。
computed:
计算属性
,也就是计算值,它更多用于计算值的场景缓存性
,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算watch:
props
$emit
或者本组件
的值,当数据变化时来执行回调进行后续操作不变化
也会执行
小结:
在下次
DOM
更新循环结束之后
执行延迟回调
。在这里面的代码会等到dom更新以后
再执行。
Hello World ~
点击
详细点击这路
Vue在初始化数据时,会使用Object.defineProperty
重新定义data中的所有属性
,当页面使用
对应属性时,首先会进行依赖收集
(收集当前组件的watcher),如果属性发生变化
会通知
相关依赖进行更新操作
(发布订阅)
具体的过程:
initData
初始化用户传入的参数
new Observer
对数据进行观测
对象类型
就会调用 this.walk(value)
对对象进行处理,内部使用 defineeReactive
循环对象属性定义响应式变化,核心就是使用 Object.defineProperty
重新定义数据。Vue3.x改用Proxy替代Object.defineProperty。因为Proxy可以直接监听
对象和数组的变化,并且有多达13种拦截方法。并且作为新标准将受到浏览器厂商重点持续的性能优化。
Proxy只会代理对象的第一层,那么Vue3又是怎样处理这个问题的呢?
判断当前Reflect.get
的返回值是否为Object
,如果是则再通过reactive
方法做代理, 这样就实现了深度观测。
监测数组的时候可能触发多次get/set,那么如何防止触发多次呢?
我们可以判断key
是否为当前被代理对象target
自身属性,也可以判断旧值
与新值
是否相等,只有满足以上两个条件之一时,才有可能执行trigger
。
不能同名 因为不管是计算属性还是data还是props 都会被挂载在vm实例
上,因此 这三个都不能同名
找到config/index.js
配置文件,找build
打包对象里的assetsPublicPath
属性 默认值为/
,更改为./
就好了
因为动态添加src
被当做静态资源
处理了,没有进行编译
,所以要加上require。
Object.freeze
适合一些 big data
的业务场景。尤其是做管理后台的时候,经常会有一些超大数据量
的 table
,或者一个含有 n 多数据的图表,这种数据量很大的东西使用起来最明显的感受就是卡。但其实很多时候其实这些数据其实并不需要响应式变化,这时候你就可以使用 Object.freeze 方法了,它可以冻结一个对象
(注意它不并是 vue 特有的 api)。
当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter,它们让 Vue 能进行追踪依赖,在属性被访问和修改时通知变化。
使用了 Object.freeze 之后,不仅可以减少 observer
的开销,还能减少不少内存开销
。
使用方式:
this.item = Object.freeze(Object.assign({}, this.item))
先了解一下,在 vue 中,有很多内置的指令.
比如:
所以,关于指令,我们可以总结下面几点:
HTML 属性
地方的,
v-
开头的.Vue自定义指令案例1
例如:我们需要一个指令,写在某个HTML表单元素上,然后让它在被加载到DOM中时,自动获取焦点.
// 和自定义过滤器一样,我们这里定义的是全局指令
Vue.directive('focus',{
inserted(el) {
el.focus()
}
})
先总结几个点:
Vue.directive()
来新建一个全局指令
,(指令使用在HTML元素属性上的)第一个参数
focus是指令名
,指令名在声明的时候,不需要加 v-
我们需要加上 v-.第二个参数
是一个对象
,对象内部
有个 inserted()
的函数,函数有 el
这个参数.DOM元素
,在这里就是后面那个有 placeholder
的 input
,el 就等价于 document.getElementById('el.id')
$(el)
无缝连接 jQuery指令的生命周期
用指令我们需要:
当一个指令绑定到一个元素上时,其实指令的内部会有五个生命周期事件函数.
bind(){}
当指令绑定
到 HTML 元素
上时触发.只调用一次.inserted()
当绑定了指令的这个HTML元素
插入到父元素
上时触发(在这里父元素是 div#app
).但不保证,父元素已经插入了 DOM 文档.updated()
所在组件的VNode更新
时调用.componentUpdate
指令所在的组件的VNode以及其子VNode 全部更新后
调用.unbind
: 指令和元素解绑
的时候调用,只调用一次Vue 指令的声明周期函数
Vue.directive('gqs',{
bind() {
// 当指令绑定到 HTML 元素上时触发.**只调用一次**
console.log('bind triggerd')
},
inserted() {
// 当绑定了指令的这个HTML元素插入到父元素上时触发(在这里父元素是 `div#app`)**.但不保证,父元素已经插入了 DOM 文档.**
console.log('inserted triggerd')
},
updated() {
// 所在组件的`VNode`更新时调用.
console.log('updated triggerd')
},
componentUpdated() {
// 指令所在组件的 VNode 及其子 VNode 全部更新后调用。
console.log('componentUpdated triggerd')
},
unbind() {
// 只调用一次,指令与元素解绑时调用.
console.log('unbind triggerd')
}
})
HTML
结果:
bind triggerd
inserted triggerd
发现默认情况下只有 bind 和 inserted 声明周期函数触发了.
那么剩下的三个什么时候触发呢?
v-if是删除或者新建dom元素,它会触发unbind指令声明周期吗?
当指令绑定的元素被销毁时,会触发指令的 unbind 事件.
(新建并显示,仍然是触发 bind & inserted)
unbind触发.gif
v-show设置元素的display:block|none,会触发componentUpdated事件
组件 (Component) 是用来构成你的 App 的业务模块,它的目标是 App.vue。
插件 (Plugin) 是用来增强你的技术栈的功能模块,它的目标是 Vue 本身。
根据官方文档定义:
如果在实例创建之后
添加新的属性
到实例上,它不会触发视图更新。
Vue 不允许在已经创建
的实例
上动态添加
新的根级响应式属性
(root-level reactive property)。
然而它可以使用 Vue.set(object, key, value)
方法将响应属性添加到嵌套的对象上。
多个实例
引用了相同或相似的方法或属性
等,可将这些重复的内容抽取出来作为mixins的js,export出去,在需要引用的vue文件通过mixins属性注入,与当前实例
的其他内容
进行merge
。
一个混入对象可以包含任意组件选项
。同一个生命周期,混入对象
会比组件
的先执行
。
//暴露两个mixins对象
export const mixinsTest1 = {
methods: {
hello1() {
console.log("hello1");
}
},
created() {
this.hello1();
},
}
export const mixinsTest2 = {
methods:{
hello2(){
console.log("hello2");
}
},
created() {
this.hello2();
},
}
home
hello2
hello1
1212
安装scss依赖包:
npm install sass-loader --save-dev npm install node-sass --save-dev
在build文件夹下修改 webpack.base.conf.js 文件,在 module 下的 rules 里添加配置,如下:
{ test: /\.scss$/, loaders: ['style', 'css', 'sass'] }
应用:
在vue文件中应用scss时,需要在style样式标签上添加lang="scss",即