Vue 原理

组件化基础

  • 传统组件,只是静态渲染,更新还要依赖于操作DOM
  • Vue 数据驱动视图:Vue MVVM
  • React 数据驱动视图:React setState

如何理解 MVVM

数据驱动视图:不再操作dom,需要更新视图就去修改数据,使开发者更关注数据,复杂度变低。
第一个M-Model,第二个V-View,第三个VM-ViewModel
在这里插入图片描述

出处:https://coding.imooc.com/lesson/419.html

Modelvue组件的data
ViewModel通过ViewModel来做关联,像监听事件、监听指令这一些操作。通过ViewModel这一层,在Model修改的时候,就可以立刻知道,然后执行View的渲染;View这一层如果有点击事件的操作,也可以立刻知道,然后去修改Model的数据。

Vue 响应式

组件data的数据一旦变化,立刻触发视图的更新。这是实现数据驱动视图的第一步。

核心 API - Object.defineProperty

Object.defineProperty有一些缺点,Vue3.0启用Proxy来实现响应式。
Proxy有兼容性问题,无法polyfill

// Object.defineProperty 基本用法

const data = {};
let name = 'zhangsan';

Object.defineProperty(data, 'name', {
	get: function () {
		console.log('get');
		return name;
	},
	set: function (newVal) {
		console.log('set');
		name = newVal;
	}
});

// 测试
console.log(data.name); // get zhangsan
data.name = 'lisi'; // set

如何深度监听 data 的变化

  • 监听对象
  • 监听数组
  • 复杂对象,深度监听
// 触发更新视图
function updateView() {
    console.log('视图更新')
}

// 重新定义数组原型
const oldArrayProperty = Array.prototype;
// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
    arrProto[methodName] = function () {
        updateView(); // 触发视图更新
        oldArrayProperty[methodName].call(this, ...arguments);
        // Array.prototype.push.call(this, ...arguments)
    }
});

// 重新定义属性,监听起来
function defineReactive(target, key, value) {
    // 深度监听
    observer(value);

    // 核心 API
    Object.defineProperty(target, key, {
        get() {
            return value;
        },
        set(newValue) {
            if (newValue !== value) {
                // 深度监听
                observer(newValue);

                // 设置新值
                // 注意,value 一直在闭包中,此处设置完之后,再 get 时也是会获取最新的值
                value = newValue;

                // 触发更新视图
                updateView();
            }
        }
    })
}

// 监听对象属性
function observer(target) {
    if (typeof target !== 'object' || target === null) {
        // 不是对象或数组
        return target;
    }

    // 污染全局的 Array 原型
    // Array.prototype.push = function () {
    //     updateView()
    //     ...
    // }

    if (Array.isArray(target)) {
        target.__proto__ = arrProto;
    }

    // 重新定义各个属性(for in 也可以遍历数组)
    for (let key in target) {
        defineReactive(target, key, target[key]);
    }
}

// 准备数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        address: '北京' // 需要深度监听
    },
    nums: [10, 20, 30]
};

// 监听数据
observer(data);

// 测试
data.name = 'lisi';
data.age = 21;
console.log('age', data.age);
// data.x = '100'; // 新增属性,监听不到 —— 所以有 Vue.set
// delete data.name; // 删除属性,监听不到 —— 所以有 Vue.delete
data.info.address = '上海'; // 深度监听
data.nums.push(4); // 监听数组

vue 如何监听数组变化

  • 重新定义数组原型
// 重新定义数组原型
const oldArrayProperty = Array.prototype;

// 创建新对象,原型指向 oldArrayProperty ,再扩展新的方法不会影响原型
const arrProto = Object.create(oldArrayProperty);

['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
    arrProto[methodName] = function () {
        updateView(); // 触发视图更新
        oldArrayProperty[methodName].call(this, ...arguments);
        // Array.prototype.push.call(this, ...arguments);
    }
});

