vue入门到精通及高级用法,原理分析

v-html 的 XSS 攻击

在 Vue 中有 v-html 这个便利的指令,可以让我们直接输出 HTML,但它有个缺点。

Vue 官网这样解释这个「指令」

双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,你需要使用 v-html 指令:

<p>Using mustaches: {{ rawHtml }}</p><p>Using v-html directive: <span v-html="rawHtml"></span></p>

输出代码:

Using mustaches:

<span style="color: red">This should be red.</span>

Using v-html directive: This should be red.

这个 span 的内容将会被替换成为属性值 rawHtml,直接作为 HTML——会忽略解析属性值中的数据绑定。注意,你不能使用 v-html 来复合局部模板,因为 Vue 不是基于字符串的模板引擎。反之,对于用户界面 (UI),组件更适合作为可重用和可组合的基本单位。

你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。

什么是 XSS 攻击?

XSS是跨站脚本攻击(Cross-Site Scripting)的简称。

XSS是一种注入脚本式攻击,攻击者利用如提交表单、发布评论等方式将事先准备好的恶意脚本注入到那些良性可信的网站中。

当其他用户进入该网站后,脚本就在用户不知情的情况下偷偷地执行了,这样的脚本可能会窃取用户的信息、修改页面内容、或者伪造用户执行其他操作等等,后果不可估量。

发送到Web浏览器的恶意内容通常采用JavaScript代码片段的形式,但也可能包括HTML,Flash或浏览器可能执行的任何其他类型的代码。

会覆盖子元素

computed 和watch

  1. componted 有缓存, data不变则不会重新计算

  2. watch如何深度监听?watch是浅监听(基础数据类型)

  3. watch监听引用类型,拿不到oldval。不能深度监听,要加deep:true才能深度监听,监听对象元素的变化

  4. computed监听v-model中的值,必须要有get()和set()

    区别
    computed
    1. 支持缓存,只有依赖数据发生改变,才会重新进行计算
    2. 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
    3.computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
    4. 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
    5.如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
    
    watch
    侦听属性watch:
    
    1. 不支持缓存,数据变,直接会触发相应的操作;
    2.watch支持异步;
    3.监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
    4. 当一个属性发生变化时,需要执行对应的操作;一对多;
    5. 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,
      immediate:组件加载立即触发回调函数执行,
      deep: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。
    
    监听的对象也可以写成字符串的形式
    "obj.name"
    
    当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。这是和computed最大的区别,请勿滥用。
    
      var vm = new Vue( {
        el: '#app',
        data: {
          childrens: {
            name: '小强',
            age: 20,
            sex: '男'
          },
          tdArray:["1","2"],
          lastName:"张三"
        },
        watch:{
          childrens:{
            handler:function(val,oldval){
              console.log(val.name)
            },
            deep:true//对象内部的属性监听,也叫深度监听
          },
          'childrens.name':function(val,oldval){
            console.log(val+"aaa")
          },//键路径必须加上引号
          lastName:function(val,oldval){
            console.log(this.lastName)
          }
        },//以V-model绑定数据时使用的数据变化监测
      } );
    
     watch: {
            // 普通监听
            name(oldVal, val){
                console.log(oldVal)
            },
            info: {
                // 监听引用类型必须使用 handler 函数
                handler(oldVal, val){
                    // 引用类型监听不到 oldval的,因为指针已经只像了相同的值
                    console.log(oldVal)
                },
                deep: true  // 深度监听
            }
        }
    

v-show v-if

1.手段:v-if是动态的向DOM树内添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显隐;
2.编译过程:v-if切换
有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换;
3.编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译(编译被缓存?编译被缓存后,然后再切换的时候进行局部卸载); v-show是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且DOM元素保留;
4.性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
5.使用场景:v-if适合运营条件不大可能改变;v-show适合频繁切换。

二、V-show和v-if的区别?
        V-show有较高的渲染成本,

        V-if有较高的切换成本。

        V-if是真正的条件渲染,确保切换过程中条件内的事件监听器和子组件适当的被销毁和重建。

        V-show的元素始终被渲染并保存在dom中,操作的只是display属性控制演示影藏。

