Vue 的长处
- 轻量级框架:只关注视图层,是一个构建数据的视图汇合,大小只有几十
kb
; - 简略易学:国人开发,中文文档,不存在语言障碍,易于了解和学习;
- 双向数据绑定:保留了
angular
的特点,在数据操作方面更为简略; - 组件化:保留了
react
的长处,实现了html
的封装和重用,在构建单页面利用方面有着独特的劣势; - 视图,数据,构造拆散:使数据的更改更为简略,不须要进行逻辑代码的批改,只须要操作数据就能实现相干操作;
- 虚构 DOM:
dom
操作是十分消耗性能的,不再应用原生的dom
操作节点,极大解放dom
操作,但具体操作的还是dom
不过是换了另一种形式; - 运行速度更快:相比拟于
react
而言,同样是操作虚构dom
,就性能而言,vue
存在很大的劣势。
Vue2.x 响应式数据原理
整体思路是数据劫持 + 观察者模式
对象外部通过 defineReactive
办法,应用 Object.defineProperty
来劫持各个属性的 setter
、getter
(只会劫持曾经存在的属性),数组则是通过 重写数组 7 个办法
来实现。当页面应用对应属性时,每个属性都领有本人的 dep
属性,寄存他所依赖的 watcher
(依赖收集),当属性变动后会告诉本人对应的 watcher
去更新(派发更新)
Object.defineProperty 根本应用
function observer(value) { // proxy reflect
if (typeof value === 'object' && typeof value !== null)
for (let key in value) {defineReactive(value, key, value[key]);
}
}
function defineReactive(obj, key, value) {observer(value);
Object.defineProperty(obj, key, {get() { // 收集对应的 key 在哪个办法(组件)中被应用
return value;
},
set(newValue) {if (newValue !== value) {observer(newValue);
value = newValue; // 让 key 对应的办法(组件从新渲染)从新执行
}
}
})
}
let obj1 = {school: { name: 'poetry', age: 20} };
observer(obj1);
console.log(obj1)
源码剖析
class Observer {
// 观测值
constructor(value) {this.walk(value);
}
walk(data) {
// 对象上的所有属性顺次进行观测
let keys = Object.keys(data);
for (let i = 0; i < keys.length; i++) {let key = keys[i];
let value = data[key];
defineReactive(data, key, value);
}
}
}
// Object.defineProperty 数据劫持外围 兼容性在 ie9 以及以上
function defineReactive(data, key, value) {observe(value); // 递归要害
// -- 如果 value 还是一个对象会持续走一遍 odefineReactive 层层遍历始终到 value 不是对象才进行
// 思考?如果 Vue 数据嵌套层级过深 >> 性能会受影响
Object.defineProperty(data, key, {get() {console.log("获取值");
// 须要做依赖收集过程 这里代码没写进去
return value;
},
set(newValue) {if (newValue === value) return;
console.log("设置值");
// 须要做派发更新过程 这里代码没写进去
value = newValue;
},
});
}
export function observe(value) {
// 如果传过来的是对象或者数组 进行属性劫持
if (Object.prototype.toString.call(value) === "[object Object]" ||
Array.isArray(value)
) {return new Observer(value);
}
}
说一说你对 vue 响应式了解答复范例
- 所谓数据响应式就是 可能使数据变动能够被检测并对这种变动做出响应的机制
MVVM
框架中要解决的一个外围问题是连贯数据层和视图层,通过 数据驱动 利用,数据变动,视图更新,要做到这点的就须要对数据做响应式解决,这样一旦数据发生变化就能够立刻做出更新解决- 以
vue
为例阐明,通过数据响应式加上虚构DOM
和patch
算法,开发人员只须要操作数据,关怀业务,齐全不必接触繁琐的 DOM 操作,从而大大晋升开发效率,升高开发难度 vue2
中的数据响应式会依据数据类型来做不同解决,如果是 对象则采纳Object.defineProperty()
的形式定义数据拦挡,当数据被拜访或发生变化时,咱们感知并作出响应;如果是数组则通过笼罩数组对象原型的 7 个变更办法 ,使这些办法能够额定的做更新告诉,从而作出响应。这种机制很好的解决了数据响应化的问题,但在理论应用中也存在一些毛病:比方初始化时的递归遍历会造成性能损失;新增或删除属性时须要用户应用Vue.set/delete
这样非凡的api
能力失效;对于es6
中新产生的Map
、Set
这些数据结构不反对等问题- 为了解决这些问题,
vue3
从新编写了这一部分的实现:利用ES6
的Proxy
代理要响应化的数据,它有很多益处,编程体验是统一的,不须要应用非凡api
,初始化性能和内存耗费都失去了大幅改善;另外因为响应化的实现代码抽取为独立的reactivity
包,使得咱们能够更灵便的应用它,第三方的扩大开发起来更加灵便了
为什么要用 Vuex 或者 Redux
因为传参的办法对于多层嵌套的组件将会十分繁琐,并且对于兄弟组件间的状态传递无能为力。咱们常常会采纳父子组件间接援用或者通过事件来变更和同步状态的多份拷贝。以上的这些模式十分软弱,通常会导致代码无奈保护。
所以须要把组件的共享状态抽取进去,以一个全局单例模式治理。在这种模式下,组件树形成了一个微小的 ” 视图 ”,不论在树的哪个地位,任何组件都能获取状态或者触发行为。
另外,通过定义和隔离状态治理中的各种概念并强制恪守肯定的规定,代码将会变得更结构化且易保护。
delete 和 Vue.delete 删除数组的区别?
delete
只是被删除的元素变成了empty/undefined
其余的元素的键值还是不变。Vue.delete
间接删除了数组 扭转了数组的键值。
var a=[1,2,3,4]
var b=[1,2,3,4]
delete a[0]
console.log(a) //[empty,2,3,4]
this.$delete(b,0)
console.log(b) //[2,3,4]
vue-cli 工程罕用的 npm 命令有哪些
- 下载
node_modules
资源包的命令:
npm install
- 启动
vue-cli
开发环境的 npm 命令:
npm run dev
vue-cli
生成 生产环境部署资源 的npm
命令:
npm run build
- 用于查看
vue-cli
生产环境部署资源文件大小的npm
命令:
npm run build --report
在浏览器上自动弹出一个 展现
vue-cli
工程打包后app.js
、manifest.js
、vendor.js
文件外面所蕴含代码的页面。能够具此优化vue-cli
生产环境部署的动态资源,晋升 页面 的加载速度
Vue 我的项目中你是如何解决跨域的呢
一、跨域是什么
跨域实质是浏览器基于 同源策略 的一种平安伎俩
同源策略(Sameoriginpolicy),是一种约定,它是浏览器最外围也最根本的平安性能
所谓同源(即指在同一个域)具备以下三个相同点
- 协定雷同(protocol)
- 主机雷同(host)
- 端口雷同(port)
反之非同源申请,也就是协定、端口、主机其中一项不雷同的时候,这时候就会产生跨域
肯定要留神跨域是浏览器的限度,你用抓包工具抓取接口数据,是能够看到接口曾经把数据返回回来了,只是浏览器的限度,你获取不到数据。用 postman 申请接口可能申请到数据。这些再次印证了跨域是浏览器的限度。
vue-loader 是什么?它有什么作用?
答复范例
vue-loader
是用于解决单文件组件(SFC
,Single-File Component
)的webpack loader
- 因为有了
vue-loader
,咱们就能够在我的项目中编写SFC
格局的Vue
组件,咱们能够把代码宰割为<template>
、<script>
和<style>
,代码会异样清晰。联合其余loader
咱们还能够用Pug
编写<template>
,用SASS
编写<style>
,用TS
编写<script>
。咱们的<style>
还能够独自作用以后组件 webpack
打包时,会以loader
的形式调用vue-loader
vue-loader
被执行时,它会对SFC
中的每个语言块用独自的loader
链解决。最初将这些独自的块装配成最终的组件模块
原理
vue-loader
会调用 @vue/compiler-sfc
模块解析 SFC
源码为一个描述符(Descriptor
),而后为每个语言块生成 import
代码,返回的代码相似上面
// source.vue 被 vue-loader 解决之后返回的代码
// import the <template> block
import render from 'source.vue?vue&type=template'
// import the <script> block
import script from 'source.vue?vue&type=script'
export * from 'source.vue?vue&type=script'
// import <style> blocks
import 'source.vue?vue&type=style&index=1'
script.render = render
export default script
咱们想要 script
块中的内容被作为 js
解决(当然如果是 <script lang="ts">
被作为 ts
理),这样咱们想要 webpack
把配置中跟 .js
匹配的规定都利用到形如 source.vue?vue&type=script
的这个申请上。例如咱们对所有 *.js
配置了babel-loader
,这个规定将被克隆并利用到所在Vue SFC
import script from 'source.vue?vue&type=script
将被开展为:
import script from 'babel-loader!vue-loader!source.vue?vue&type=script'
相似的,如果咱们对 .sass
文件配置了style-loader + css-loader + sass-loader
,对上面的代码
<style scoped lang="scss">
vue-loader
将会返回给咱们上面后果:
import 'source.vue?vue&type=style&index=1&scoped&lang=scss'
而后 webpack
会开展如下:
import 'style-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'
- 当解决开展申请时,
vue-loader
将被再次调用。这次,loader
将会关注那些有查问串的申请,且仅针对特定块,它会选中特定块外部的内容并传递给前面匹配的loader
-
对于
<script>
块,解决到这就能够了,然而<template>
和<style>
还有一些额定工作要做,比方- 须要用
Vue
模板编译器编译template
,从而失去render
函数 - 须要对
<style scoped>
中的CSS
做后处理(post-process
),该操作在css-loader
之后但在style-loader
之前
- 须要用
实现上这些附加的 loader
须要被注入到曾经开展的 loader
链上,最终的申请会像上面这样:
// <template lang="pug">
import 'vue-loader/template-loader!pug-loader!source.vue?vue&type=template'
// <style scoped lang="scss">
import 'style-loader!vue-loader/style-post-loader!css-loader!sass-loader!vue-loader!source.vue?vue&type=style&index=1&scoped&lang=scss'
参考:前端 vue 面试题具体解答
vue 中应用了哪些设计模式
1. 工厂模式 – 传入参数即可创立实例
虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode
2. 单例模式 – 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉
3. 公布 - 订阅模式 (vue 事件机制)
4. 观察者模式 (响应式数据原理)
5. 装璜模式: (@装璜器的用法)
6. 策略模式 策略模式指对象有某个行为, 然而在不同的场景中, 该行为有不同的实现计划 - 比方选项的合并策略
如何了解 Vue 中模板编译原理
Vue
的编译过程就是将template
转化为render
函数的过程
- 解析生成 AST 树 将
template
模板转化成AST
语法树,应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决 -
标记优化 对动态语法做动态标记
markup
(动态节点如div
下有p
标签内容不会变动)diff
来做优化 动态节点跳过diff
操作Vue
的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的DOM
也不会变动。那么优化过程就是深度遍历AST
树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够跳过对它们的比对,对运行时的模板起到很大的优化作用- 期待后续节点更新,如果是动态的,不会在比拟
children
了
- 代码生成 编译的最初一步是将优化后的
AST
树转换为可执行的代码
答复范例
思路
- 引入
vue
编译器概念 - 阐明编译器的必要性
- 论述编译器工作流程
答复范例
Vue
中有个独特的编译器模块,称为compiler
,它的次要作用是将用户编写的template
编译为js
中可执行的render
函数。- 之所以须要这个编译过程是为了便于前端能高效的编写视图模板。相比而言,咱们还是更违心用
HTML
来编写视图,直观且高效。手写render
函数不仅效率底下,而且失去了编译期的优化能力。 - 在
Vue
中编译器会先对template
进行解析,这一步称为parse
,完结之后会失去一个JS
对象,咱们称为 形象语法树 AST,而后是对AST
进行深加工的转换过程,这一步成为transform
,最初将后面失去的AST
生成为JS
代码,也就是render
函数
可能的诘问
Vue
中编译器何时执行?
在
new Vue()
之后。Vue
会调用_init
函数进行初始化,也就是这里的 init
过程,它会初始化生命周期、事件、props
、methods
、data
、computed
与watch
等。其中最重要的是通过Object.defineProperty
设置setter
与getter
函数,用来实现「响应式」以及「依赖收集」
- 初始化之后调用
$mount
会挂载组件,如果是运行时编译,即不存在render function
然而存在template
的状况,须要进行「编译」步骤 compile
编译能够分成parse
、optimize
与generate
三个阶段,最终须要失去render function
React
有没有编译器?
react
应用 babel
将JSX
语法解析
<div id="app"></div>
<script>
let vm = new Vue({
el: '#app',
template: `<div>
// <span>hello world</span> 是动态节点
<span>hello world</span>
// <p>{{name}}</p> 是动静节点
<p>{{name}}</p>
</div>`,
data() {return { name: 'test'}
}
});
</script>
源码剖析
export function compileToFunctions(template) {
// 咱们须要把 html 字符串变成 render 函数
// 1. 把 html 代码转成 ast 语法树 ast 用来形容代码自身造成树结构 不仅能够形容 html 也能形容 css 以及 js 语法
// 很多库都使用到了 ast 比方 webpack babel eslint 等等
let ast = parse(template);
// 2. 优化动态节点:对 ast 树进行标记, 标记动态节点
if (options.optimize !== false) {optimize(ast, options);
}
// 3. 通过 ast 从新生成代码
// 咱们最初生成的代码须要和 render 函数一样
// 相似_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world"))))
// _c 代表创立元素 _v 代表创立文本 _s 代表文 Json.stringify-- 把对象解析成文本
let code = generate(ast);
// 应用 with 语法扭转作用域为 this 之后调用 render 函数能够应用 call 扭转 this 不便 code 外面的变量取值
let renderFn = new Function(`with(this){return ${code}}`);
return renderFn;
}
Vue 中的过滤器理解吗?过滤器的利用场景有哪些?
过滤器本质不扭转原始数据,只是对数据进行加工解决后返回过滤后的数据再进行调用解决,咱们也能够了解其为一个纯函数
Vue 容许你自定义过滤器,可被用于一些常见的文本格式化
ps: Vue3
中已废除filter
如何用
vue 中的过滤器能够用在两个中央:双花括号插值和 v-bind
表达式,过滤器应该被增加在 JavaScript 表达式的尾部,由“管道”符号批示:
<!-- 在双花括号中 -->
{message | capitalize}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
定义 filter
在组件的选项中定义本地的过滤器
filters: {capitalize: function (value) {if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
定义全局过滤器:
Vue.filter('capitalize', function (value) {if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({// ...})
留神:当全局过滤器和部分过滤器重名时,会采纳部分过滤器
过滤器函数总接管表达式的值 (之前的操作链的后果) 作为第一个参数。在上述例子中,capitalize
过滤器函数将会收到 message
的值作为第一个参数
过滤器能够串联:
{message | filterA | filterB}
在这个例子中,filterA
被定义为接管单个参数的过滤器函数,表达式 message
的值将作为参数传入到函数中。而后持续调用同样被定义为接管单个参数的过滤器函数 filterB
,将 filterA
的后果传递到 filterB
中。
过滤器是 JavaScript
函数,因而能够接管参数:
{{message | filterA('arg1', arg2) }}
这里,filterA
被定义为接管三个参数的过滤器函数。
其中 message
的值作为第一个参数,一般字符串 'arg1'
作为第二个参数,表达式 arg2
的值作为第三个参数
举个例子:
<div id="app">
<p>{{msg | msgFormat('疯狂','--')}}</p>
</div>
<script>
// 定义一个 Vue 全局的过滤器,名字叫做 msgFormat
Vue.filter('msgFormat', function(msg, arg, arg2) {
// 字符串的 replace 办法,第一个参数,除了可写一个 字符串之外,还能够定义一个正则
return msg.replace(/ 单纯 /g, arg+arg2)
})
</script>
小结:
- 部过滤器优先于全局过滤器被调用
- 一个表达式能够应用多个过滤器。过滤器之间须要用管道符“|”隔开。其执行程序从左往右
利用场景
平时开发中,须要用到过滤器的中央有很多,比方 单位转换
、 数字打点
、 文本格式化
、 工夫格式化
之类的等
比方咱们要实现将30000 => 30,000
,这时候咱们就须要应用过滤器
Vue.filter('toThousandFilter', function (value) {if (!value) return ''
value = value.toString()
return .replace(str.indexOf('.') > -1 ? /(\d)(?=(\d{3})+\.)/g : /(\d)(?=(?:\d{3})+$)/g, '$1,')
})
原理剖析
应用过滤器
{{message | capitalize}}
在模板编译阶段过滤器表达式将会被编译为过滤器函数,次要是用过parseFilters
,咱们放到最初讲
_s(_f('filterFormat')(message))
首先剖析一下_f
:
_f
函数全名是:resolveFilter
,这个函数的作用是从 this.$options.filters
中找出注册的过滤器并返回
// 变为
this.$options.filters['filterFormat'](message) // message 为参数
对于resolveFilter
import {indentity,resolveAsset} from 'core/util/index'
export function resolveFilter(id){return resolveAsset(this.$options,'filters',id,true) || identity
}
外部间接调用 resolveAsset
,将option
对象,类型,过滤器id
,以及一个触发正告的标记作为参数传递,如果找到,则返回过滤器;
resolveAsset
的代码如下:
export function resolveAsset(options,type,id,warnMissing){ // 因为咱们找的是过滤器,所以在 resolveFilter 函数中调用时 type 的值间接给的 'filters', 理论这个函数还能够拿到其余很多货色
if(typeof id !== 'string'){ // 判断传递的过滤器 id 是不是字符串,不是则间接返回
return
}
const assets = options[type] // 将咱们注册的所有过滤器保留在变量中
// 接下来的逻辑便是判断 id 是否在 assets 中存在,即进行匹配
if(hasOwn(assets,id)) return assets[id] // 如找到,间接返回过滤器
// 没有找到,代码继续执行
const camelizedId = camelize(id) // 万一你是驼峰的呢
if(hasOwn(assets,camelizedId)) return assets[camelizedId]
// 没找到,继续执行
const PascalCaseId = capitalize(camelizedId) // 万一你是首字母大写的驼峰呢
if(hasOwn(assets,PascalCaseId)) return assets[PascalCaseId]
// 如果还是没找到,则查看原型链(即拜访属性)
const result = assets[id] || assets[camelizedId] || assets[PascalCaseId]
// 如果仍然没找到,则在非生产环境的控制台打印正告
if(process.env.NODE_ENV !== 'production' && warnMissing && !result){warn('Failed to resolve' + type.slice(0,-1) + ':' + id, options)
}
// 无论是否找到,都返回查找后果
return result
}
上面再来剖析一下_s
:
_s
函数的全称是 toString
, 过滤器解决后的后果会当作参数传递给 toString
函数,最终 toString
函数执行后的后果会保留到 Vnode
中的 text 属性中,渲染到视图中
function toString(value){
return value == null
? '': typeof value ==='object'
? JSON.stringify(value,null,2)// JSON.stringify()第三个参数可用来管制字符串外面的间距
: String(value)
}
最初,在剖析下parseFilters
,在模板编译阶段应用该函数阶段将模板过滤器解析为过滤器函数调用表达式
function parseFilters (filter) {let filters = filter.split('|')
let expression = filters.shift().trim() // shift()删除数组第一个元素并将其返回,该办法会更改原数组
let i
if (filters) {for(i = 0;i < filters.length;i++){experssion = warpFilter(expression,filters[i].trim()) // 这里传进去的 expression 实际上是管道符号后面的字符串,即过滤器的第一个参数
}
}
return expression
}
// warpFilter 函数实现
function warpFilter(exp,filter){
// 首先判断过滤器是否有其余参数
const i = filter.indexof('(')
if(i<0){ // 不含其余参数,间接进行过滤器表达式字符串的拼接
return `_f("${filter}")(${exp})`
}else{const name = filter.slice(0,i) // 过滤器名称
const args = filter.slice(i+1) // 参数,但还多了‘)’return `_f('${name}')(${exp},${args}` // 留神这一步少给了一个 ')'
}
}
小结:
- 在编译阶段通过
parseFilters
将过滤器编译成函数调用(串联过滤器则是一个嵌套的函数调用,前一个过滤器执行的后果是后一个过滤器函数的参数) - 编译后通过调用
resolveFilter
函数找到对应过滤器并返回后果 - 执行后果作为参数传递给
toString
函数,而toString
执行后,其后果会保留在Vnode
的text
属性中,渲染到视图
Vue 我的项目中有封装过 axios 吗?次要是封装哪方面的?
一、axios 是什么
axios
是一个轻量的 HTTP
客户端
基于 XMLHttpRequest
服务来执行 HTTP
申请,反对丰盛的配置,反对 Promise
,反对浏览器端和 Node.js
端。自Vue
2.0 起,尤大发表勾销对 vue-resource
的官网举荐,转而举荐 axios
。当初 axios
曾经成为大部分 Vue
开发者的首选
个性
- 从浏览器中创立
XMLHttpRequests
- 从
node.js
创立http
申请 - 反对
Promise
API - 拦挡申请和响应
- 转换申请数据和响应数据
- 勾销申请
- 主动转换
JSON
数据 - 客户端反对进攻
XSRF
根本应用
装置
// 我的项目中装置
npm install axios --S
// cdn 引入
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
导入
import axios from 'axios'
发送申请
axios({
url:'xxx', // 设置申请的地址
method:"GET", // 设置申请办法
params:{ // get 申请应用 params 进行参数凭借, 如果是 post 申请用 data
type: '',
page: 1
}
}).then(res => {
// res 为后端返回的数据
console.log(res);
})
并发申请axios.all([])
function getUserAccount() {return axios.get('/user/12345');
}
function getUserPermissions() {return axios.get('/user/12345/permissions');
}
axios.all([getUserAccount(), getUserPermissions()])
.then(axios.spread(function (res1, res2) {
// res1 第一个申请的返回的内容,res2 第二个申请返回的内容
// 两个申请都执行实现才会执行
}));
二、为什么要封装
axios
的 API 很敌对,你齐全能够很轻松地在我的项目中间接应用。
不过随着我的项目规模增大,如果每发动一次 HTTP
申请,就要把这些比方设置超时工夫、设置申请头、依据我的项目环境判断应用哪个申请地址、错误处理等等操作,都须要写一遍
这种重复劳动不仅浪费时间,而且让代码变得冗余不堪,难以保护。为了进步咱们的代码品质,咱们应该在我的项目中二次封装一下 axios
再应用
举个例子:
axios('http://localhost:3000/data', {
// 配置代码
method: 'GET',
timeout: 1000,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
Authorization: 'xxx',
},
transformRequest: [function (data, headers) {return data;}],
// 其余申请配置...
})
.then((data) => {
// todo: 真正业务逻辑代码
console.log(data);
}, (err) => {
// 错误处理代码
if (err.response.status === 401) {// handle authorization error}
if (err.response.status === 403) {// handle server forbidden error}
// 其余错误处理.....
console.log(err);
});
如果每个页面都发送相似的申请,都要写一堆的配置与错误处理,就显得过于繁琐了
这时候咱们就须要对 axios
进行二次封装,让应用更为便当
三、如何封装
- 封装的同时,你须要和 后端协商好一些约定,申请头,状态码,申请超时工夫 …….
- 设置接口申请前缀:依据开发、测试、生产环境的不同,前缀须要加以辨别
- 申请头 : 来实现一些具体的业务,必须携带一些参数才能够申请(例如:会员业务)
- 状态码: 依据接口返回的不同
status
,来执行不同的业务,这块须要和后端约定好 - 申请办法:依据
get
、post
等办法进行一个再次封装,应用起来更为不便 - 申请拦截器: 依据申请的申请头设定,来决定哪些申请能够拜访
- 响应拦截器:这块就是依据 后端 ` 返回来的状态码断定执行不同业务
设置接口申请前缀
利用 node
环境变量来作判断,用来辨别开发、测试、生产环境
if (process.env.NODE_ENV === 'development') {axios.defaults.baseURL = 'http://dev.xxx.com'} else if (process.env.NODE_ENV === 'production') {axios.defaults.baseURL = 'http://prod.xxx.com'}
在本地调试的时候,还须要在 vue.config.js
文件中配置 devServer
实现代理转发,从而实现跨域
devServer: {
proxy: {
'/proxyApi': {
target: 'http://dev.xxx.com',
changeOrigin: true,
pathRewrite: {'/proxyApi': ''}
}
}
}
设置申请头与超时工夫
大部分状况下,申请头都是固定的,只有少部分状况下,会须要一些非凡的申请头,这里将普适性的申请头作为根底配置。当须要非凡申请头时,将非凡申请头作为参数传入,笼罩根底配置
const service = axios.create({
...
timeout: 30000, // 申请 30s 超时
headers: {
get: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
// 在开发中,个别还须要单点登录或者其余性能的通用申请头,能够一并配置进来
},
post: {
'Content-Type': 'application/json;charset=utf-8'
// 在开发中,个别还须要单点登录或者其余性能的通用申请头,能够一并配置进来
}
},
})
封装申请办法
先引入封装好的办法,在要调用的接口从新封装成一个办法裸露进来
// get 申请
export function httpGet({
url,
params = {}}) {return new Promise((resolve, reject) => {
axios.get(url, {params}).then((res) => {resolve(res.data)
}).catch(err => {reject(err)
})
})
}
// post
// post 申请
export function httpPost({
url,
data = {},
params = {}}) {return new Promise((resolve, reject) => {
axios({
url,
method: 'post',
transformRequest: [function (data) {
let ret = ''
for (let it in data) {ret += encodeURIComponent(it) + '=' + encodeURIComponent(data[it]) + '&'
}
return ret
}],
// 发送的数据
data,
// url 参数
params
}).then(res => {resolve(res.data)
})
})
}
把封装的办法放在一个 api.js
文件中
import {httpGet, httpPost} from './http'
export const getorglist = (params = {}) => httpGet({url: 'apps/api/org/list', params})
页面中就能间接调用
// .vue
import {getorglist} from '@/assets/js/api'
getorglist({id: 200}).then(res => {console.log(res)
})
这样能够把 api
对立治理起来,当前保护批改只须要在 api.js
文件操作即可
申请拦截器
申请拦截器能够在每个申请里加上 token,做了对立解决后保护起来也不便
// 申请拦截器
axios.interceptors.request.use(
config => {
// 每次发送申请之前判断是否存在 token
// 如果存在,则对立在 http 申请的 header 都加上 token,这样后盾依据 token 判断你的登录状况,此处 token 个别是用户实现登录后贮存到 localstorage 里的
token && (config.headers.Authorization = token)
return config
},
error => {return Promise.error(error)
})
响应拦截器
响应拦截器能够在接管到响应后先做一层操作,如依据状态码判断登录状态、受权
// 响应拦截器
axios.interceptors.response.use(response => {
// 如果返回的状态码为 200,阐明接口申请胜利,能够失常拿到数据
// 否则的话抛出谬误
if (response.status === 200) {if (response.data.code === 511) {// 未受权调取受权接口} else if (response.data.code === 510) {// 未登录跳转登录页} else {return Promise.resolve(response)
}
} else {return Promise.reject(response)
}
}, error => {
// 咱们能够在这里对异样状态作对立解决
if (error.response.status) {
// 解决申请失败的状况
// 对不同返回码对相应解决
return Promise.reject(error.response)
}
})
小结
- 封装是编程中很有意义的伎俩,简略的
axios
封装,就能够让咱们能够领略到它的魅力 - 封装
axios
没有一个相对的规范,只有你的封装能够满足你的我的项目需要,并且用起来不便,那就是一个好的封装计划
MVVM的优缺点?
长处:
- 拆散视图(View)和模型(Model),升高代码耦合,提⾼视图或者逻辑的重⽤性: ⽐如视图(View)能够独⽴于 Model 变动和批改,⼀个 ViewModel 能够绑定不同的 ”View” 上,当 View 变动的时候 Model 不能够不变,当 Model 变动的时候 View 也能够不变。你能够把⼀些视图逻辑放在⼀个 ViewModel ⾥⾯,让很多 view 重⽤这段视图逻辑
- 提⾼可测试性: ViewModel 的存在能够帮忙开发者更好地编写测试代码
- ⾃动更新 dom: 利⽤双向绑定, 数据更新后视图⾃动更新, 让开发者从繁琐的⼿动 dom 中解放
毛病:
- Bug 很难被调试: 因为使⽤双向绑定的模式,当你看到界⾯异样了,有可能是你 View 的代码有 Bug,也可能是 Model 的代码有问题。数据绑定使得⼀个地位的 Bug 被疾速传递到别的地位,要定位原始出问题的地⽅就变得不那么容易了。另外,数据绑定的申明是指令式地写在 View 的模版当中的,这些内容是没方法去打断点 debug 的
- ⼀个⼤的模块中 model 也会很⼤,尽管使⽤⽅便了也很容易保障了数据的⼀致性,过后⻓期持有,不开释内存就造成了破费更多的内存
- 对于⼤型的图形应⽤程序,视图状态较多,ViewModel 的构建和保护的老本都会⽐较⾼。
action 与 mutation 的区别
mutation
是同步更新,$watch
严格模式下会报错action
是异步操作,能够获取数据后调用mutation
提交最终数据
Vue 模版编译原理晓得吗,能简略说一下吗?
简略说,Vue 的编译过程就是将 template
转化为 render
函数的过程。会经验以下阶段:
- 生成 AST 树
- 优化
- codegen
首先解析模版,生成AST 语法树
(一种用 JavaScript 对象的模式来形容整个模板)。应用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相干解决。
Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变动,对应的 DOM 也不会变动。那么优化过程就是深度遍历 AST 树,依照相干条件对树节点进行标记。这些被标记的节点 (动态节点) 咱们就能够 跳过对它们的比对
,对运行时的模板起到很大的优化作用。
编译的最初一步是 将优化后的 AST 树转换为可执行的代码
。
v-model 的原理?
咱们在 vue 我的项目中次要应用 v-model 指令在表单 input、textarea、select 等元素上创立双向数据绑定,咱们晓得 v-model 实质上不过是语法糖,v-model 在外部为不同的输出元素应用不同的属性并抛出不同的事件:
- text 和 textarea 元素应用 value 属性和 input 事件;
- checkbox 和 radio 应用 checked 属性和 change 事件;
- select 字段将 value 作为 prop 并将 change 作为事件。
以 input 表单元素为例:
<input v-model='something'>
相当于
<input v-bind:value="something" v-on:input="something = $event.target.value">
如果在自定义组件中,v-model 默认会利用名为 value 的 prop 和名为 input 的事件,如下所示:
父组件:<ModelChild v-model="message"></ModelChild>
子组件:<div>{{value}}</div>
props:{value: String},
methods: {test1(){this.$emit('input', '小红')
},
},
应用 vue 渲染大量数据时应该怎么优化?说下你的思路!
剖析
企业级我的项目中渲染大量数据的状况比拟常见,因而这是一道十分好的综合实际题目。
答复
- 在大型企业级我的项目中常常须要渲染大量数据,此时很容易呈现卡顿的状况。比方大数据量的表格、树
- 解决时要依据状况做不同解决:
- 能够采取分页的形式获取,防止渲染大量数据
- vue-virtual-scroller (opens new window)等虚构滚动计划,只渲染视口范畴内的数据
- 如果不须要更新,能够应用 v -once 形式只渲染一次
- 通过 v -memo (opens new window)能够缓存后果,联合
v-for
应用,防止数据变动时不必要的VNode
创立 - 能够采纳懒加载形式,在用户须要的时候再加载数据,比方
tree
组件子树的懒加载 - 还是要看具体需要,首先从设计上防止大数据获取和渲染;切实须要这样做能够采纳虚表的形式优化渲染;最初优化更新,如果不须要更新能够
v-once
解决,须要更新能够v-memo
进一步优化大数据更新性能。其余能够采纳的是交互方式优化,无线滚动、懒加载等计划
computed 的实现原理
computed 实质是一个惰性求值的观察者。
computed 外部实现了一个惰性的 watcher, 也就是 computed watcher,computed watcher 不会立即求值, 同时持有一个 dep 实例。
其外部通过 this.dirty 属性标记计算属性是否须要从新求值。
当 computed 的依赖状态产生扭转时, 就会告诉这个惰性的 watcher,
computed watcher 通过 this.dep.subs.length 判断有没有订阅者,
有的话, 会从新计算, 而后比照新旧值, 如果变动了, 会从新渲染。(Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性最终计算的值发生变化时才会触发渲染 watcher 从新渲染,实质上是一种优化。)
没有的话, 仅仅把 this.dirty = true。(当计算属性依赖于其余数据时,属性并不会立刻从新计算,只有之后其余中央须要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)个性。)
说下 $attrs 和 $listeners 的应用场景
API 考查,但 $attrs
和$listeners
是比拟少用的边界常识,而且 vue3
有变动,$listeners
曾经移除,还是有细节可说的
体验
一个蕴含组件透传属性的对象
<template>
<child-component v-bind="$attrs">
将非属性个性透传给外部的子组件
</child-component>
</template>
答复范例
- 咱们可能会有一些属性和事件没有在
props
中定义,这类称为非属性个性,联合v-bind
指令能够间接透传给外部的子组件。 - 这类“属性透传”经常用于包装高阶组件时往外部传递属性,罕用于爷孙组件之间传参。比方我在扩大 A 组件时创立了组件 B 组件,而后在 C 组件中应用 B,此时传递给 C 的属性中只有
props
外面申明的属性是给 B 应用的,其余的都是 A 须要的,此时就能够利用v-bind="$attrs"
透传下去。 - 最常见用法是联合
v-bind
做开展;$attrs
自身不是响应式的,除非拜访的属性自身是响应式对象。 vue2
中应用listeners
获取事件,vue3
中已移除,均合并到attrs
中, 应用起来更简略了
原理
查看透传属性 foo
和一般属性 bar
,发现vnode
构造完全相同,这阐明 vue3
中将分辨两者工作由框架实现而非用户指定:
<template>
<h1>{{msg}}</h1>
<comp foo="foo" bar="bar" />
</template>
<template>
<div>
{{$attrs.foo}} {{bar}}
</div>
</template>
<script setup>
defineProps({bar: String})
</script>
_createVNode(Comp, {
foo: "foo",
bar: "bar"
})
keep-alive 中的生命周期哪些
keep-alive 是 Vue 提供的一个内置组件,用来对组件进行缓存——在组件切换过程中将状态保留在内存中,避免反复渲染 DOM。
如果为一个组件包裹了 keep-alive,那么它会多出两个生命周期:deactivated、activated。同时,beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。
当组件被换掉时,会被缓存到内存中、触发 deactivated 生命周期;当组件被切回来时,再去缓存里找这个组件、触发 activated 钩子函数。
构建的 vue-cli 工程都到了哪些技术,它们的作用别离是什么
vue.js
:vue-cli
工程的外围,次要特点是 双向数据绑定 和 组件零碎。vue-router
:vue
官网举荐应用的路由框架。vuex
:专为Vue.js
利用我的项目开发的状态管理器,次要用于保护vue
组件间共用的一些 变量 和 办法。axios
(或者fetch
、ajax
):用于发动GET
、或POST
等http
申请,基于Promise
设计。vuex
等:一个专为vue
设计的挪动端 UI 组件库。- 创立一个
emit.js
文件,用于vue
事件机制的治理。 webpack
:模块加载和vue-cli
工程打包器。