Object.defineProperty 的缺点

  • 深度监听,需要递归到底,一次性计算量很大
  • 无法监听新增属性、删除属性(所以需要Vue.setVue.delete这两个API去做这个事情)
  • 无法原生监听数组,需要特殊处理

虚拟 DOM 和 diff

vdom 是实现 VueReact 的重要基石。
diff 算法是vdom中最核心、最关键的部分。

背景

dom 操作非常耗费性能。
以前用jQuery,可以自行控制DOM操作的时机,手动调整。

vuereact是数据驱动视图,如何有效控制dom操作?
有了一定复杂度,想减少计算次数比较难,就想能不能把计算更多的转移为js计算,因为js执行速度很快。
解决方案-vdom

用 JS 来模拟 DOM 结构 - vnode

<div id ="div1" class="container">
	<p>vdom</p>
	<ul style="font-size: 20px">
		<li>a</li>
	</ul>
</div>
{
	tag: 'div',
	props: {
		className: 'container',
		id: 'div1'
	},
	children: [
		{
			tag: 'p',
			props: null,
			children: 'vdom'
		},
		{
			tag: 'ul',
			props: {
				style: 'font-size: 20px'
			},
			children: [
				{
					tag: 'li',
					props: null,
					children: 'a'
				}
			]
		}
	]
}

通过 snabbdom 学习 vdom

简介强大的vdom库,易学易用。Vue参考它实现的vdomdiff
github地址:https://github.com/snabbdom/snabbdom

h 函数

首先通过 h 函数,返回 vnode
在这里插入图片描述

出处:https://coding.imooc.com/lesson/419.html

patch 函数

然后通过 patch 函数将 vnode 挂载到真实 dom 上。(初次渲染:将虚拟 dom 真正渲染到节点上)
再通过 patch 函数将新的 vnode 给旧的 vnode 做更新。(dom 更新)
最后将新的 vnode 清空。
在这里插入图片描述

出处:https://coding.imooc.com/lesson/419.html

vdom 总结

  • js 模拟 dom 结构(vnode
  • 新旧 vnode 对比,得出最小的更新范围,最后更新 dom

这样在数据驱动视图的模式下,可以有效控制 dom 操作。

diff 算法

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

diff 即对比,是一个广泛的概念,如 linux diff 命令(对比两个文件的差异)、git diff等。
两个 js 对象也可以做 diff,如 https://github.com/cujojs/giff
两棵树做 diff,如这里的 vdom diff
在这里插入图片描述

出处:https://coding.imooc.com/lesson/419.html

以上 树 diff 的复杂度 O(n^3):
第一,遍历tree1
第二,遍历tree2
第三,排序

优化时间复杂度到O(n)

  • 只比较同一层级,不跨级比较
  • tag 不相同,直接删掉重建,不再深度比较
  • tagkey 两者都相同,则认为是相同节点,不再深度比较。

diff 算法的对比过程(snabbdom 的对比方式)

  1. patch 函数
patch(vnode, newVnode);
  • 第一个参数不是vnode,就创建一个空的vnode,关联到这个dom元素。
  • 如果是相同的vnodekeytag都相同),就执行patchVnode
  • 如果是不同的vnode,删掉重建。
  1. patchVnodeaddVnodesremoveVnodes
patchVnode(oldVnode, vnode, insertedVnodeQueue);
  • 新的如果有children
    • 新旧都有children:执行updateChildren深度比较。
    • 新的有children,旧的无children:添加vnodeaddVnodes进行添加dom操作)。
    • 旧的有children,新的无children:移除vnoderemoveVnodes进行删除dom操作)。
    • 旧的text有值:清空text
  • 新的如果无 children
    • 新旧 text 不相同,移除,重新设置内容。
  1. updateChildrenkey 的重要性)
    在这里插入图片描述