v-for

遍历对象
v-for="(value, key, index) in obj"
key的重要性, key不能乱写(random, index)
v-for 和 v-if 不建议一起使用(v-for的层级更高)

ul和li搭配使用,或者是渲染父级标签下的子标签,可以使用如下方法:

<ul v-if="state">
	<li v-for="(item,id) in list" :key="id"></li>
</ul>

2>可以使用过滤器将v-if中的判断转移到vue的computed的计算属性中,方法如下:

<ul>
	<li v-for="(item,id) in formList" :key="id"></li>
</ul>
 
//利用vue的计算属性,过滤掉不需要的数据,剩下需要的数据再利用v-for循环遍历取出渲染
computed: {
	formList: function () {
		return this.list.filter(function (item) {
			return item.state
		})
	}
}
 

为什么在v-for中用key

  • 必须用key, 且不能是index和random

  • diff算法中是通过tag和key来判断,是否为sameNode

  • 减少渲染次数,提升渲染性能

vue组件如何通信

  • 父子组件 props和this.$emit
  • 自定义组件evnet. o n 和 e v e n t . on 和event. onevent.emit
  • vuex

事件

@click='onhandle'
onhandle(e){
	console.log(e) //e是原生的事件对象
}
事件被挂载到当前元素

组件之间如何进行通信的
props, this.$emit

自定义事件(兄弟组件)
const event = new Vue()
event.$emit('handle', 参数)
其他组件
event.$on('handle', this.handle)
最后,必须注销该事件,在beforeDestroy
event.$off('handle', 指定上面的函数)

事件修饰符

// 阻止单击事件继续传播
<a v-on:click.stop='doThis'></a>
//提交事件不再重载页面
<form v-on:submit.prevent='onSubmit'>
    
</form>
// 修饰符可以串联
<a v-on:click.stop.prevent=''></a>
// 只有修饰符
<form  v-on:submit.prevent>
    
</form>
// 添加事件监听器时使用事件捕获模式
// 即内部元素触发的事件(内部还有click事件)先在此处理,然后才交由内部元素进行处理
<div v-on:click.capture=''>
    
</div>
// 只当event.target是当前元素自身时触发处理函数
// 即事件不是从内部元素触发的
<div v-on:click.self=''>
    
</div>

按键修饰符

// 即alt, shift被一同按下时会触发
<button @click.ctrl=''></button>
// 有且只有ctrl被按下的时候触发
<button @click.ctrl.exact=''></button>
// 没有任何系统修饰符被按下的时候才触发
<button @click.exact=>
    
</button>

表单

v-model
常见表单项 textarea checkbox redio select(下拉列表)
修饰符 lazy number trim
<input type='text' v-model.lazy=''/> // 输完内容相应的数据才会变
<input type='text' v-model.trim=''/> // 去除输入内容的前后空格
<input type='text' v-model.number=''/> // 输入内容必须是数字


复选框
v-model绑定的是一个数组
选中那个选框, 那个选框的value就会添加到数组中

单选框
v-model绑定的是一个字符串
选中那个选框, 那个选框的value就会改变字符串

Vue生命周期

https://segmentfault.com/a/1190000011381906
render函数选项 > template选项 > outer HTML.

在beforeUpdate,可以监听到data的变化但是view层没有被重新渲染,view层的数据没有变化。等到updated的时候 view层才被重新渲染,数据更新。

<div id="app">
    <!--html中修改的-->
    <h1>{{message + '这是在outer HTML中的'}}</h1>
  </div>
