目录
v-model
- 可以用 v-model 指令在表单及元素上创建双向数据绑定
(1)它会根据控件类型自动选取正确的方法来更新元素
(2)它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理
(3)v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值,而总是将 Vue 实例的数据作为数据来源,因此我们应该通过 JavaScript 在组件的 data 选项中声明初始值 - v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
(1)text 和 textarea 元素使用 value 属性和 input 事件;
(2)checkbox 和 radio 使用 checked 属性和 change 事件;
(3)select 字段将 value 作为 prop 并将 change 作为事件。 - 实现原理
(1)v-bind绑定一个value属性
(2)v-on指令给当前元素绑定input事件
<input v-model="sth" />
<!-- 等同于-->
<input :value="sth" @input="sth = $event.target.value" />
<!--自html5开始,input每次输入都会触发oninput事件,所以输入时input的内容会绑定到sth中,于是sth的值就被改变-->
<!--$event 指代当前触发的事件对象;-->
<!--$event.target 指代当前触发的事件对象的dom;-->
<!--$event.target.value 就是当前dom的value值;-->
<!--在@input方法中,value => sth;-->
<!--在:value中,sth => value;-->
keep-alive
- keep-alive:keep-alive可以实现组件缓存,是Vue.js的一个内置组件。
- 作用:
(1)它能够把不活动的组件实例保存在内存中,而不是直接将其销毁
(2)它是一个抽象组件,不会被渲染到真实DOM中,也不会出现在父组件链中 - 使用方式:
(1)常用的两个属性include/exclude,允许组件有条件的进行缓存。
(2)两个生命周期activated/deactivated,用来得知当前组件是否处于活跃状态。 - 原理:keep- alive的缓存是基于虚拟节点的。其实就是将需要缓存的虚拟节点保存在this.cache中,在渲染时,如果虚拟节点的name符合缓存条件(可以用include以及exclude控制),则会从this.cache中取出之前缓存的虚拟节点实例进行渲染。
- keep-alive的中还运用了LRU算法:决定要缓存组件实例时 根据组件ID和tag生成缓存Key,并在缓存对象中查找是否已缓存过该组件实例。如果缓存对象中存在该组件实例,直接取出缓存值并更新该key在this.keys中的位置。否则在this.cache对象中存储该组件实例并保存key值,之后检查缓存的实例数量是否超过max的设置值,超过则删除最近最久未使用的实例(即下标为0的那个key)。
LRU算法设计原则:如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。
LRU算法:新数据插入到数组头部;每当缓存数据被访问时,将数据移到数组头部;当数组满的时候,将尾部的数据丢弃。
$nextTick
- 作用:是为了可以获取更新后的DOM 。
- 原理:由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的DOM,所以设置了
Vue.nextTick()
,就是在DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。 - 运营场景:生命周期的created()钩子函数进行的DOM操作一定要放在
Vue.nextTick()
的回调函数中,原因是在created()钩子函数执行的时候DOM其实并未进行渲染 - 使用:
$nextTick(回调函数)
DOM更新循环结束之后会自动执行回调 - 使用了宏任务和微任务 会根据具体执行环境决定究竟使用哪个任务 promise mutationObserver setImmediate 以上都不支持使用setTimeout(前两个是微任务 后两个是宏任务)
MutationObserver是Html5的一个新特性,用来监听目标DOM结构是否改变,也就是代码中新建的textNode;如果改变了就执行MutationObserver构造函数中的回调函数,不过是它是在微任务中执行的。
双向数据绑定原理
- 发布者-订阅者模式:一般通过sub, pub的方式实现数据和视图的绑定监听
- 脏值检查:angular.js是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过setInterval()定时轮询检测数据变动,angular只有在指定的事件触发时,才进入脏值检测,大致如下:
(1)DOM事件,譬如用户输入文本,点击按钮等。( ng-click )
(2)XHR响应事件 ( $http )
(3)浏览器Location变更事件 ( $location )
(4)Timer事件( $timeout , $interval )
(5)执行 $digest() 或 $apply() - 数据劫持:vue.js则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
Vue2.0、Vue3.0双向数据绑定
- Vue2.0的数据响应是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty () 来劫持各个属性的setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调,它有两个缺陷:
(1)不能监听数组的变化:无法监听数组中数据的变化,通过重写数组的部分方法(push/pop/shift/unshift/splice/sort/reverse)来解决,其他数组方法及数组的使用依旧无法检测到
(2)vue 实例创建后,无法检测到对象属性的新增或删除,只能追踪到数据是否被修改:当创建一个Vue实例时,将遍历所有DOM对象,并为每个数据属性添加了get和set。get和set 允许Vue观察数据的更改并触发更新。但是,如果你在Vue实例化后添加(或删除)一个属性,这个属性不会被vue处理,改变get和set。
- 为什么使用vm.$set()解决对象新增属性不能响应的问题,vm.$set()的实现原理?
因为属性必须在data对象上存在才能让Vue将它转换为响应式的 所以使用vm.$set()解决- 实现原理
(1)如果目标是数组,直接使用数组的 splice 方法触发相应式;
(2)如果目标是对象,会先判读属性是否存在、对象是否是响应式,
(3)最终如果要对属性进行响应式处理,则是通过调用defineReactive方法进行响应式处理- 用法: Vue.set (对象, 属性名, 值)
- 此外 还可以使用Object.assign方法解决 Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
var newObj = Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
newObj // {a:1, b:2, c:3}
- vue3.0 实现数据双向绑定是通过Proxy,Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。使用 Proxy 的核心优点是可以交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等)。 从而可以让对象只需关注于核心逻辑,达到关注点分离,降低对象复杂度等目的。
- proxy的缺点:proxy时ES6的语法 存在兼容性问题、性能差
- 使用proxy实现双向数据绑定,相比2.0的Object.defineProperty ()优势:
(1)可以劫持整个对象,并返回一个新对象
(2)有13种劫持操作 - Vue2.0双向数据实现流程:
(1)beforeCreate之后created之前 data会被传入Observe对象中 Observer对象使用Object.defineProperty监听数据的getter setter 为每个属性创建一个Dep对象(以后发现页面上某个地方用到该属性了 当属性改变时 通知Dep对象中的subs subs是一个数组 里面存放着该属性的观察者watcher)
(2)在beforeMount中对el模板执行编译,发现页面中使用了某个属性就为该属性创建一个watcher 该watcher被加到相应属性的Dep对象的subs数组中
(3)Watcher中定义了一个更新函数update 将来对应数据变化时 Watcher会调用更新函数 更新函数会更改页面上用到该属性的值
(4)当页面上属性值发生了改变 由于在Observer对象中监听了属性 所以值一变 vue就会调用相应属性的Dep对象的notify函数 一调用notify函数就会遍历subs 调用每个watcher的update方法
编译Compile:对每个元素节点的指令进行扫描跟解析,根据指令模板替换数据,以及绑定相应的更新函数
vuex
- vuex是一种状态管理工具,将全局组件的共享状态抽取出来为一个store,以单例模式存在,任何一个组件都可以使用。
- 状态/属性
(1)state:存储数据
(2)mutations:定义操作数据的方法
(3)actions:定义操作数据的方法的方法
(4)gettter:对数据进行过滤操作,一般是对数组或对象进行处理
(5)module:定义模块,可以通过模块让vuex中的数据不共享,每个模块拥有自己的state、getters、mutations、actions
(6)辅助函数:mapState、MapGetters、MapActions、mapMutations等辅助函数,他们都是通过解构的方式获得getters、actions、mutations中的多个方法 - Vuex实现原理
- mutations和actions的区别:
(1)mutations:定义操作数据的方法,actions:定义操作数据的方法的方法。mutation需要commit触发, action触发mutation
(2)mutation处理同步任务,action处理异步任务。 - 为什么mutations只能处理同步任务:因为vue是响应式的,我们希望state中的数据一改变,页面中相应位置的数据也跟着改变,那么mutations中就只能放同步任务了
- 为什么不声明一个全局变量来管理数据,而要用vuex:
(1)vuex的存储是响应式的 数据发生改变 页面上的数据也跟着变 全局变量做不到
(2)无法跟踪全局变量的状态 全局变量中的数据发生改变后 你不知道他是在哪里改的 找Bug太难 但是vuex可以追踪到
vue的方法
- 为什么data是函数:因为vue是单页面应用 为了不让各组件的数据相互干扰 所以将data设为函数 保证每个组件都有自己的作用域
- computed:相当于vuex中的getters用来对数据进行过滤处理 当data中的数据改变时 computed中用到该数据的函数会被调用 函数的返回值发生相应的变化 只有data中的数据改变能出发computed中的函数
- methods:定义方法 调用即触发
- watch:监听数据的变化 监听普通数据类型时可以得到变化前后的值 监听复杂数据类型时 只能得到变化后的值 因为复杂数据类型在堆中存数据 在栈中存变量 变量中存储的是堆中数据的地址值 改变数据是改变堆中的数据值 栈中变量存储的地址没有改变 所以只能获取到变化后的值 原来的值已经被我们改了 拿不到 另外 在监听复杂数据类型时 需要开启深度监听
deep:true
只有这样才会遍历复杂数据类型 看哪个值发生了变化
应用:当被监听的值发生变化时,执行相关操作
路由(切换/跳转)模式
vue是单页面应用 意味着我们向后端请求页面时之请求了一个HTML页面 在切换页面时 通过路由进行组件的切换,这种实现方案有两种:hash模式、history模式
hash模式
- 利用锚点实现 是单页面应用最主要的路由方式
- 改变hash的方法:
(1)a标签锚点:a标签的herf属性可定位到页面的某个位置
(2)使用JS修改location.hash=‘#index’
(3)手动在url后面输入
(4)浏览器前进后退 - 实现原理:使用window.onhashchange方法监听hash值的改变 当hash变化时 拿到新的hash值 让页面的相应部分展示出来
history模式
实现原理:使用HTML5的pushState()和replaceState()这两个api结合window.popstate事件(监听浏览器前进后退)来实现的,pushState()可以改变url地址,replaceState()可以读取历史记录栈,还可以对浏览器记录进行修改
hash模式和history模式的区别:
- hash模式较丑,history模式较优雅
- pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL
- pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中
- pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串
pushState可额外设置title属性供后续使用 - hash兼容IE8以上,history兼容IE10以上
- history模式需要后端配合将所有访问都指向index.html,否则用户刷新页面,会导致404错误
hash模式和history模式谁不会向服务器发送请求
- hash模式:hash虽然出现在URL中,但不会被包括在HTTP请求中,不会向服务器发送请求,因此改变hash不会重新加载页面。
- HTML5的History模式,它使url看起来像普通网站那样,以“/”分割,所以会向服务器发请求,需要服务器的配合,服务端在接收到所有请求后,都只响应同一个html文件,不然会出现404。
组件通信的方式
- 父子组件
(1)props
和$emit
:父组件向子组件传递数据是通过prop传递的,子组件接收到数据之后,不能直接修改父组件的数据。
// Parent.vue 传送
<template>
<child :msg="msg"></child>
</template>
// Child.vue 接收
export default {
// 写法一 用数组接收
props:['msg'],
// 写法二 用对象接收,可以限定接收的数据类型、设置默认值、验证等
props:{
msg:{
type:String,
default:'这是默认数据'
}
},
mounted(){
console.log(this.msg)
},
}
子组件传递数据给父组件是通过$emit
触发事件来做到的,父组件使用v-on
监听子组件派发的事件
// Child.vue 派发
export default {
data(){
return { msg: "这是发给父组件的信息" }
},
methods: {
handleClick(){
this.$emit("sendMsg",this.msg)
}
},
}
// Parent.vue 响应
<template>
<child v-on:sendMsg="getChildMsg"></child>
// 或 简写
<child @sendMsg="getChildMsg"></child>
</template>
export default {
methods:{
getChildMsg(msg){
console.log(msg) // 这是父组件接收到的消息
}
}
}
(2)attrs和listeners:父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C可以使用该方法
$attrs
:包含父作用域里除class和style除外的非props属性集合。通过this.$attrs
获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过v-bind="$attrs"
$listeners
:包含父作用域里.native
除外的监听事件集合。如果还要继续传给子组件内部的其他组件,就可以通过v-on="$linteners"
// Parent.vue
<template>
<child :name="name" title="1111" ></child>
</template
export default{
data(){
return {
name:"cara"
}
}
}
// Child.vue
<template>
// 继续传给孙子组件
<sun-child v-bind="$attrs"></sun-child>
</template>
export default{
props:["name"], // 这里可以接收,也可以不接收
mounted(){
// 如果props接收了name 就是 { title:1111 },否则就是{ name:"cara", title:1111 }
console.log(this.$attrs)
}
}
(3)$parent
和$children
:分别可以获得父组件、子组件实例,就可以获得数据
$children
:获取到一个包含所有子组件(不包含孙子组件)的VueComponent对象数组,可以直接拿到子组件中所有数据和方法等
$parent
:获取到一个父节点的VueComponent对象,同样包含父节点中所有数据和方法等
// Parent.vue
export default{
mounted(){
this.$children[0].someMethod() // 调用第一个子组件的方法
this.$children[0].name // 获取第一个子组件中的属性
}
}
// Child.vue
export default{
mounted(){
this.$parent.someMethod() // 调用父组件的方法
this.$parent.name // 获取父组件中的属性
}
}
(4)v-model
:可以实现将父组件传给子组件的数据为双向绑定,子组件通过$emit
修改父组件的数据
// Parent.vue
<template>
<child v-model="value"></child>
</template>
<script>
export default {
data(){
return {
value:1
}
}
}
// Child.vue
<template>
<input :value="value" @input="handlerChange">
</template>
export default {
props:["value"],
// 可以修改事件名,默认为 input
model:{
event:"updateValue"
},
methods:{
handlerChange(e){
this.$emit("input", e.target.value)
// 如果有上面的重命名就是这样
this.$emit("updateValue", e.target.value)
}
}
}
</script>
(5).sync
:和v-model
类似,可以实现父组件向子组件传递的数据的双向绑定,区别在于子组件接收到.sync
传来的数据后可以直接修改,并且会同时修改父组件的数据
// Parent.vue
<template>
<child :page.sync="page"></child>
</template>
<script>
export default {
data(){
return {
page:1
}
}
}
// Child.vue
export default {
props:["page"],
computed(){
// 当我们在子组件里修改currentPage时,父组件的page也会随之改变
currentPage {
get(){
return this.page
},
set(newVal){
this.$emit("update:page", newVal)
}
}
}
}
</script>
(6)ref
:ref 如果在普通的DOM元素上,引用指向的就是该DOM元素;如果在子组件上,引用的指向就是子组件实例,然后父组件就可以通过ref获取子组件的属性及方法
// Child.vue
export default {
data(){
return {
name:"cara"
}
},
methods:{
someMethod(msg){
console.log(msg)
}
}
}
// Parent.vue
<template>
<child ref="child"></child>
</template>
<script>
export default {
mounted(){
const child = this.$refs.child
console.log(child.name) // cara
child.someMethod("调用了子组件的方法")
}
}
</script>
(7)provide
和inject
:
provide
:可以让我们指定想要提供给后代组件的数据或方法
inject
:在任何后代组件中接收想要添加在这个组件上的数据或方法,不管组件嵌套多深都可以直接拿来用
注意:provide和inject传递的数据不是响应式的,也就是说用inject接收来数据后,provide里的数据改变了,后代组件中的数据不会改变,除非传入的就是一个可监听的对象,所以建议还是传递一些常量或者方法
// 父组件
export default{
// 方法一 不能获取 methods 中的方法
provide:{
name:"cara",
age: this.data中的属性
},
// 方法二 不能获取 data 中的属性
provide(){
return {
name:"沐华",
someMethod:this.someMethod // methods 中的方法
}
},
methods:{
someMethod(){
console.log("这是注入的方法")
}
}
}
// 后代组件
export default{
inject:["name","someMethod"],
mounted(){
console.log(this.name)
this.someMethod()
}
}
- 其他组件
(1)事件总线:创建一个新的vue实例bus
// main.js
import Vue from "vue"
Vue.prototype.$bus = new Vue()
A组件调用$emit
方法发送数据:this.bus.$emit('事件名称‘, 数据)
B组件通过$on
接收数据:this.bus.$on('事件名称‘, 接收数据的变量)
为什么要用事件总线不用this:因为每个组件时隔离的 他们有自己的作用域 事件总线相当于跳出来组件在全局作用域下发送、接收 用this是无法通信的
(2)vuex
:可以把多个组件公用的属性放在vuex中
(3)$root
:可以拿到App.vue里的数据和方法
生命周期及钩子函数
- beforeCreate函数:在实例初始化之后调用,此时只初始化了VUE实例
- created函数:实例已经创建完成之后被调用,此时,实例已完成以下配置:数据观测、属性和方法的运算,event/watch事件回调,data 数据的初始化。不能访问el和ref属性,他们都是空数组。
- beforeMount函数:挂载(把数据显示在模板里)之前执行。实例已完成以下的配置: 编译模板(把模板编译成render函数),把data里面的数据和模板生成html,完成了el和data初始化,注意此时还没有挂载html到页面上。
- mounted函数:实例已经挂载到具体的DOM上。一般我们在此处发送异步请求(ajax,fetch,axios等)。可操作DOM,可访问ref el属性,常用于获取虚拟节点信息和操作。
为什么在这里发异步请求:虚拟DOM挂载到真实DOM上面后执行mounted函数 由于生命周期函数是同步的 执行完上一个生命周期函数才能执行下一个 如果把ajax请求放在created里面 那么当执行到mounted前面的生命周期函数时 假如请求的数据回来了 假如需要根据请求的数据改变现有的数据 那么就会把我刚新建好的需要渲染的数据全部打乱 重新新建 如果有多个异步请求 那么就需要反复打乱 反复新建 太消耗性能 若在mounted中发送数据请求 此时虚拟DOM已经挂载到真实DOM上面了 页面已经渲染出来了 数据已经可以看到了 再去修改数据就是一次性操作了
- beforeUpdate函数:在数据更新之前被调用,发生在虚拟DOM重新渲染之前。
- updated函数:在虚拟DOM重新渲染之后调用。对新组件的DOM进行操作时在这里操作,可以执行依赖于DOM的操作。该钩子在服务器端渲染期间不被调用。
不能在此操作数据,因为这可能会导致更新无限循环。假如DOM中有数据 你在updated中又写了更新数据的代码 只要数据一更新就会执行updated方法 updated方法中又会更新数据 又会执行updated方法 造成死循环
- beforeDestroy函数:在实例销毁之前调用,实例仍然完全可用,这一步还可以用this来获取实例,一般在这一步销毁定时器,解绑全局事件,销毁插件对象等。
- destroyed函数:在实例销毁之后调用。
v-if与v-show区别
- v-show和v-if都是用来显示隐藏元素,v-if可以和v-else配合使用,两者达到的效果都一样。
- v-show:首次渲染时,不管条件是真是假都会编译出来,也就是标签都会添加到DOM中。之后切换的时候,通过
display: none;
样式来显示隐藏元素。可以说只是改变css的样式,几乎不会影响什么性能。 - v-if:首次渲染时,如果条件为假,什么也不操作,页面当作没有这些元素。当条件为真的时候,开始局部编译,动态的向DOM元素里面添加元素。当条件从真变为假的时候,开始局部编译,卸载这些元素,也就是删除。
- 应用场景:频繁隐藏显示使用v-show
vue首屏白屏如何解决
- 路由懒加载
- vue-cli开启打包压缩 和后台配合 gzip访问
- 进行cdn加速
- 开启vue服务渲染模式
- 用webpack的externals属性把不需要打包的库文件分离出去,减少打包后文件的大小
- 在生产环境中删除掉不必要的console.log
- 开启nginx的gzip ,在nginx.conf配置文件中配置
- 添加loading效果,给用户一种进度感受
scss是什么?在vue.cli中的安装使用步骤是?有哪几大特性?
- SCSS:css的预编译。
- 使用步骤:
(1)先装css-loader、node-loader、sass-loader等加载器模块
(2)在build目录找到webpack.base.config.js,在那个extends属性中加一个拓展.scss
(3)在同一个文件,配置一个module属性
(4)然后在组件的style标签加上lang属性 ,例如:lang=”scss” - 特性:可以用变量、混合器、嵌套
jquery和vue相比
- jquery:轻量级的js库
vue:前端js库,是一个精简的MVVM,它专注于MVVM模型的viewModel层,通过双向数据绑定把view和model层连接起来,通过对数据的操作就可以完成对页面视图的渲染。
- vue适用的场景:复杂数据操作的后台页面,表单填写页面
jquery适用的场景:比如说一些html5的动画页面,一些需要js来操作页面样式的页面
二者也是可以结合起来一起使用的,vue侧重数据绑定,jquery侧重样式操作,动画效果等,则会更加高效率的完成业务需求
vue单页面和传统的多页面区别?
单页面应用SPA:指只有一个主页面的应用,浏览器一开始要加载所有必须的html、js、css。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写,然后在交互的时候,由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于pc端。
优点
- 良好的交互体验:单页应用的内容的改变不需要重新加载整个页面,获取数据也是通过Ajax异步获取,没有页面之间的切换,就不会出现“白屏现象”,也不会出现假死并有“闪烁”现象,页面显示流畅,web应用更具响应性和更令人着迷。
- 良好的前后端工作分离模式:后端不再负责模板渲染、输出页面工作,后端API通用化,即同一套后端程序代码,不用修改就可以用于Web界面、手机、平板等多种客户端。
- 减轻服务器压力:单页应用相对服务器压力小,服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍。
缺点:
- 首屏加载慢:如果不对路由进行处理,在加载首页的时候,就会将所有组件全部加载,并向服务器请求数据,这必将拖慢加载速度;通过查看Network,发现整个网站加载试讲长达10几秒,加载时间最长的就是js、css文件和媒体文件及图片
解决方案:
(1)Vue-router懒加载:Vue-router懒加载就是按需加载组件,只有当路由被访问时才会加载对应的组件,而不是在加载首页的时候就加载,项目越大,对首屏加载的速度提升得越明显。
(2)使用CDN加速:在做项目时,我们会用到很多库,采用cdn加载可以加快加载速度。
(3)异步加载组件
(4)服务端渲染:服务端渲染还能对seo优化起到作用,有利于搜索引擎抓取更多有用的信息(如果页面纯前端渲染,搜索引擎抓取到的就只是空页面)- 不利于SEO:seo 本质是一个服务器向另一个服务器发起请求,解析请求内容。但一般来说搜索引擎是不会去执行请求到的js的。也就是说,搜索引擎的基础爬虫的原理就是抓取url,然后获取html源代码并解析。 如果一个单页应用,html在服务器端还没有渲染部分数据,在浏览器才渲染出数据,即搜索引擎请求到的html是模型页面而不是最终数据的渲染页面。 这样就很不利于内容被搜索引擎搜索到。
解决方案:
(1)服务端渲染:服务器合成完整的 html 文件再输出到浏览器
(2)页面预渲染
(3)路由采用h5 history模式- 不适合开发大型项目:大型项目中可能会涉及大量的DOM操作、复杂的动画效果,也就不适合使用Vue、react框架进行开发。
多页面MPA:指一个应用中有多个页面,页面跳转时是整页刷新
框架带来的好处和弊端
优势:
- 组件化:其中以react的组件化最为彻底,甚至可以到函数级别的原子组件,高度的组件化可以使我们的工程易于维护,易于组合扩展;
- 天然分层:jQuery时代的代码大部分情况下是面条代码,耦合严重,现代框架不管是MVC、MVP还是MVVM模式都可以帮我们进行分层,代码解耦更易于读写;
- 生态:现代主流框架都自带生态,不管是数据流管理架构还是UI库都有成熟的解决方案;
- 开发效率:现在前端框架都默认自动更新DOM,而非我们手动操作,解放了开发者的手动DOM成本,提高开发效率,从根本上解决了UI与状体同步问题。
劣势:
- 兼容性问题,SEO不友好
- 有场景要求,开发自由度降低
- 有黑盒开发,框架本身有出错的风险
- 有学习成本
模块化、组件化、工程化
- 工程化:前端工程化是一个高层次的思想,而模块化和组件化是为工程化思想下相对较具体的开发方式,因此可以简单的认为模块化和组件化是工程化的表现形式。工程化是将前端项目当成一项系统工程进行分析、组织和构建从而达到项目结构清晰、分工明确、团队配合默契、开发效率提高的目的。
- 模块化:一个模块就是一个实现特定功能的文件,有了模块我们就可以更方便的使用别人的代码,要用什么功能就加载什么模块。js模块化方案很多有AMD、CommonJS、UMD、ES6 Module等。css模块化开发大多数是在less、sass、stylus等预处理器的import、minxin特性支持下实现。
模块化优势:
(1)避免变量污染,命名冲突
(2)提高代码复用率
(3)提高维护性
(4)依赖关系的管理
- 组件化:页面上的每个独立的、可视/可交互区域视为一个组件。每个组件对应一个工程目录,组件所需的各种资源都在这个目录下就近维护;由于组件具有独立性,因此组件与组件之间可以自由组合;页面不过是组件的容器,负责组合组件形成功能完整的界面
MVC、MVP、MVVM 模式
在开发图形界面应用程序的时候,会把管理用户界面的层次称为 View,应用程序的数据为 Model,Model 提供数据操作的接口,执行相应的业务逻辑。
- MVC:MVC 除了把应用程序分为 View、Model层,还额外的加了一个 Controller层,它的职责是进行 Model 和 View 之间的协作(路由、输入预处理等)的应由逻辑(application logic);Model 进行处理业务逻辑。用户对 View 操作以后,View 捕获到这个操作,会把处理的权利交移给Controller(Pass calls);Controller 会对来自 View 数据进行预处理、决定调用哪个 Model 的接口;然后由 Model 执行相关的业务逻辑;当Model 变更了以后,会通过观察者模式(Observer Pattern)通知 View;View 通过观察者模式收到 Model 变更的消息以后,会向 Model 请求最新的数据,然后重新更新界面。
- MVP:和 MVC 模式一样,用户对 View 的操作都会从 View 交易给 Presenter。Presenter 会执行相应的应用程序逻辑,并且会对 Model 进行相应的操作;而这时候 Model 执行业务逻辑以后,也是通过观察者模式把自己变更的消息传递出去,但是是传给 Presenter 而不是 View。Presenter 获取到 Model变更的消息以后,通过 View 提供的接口更新界面。
- MVVM:MVVM 代表的是 Model-View-ViewModel,MVVM 的调用关系和 MVP 一样。但是,在 ViewModel 当中会有一个叫 Binder,或者是 Data-binding engine 的东西。以前全部由 Presenter 负责的 View 和 Model 之间数据同步操作交由给 Binder 处理。你只需要在View 的模板语法当中,指令式声明 View 上的显示的内容是和 Model 的哪一块数据绑定的。当 ViewModel 对进行 Model 更新的时候,Binder 会自动把数据更新到 View 上,当用户对 View 进行操作(例如表单输入),Binder 也会自动把数据更新到 Model 上。这种方式称为:Two-way data-binding,双向数据绑定。可以简单而不恰当地理解为一个模板引擎,但是会根据数据变更实时渲染。
其他问题
- vue的特性:表单操作、自定义指令、计算属性、过滤器、侦听器、生命周期
- v-for为什么加 key:需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点。主要是为了高效的更新虚拟DOM。
- 为什么使用vue
(1)MVVM 框架:Vue 正是使用了这种 MVVM 的框架形式,并且通过声明式渲染和响应式数据绑定的方式来帮助我们完全避免了对 DOM 的操作。
(2)单页面应用程序:Vue 配合生态圈中的 Vue-Router 就可以非常方便的开发复杂的单页应用
(3)轻量化与易学习:Vue 的生产版本只有 30.90KB 的大小,几乎不会对我们的网页加载速度产生影响。同时因为 Vue 只专注于视图层,单独的 Vue 就像一个库一样,所以使我们的学习成本变得非常低
(4)渐进式与兼容性:Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。Vue 只做界面,而把其他的一切都交给了它的周边生态(axios(Vue 官方推荐)、Loadsh.js、Velocity.js 等)来做处理,这就要求 Vue 必须要对其他的框架拥有最大程度的兼容性
(5)视图组件化:Vue 允许通过组件来去拼装一个页面,每个组件都是一个可复用的 Vue 实例,组件里面可以包含自己的数据,视图和代码逻辑。方便复用
(6)虚拟 DOM:Vue 之所以可以完全避免对 DOM 的操作,就是因为 Vue 采用了虚拟 DOM 的方式,不但避免了我们对 DOM 的复杂操作,并且大大的加快了我们应用的运行速度。
(7)社区支持:得益于 Vue 的本土化身份(Vue 的作者为国人尤雨溪),再加上 Vue 本身的强大,所以涌现出了特别多的国内社区,这种情况在其他的框架身上是没有出现过的,这使得我们在学习或者使用 Vue 的时候,可以获得更多的帮助
(8)未来的 Vue 走向:Vue 是由国人尤雨溪在 Google 工作的时候,为了方便自己的工作而开发出来的一个库,而在 Vue 被使用的过程中,突然发现越来越多的人喜欢上了它。所以尤雨溪就进入了一个边工作、边维护的状态,在这种情况下 Vue 依然迅速的发展。而现在尤雨溪已经正式辞去了 Google 的工作,开始专职维护 Vue,同时加入进来的还有几十位优秀的开发者,他们致力于把 Vue 打造为最受欢迎的前端框架。事实证明 Vue 确实在往越来越好的方向发展了(从 Angular、React、Vue 的对比图中可以看出 Vue 的势头)。所以我觉得完全不需要担心未来 Vue 的发展,至少在没有新的颠覆性创新出来之前,Vue 都会越做越好。 $root
:可以用来获取vue的根实例
$refs
:在子组件上使用ref特性后,this.属性可以直接访问该子组件。可以代替事件emit和on的作用 refs.testId获取指定元素。使用方式是通过ref特性为这个子组件赋予一个ID引用,再通过this.refs.id获取指定元素。refs只会再组件渲染完成之后生效 并且他们不是响应式的。- 路由跳转和location.href的区别:使用location.href='/url’来跳转,简单方便,但是刷新了页面;使用路由方式跳转,无刷新页面,静态跳转;
- delete与vue.delete区别:delte会删除数组的值,但是它依然会在内存中占位置;而vue.delete会删除数组在内存中的占位。
导航守卫
导航守卫分为:全局的、单个路由独享的、组件内的三种。
- 全局的:
(1)全局前置路由守卫:初始化的时候被调用、每次路由切换之前被调用
使用router.beforeEach((to,from,next)=>{})
指定一个函数,在每次初始化时、每次路由切换之前都会调用这个函数
to会接收到目标路由的信息,from会接收到源路由的信息,next函数用于控制是否进行下一步,调用才会进行,不调用的话,路由切换会卡在router.beforeEach(函数)中不进行下一步。
常用于登陆验证
(2)全局后置路由守卫:初始化的时候被调用、每次路由切换之后被调用
使用router.afterEach((to,from)=>{})
指定一个函数,在每次初始化时、每次路由切换之后都会调用这个函数
to会接收到目标路由的信息,from会接收到源路由的信息
有这么一个场景:你一进入一个网页,页签展示xxx,然后你点击了HOME,页签展示主页,你点击About,页签展示关于。在这种情况下,就需要用到全局后置路由守卫 - 独享路由守卫:某一个路由所单独享用的路由守卫,只有前置路由守卫。进入News组件前会调用beforeEnter指定的函数:
beforeEnter: (to, from, next) => {}
,该函数同样会收到to、from、next三个参数,用法同全局路由守卫。 - 组件内路由守卫:在组件里写路由守卫。通过路由规则,进入该组件时调用组件内路由守卫
beforeRouteEnter (to, from, next) {}
。当你想给某组件单独写一些逻辑,可以在组件内路由守卫中实现。
通过路由规则,进入组件的过程:点击后路径变成/about,前端路由器检测到路径的变化,匹配规则后进入组件展示
不通过路由规则进入组件:比如我一打开页面,xx组件就展示了(通过在页面中写<xx />
展示组件),xx组件就不是通过路由规则进入组件的
虚拟DOM
- 虚拟DOM:用 JavaScript 对象描述 DOM 的层次结构。DOM 中的一切属性都在虚拟 DOM 中有对应的属性。
- DOM 如何变为虚拟 DOM,属于模板编译原理范畴
Diff算法
- diff 是发生在虚拟 DOM 上的:新虚拟 DOM 和老虚拟 DOM 进行 diff算法比较 找出不同的地方 然后对真实的DOM进行局部操作
- diff 算法确实是最小量更新,key 很重要,key 是这个节点的唯一标识,告诉 diff 算法,在更改前后它们是同一个 DOM 节点
- 只有是同一个虚拟节点才进行精细化比较,否则就是暴力删除旧的、插入新的。同一个虚拟节点:选择器相同且 key 相同。
- 只进行同层比较,不会进行跨层比较。即使是同一片虚拟节点,但是如果跨层了,那么 diff 算法也不会进行精细化比较。而是暴力删除旧的、然后插入新的。
- diff并不是那么“无微不至” 真的能提高效率吗:上面进行暴力删除的情况在实际vue开发中并不常见 比如根据条件改变选择器 条件为真时显示有序列表 为假时显示无序列表 比如你本来有4个div 点击了某个按钮后将4个div用一个selection标签包裹起来 原来的4格div和现在的4格div已经不在同一层了 这些情况在实际开发中不常见。所以这是合理的优化机制
- DIFF算法过程:
(1)新旧虚拟节点是不是同一个虚拟节点 如果不是 删除旧虚拟节点 添加虚拟节点
(2)如果新旧虚拟节点是同一个虚拟节点 比较新旧虚拟节点是不是内存中的同一个对象 如果是 不操作
(3)如果新旧虚拟节点不是内存中的同一个对象 查看新虚拟节点有没有text属性 如果有 比较新旧虚拟节点text属性是否相同 如果相同 不操作 如果不同 把旧虚拟节点的elm属性( 对应的真正的dom节点(对象),undefined表示节点还没有上dom树)中的innerText改为新虚拟节点text属性的内容oldVnode.elm.innerText = newVnode.text;
即使旧虚拟节点有children属性 没有text属性也没关系 innerText一旦变为新text 原来的children属性就没了
(4)如果新虚拟节点没有text属性 意味着新虚拟节点有children属性 查看老虚拟节点有没有children属性 如果没有 意味着老虚拟节点有text属性 清空老虚拟节点text 并把新虚拟节点的children添加到dom中
(5)如果老虚拟节点有children属性 此时新旧虚拟节点都有children属性 需要对子节点依次进行以下判断:(这里讲的有点抽象 具体看我的另一篇文章)
① 新前与旧前指针指向的节点是同一个节点
② 新后与旧后
③ 新后与旧前(此种命中,涉及移动节点,那么旧前指向的节点,移动到旧后之后 也就是移动到所有旧节点之后)
④ 新前与旧后(此种命中,涉及移动节点,那么旧后指向的节点,移动到旧前之前 也就是移动到所有旧节点之前)
新前:新的虚拟节点中所有没有处理的开头的节点
新后:所有新的虚拟节点的子结点中没有处理的节点的最后一个节点
以上只要有一种情况成立就不继续比较了 命中指针指向下一个节点从头开始比较
如果以上四种情况都不成立 需要根据key来比较 遍历旧虚拟节点 看看有没有新前 新后范围内(包括新前 新后指向的节点)的节点 如果有 将旧前指向的节点插入到旧前指针前面 然后将旧节点中的节点设为undefined 然后新前指针下移
如果旧节点先遍历完新节点还有剩余项 说明新节点中有要插入的节点 需要把新前 新后范围内的节点添加到旧虚拟节点中 如果新节点先遍历完 旧节点中还有剩余项 需要把旧前 旧后范围内的节点删除