出处:https://coding.imooc.com/lesson/419.html

  • 开始和开始对比:执行sameVnode对比
  • 结束和结束对比:执行sameVnode对比
  • 开始和结束对比:执行sameVnode对比
  • 结束和开始对比:执行sameVnode对比
  • 其他
    • 拿新节点的key,看能否对应上oldChildren中的某个节点的key
      • key 没对应上:新增 dom
      • key 对应上了:再判断 tag是否相等(sameVnode的条件)
        • tag 不相等:新增 dom
        • tag 相等:执行patchVnode函数
          在这里插入图片描述

出处:https://coding.imooc.com/lesson/419.html

模板编译

JS 的 with 语法

const obj ={ a: 100, b: 200 };

console.log(obj.a);
console.log(obj.b);
console.log(obj.c); // undefined
// 使用 with,能改变 {} 内自由变量的查找方式
// 将 {} 内自由变量当作 obj 的属性来查找
with(obj) {
	console.log(a);
	console.log(b);
	console.log(c); // 会报错!!!
}
  • 改变 {} 内自由变量的查找规则,当作obj属性来查找。
  • 如果找不到匹配的obj属性,就会报错。

with要慎用,它打破了作用域规则,易读性变差。

vue 模板

模板不是html,有指令、插值、JS表达式,能实现判断、循环。
html是标签语言,只有js才能实现判断、循环(图灵完备的语言)。
因此,模板一定是转换为某种js代码,即编译模板。

// package.json
{
  "name": "vue-template-compiler-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "vue-template-compiler": "^2.6.10"
  }
}
// index.js
const compiler = require('vue-template-compiler');

// 插值
const template = `<p>{{message}}</p>`;
// with(this){return createElement('p',[createTextVNode(toString(message))])}
// h -> vnode
// createElement -> vnode

// 表达式
const template = `<p>{{flag ? message : 'no message found'}}</p>`;
// with(this){return _c('p',[_v(_s(flag ? message : 'no message found'))])}

// 属性和动态属性
const template = `
    <div id="div1" class="container">
        <img :src="imgUrl"/>
    </div>
`;
// with(this){return _c('div',
//      {staticClass:"container",attrs:{"id":"div1"}},
//      [
//          _c('img',{attrs:{"src":imgUrl}})])}

// 条件
const template = `
     <div>
        <p v-if="flag === 'a'">A</p>
        <p v-else>B</p>
    </div>
`;
// with(this){return _c('div',[(flag === 'a')?_c('p',[_v("A")]):_c('p',[_v("B")])])}

// 循环
const template = `
    <ul>
        <li v-for="item in list" :key="item.id">{{item.title}}</li>
    </ul>
`;
// with(this){return _c('ul',_l((list),function(item){return _c('li',{key:item.id},[_v(_s(item.title))])}),0)}

// 事件
const template = `
    <button @click="clickHandler">submit</button>
`;
// with(this){return _c('button',{on:{"click":clickHandler}},[_v("submit")])}

// v-model
const template = `<input type="text" v-model="name">`
// 主要看 input 事件
// with(this){return _c('input',{directives:[{name:"model",rawName:"v-model",value:(name),expression:"name"}],attrs:{"type":"text"},domProps:{"value":(name)},on:{"input":function($event){if($event.target.composing)return;name=$event.target.value}}})}

// render 函数
// 返回 vnode
// patch

// 编译
const res = compiler.compile(template)
console.log(res.render)

// ---------------分割线--------------

// // 从 vue 源码中找到缩写函数的含义
// function installRenderHelpers (target) {
//     target._c = createElement;
//     target._o = markOnce;
//     target._n = toNumber;
//     target._s = toString;
//     target._l = renderList;
//     target._t = renderSlot;
//     target._q = looseEqual;
//     target._i = looseIndexOf;
//     target._m = renderStatic;
//     target._f = resolveFilter;
//     target._k = checkKeyCodes;
//     target._b = bindObjectProps;
//     target._v = createTextVNode;
//     target._e = createEmptyVNode;
//     target._u = resolveScopedSlots;
//     target._g = bindObjectListeners;
//     target._d = bindDynamicKeys;
//     target._p = prependModifier;
// }
  • 模板编译为render函数,执行render函数返回vnode
  • 基于vnode再执行patch函数和diff算法
  • 使用webpack vue-loader,会在开发环境下编译模板
