√
1.vue
优点?
- 轻量级框架:
只关注视图层,是一个构建数据的视图集合,大小只有几十kb; - 数据绑定:
保留了angular的特点,在数据操作方面更为简单; - 组件化:
保留了react的优点,实现了html的封装和重用,在构建单页面应用方面有着独特的优势;
视图,数据,结构分离:
使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作; - 虚拟DOM:
dom操作是非常耗费性能的, 不再使用原生的dom操作节点,极大解放dom操作,但具体操作的还是dom不过是换了另一种方式;
运行速度更快:
相比较与react而言,同样是操作虚拟dom,就性能而言,vue存在很大的优势。
2.vue
父组件向子组件传递数据?
父子间通信:父亲提供数据通过属性 props传给儿子;儿子通过 $on 绑父亲的事件,再通过 $emit 触发自己的事件(发布订阅)
利用父子关系 $parent 、$children
父组件提供数据,子组件注入。 provide 、 inject ,插件用得多。
ref 获取组件实例,调用组件的属性、方法
vuex 状态管理实现通信
祖孙节点可以使用: $attrs/$listeners
3.子组件向父组件传递事件?
- $emit方法
自定义子组件
{{titleName}}
export default {
name: "HelloWorld",
props:{
titleName:{
type:string,
default:""
}
},
methods: {
clickAction(){
this.$emit('clickChild',this.titleName);
}
}
};
父组件中调用子组件
import ActivityHead from "./ActivityHead.vue";
export default {
name: "HelloWorld",
components: {
ActivityHead
},
methods: {
clickChild(msg){
console.log(msg);
}
}
};
4.v-if
与v-show
的区别
共同点:
都能控制元素的显示和隐藏;
不同点:
实现本质方法不同,v-show本质就是通过控制css中的display设置为none,控制隐藏,只会编译一次;v-if是动态的向DOM树内添加或者删除DOM元素,若初始值为false,就不会编译了。
而且v-if不停的销毁和创建比较消耗性能。 总结:如果要频繁切换某节点,使用v-show(切换开销比较小,初始开销较大)。
如果不需要频繁切换某节点使用v-if(初始渲染开销较小,切换开销比较大)。
5.v-if
与 v-for
的优先级?
核心答案:
1、v-for优先于v-if被解析
2、如果同时出现,每次渲染都会先执行循环再判断条件,无论如何循环都不可避免,浪费了性能
3、要避免出现这种情况,则在外层嵌套template,在这一层进行v-if判断,然后在内部进行v-for循环
4、如果条件出现在循环内部,可通过计算属性提前过滤掉那些不需要显示的项
6.vue
组件中data
为什么必须是一个函数?
如果data
是一个函数的话,这样每复用一次组件,就会返回一份新的data
,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。
而单纯的写成对象形式,就使得所有组件实例共用了一份data
,就会造成一个变了全都会变的结果。
所以说vue
组件的data
必须是函数。这都是因为js
的特性带来的,跟vue本身设计无关。
js
本身的面向对象编程也是基于原型链和构造函数,应该会注意原型链上添加一般都是一个函数方法而不会去添加一个对象了。
7.VueX
之actions
与mutations
的区别?
actions
1、用于通过提交mutation改变数据
2、会默认将自身封装为一个Promise
3、可以包含任意的异步操作
mutations
1、通过提交commit
改变数据
2、只是一个单纯的函数
3、不要使用异步操作,异步操作会导致变量不能追踪
2.如何在vuex中使用异步修改?
在调用vuex
中的方法action
的时候,用promise
实现异步修改
const actions = {
asyncLogin({ commit }, n){
return new Promise(resolve => {
setTimeout(() => {
commit(types.UserLogin, n);
resolve();
},3000)
})
}
}
8.Vue
有哪些组件间的通信方式?
核心答案:
Vue 组件间通信只要指以下 3 类通信:
父子组件通信、隔代组件通信、兄弟组件通信,下面我们分别介绍每种通信方式且会说明此种方法可适用于哪类组件间通信。
方法一
props/$emit
父组件A
通过props
的方式向子组件B
传递,B to A
通过在 B 组件中 $emit
, A
组件中 v-on
的方式实现。
1.父组件向子组件传值
接下来我们通过一个例子,说明父组件如何向子组件传递值:在子组件Users.vue中如何获取父组件App.vue中的数据
userList:["Henry","Bucky","Emily"]
//App.vue父组件
//前者自定义名称便于子组件调用,后者要传递数据名
总结:
父组件通过props
向下传递数据给子组件。注:组件中的数据共有三种形式:data
、props
、computed
2.子组件向父组件传值(通过事件形式)
// 子组件
{{title}}
//绑定一个点击事件
// 父组件
//与子组件titleChanged自定义事件保持一致
// updateTitle($event)接受传递过来的文字
{{title}}
总结:
子组件通过events
给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。
方法二、$emit/$on
这种方法通过一个空的Vue
实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex
。
1.具体实现方式:
var App=new Vue();
App.$emit(事件名,数据);
App.$on(事件名,data => {});
或者自己实现一个
class MyEventEmitter {
constructor() {
this.event = {};
}
// 监听
on(type, listener) {
if (this.event[type]) {
this.event[type].push(listener);
} else {
this.event[type] = [listener];
}
}
//发送监听
emit(type, ...rest) {
if (this.event[type]) {
this.event[type].map(fn => fn.apply(this, rest));
}
}
//移除监听器
removeListener(type) {
if (this.event[type]) {
delete this.event[type];
console.log(this.event);
}
}
//移除所有的监听器
removeAllListener() {
this.event = {};
}
}
var MyEvent=new MyEventEmitter();
MyEvent.$emit(事件名,数据);
MyEvent.$on(事件名,data => {});
但是这种方式,记得在每次触发监听的时候,记得移除上一个监听器
方法三、Vuex与localStorage
vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来,刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。
const jsonToString=(json)=>{
return JSON.stringify(json)
}
const stringToJson=(keyName)=>{
//暂不验证数据格式
return window.localStorage.getItem(keyName)?
JSON.parse(window.localStorage.getItem(keyName))
:{};
}
export default new Vuex.Store({
state: {
selectCity:stringToJson("selectCity")
},
mutations: {
changeCity(state, selectCity) {
state.selectCity = selectCity
try {
window.localStorage.setItem('selectCity',jsonToString(state.selectCity));
// 数据改变的时候把数据拷贝一份保存到localStorage里面
} catch (e) {}
}
}
})
方法四、$attrs/$listeners
如图:
场景
有些变态需求:比如说A父组件里面导入了B组件,可是B组件里面又导入了C组件,现在需要A父组件传值给C组件,或者是C组件需要传值给父组件,这时候就需要用到$attrs和$listeners了。
$attrs
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。(父传孙专用)
$listener
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。(孙传父专用)
在父组件当中,最外层组件
···
//在子组件中
在子组件当中:
props-child1Info: {{child1Info}}
$attrs: {{$attrs}}
//在孙子组件当中:
在最里层组件当中child2:
props-child2Info: {{child2Info}}
$attrs 的值: {{$attrs}}
#### 代码详细说明:
![](https://yangyunhaiimagesoss.oss-cn-shanghai.aliyuncs.com/2009171558_1600329518020.png)
![](https://yangyunhaiimagesoss.oss-cn-shanghai.aliyuncs.com/2009171558_1600329528854.png)
![](https://yangyunhaiimagesoss.oss-cn-shanghai.aliyuncs.com/2009171600_1600329603285.png)
## 9.`Vue`中双向数据绑定是如何实现的?
1.`vue.js` 则是采用数据劫持结合发布者-订阅者模式的方式。
2.通过`Object.defineProperty()`来劫持各个属性的`setter`,`getter`.
3.在数据变动时发布消息给订阅者,触发相应的监听回调。我们先来看`Object.defineProperty()`这个方法:
var obj = {};
Object.defineProperty(obj, 'name', {
get: function() {
console.log('我被获取了')
return val;
},
set: function (newVal) {
console.log('我被设置了')
}
})
obj.name = 'fei';
//在给obj设置name属性的时候,触发了set这个方法
var val = obj.name;
//在得到obj的name属性,会触发get方法
## 10.单页面应用和多页面应用区别及优缺点?
单页面应用(`SPA`),通俗一点说就是指只有一个主页面的应用,浏览器一开始要加载所有必须的 `html`, `js`, `css`。所有的页面内容都包含在这个所谓的主页面中。但在写的时候,还是会分开写(页面片段),然后在交互的时候由路由程序动态载入,单页面的页面跳转,仅刷新局部资源。多应用于`pc`端。
多页面(`MPA`),就是指一个应用中有多个页面,页面跳转时是整页刷新
#### 单页面的优点:
1,用户体验好,快,内容的改变不需要重新加载整个页面,基于这一点spa对服务器压力较小
2,前后端分离
3,页面效果会比较炫酷(比如切换页面内容时的专场动画)
#### 单页面缺点:
1,不利于`seo`
2,导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理)
3,初次加载时耗时多
4,页面复杂度提高很多
## 11.`vue`中`v-if`和`v-for`优先级?
`v-for`和`v-if`不应该一起使用,必要情况下应该替换成`computed`属性。原因:`v-for`比`v-if`优先,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候。
v-if="user.isActive"
:key="user.id">
{{ user.name }}
如上情况,即使100个user中之需要使用一个数据,也会循环整个数组。
omputed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isActive
})
}
}
- v-for="user in activeUsers"
:key="user.id">
{{ user.name }}
## 12.`Vue`事件的修饰符()?
1)``.stop`:等同于`JavaScript`中的`event.stopPropagation()``,防止事件冒泡
2)`.prevent`:等同于`JavaScript`中的`event.preventDefault()`,防止执行预设的行为(如果事件可取消,则取消该事件,而不停止事件的进一步传播)
3)`.capture`:与事件冒泡的方向相反,事件捕获由外到内
4)`.self`:只会触发自己范围内的事件,不包含子元素
5)`.once`:只会触发一次
6)`.passive`:`passive`表示`listener`函数不会调用`preventDefault()`
`passive`主要用在移动端的`scroll`事件,来提高浏览器响应速度,提升用户体验。因为`passive=true`等于提前告诉了浏览器,`touchstart`和`touchmove`不会阻止默认事件,手刚开始触摸,浏览器就可以立刻给与响应;
否则,手触摸屏幕了,但要等待`touchstart`和`touchmove`的结果,多了这一步,响应时间就长了,用户体验也就差了。
## 13.`Vue`的两个核心是什么?
#### 1、数据驱动:
在vue中,数据的改变会驱动视图的自动更新。传统的做法是需要手动改变DOM来使得视图更新,而vue只需要改变数据。
#### 2、组件
组件化开发,优点很多,可以很好的降低数据之间的耦合度。将常用的代码封装成组件之后(vue组件封装方法),就能高度的复用,提高代码的可重用性。一个页面/模块可以由多个组件所组成。
### 14、`react`和`vue`的区别
#### 相同点
* 数据驱动页面提供响应式的试图组件
* 都有`virtual DOM`,组件化的开发通过`props`参数进行父子之间组件传递数据都实现了`webComponents`规范
* 数据流动单向都支持服务器的渲染SSR
* 都有支持`native`的方法`react`有`React native vue`有`wexx`
#### 不同点
* 数据绑定`Vue`实现了双向的数据绑定`react`数据流动是单向的
* 数据渲染大规模的数据渲染`react`更快
* 使用场景`React`配合`Redux`架构适合大规模多人协作复杂项目Vue适合小快的项目
* 开发风格`react`推荐做法`jsx` + `inline style`把`html`和`css`都写在`js`了
* `vue`是采用`webpack` +`vue-loader`单文件组件格式`html`, `js`, `css`同一个文件
## 15.`vue3.0`有哪些新特性
#### vue3.0的设计目标
* 更小
* 更快
* 加强TypeScript支持
* 加强API设计一致性
* 提高自身可维护性
* 开放更多底层功能
具体可以从以下方面来理解
#### 1,压缩包体积更小
当前最小化并被压缩的 `Vue` 运行时大小约为 20kB(2.6.10 版为 22.8kB)。`Vue 3.0`捆绑包的大小大约会`减少一半`,即只有`10kB`!
#### 2,Object.defineProperty -> Proxy
`Object.defineProperty`是一个相对比较昂贵的操作,因为它直接操作对象的属性,颗粒度比较小。将它替换为`es6`的`Proxy`,在目标对象之上架了一层拦截,代理的是对象而不是对象的属性。这样可以将原本对对象属性的操作变为对整个对象的操作,颗粒度变大。
`javascript`引擎在解析的时候希望对象的结构越稳定越好,如果对象一直在变,可优化性降低,`proxy`不需要对原始对象做太多操作。
#### 3,Virtual DOM 重构
vdom的本质是一个抽象层,用`javascript`描述界面渲染成什么样子。`react`用`jsx`,没办法检测出可以优化的动态代码,所以做时间分片,`vue`中足够快的话可以不用时间分片。
#### 传统vdom的性能瓶颈:
虽然 Vue 能够保证触发更新的组件最小化,但在单个组件内部依然需要遍历该组件的整个 vdom 树。
传统 vdom 的性能跟模版大小正相关,跟动态节点的数量无关。在一些组件整个模版内只有少量动态节点的情况下,这些遍历都是性能的浪费。
`JSX` 和手写的 `render function` 是完全动态的,过度的灵活性导致运行时可以用于优化的信息不足
那为什么不直接抛弃vdom呢?
高级场景下手写 `render function` 获得更强的表达力
生成的代码更简洁
#### 兼容2.x
`vue`的特点是底层为`Virtual DOM`,上层包含有大量静态信息的模版。为了兼容手写 `render function`,最大化利用模版静态信息,`vue3.0`采用了动静结合的解决方案,将`vdom`的操作颗粒度变小,每次触发更新不再以组件为单位进行遍历,主要更改如下
将模版基于动态节点指令切割为嵌套的区块
每个区块内部的节点结构是固定的
每个区块只需要以一个 Array 追踪自身包含的动态节点
vue3.0将 vdom 更新性能由与模版整体大小相关提升为与动态内容的数量相关
#### 4, 更多编译时优化
Slot 默认编译为函数:父子之间不存在强耦合,提升性能
Monomorphic vnode factory:参数一致化,给它children信息,
Compiler-generated flags for vnode/children types
#### 5,选用Function_based API
为什么撤销 `Class API` ?
1,更好地支持`TypeScript`
`Props` 和其它需要注入到 `this` 的属性导致类型声明依然存在问题
`Decorators`提案的严重不稳定使得依赖它的方案具有重大风险
2,除了类型支持以外 `Class API` 并不带来任何新的优势
3,`vue`中的`UI`组件很少用到继承,一般都是组合,可以用`Function-based API`
## 16.`Vue`性能优化方法
### 1)编码阶段
* 尽量减少data中的数据,data中的数据都会增加getter和setter,会收集对应的watcher;
* 如果需要使用v-for给每项元素绑定事件时使用事件代理;
* SPA 页面采用keep-alive缓存组件;
* 在更多的情况下,使用v-if替代v-show;
* key保证唯一;
* 使用路由懒加载、异步组件;
* 防抖、节流;
* 第三方模块按需导入;
* 长列表滚动到可视区域动态加载;
* 图片懒加载;
### 2)用户体验:
* 骨架屏;
* PWA;
* 还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
### 3)SEO优化
* 预渲染;
* 服务端渲染SSR;
### 4)打包优化
* 压缩代码;
* Tree Shaking/Scope Hoisting;
* 使用cdn加载第三方模块;
* 多线程打包happypack;
* splitChunks抽离公共文件;
* sourceMap优化;
## 17.`v-model`的原理
`v-model`本质就是一个语法糖,可以看成是value + input方法的语法糖。可以通过model属性的prop和event属性来进行自定义。
>原生的v-model,会根据标签的不同生成不同的事件和属性。
>v-model 在内部为不同的输入元素使用不同的属性并抛出不同的事件:
1)text 和 textarea 元素使用 value 属性和 input 事件;
2)checkbox 和 radio 使用 checked 属性和 change 事件;
3)select 字段将 value 作为 prop 并将 change 作为事件。
### 例子
model: {
prop: 'checked',
event: 'change'
}
如果想要更改 `checked` 这个 `prop` 可以在 `Vue` 的 `instance` 中用以下这行代码发送 `change` 这个 `event`,并将目标的变动值传给 `checked` 这个 `prop`。
this.$emit('change', $event.target.value);
## 18.`nextTick`的实现原理是什么?
在下次 `DOM` 更新循环结束之后执行延迟回调。`nextTick`主要使用了宏任务和微任务。根据执行环境分别尝试采用
* Promise
* MutationObserver
* setImmediate
* 如果以上都不行则采用setTimeout
定义了一个异步方法,多次调用`nextTick`会将方法存入队列中,通过这个异步方法清空当前队列。
#### 19.谈谈`Computed`和`Watch`!
`Computed`本质是一个具备缓存的`watcher`,依赖的属性发生变化就会更新视图。
适用于计算比较消耗性能的计算场景。当表达式过于复杂时,在模板中放入过多逻辑会让模板难以维护,可以将复杂的逻辑放入计算属性中处理。
`Watch`没有缓存性,更多的是观察的作用,可以监听某些数据执行回调。
当我们需要深度监听对象中的属性时,可以打开`deep:true`选项,这样便会对对象中的每一项进行监听。
这样会带来性能问题,优化的话可以使用字符串形式监听,如果没有写到组件中,不要忘记使用`unWatch`手动注销哦。
#### 20.说一下`Vue`的生命周期
`beforeCreate`是`new Vue()`之后触发的第一个钩子,在当前阶段`data`、`methods`、`computed`以及`watch`上的数据和方法都不能被访问。
`created`在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发updated函数。可以做一些初始数据的获取,在当前阶段无法与Dom进行交互,如果非要想,可以通过`vm.$nextTick`来访问`Dom`。
`beforeMount`发生在挂载之前,在这之前`template`模板已导入渲染函数编译。而当前阶段虚拟`Dom`已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发`updated`。
`mounted`在挂载完成后发生,在当前阶段,真实的`Dom`挂载完毕,数据完成双向绑定,可以访问到`Dom`节点,使用`$refs`属性对`Dom`进行操作。
`beforeUpdate`发生在更新之前,也就是响应式数据发生更新,虚拟`dom`重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。
`updated`发生在更新完成之后,当前阶段组件`Dom`已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。
`beforeDestroy`发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。
`destroyed`发生在实例销毁之后,这个时候只剩下了`dom`空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。
## 21.`Vue`模版编译原理知道吗,能简单说一下?
简单说,`Vue`的编译过程就是将`template`转化为`render`函数的过程。会经历以下阶段:
* 生成AST树
* 优化
* codegen
首先解析模版,生成`AST`语法树(一种用`JavaScript`对象的形式来描述整个模板)。
使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。
`Vue`的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的`DOM`也不会变化。那么优化过程就是深度遍历AST树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。
编译的最后一步是将优化后的`AST`树转换为可执行的代码。
## 22.`Vue`的优点及缺点?
首先Vue最核心的两个特点,数据驱动和组件化。
### 响应式:
这也就是vue.js最大的优点,通过MVVM思想实现数据的双向绑定,通过虚拟DOM让我们可以用数据来操作DOM,而不必去操作真实的DOM,提升了性能。且让开发者有更多的时间去思考业务逻辑。
### 组件化:
把一个单页应用中的各个模块拆分到一个个组件当中,或者把一些公共的部分抽离出来做成一个可复用的组件。所以组件化带来的好处就是,提高了开发效率,方便重复使用,使项目的可维护性更强。
### 虚拟DOM:
当然,这个不是vue中独有的。
### 缺点:
基于对象配置文件的写法,也就是options写法,开发时不利于对一个属性的查找。另外一些缺点,在小项目中感觉不太出什么,vuex的魔法字符串,对ts的支持。兼容性上存在一些问题。
### 不利于seo:
导航不可用,如果一定要导航需要自行实现前进、后退。(由于是单页面不能用浏览器的前进后退功能,所以需要自己建立堆栈管理)。
初次加载时耗时多。
## 23.`Vue`中`hash`模式和`history`模式的区别?
最明显的是在显示上,`hash`模式的`URL`中会夹杂着`#`号,而history没有。
`Vue`底层对它们的实现方式不同。hash模式是依靠`onhashchange`事件(监听`location.hash`的改变),而`history`模式是主要是依靠的`HTML5 history`中新增的两个方法,`pushState()`可以改变url地址且不会发送请求,`replaceState()`可以读取历史记录栈,还可以对浏览器记录进行修改。
当真正需要通过`URL`向后端发送HTTP请求的时候,比如常见的用户手动输入`URL`后回车,或者是刷新(重启)浏览器,这时候`history`模式需要后端的支持。
因为`history`模式下,前端的`URL`必须和实际向后端发送请求的URL一致,例如有一个`URL`是带有路径`path`的(例如`www.lindaidai.wang/blogs/id`),如果后端没有对这个路径做处理的话,就会返回`404`错误。所以需要后端增加一个覆盖所有情况的候选资源,一般会配合前端给出的一个`404`页面。
hash:
window.onhashchange = function(event){
// location.hash获取到的是包括#号的,如"#heading-3"
// 所以可以截取一下
let hash = location.hash.slice(1);
}
## 24.你的接口请求一般放在哪个生命周期中?
接口请求一般放在`mounted`中,但需要注意的是服务端渲染时不支持`mounted`,需要放到`created`中。
1. 如果不需要操作Dom元素或子组件, 那放到created或者mounted中都可以, 如果需要操作Dom元素, 则需要放到mounted中. 并且, 需要注意的是, 即便是放到created, Vue不会等待异步返回的数据再去执行下一步. 从体验的角度考虑, 放在mounted中更好.
#### 25.`Vue SSR`渲染原理?
### 优点:
#### 更利于SEO
不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本(Google除外,据说Googlebot可以运行javaScript)。使用了Vue或者其它MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大减少。另外,浏览器爬虫不会等待我们的数据完成之后再去抓取我们的页面数据。服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,网络爬中就可以抓取到完整页面的信息。
#### 更利于首屏渲染
首屏的渲染是node发送过来的html字符串,并不依赖于js文件了,这就会使用户更快的看到页面的内容。尤其是针对大型单页应用,打包后文件体积比较大,普通客户端渲染加载所有所需文件时间较长,首页就会有一个很长的白屏等待时间。
### 场景:
交互少,数据多,例如新闻,博客,论坛类等
### 原理:
相当于服务端前面加了一层url分配,可以假想为服务端的中间层,
当地址栏url改变或者直接刷新,其实直接从服务器返回内容,是一个包含内容部分的html模板,是服务端渲染
而在交互过程中则是ajax处理操作,局部刷新,首先是在history模式下,通过history. pushState方式进而url改变,然后请求后台数据服务,拿到真正的数据,做到局部刷新,这时候接收的是数据而不是模板
### 缺点
#### 服务端压力较大
本来是通过客户端完成渲染,现在统一到服务端node服务去做。尤其是高并发访问的情况,会大量占用服务端CPU资源;
#### 开发条件受限
在服务端渲染中,created和beforeCreate之外的生命周期钩子不可用,因此项目引用的第三方的库也不可用其它生命周期钩子,这对引用库的选择产生了很大的限制;
#### 安全问题
因为做了node服务,因此安全方面也需要考虑DDOS攻击和sql注入
#### 学习成本相对较高
除了对webpack、Vue要熟悉,还需要掌握node、Express相关技术。相对于客户端渲染,项目构建、部署过程更加复杂。
#### 26.`new Vue()` 发生了什么?
1)`new Vue()`是创建`Vue`实例,它内部执行了根实例的初始化过程。
2)具体包括以下操作:
* 选项合并
* `$children`,`$refs`,`$slots`,`$createElement`等实例属性的方法初始化
* 自定义事件处理
* 数据响应式处理
* 生命周期钩子调用 (`beforecreate created`)
* 可能的挂载
### 总结:
`new Vue()`创建了根实例并准备好数据和方法,未来执行挂载时,此过程还会递归的应用于它的子组件上,最终形成一个有紧密关系的组件实例树。
#### 27.`Vue.use`是干什么的?原理是什么?
>`vue.use` 是用来使用插件的,我们可以在插件中扩展全局组件、指令、原型方法等。
1、检查插件是否注册,若已注册,则直接跳出;
2、处理入参,将第一个参数之后的参数归集,并在首部塞入 `this` 上下文;
3、执行注册方法,调用定义好的 `install` 方法,传入处理的参数,若没有 `install` 方法并且插件本身为 `function` 则直接进行注册;
1) 插件不能重复的加载
`install` 方法的第一个参数是vue的构造函数,其他参数是Vue.set中除了第一个参数的其他参数; 代码:`args.unshift(this)`
2) 调用插件的install 方法 代码:
typeof plugin.install === 'function'
3) 插件本身是一个函数,直接让函数执行。 代码:
plugin.apply(null, args)
4) 缓存插件。 代码:
installedPlugins.push(plugin)
#### 28.请说一下响应式数据的理解?
1) 对象内部通过`defineReactive`方法,使用 `Object.defineProperty()` 监听数据属性的 `get` 来进行数据依赖收集,再通过 `set` 来完成数据更新的派发;
2) 数组则通过重写数组方法来实现的。扩展它的 7 个变更⽅法,通过监听这些方法可以做到依赖收集和派发更新;
#### 对应源码
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() // ** 收集依赖 ** /
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
val = newVal
childOb = !shallow && observe(newVal)
dep.notify() /**通知相关依赖进行更新**/
}
})
#### 29.`Vue`中是如何检测数组变化?
数组考虑性能原因没有用`defineProperty`对数组的每一项进行拦截,而是选择重写数组 方法以进行重写。
当数组调用到这 7 个方法的时候,执行 `ob.dep.notify()` 进行派发通知 `Watcher` 更新;
在Vue中修改数组的索引和长度是无法监控到的。
需要通过以下7种变异方法修改数组才会触发数组对应的`wacther`进行更新。
数组中如果是对象数据类型也会进行递归劫持。
#### 说明:那如果想要改索引更新数据怎么办?
可以通过`Vue.set()`来进行处理 =》 核心内部用的是 `splice` 方法。
#### 源码
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) { // 重写原型方法
const original = arrayProto[method] // 调用原数组的方法
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify() // 当调用数组方法后,手动通知视图更新
return result
})
})
this.observeArray(value) // 进行深度监控
#### 30.`Vue.set` 方法是如何实现的?
### 核心答案
为什么`$set`可以触发更新,我们给对象和数组本身都增加了`dep`属性,当给对象新增不存在的属性则触发对象依赖的`watcher`去更新,当修改数组索引时我们调用数组本身的`splice`方法去更新数组。
### 补充答案
1) 如果是数组,调用重写的splice方法 (这样可以更新视图 )
代码:
target.splice(key, 1, val)
2) 如果不是响应式的也不需要将其定义成响应式属性。
3) 如果是对象,将属性定义成响应式的
defineReactive(ob.value, key, val)
### 源码地址
export function set (target: Array
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.'
)
return val
}
if (!ob) {
target[key] = val
return val
}
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
#### 31.`Vue`中模板编译原理?
### 核心答案
将`template`转换成`render`函数
### 补充说明
这里要注意的是我们在开发时尽量不要使用template.
因为将template转化成render方法需要在运行时进行编译操作会有性能损耗,同时引用带有complier包的vue体积也会变大.
默认.vue文件中的 template处理是通过vue-loader 来进行处理的并不是通过运行时的编译。
### 流程如下
1) 将 template 模板转换成 ast 语法树 - parserHTML
2) 对静态语法做静态标记 - markUp
3) 重新生成代码 - codeGen
### 源码地址
function baseCompile (
template: string,
options: CompilerOptions
) {
const ast = parse(template.trim(), options) // 1.将模板转化成ast语法树
if (options.optimize !== false) { // 2.优化树
optimize(ast, options)
}
const code = generate(ast, options) // 3.生成树
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
})
const ncname = [a-zA-Z_][\\-\\.0-9_a-zA-Z]*
;
const qnameCapture = ((?:${ncname}\\:)?${ncname})
;
const startTagOpen = new RegExp(^<${qnameCapture}
); // 标签开头的正则 捕获的内容是标签名
const endTag = new RegExp(^<\\/${qnameCapture}[^>]*>
); // 匹配标签结尾的