</body>
<script>
  var vm = new Vue({
    el: '#app',
    template: "<h1>{{message +'这是在template中的'}}</h1>", //在vue配置项中修改的
    data: {
      message: 'Vue的生命周期'
    }
</script>
</html>
Vue父子组件生命周期钩子的执行顺序遵循:从外到内,然后再从内到外,不管嵌套几层深,也遵循这个规律。

父子组件渲染的过程(要保证子组件先渲染然后在渲染父组件 )
create 创建是从外到内
mounted 挂载是从内到外
当然,甩张Image给面试官这句话肯定是开玩笑的(适度幽默,缓解紧张气氛)。不过这张流程图还是有用的,因为它是我从Vue官网上拷贝下来的,只要你能理解了这张图,也就对Vue的生命周期有了一个大致的了解。那么接下来,闰土大叔将手摸手教你如何深入浅出地说出令面试官满意的、有亮点的回答。

在谈到Vue的生命周期的时候,我们首先需要创建一个实例,也就是在 new Vue ( ) 的对象过程当中,首先执行了init(init是vue组件里面默认去执行的),在init的过程当中首先调用了beforeCreate,然后在injections(注射)和reactivity(反应性)的时候,它会再去调用created。所以在init的时候,事件已经调用了,我们在beforeCreate的时候千万不要去修改data里面赋值的数据,最早也要放在created里面去做(添加一些行为)。

当created完成之后,它会去判断instance(实例)里面是否含有“el”option(选项),如果没有的话,它会调用vm.$mount(el)这个方法,然后执行下一步;如果有的话,直接执行下一步。紧接着会判断是否含有“template”这个选项,如果有的话,它会把template解析成一个render function ,这是一个template编译的过程,结果是解析成了render函数:

render (h) {
  return h('div', {}, this.text)
}
解释一下,render函数里面的传参h就是Vue里面的createElement方法,return返回一个createElement方法,其中要传3个参数,第一个参数就是创建的div标签;第二个参数传了一个对象,对象里面可以是我们组件上面的props,或者是事件之类的东西;第三个参数就是div标签里面的内容,这里我们指向了data里面的text。

使用render函数的结果和我们之前使用template解析出来的结果是一样的。render函数是发生在beforeMount和mounted之间的,这也从侧面说明了,在beforeMount的时候,$el还只是我们在HTML里面写的节点,然后到mounted的时候,它就把渲染出来的内容挂载到了DOM节点上。这中间的过程其实是执行了render function的内容。

在使用.vue文件开发的过程当中,我们在里面写了template模板,在经过了vue-loader的处理之后,就变成了render function,最终放到了vue-loader解析过的文件里面。这样做有什么好处呢?原因是由于在解析template变成render function的过程,是一个非常耗时的过程,vue-loader帮我们处理了这些内容之后,当我们在页面上执行vue代码的时候,效率会变得更高。

beforeMount在有了render function的时候才会执行,当执行完render function之后,就会调用mounted这个钩子,在mounted挂载完毕之后,这个实例就算是走完流程了。

后续的钩子函数执行的过程都是需要外部的触发才会执行。比如说有数据的变化,会调用beforeUpdate,然后经过Virtual DOM,最后updated更新完毕。当组件被销毁的时候,它会调用beforeDestory,以及destoryed。

这就是vue实例从新建到销毁的一个完整流程,以及在这个过程中它会触发哪些生命周期的钩子函数。那说到这儿,可能很多童鞋会问,钩子函数是什么意思?

钩子函数,其实和回调是一个概念,当系统执行到某处时,检查是否有hook,有则回调。说的更直白一点,每个组件都有属性,方法和事件。所有的生命周期都归于事件,在某个时刻自动执行。

其实,当你跟面试官阐述到这儿的时候,面试官基本上已经满意你的回答了,隐约看到了你的技术功底。当然,如果你还想更进一步,让面试官对你刮目相看,达到加分的效果,你还可以这样说:

在这个过程当中,Vue为我们提供了renderError方法,这个方法只有在开发的时候它才会被调用,在正式打包上线的过程当中,它是不会被调用的。它主要是帮助我们调试render里面的一些错误。

renderError (h, err) {
  return h('div', {}, err.stack)
}
有且只有当render方法里面报错了,才会执行renderError方法。

所以我们主动让render函数报个错:

Vue高级特性

  • 自定义v-model
  • $nextTick
  • slot
  • 动态、异步组件
  • keep-alive
  • mixin

自定义组件v-model

https://blog.csdn.net/starleejay/article/details/83654491
还有一种数据的交互方式是使用 v-model, 这种方法专门是 input 组件, 这只不过是 一种语法糖的形式:
input 组件上面本身有一个 onInput 事件。每当素输入框内容发生发生的时候,就会触发这个事件,然后把 input 的值 通过 $emit 传递出去。

<!-- 下面两个表达 实现同样的效果 -->
<input v-model='val'>
<input :value='val' @input="val = $event.target.value"/>

默认v-model

//这里需要注意的是 v-model 默认情况下只会接受 value 属性和响应 input 事件。 因为 v-model 本身是基于 input 框定制的, value 是 input 内部定制的绑定值的属性, input 方法是内部定制的当值改变出发的事件。
<!DOCTYPE html>
<html>
<head>
	<title></title>
	<script type="text/javascript" src="./vue.js"></script>
</head>
<body>
<div id="app">
	<p>{{text}}</p>
	<custom-model v-model='text'/>
</div>
	<script type="text/javascript">
		Vue.component('CustomModel', {
			props: {
				value: String
			},
			template: `
				<input type='text' :value='value' @input="$emit('input', $event.target.value)"/>
			`
		})
		
		var vm = new Vue({
			el: '#app',
			data: {
				text: ''
			}
		})
	</script>
</body>
</html>

私人定制model

上面说 v-model 只能接受 value 属性和响应 input 事件,其实这种说法并不是绝对的,我们可以通过 model 选项来改变这种情况。

model 接受有两个属性:

props 代替原来 value 的值。
event 代替原本 input 出发的事件。
<!DOCTYPE html>
<html>
<head>
	<title></title>
	<script type="text/javascript" src="./vue.js"></script>
</head>
<body>
<div id="app">
	<p>{{text}}</p>
	<custom-model v-model='text'/>
</div>
	<script type="text/javascript">
		Vue.component('CustomModel', {
			model: {
				prop: 'text1',
				event: 'change'
				// 父组件v-model相当于把:text1='text' @change='' 一起绑定了
			},
			props: {
				text1: String // 定义父组件传值的名字
			},
			template: `
				<input type='text' :value='text1' @input="$emit('change', $event.target.value)"/>
			` // 
                <child  :vlaue='val' @input='val = $event'/>
                <!-- 等同于 -->
                <child v-model='val'/>
		})
		
		var vm = new Vue({
			el: '#app',
			data: {
				text: ''
			}
		})
	</script>
</body>
</html>

$nextTick

vue 是异步渲染
data改变之后,DOM不会立刻渲染
$nextTick 会在DOM渲染之后被触发(回调),以获取最新DOM节点

$nextTick 是data更新导致dom渲染,渲染完之后待回调
this.$nextTick(() => {

})
页面渲染时会将data的修改做整合,多次data修改只会渲染一次(因为只有异步渲染才会做整合)
如果是同步渲染,改一次data,就会渲染一次,浪费资源

slot

 https://www.jianshu.com/p/0c0864d4b359
 
 新版本语法 v-slot
Vue的版本官方一直在更新变化,其中上面使用的 slot="xx" 以及后面会提及的 scope-slot="xx" 在2.6.0+ 中已弃用
所以我们要使用新的v-slot
父组件使用子组件
# 相当于 v-slot: 的简写 
tt 对插槽命名, 不能带引号
{a,b} 作用域插槽 子组件把数据传到父组件, 结构赋值的形式获取数据
作用域插槽是子组件可以向父组件传值
 <Child>
		<template #tt='{a,b}'>{{father}} {{a}}{{b}}</template>
</Child>
 子组件
<div>
	<slot name='tt' :a='son' :b='b'></slot>
</div>

动态组件

让多个组件使用同一个挂载点,并动态切换,这就是动态组件

https://blog.csdn.net/xiaohanzhu000/article/details/82195037
<component :is='组件名'/>

需要根据数据,动态渲染的场景。即组件类型不确定

异步组件

提高性能
有些组件太大,加载时间慢,不影响其他组件的加载。
有些组件暂时在该页面没有使用到,可以异步加载,需要的时候加载
import() 函数
按需加载,异步加载大组件

老版本
import {Comp} from ''
export default {
	components: {
		Comp
	}
}
异步组件
export default {
	components: {
		Comp: () => import('../../')
	}
}
路由加载异步组件
const Recommend = (resolve, reject) => {
  import('components/recommend/recommend').then((module) => {
    resolve(module)
  })
}

keep-alive

注意:只有当组件在 内被切换,才会有activated 和 deactivated 这两个钩子函数

https://www.jianshu.com/p/f5a58a17fb79

⑴.为什么要使用keep-alive?
            在vue中,我们使用component内置组件或者vue-router切换视图的时候,由于vue会主动卸载不使用的组件,所以我们不能保存组件之前的状态,而我们经常能遇到需要保存之前状态的需求,例如:搜索页(保存搜索记录),列表页(保存之前的浏览记录)等等。

    ⑵.keep-alive的作用?
            Keep-alive是一个vue的内置组件,它能将不活动的组件保存下来,而不是直接销毁,当我们再次访问这个组件的时候,会先从keep-alive中存储的组件中寻找,如果有缓存的话,直接渲染之前缓存的,如果没有的话,再加载对应的组件。

            作为抽象组件,keep-alive是不会直接渲染在DOM中的。

    ⑶.Keep-alive的属性?
        Keep-alive提供了三种可选属性

        Include-字符串或数组或正则表达式。只有名称匹配的组件被缓存。

        Exclude -字符串或数组或正则表达式。名称匹配的组件不会被缓存。

        Max -数字类型。表示最多可以缓存多少组件实例。

    ⑷.对于keep-alive需要知道的事情?
        Keep-alive提供了两个生命钩子,分别是activated与 deactivated。

        因为Keep-alive会将组件保存在内存中,并不会销毁以及重新创建,所以不会重新调用组件的created等方法,需要用activated与deactivated这两个生命钩子来得知当前组件是否处于活动状态。


如果没有这个,则每次组件的切换都有一个创建和销毁的过程,耗性能
组件进行缓存,从而节省性能,由于是一个抽象组件,所以在v页面渲染完毕后不会被渲染成一个DOM元素

当组件在keep-alive内被切换时组件的activated、deactivated这两个生命周期钩子函数会被执行

被包裹在keep-alive中的组件的状态将会被保留,例如我们将某个列表类组件内容滑动到第100条位置,那么我们在切换到一个组件后再次切换回到该组件,该组件的位置状态依旧会保持在第100条列表处

场景
缓存组件
频繁切换,不需要重复渲染
Vue常见性能优化
去哪儿网中,路由切换使用了keep-alive
当在城市选择页面中,选中一个城市,自动切换到首页时,会自动执行activated生命钩子函数,城市改变,然后请求对应城市的数据

mixin

多个组件有相同的逻辑(js代码),抽离出来
mixin 并不是完美的解决方案,会有一些问题
Vue3 提出的Composition API 旨在解决这些问题

mixin.js
export default {
	computed: {},
	mounted(){}
}

import myMixin from '..'
export default { 
	mixins: [myMixin] // 可以添加多个,自动合并起来
}
问题
变量来源不明确,不利于阅读
多个mixin可以造成命名冲突
mixin和组件可以出现多对多的关系,复杂度较高

Vuex

基本概念、基本使用、API必须要掌握

可能会考察state的数据结构设计(实现一个购物车)

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

个人的理解是全局状态管理更合适;简单的理解就是你在state中定义了一个数据之后,你可以在所在项目中的任何一个组件里进行获取、进行修改,并且你的修改可以得到全局的响应变更。

Vue-router

路由模式(hash, h5 history)
路由配置(动态路由,懒加载)

hash模式(默认) 如http://abc/com/#/user/10
h5 history 如http://abc.com/user/20 后者需要server端支持,因此无特殊需求可选择前者

懒加载
export default new VueRouter({
	route: [
		{
			path: '/home',
			component: () => import('../') //实现了懒加载
		}
	]
})

ref

ref 被用来给元素或子组件注册引用信息,引用信息将会注册在父组件的 $refs 对象上。

如果在普通dom元素上使用,应用指向的就是dom元素。如果是在子组件上使用,则指向的是子组件实例

ref和$refs其实就是用来获取/操作DOM元素的;类似于jquey中的$(".xxx");

Vue 原理

vdom 虚拟dom

diff diff算法

  • 组件化
  • 响应式
  • vdom 和 diff
  • 模板编译
  • 渲染过程
  • 前端路由

组件化基础

数据驱动视图(MVVM, setState)

组件化 很久以前就有了,vue和react只是搬过来了,只做了数据驱动视图的创新

MVVM:
M:负责存储数据(模型层)
V:负责显示数据(视图层)
VM:Vue自带的层(内置,连接层)MVVM 不用关注ViewModel如何实现的,它是Vue内置的(view和model连接的一部分)
怎么连接的呢? view通过触发一个事件,通过VM来监听dom事件对model进行操作。view通过vm来绑定model的数据
最重的是M层,dom操作被极大简化,使用MVVM是面向数据进行编程
ViewModel就是完全反应View的状态和行为,是View的内在抽象。

家切记ViewModel不是数据模型的封装,不是数据模型的封装,不是数据模型的封装,重说三!从ViewModel的外在属性来看,ViewModel和Model层的数据模型半毛钱关系都没有,它不过是使用了数据模型所携带的数据而已。另外,在MVVM模式的开发设计中,是重View和ViewModel,而轻Model的。当然说轻Model,不是说你Model层就可以随心所欲的设计,而是强调设计师的中心在界面上,程序员的重心在ViewModel上,最后将这精心设计的两层binding起来,就可以保证咱们项目的高大上~


Vue响应式数据原理

  • 核心点: Object.defineProperty实现响应式
  • 默认vue 在初始化数据时,会给data中的属性使用Object.defineProperty重新定义所有属性,当页面取到对应属性时,会进行依赖收集(收集当前组件的watcher)如果属性发生变化会通知相关依赖进行更新操作

(proxy 破K色)

  • vue 3.0 proxy 有兼容性问题,兼容性不好,且无法polyfill
Object.defineProperty缺点
深度监听,需要递归到底,一次性计算量大
无法监听新增属性/删除属性 (需要用Vue.set, Vue.delete来监听属性)
无法原生监听数组,需要特殊处理
	function updateView(){
		console.log('更新视图')
	}
	// 重新定义数组原型
	const oldArrayPropery = Array.prototype;
	const arrProto = Object.create(oldArrayPropery);
	['push','pop'].forEach(item => {
		arrProto[item] = function(){
			updateView()
			oldArrayPropery[item].call(this,...arguments)
		}
	})

	function defineReactive(target, key, value){
		// 深度监听
		// 在data中定义的本来就是对象,对对象中的每一个值增加监听
		observer(value)
		Object.defineProperty(target, key, {
			get(){
				return value
			},
			set(newVal){
				if(value !== newVal){
					// 对本来是简单数据类型,修改成复杂数据类型,然后对修改的数据进行监听
					observer(newVal)
                    // 注意value一直在闭包中,此处设置完之后,再get时是最新的数据
					value = newVal
					updateView()
				}
			}
		})
	}

	function observer(target){
		if(typeof target !== 'object' || typeof target === null){
			// 不是对象或数组
			return null
		}
		if(Array.isArray(target)){
			target.__proto__ = arrProto
		}
		for(let key in target){
			defineReactive(target, key, target[key])
		}
	}

	const data = {
		name: 'fhx',
		age: 20,
		info: {
			city: '成都' // 需要深度监听
		},
		arr: [1,2,3,4]
	}
    // 监听数据
	observer(data)
	// data.age = {num: 21}
	// data.age.num = 22
	// data.info.city = '北京'
	// data.x = 5 // 新增属性
	// delete data.name // 删除属性
	data.arr.push('5')

虚拟dom 和diff

DOM操作非常耗费性能
以前用jQuery,可以自行控制DOM操作的时机,手动调整
产生背景:

当传统的api或jQuery操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程.轻微的触碰可能就会导致页面重排,频繁操作可能导致页面卡顿,耗费性能.

vue和react都是数据驱动视图,不直接操作DOM,如何有效操作DOM?
相对于DOM对象,原生的 JS 对象处理起来更快,而且更简单。DOM 树上的结构、属性信息我们都可以用 JS对象表示出来.JS的执行速度很快, 特别v8引擎普及的背景下.


解决方案
有了一定的复杂度,想减少计算次数比较难
能不能把计算,更多的转移为js计算?因为js执行速度很快
vdom- 用js模拟dom结构,计算出最小的变更(对比虚拟dom,就用到了diff算法),操作dom

diff算法从时间复杂度O(n^3)(节点1000,计算1亿次,该算法不可用) 优化到时间复杂度为O(n)
只比较同一层级,不跨级比较
tag不相同,则直接删掉重建,不再深度比较
tag和key,两者相同,则认为是相同节点,不载深度比较

diff算法
diff算法是vdom中最核心,最关键的部分
diff算法能在日常使用中提现出来的如key

key
https://www.jianshu.com/p/a634eb3c19c2
为什么需要虚拟DOM,它有什么好处?
        Web界面由DOM树(树的意思是数据结构)来构建,当其中一部分发生变化时,其实就是对应某个DOM节点发生了变化,

        虚拟DOM就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有10次更新DOM的动作,虚拟DOM不会立即操作DOM,而是将这10次更新的diff内容保存到本地一个JS对象中,最终将这个JS对象一次性attch到DOM树上,再进行后续操作,避免大量无谓的计算量。所以,用JS对象模拟DOM节点的好处是,页面的更新可以先全部反映在JS对象(虚拟DOM)上,操作内存中的JS对象的速度显然要更快,等更新完成后,再将最终的JS对象映射成真实的DOM,交由浏览器去绘制。
https://www.jianshu.com/p/af0b398602bc

编译模板

实现模板编译使用了with语法
let obj = {a: 'a', b: 'b'}
with(obj){
	console.log(a) // 'a'
	console.log(b) // 'b' 改变了自由变量的作用域看,可以直接调用obj的属性
	console.log(c) // 如果obj中没有对应的属性,则直接报错
}

模板不是HTML,有指令、插值、js表达式,能实现判断、循环
html是标签语言,只有js才能实现判断、循环
模板编译为render函数,执行render函数返回vnode,再到渲染和更新
基于vnode 再执行patch和diff
vue组件可以用render代替template

组件 渲染、更新 过程

一个组件渲染到页面,修改data触发更新(数据驱动视图)
其背后原理是什么,需要掌握哪些要点?
考察对流程了解的全面程度

响应式: 监听data属性getter setter(包括数组)
模板编译: 模板到render函数,再到vnode
vdom: patch(elem, vnode) (把vnode渲染到一个空的elem上,初始渲染)和patch(vnode, newVnode)(newvnode去更新oldvnode)
初次渲染过程
解析模板为render函数(或在开发环境(页面刷新,打包,编译)已完成,使用vue-loader)
触发响应式,监听data属性getter setter
执行render函数(执行的时候已经从vue实例中拿data数据,触发data中getter事件),生成vnode, patch(elem, vnode)

更新过程
修改data, 触发setter(此前在getter中已被监听)
重新执行render函数,生成newVnode
patch(vnode, newVnode) patch会帮我们执行最短距离(最小的变更)更新的dom上

异步渲染
$nextTick
汇总data的修改,一次性更新视图(页面渲染会将data修改做整合)
减少DOM操作次数,提高性能

vue的整体实现流程
第一步:解析模板成render函数(因为预编译,这一步在打包的时候就发生了)
1. with的用法
2. 模板中所有信息都被render函数包含
3. 模板中用到的data中的属性都变成了js变量
4. 模板中的v-modal,v-for,v-on都变成js逻辑
5. render函数返回vnode

第二步:响应式开始监听(页面打开js开始执行的时候发生)
1. Object.defineProperty
2. 将data的属性代理到vm上

第三步:首次渲染,显示页面且绑定依赖
1.虚拟dom中的patch函数(第一种用法) patch(el, vnode)

 第四步:data属性变化,触发re-render
1.虚拟dom中的patch函数(第二种用法) patch(preVnode, newVnode)

前端路由原理

稍微复杂一点的SPA, 都需要路由
vue-router 也是Vue全家桶的标配之一
hash的特点
hash变化会触发网页跳转,即浏览器的前进与后退
hash变化不会刷新页面,SPA必需的特点
hash永远不会提交到server端(前端自生自灭)

修改hash
js修改url
手动修改url的hash
浏览器的前进后退

监听hash
window.onhashchange = (event) => {
console.log(event.oldURL, event.newURL, location.hash)
}
h5 history 
用url规范的路由,但跳转时不刷新页面(用户看不出是前端路由还是后端路由)
history.pushState(点击事件等操作)(打开一个新的路由)
const state = {name: ''}
history.pushState(state, '', 'page1')第三个参数就是要跳转的路由
window.onpopstate // 监听浏览器的前进和后退
window.onpopstate = (event) => {
console.log(event.state)//通过history.pushState定义的state,必须是到有state的路由才会打印
}
to B 的系统推荐用hash,简单易用,对url规范不敏感
to C 的系统,可考虑选择h5 history ,但需要服务端支持
能选择简单的就别用复杂的,要考虑成本和收益

对MVVM的理解

MVVM分为Model、View、ViewModel三者。

  • Model:代表数据模型,数据和业务逻辑都在Model层中定义;
  • View:代表UI视图,负责数据的展示;
  • ViewModel:就是与界面(view)对应的Model。因为,数据库结构往往是不能直接跟界面控件一一对应上的,所以,需要再定义一个数据对象专门对应view上的控件。而ViewModel的职责就是把model对象封装成可以显示和接受输入的界面数据对象。
Model和View并无直接关联,而是通过ViewModel来进行联系的,Model和ViewModel之间有着双向数据绑定的联系。因此当Model中的数据改变时会触发View层的刷新,View中由于用户交互操作而改变的数据也会在Model中同步。

简单的说,ViewModel就是View与Model的连接器,View与Model通过ViewModel实现双向绑定。

data为什么是一个函数

data是一个函数的话,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。

所以说vue组件的data必须是函数。这都是因为js的特性带来的,跟vue本身设计无关。

js本身的面向对象编程也是基于原型链和构造函数,应该会注意原型链上添加一般都是一个函数方法而不会去添加一个对象了。

ajax请求应该放在哪个生命周期

Mounted
js是单线程,ajax异步获取数据

为什么不在 created 里去发ajax?created 可是比 mounted 更早调用啊,更早调用意味着更早返回结果,那样性能不是更高?
首先,一个组件的 created 比 mounted 也早调用不了几微秒,性能没啥提高;
而且,等到异步渲染开启的时候,created 就可能被中途打断,中断之后渲染又要重做一遍,想一想,在 created 中做ajax调用,代码里看到只有调用一次,但是实际上可能调用 N 多次,这明显不合适。
相反,若把发ajax 放在 mounted,因为 mounted 在第二阶段,所以绝对不会多次重复调用,这才是ajax合适的位置.


在created的时候,视图中的dom并没有被渲染出来,所以此时如果直接去操作dom节点(获取到的数据动态渲染到页面上),无法找到相关元素。
在mounted中,由于此时的dom元素已经渲染出来了,所以可以直接使用dom节点。

一般情况下,都放在mounted中,保证逻辑的统一性。因为生命周期是同步执行的,ajax是异步执行的。

服务端渲染不支持mounted方法,所以在服务端渲染的情况下统一放在created中。

何时要使用异步组件

  • 加载大组件
  • 路由异步加载
  • 优化性能

何时需要使用beforeDestory

  • 解绑自定义事件 event.$off
  • 清除定时器
  • 解绑自定义的dom事件,如window.scroll addEventListener( )
  • 不解绑都有可能造成内存泄漏(页面越来越卡,直到卡死)

vuex中action和mutation有何区别

  • action中处理异步,mutation不可以
  • mutation做原子操作 (一次只能做一个操作)
  • action可以整合多个mutation
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值