// vue 组件中可以使用 render 代替 template
Vue.component('heading', {
	// template: 'xxx',
	render: function(createElement) {
		return createElement(
			'h' + this.level,
			[
				createElement('a', {
					attrs: {
						name: 'headerId',
						href: '#' + 'headerId'
					}
				}, 'this is a tag')
			]
		);
	}
});

// 在有些复杂情况中,不能用 template,可以考虑用 render
// react 一直都用 render (没有模板)

总结:

  • vue template complier 将模板编译为 render 函数
  • 执行 render 函数生成 vnode

组件渲染/更新的过程

在这里插入图片描述

出处:https://coding.imooc.com/lesson/419.html

初次渲染的过程:

  • 解析模板为render函数(或者使用vue-loader在开发过程中就解析完成)
  • 触发响应式,监听data属性gettersetter
  • 执行 render 函数,生成 vnode,然后通过patch函数将vnode绑定到真实dompatch(elem, vnode)
// 执行 render 函数会触发 getter
<p>{{message}}</p>

<script>
	export default {
		data() {
			return {
				message: 'hello', // 会触发 get
				city: '北京' // 不会触发 get,因为模板没用到,即和视图没关系
			};
		}
	}
</script>

更新过程:

  • 修改data,触发setter(此前在getter中已被监听)
  • 重新执行 render函数,生成 newVnode
  • 再执行patch函数用新的vnode去更新旧的vnodepatch(vnode, newVnode)

异步渲染:
vue 是异步渲染的,汇总 data 的修改,一次性更新视图,目的是减少 dom 操作次数,提高性能。(react 也是异步渲染)

前端路由原理

稍微复杂一点的SPA,都需要路由。
浏览器刷新指的是重新进行网页的http请求。

vue-router 的路由模式

  • hash
  • H5 history

网页 url 组成部分

// http://127.0.0.1:8881/01-hash.html?a=100&b=20#/aaa/bbb 
location.protocol // 'http:'
location.hostname // '127.0.0.1' 
location.host // '127.0.0.1:8881' 
location.port // '8881'
location.pathname // '/01-hash.html' 
location.search // '?a=100&b=20' 
location.hash // '#/aaa/bbb'

hash 的特点

  • hash 变化会触发网页跳转,即浏览器的前进、后退
  • hash 变化不会刷新页面,SPA必需的特点
  • hash用于不会提交到server端(前端自生自灭)
  • 使用 window.onhashchange 来实现

*[浏览器刷新 ]:指的是重新进行网页的http请求。

<!-- hash.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>hash test</title>
</head>
<body>
    <p>hash test</p>
    <button id="btn1">修改 hash</button>

    <script>
        // hash 变化,包括:
        // a. JS 修改 url
        // b. 手动修改 url 的 hash
        // c. 浏览器前进、后退
        window.onhashchange = (event) => {
            console.log('old url', event.oldURL)
            console.log('new url', event.newURL)

            console.log('hash:', location.hash)
        }
        
		// 谷歌有效,火狐无效
		// window.onhashchange = (event) => {
		//   console.log(`old url:${event.oldURL}`)
		//   console.log(`new url:${event.newURL}`)
		//   console.log(`hash:${location.hash}`)
		// }
		
		// 谷歌、火狐浏览器都有效
		// window.addEventListener('hashchange', (event) => {
		//   console.log(`old url:${event.oldURL}`)
		//   console.log(`new url:${event.newURL}`)
		//   console.log(`hash:${location.hash}`)
		// })

        // 页面初次加载,获取 hash
        document.addEventListener('DOMContentLoaded', () => {
            console.log('hash:', location.hash)
        })

        // JS 修改 url
        document.getElementById('btn1').addEventListener('click', () => {
            location.href = '#/user'
        })
    </script>
</body>
</html>

H5 history

url 规范的路由,但跳转时不刷新页面。
主要通过 history.pushStatewindow.onpopstate来实现。
需要server端支持。

正常页面浏览:
首次访问 https://github.com/xxx 刷新页面
点击跳转 https://github.com/xxx/yyy刷新页面
点击跳转 https://github.com/xxx/yyy/zzz 刷新页面

改造成 H5 history 模式:
首次访问 https://github.com/xxx 刷新页面
点击跳转 https://github.com/xxx/yyy 前端跳转,不刷新页面
点击跳转 https://github.com/xxx/yyy/zzz 前端跳转,不刷新页面

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>history API test</title>
</head>
<body>
    <p>history API test</p>
    <button id="btn1">修改 url</button>

    <script>
        // 页面初次加载,获取 path
        document.addEventListener('DOMContentLoaded', () => {
            console.log('load', location.pathname)
        })

        // 打开一个新的路由
        // 【注意】用 pushState 方式,浏览器不会刷新页面
        document.getElementById('btn1').addEventListener('click', () => {
            const state = { name: 'page1' }
            console.log('切换路由到', 'page1')
            history.pushState(state, '', 'page1') // 重要!!
        })

        // 监听浏览器前进、后退
        window.onpopstate = (event) => { // 重要!!
            console.log('onpopstate', event.state, location.pathname)
        }

        // 需要 server 端配合,可参考
        // https://router.vuejs.org/zh/guide/essentials/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90
    </script>
</body>
</html>

两者选择

  • to B 的系统推荐用 hash,简单易用,对 url 规范不敏感
  • to C 的系统,可以考虑选择 H5 history,但需要服务端支持

FQA

  1. 关于前端路由原理。

    • 前端常用路由模式分为 hash 模式和H5 history模式。
    • 其中hash模式,可以作为SPA,是因为 # 后面的内容变化,浏览器不会刷新页面,并且服务器也不会收到请求,可以通过 window.onhashchange 事件来感知这个动作。
    • 另外H5 history模式,剔除了 #,直接请求就会刷新页面并发送到服务器,所以我们需要在服务器端进行设置,让它只识别“协议 + 端口 + path”,斜杠后面的都忽略,这样我们只更换斜杠后面的内容,服务器也就不会认为是浏览器重新请求了页面。H5 history 模式需要使用 window.onpopstate 来进行监听,使用 history.pushState 来进行斜杠后面内容的更新。
  2. 什么是虚拟domdiff算法?

    • 由于DOM操作比较耗时,所以虚拟DOM技术的出现,减少了DOM的操作次数,提升了浏览器的性能。所谓的虚拟DOM就是使用JS对象来模拟DOM结构,当js程序中需要对DOM进行操作时,首先会修改虚拟DOM,然后通过diff算法对比新旧虚拟DOM,最终使浏览器可以通过最少的DOM操作,完成页面的更新。
    • diff算法就是虚拟DOM的核心,对比新旧DOM时,有三个原则:第一,只进行同一层级的虚拟DOM对比,不会跨级对比;第二、如果新旧虚拟DOM的标签不一样,直接按照新的虚拟DOM进行渲染;第三、当新旧DOM的标签相同,并且key也相同的时候,那么则认为这两个虚拟DOM相同,不在对这两个虚拟DOM进行深层次的对比。也正是由于这三条原则,使得diff算法的时间复杂度为O(n)
  3. 为什么 v-for 中需要使用 key ?

    • diff算法中,如果没有 key 值,则会对内容进行清空,并重新赋予新值;
    • 如果有 key 值的存在,那么会对新旧vnode进行对比,如果在oldvnode找到了与newvnode相同的key,如果内容没变则直接使用之前的真实dom,如果内容变了,才生成新的真实dom进行替换。这样可以减少渲染次数,提升渲染性能。
    • 每个 key 唯一才能知道一个元素前后改变的位置,如果不是唯一的话元素可能会移动到其他元素的位置而不是这个元素新变化的位置。
  4. 既然可以用window去定义全局变量,为什么vue里还有用vuex呢?

    • Vuex 不仅仅是全局的,它是能结合 Vue 数据响应式特性的全局。
    • window 上可以定义全局变量,但没有响应式。
  5. v-showv-if 的区别。

    • v-show 通过 cssdisplay 控制显示和隐藏
    • v-if 控制组件真正的渲染和销毁,而不是显示和隐藏
  6. watcherwatch的区别。

    • watcher 是响应式内部的监听,会监听组件 data 属性.
    • watchdata 属性改变之后的一个回调函数。
  7. 描述 vue 的生命周期,以及父子组件生命周期的关系。
    单个组件生命周期:

    • 初始化阶段:beforeCreate、created
    • 挂载阶段:beforeMount、mounted
    • 更新阶段:beforeUpdate、updated
    • 销毁阶段:beforeDestroy、destroyed

    父子组件生命周期的关系:

    • 父组件初始化完成、子组件才初始化完成
    • 子组件挂载完成、父组件才挂载完成
    • 父组件开始更新、子组件开始更新、子组件更新完成、父组件才更新完成
    • 父组件开始销毁、子组件开始销毁、子组件销毁完成、父组件才销毁完成
  8. vue组件如何通讯?

    • 父子组件之间通过propsthis.$emit
    • 兄弟组件之间通过自定义事件(event.$onevent.$emitevent.$off
    • 所有组件之间可通过vuex
  9. 描述组件渲染和更新的过程。

    • 解析模板为render函数(或者使用vue-loader在开发过程中就解析完成)
    • 触发响应式,监听data属性gettersetter
    • 执行 render 函数,生成 vnode,然后通过patch函数将vnode渲染成真实dompatch(elem, vnode)
    • 修改data,触发setter(此前在getter中已被监听)
    • 会重新执行 render函数,生成 newVnode
    • 再执行patch函数用新的vnode去更新旧的vnodepatch(vnode, newVnode)),对比后渲染出来
  10. 双向数据绑定 v-model 的实现原理。

    • input元素的value设置为this.name
    • 绑定 input事件,将$event.target.value赋值到this.name
    • data更新,会触发re-render,从而实现双向绑定。
  11. 对 MVVM 的理解。

    • 第一个字母M指的是Model,也就是data;第二个字母V指的是View;最后的VM指的是ViewModel
    • ViewModel通过ViewModel来做关联,像监听事件、监听指令这一些操作,通过ViewModel这一层,在Model修改的时候,就可以立刻知道,然后执行View的渲染;View这一层如果有点击事件的操作,也可以立刻知道,然后去修改Model的数据。
  12. ajax 请求应该放在哪个生命周期?
    - 一般放在moounted中执行。
    - 页面初始化时,createdmounted 的耗时非常短,所以 ajax 放在 created 里所能带来的优化效果,并不明显。而且js是单线程的,ajax是异步获取数据,拿到数据后也是等dom渲染完成后才能去渲染数据,放在 created 里,更早地获取到数据也没什么用。

  13. 为什么vue组件的 data 必须是一个函数?

    • 因为export出去的vue组件是一个class,每次使用这个组件的时候是对这个类的实例化,在实例化的时候去执行这个data,如果这个data不是函数的话,那每个组件的data都是一样的了,就共享了,如果是函数的话,那每次执行这个data就不会相互影响。
  14. 如何将组件所有 props 传递给子组件?

    • $props
<User v-bind="$props" />
  1. 多个组件有相同逻辑,如何抽离?
    • mixin
    • 但是它有一些缺点:代码可读性变差;命名可能导致冲突;可能出现多对多的关系,复杂度变高。
  2. 何时使用异步组件?
    • 加载大组件的时候:比较编辑器、表格
    • 路由异步加载:多路由,切换的时候可以异步加载
  3. 何时使用 keep-alive
    • 需要缓存组件,不需要重复渲染的时候使用:比如多个静态tab 的页切换
  4. 何时需要使用 beforeDestory
    • 解绑自定义事件,比如:event.$off
    • 清除定时器
    • 解绑自定义的dom事件,比如:addEventListner
  5. 什么是作用域插槽?
    • slot 里面有一个数据,想把这个数据暴露给使用它的地方。
    • 子组件的<slot>指定:slotData将数据暴露出去;父组件通过v-slot定义一个名字xxx,然后通过xxx.slotData获取到子组件暴露出去的数据。
  6. vuexactionmutation 的区别。
    • action 中可以处理异步,mutation不可以
    • mutation只能做原子操作
    • action 可以整合多个 mutation
  7. vue-router 常用的路由模式
    • hash(默认)
    • H5 history(需要server端支持)
  8. 如何配置 vue-router 异步加载?
    - componentsimport()函数这种方式。
export default new VueRouter({
	routes: [
		{
			path: '/',
			component: () => import('./../components/Navigator')
		},
		{
			path: '/feedback',
			component: () => import('./../components/FeedBack')
		}
	]
});
  1. 请用vdom描述一个dom结构。
<div id ="div1" class="container">
	<p>vdom</p>
	<ul style="font-size: 20px">
		<li>a</li>
	</ul>
</div>
{
	tag: 'div',
	props: {
		className: 'container',
		id: 'div1'
	},
	children: [
		{
			tag: 'p',
			props: null,
			children: 'vdom'
		},
		{
			tag: 'ul',
			props: {
				style: 'font-size: 20px'
			},
			children: [
				{
					tag: 'li',
					props: null,
					children: 'a'
				}
			]
		}
	]
}
  1. 监听data变化的核心api是什么?

    • Object.defineProperty
    • 对象的深度监听需要递归实现
    • 数组的监听需要重新定义原型,重写pop、posh等方法

    缺点:

    • 深度监听,需要递归到底,一次性计算量很大
    • 无法监听新增属性、删除属性(所以需要Vue.setVue.delete这两个API去做这个事情)
    • 无法原生监听数组,需要特殊处理
  2. 请描述响应式原理。

    • 解析模板为render函数
    • 触发响应式,通过Object.defineProperty监听data属性gettersetter
    • 执行 render 函数,生成 vnode,然后通过patch函数将vnode渲染成真实dom
    • 修改data,触发setter
    • 会重新执行 render函数,生成 newVnode
    • 再执行patch函数对比新旧vnode后重新渲染
  3. diff 算法的复杂度?

    • O(n)
    • 是因为它做了一些调整,第一,只比较同一层级,不跨级比较,第二,tag 不相同,则直接删掉重建,第三,如果tagkey都相同,则认为是相同节点,不再深度比较。从而将O(n^3)变成了O(n)
  4. 简述 diff 算法的过程。

    • 通过patch函数去对比新旧vnode,如果是相同节点,也就是tagkey都相同,就去执行patchVnode对比节点内容,使用addVnodesremoveVnodes进行dom的添加和删除操作。在patchVnode对比的过程中,如果新旧vnode都有children,就再执行updateChildren进行深度比较。
  5. vue 常见性能优化方式。

    • 合理使用 v-showv-if
    • 合理使用 computed
    • v-for时加key,以及避免和v-if同时使用(因为v-for优先级更高,每次循环v-if都会重新计算,浪费性能)
    • 自定义事件、dom事件即时销毁
    • 合理使用异步组件
    • 合理使用keep-alive
    • data 层级不要太深(因为响应式的深度监听需要一次性递归到底,递归的层级比较多,可能导致页面卡一下)
    • 使用 vue-loader 在开发环境下做模版编译(预编译)
    • webpack 层级的优化
    • 前端通用性能的优化:图片懒加载
    • 使用 SSR
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值