标题
ev.currentTarget和ev.target的区别
ev.currentTarget是绑定事件处理程序的元素(变),而ev.target是触发事件的元素(不变)。
箭头函数和普通函数的区别
- 没有自己的
this
、super
、arguments
和new.target
绑定; - 不能使用
new
来调用; - 没有原型对象;
- 不可以改变
this
的绑定; - 形参名称不能重复;
引用:https://juejin.cn/post/6844903746984476686#heading-8
Get和POST的区别
- GET是不安全的,因为在传输过程,数据被放在请求的URL中;POST通过request body传递参数,所有操作对用户来说都是不可见的。
- GET传送的数据量较小,这主要是因为受URL长度限制;POST传送的数据量较大,一般被默认为不受限制。
- GET限制Form表单的数据集的值必须为ASCII字符;而POST支持整个ISO10646字符集。
- GET执行效率却比POST方法好。GET是form提交的默认方法。
- GET产生一个TCP数据包;POST产生两个TCP数据包。
对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
而对于POST,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)。
总结: GET和POST本质上就是TCP链接,并无差别。但是由于HTTP的规定和浏览器/服务器的限制,导致他们在应用过程中体现出一些不同。
数据类型
数据分为基本数据类型
(String, Number, Boolean, Null, Undefined,Symbol(new in ES 6)和 引用数据类型
(统称为 Object 类型,细分的话有:Object 、Array 、Date 、RegExp、Function… )。
- 基本数据类型的特点:直接存储在
栈(stack)
中的数据 - 引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在
堆内存
里
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
深浅拷贝
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。
浅拷贝的实现方式
- Es6中扩展运算符(…)
- Object.assign()
注意:当object只有一层的时候,是深拷贝 - Array的slice和concat方法
深拷贝的实现方式
- JSON.parse(JSON.stringify())
弊端:
* 1.如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将只是字符串的形式,而不是对象的形式
* 2.如果obj里有RegExp(正则表达式的缩写)、Error对象,则序列化的结果将只得到空对象;
* 3.如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失;
* 4.如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null
* 5.JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor;
* 6.如果对象中存在循环引用的情况也无法正确实现深拷贝;
总结: 只能正确处理Number、String、Array等json数据结构
- 手写递归方法
function deepClone(source) {
const targetObj = source.constructor === Array ? [] : {
}; // 判断复制的目标是数组还是对象
for (let keys in source) {
// 遍历目标
if (source.hasOwnProperty(keys)) {
let value = source[keys];
if (value && typeof value === 'object') {
// 如果值是对象,就递归一下
//targetObj[keys] = value.constructor === Array ? [] : {};
targetObj[keys] = deepClone(value);
} else {
// 如果不是,就直接赋值
targetObj[keys] = value;
}
}
}
return targetObj;
}
- 函数库lodash
该函数库也有提供_.cloneDeep用来做 Deep Copy
var _ = require('lodash');
var obj1 = {
a: 1,
b: {
f: {
g: 1 } },
c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
BFC
BFC(block formatting context):块级格式化上下文,简单来说,BFC 就是一种属性,这种属性会影响着元素的定位以及与其兄弟元素之间的相互作用。
1.创建、触发BFC
- float的值
不是none
。 - position的值
不是static或者relative
(absolute,fixed)。 - display的值
是inline-block、table-cell、flex
、table-caption或者inline-flex - overflow的值
不是visible
(hidden,auto,scroll)
2.BFC常见作用/应用场景
- 利用BFC避免margin重叠。
- 不被浮动元素覆盖(自适应两栏布局)
- 清除浮动
data为什么是函数
组件复用,每个实例可以维护一份被返回对象的独立的拷贝,使数据相互隔离,不受影响
v-if和v-show
v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
相比之下,v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销
。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。
父子组件生命周期顺序
父beforeCreate->父created->父beforeMount->
子beforeCreate->子created->子beforeMount->子mounted->
父mounted
Vue组件通信
-
父传子
props -
子传父
$emit & v-on -
组件之间通信
1.中央事件总线 Bus 进行通信
2.$parent / $children & $refs
3.发布订阅模式
4.Vuex
Vue的单向数据流
单向下行绑定
:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。不应该在一个子组件内部改变 prop
- 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。
- 这个 prop 以一种原始的值传入且需要进行转换。在这种
- 情况下,最好使用这个 prop 的值来定义一个计算属性:
注意:在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop
来说,在子组件中改变变更这个对象或数组本身将会影响到父组件的状态。
vue-router有哪几种导航钩子
- 全局导航钩子
- router.beforeEach(to, from, next)
- router.beforeResolve(to, from, next)
- router.afterEach(to, from)
- 路由钩子
- beforeEnter(to, from, next)
- 组件钩子
- beforeRouteEnter(to, from, next)
- beforeRouteUpdate(to, from, next)
- beforeRouteLeave(to, from, next)
完整的导航解析流程
1.导航被触发。
2.在失活的组件里调用 beforeRouteLeave 守卫。
3.调用全局的 beforeEach 守卫。
4.在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
5.在路由配置里调用 beforeEnter。
6.解析异步路由组件。
7.在被激活的组件里调用 beforeRouteEnter。
8.调用全局的 beforeResolve 守卫 (2.5+)。
9.导航被确认。
10.调用全局的 afterEach 钩子。
11.触发 DOM 更新。
12.调用 beforeRouteEnter 守卫中传给 next 的回调函数,创建好的组件实例会作为回调函数的参数传入。
路由传参
在组件中使用 $route
会使之与其对应路由形成高度耦合,从而使组件只能在某些特定的 URL 上使用,限制了其灵活性。
- props(推荐)
使用 props 将组件和路由解耦,取代与 $route 的耦合 - 路径传参
this.$route.params.id - path & query
url后面?id=?,this.$route.query.id - name & params
this.$route.params.id
总结: 使用params刷新页面参数会失效,参数不会再URL中显示.其它方式刷新页面参数不会失效
Vue自定义指令
1.全局注册
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
2.局部注册
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
钩子函数
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
- bind :只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
- inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
- update:所在组件的 VNode 更新时调用
- componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
- unbind:只调用一次,指令与元素解绑时调用。
钩子函数参数
- el:指令所绑定的元素,可以用来直接操作 DOM。
- binding:一个对象,包含以下 property:
- name:指令名,不包括 v- 前缀
- value:指令的绑定值
- oldValue:指令绑定的前一个值
- expression:字符串形式的指令表达式
- arg:传给指令的参数
- modifiers:一个包含修饰符的对象
- vnode:Vue 编译生成的虚拟节点。
- oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
注意:除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
computed、watch和methos的区别
计算属性computed:
- 支持缓存,只有依赖数据发生改变,才会重新进行计算
- 不支持异步,当computed内有异步操作时无效,无法监听数据的变化
- computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
- 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
侦听属性watch
- 不支持缓存,数据变,直接会触发相应的操作;
- watch支持异步;
- 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
- 当一个属性发生变化时,需要执行对应的操作;一对多;
- 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数:
immediate
:组件加载立即触发回调函数执行,
deep
: 深度监听,为了发现对象内部值的变化,复杂类型的数据时使用,例如数组中的对象内容的改变,注意监听数组的变动不需要这么做。注意:deep无法监听到数组的变动和对象的新增,参考vue数组变异,只有以响应式的方式触发才会被监听到。
mthods
ing…
Promise
异步编程的一种解决方案,异步操作队列化,解决回调地狱;
它有三个状态,状态一旦改变,不会再变。
- pending 进行中
- fulfilled 已完成
- rejected 已失败
基本的 api
- Promise.resolve()
- Promise.reject()
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.all() // 所有的完成
- Promise.race() // 竞速,完成一个即可
new Promise(
function (resolve, reject) {
// 一段耗时的异步操作
resolve('成功') // 数据处理完成
// reject('失败') // 数据处理出错
}
).then(
(res) => {
console.log(res)}, // 成功
(err) => {
console.log(err)} // 失败
)
错误处理两种做法:
第一种:reject(‘错误信息’).then(() => {}, () => {错误处理逻辑})
第二种:throw new Error(‘错误信息’).catch( () => {错误处理逻辑})
推荐使用第二种方式,更加清晰好读,并且可以捕获前面所有的错误(可以捕获N个then回调错误)
vue的特性以及优缺点
特性
- 轻量级
- 双向数据绑定(通过MVVM思想实现数据的双向绑定,让开发者不用再操作dom对象,有更多的时间去思考业务逻辑。)
- 指令
- 插件化
- 动画系统
优点
- 易用,灵活,高效
- Virtual DOM
- 响应式和组件化组件
- 性能高
缺点
- 不支持IE8
- 生态环境不如angular和react
- 不利于seo,加载慢
es6、es7、es8
ES6常用新特性
- let && const
let 命令也用于变量声明,但是作用域为局部
{
let a = 10;
var b = 1;
}
//作用域
JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域。块作用域由 { } 包括,if语句和for语句里面的{ }也属于块作用域。
var 定义的变量,没有块的概念,可以跨块访问, 不能跨函数访问。
let 定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问。
const 用来定义常量,使用时必须初始化(即必须赋值),只能在块作用域里访问,而且不能修改。
同一个变量只能使用一种方式声明,不然会报错
追加:
var name = "window";
console.log(window.name); //window
let name2 = 'window2';
console.log(window.name2); //undefined
let和const不会给顶层对象中(浏览器是window)添加属性,var
- 解构赋值
let [a, b, c] = [1, 2, 3];
var {
name, age, sex } = Person;
- 模板字符串 (``)
- 箭头函数
箭头函数修复了this的指向,使其永远指向词法作用域: - …操作符
- 类
- Promise
ES7新特性
- 异步函数(Async Functions) async/await
promise的语法糖,返回值是promise;
我们的代码执行异步看起来像执行同步一样。可以从头到尾读起来非常简单和易懂 - Array.prototype.includes
作用:替代indexOf;
在用indexOf作为判断条件时,若Array.prototype.indexOf返回-1变成了true(转换成true),但是当匹配的元素为0位置时候,该数组包含元素,却变成了false。
includes和find的区别:
1.inculdes返回true|false
2.find返回找到的第一个元素 - Exponentiation Operator(求幂运算)
ES8新特性
- Object.values/Object.entries
- String padding(字符串填充)
console.log(‘react’.padStart(10, ‘_’)) // “_____react”
MVC和MVVM的区别
在MVC模型里,Model不依赖于View,但是 View是依赖于Model的。
MVVM实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再自己手动操作Dom元素,来改变View的显示,而是改变属性后该属性对应View层显示会自动改变。
虚拟DOM
模板 → 渲染函数 → 虚拟DOM树 → 真实DOM
为什么使用虚拟DOM/操作真实DOM的代价
1.dom操作是"昂贵"的,因此尽量减少DOM操作
2.vnode和oldvnode通过diff算法,找出需要更新的节点
3.patch
微信支付
- 前台将商品数据发送到后台,后台创建订单入库并返回订单id等信息
url: '', // 创建订单接口
method: 'POST',
data: {
openId: '获取到的该用户的openid,必传',
number: '商品数量',
goodsId: '商品id',
goodsFee: '商品价格'
},
- 根据后台返回的订单id生成商户订单
url:"",// 生成订单接口
method: 'POST',
data: {
openId: '获取到的该用户的openid,必传',
totalFee: res.paidAmount, // 商品支付价格
uid: res.uid // 后台生成的订单id
},
success: res => {
console.log('获取数据成功')
this.handlePayment(res)
},
- 调用微信支付接口发起支付(我们后台返回的是JSON字符串,所以要转为JSON对象)
handlePayment (res) {
let param = JSON.parse(res.data.data.payInfo)
// console.log(param)
wx.requestPayment({
timeStamp: param.timeStamp,
nonceStr: param.nonceStr,
package: param.package,
signType: param.signType,
paySign: param.paySign,
success: response => {
console.log('支付成功')
},
fail: err => {
console.log(err)
}
})
}
小程序登录流程
- 小程序端 wx.login 获取code 并 wx.request 提交 code 给己方服务器
- 服务器 提交Appid + appSecret + code 到微信方服务器 获取 session_key & openid
- 服务器 根据 session_key & openid 生成 3rd_session(微信方提出的基于安全性的考虑,建议开发者不要将openid等关键性信息进行数据传输) 并返回 3rd_session 到小程序端
- 小程序端 wx.setStorage 存储 3rd_session 在后续用户操作需要凭证时 附带该参数
- 小程序端 wx.getUserInfo 获取用户信息 + wx.getStorage 获取 3rd_session 数据后,一并 wx.request 提交给己方服务器
- 服务器 SQL 用户数据信息更新
openid是小程序使用者的唯一标识,session_key是用来维持登录状态的
客户端渲染和服务端渲染介绍与区别(SSR)
-
客户端渲染:
在服务端放了一个html 页面,里面有<script>//发请求,拿数据,模版引擎渲染等,$.ajax ,</script>
客户端发起请求,服务端把页面(响应的是字符串)发送过去,客户端从上到下依次解析,如果在解析的过程中,发现ajax请求,再次像服务器发送新的请求,客户端拿到ajax 响应结果,模板引擎渲染。过程至少和服务端发起两次请求 -
服务端渲染:
sever 端页面+数据,服务端过程:1.读取index.html 2.模版进行渲染,在发送给客户端之前,在服务端已经把index.html 渲染处理了。var 渲染结果 = tempeter.render(模板字符串,{解析替换对象}) response.end(渲染结果),服务端响应的就是最总的结果
服务端只请求一次,多数网站既有服务端渲染又有客户端渲染. -
服务端渲染和客户端渲染的区别
客户端渲染不利于 SEO 搜索引擎优化
服务端渲染是可以被爬虫抓取到的,客户端异步渲染是很难被爬虫抓取到的
所以你会发现真正的网站既不是纯异步也不是纯服务端渲染出来的,而是两者结合来做的
例如京东的商品列表就采用的是服务端渲染,目的了为了 SEO 搜索引擎优化
而它的商品评论列表为了用户体验,而且也不需要 SEO 优化,所以采用是客户端渲染
详见: https://blog.csdn.net/qq593249106/article/details/83240805
Vue首页加载过慢 解决方案
一、什么导致了首页初步加载过慢:app.js文件体积过大
二、解决方法:
- Vue-router懒加载
vue-router懒加载可以解决首次加载资源过多导致的速度缓慢问题:vue-router支持WebPack内置的异步模块加载系统。所以,那些使用较少的路由组件不必打包进bundles里,只需要在路由被访问时按需加载。 - 在webpack打包的过程中,将多余文件去掉
在webpack打包的过程中,将多余文件去掉,如map文件,即在config/index.js中将productionSourceMap的值修改为false
,就可以在编译时不生成.map文件了 - 第三方库使用CDN引入
在项目开发中,我们会用到很多第三方库,如果可以按需引入,我们可以只引入自己需要的组件,来减少所占空间,但也会有一些不能按需引入,我们可以采用CDN外部加载,在index.html中从CDN引入组件,去掉其他页面的组件import,修改webpack.base.config.js,在externals中加入该组件,这是为了避免编译时找不到组件报错。
注意:删掉项目中import的这几个相关的,以及Vue.use()。eslint插件报错not defined的话,前面加个window,如window.VueRouter。 - vue-cli开启打包压缩和后台配置gzip访问
首先安装插件:compression-webpack-plugin
在 config/index.js中将productionGzip 改为 true
v-for和v-if不应该一起使用。
原因:v-for比v-if优先,即每一次都需要遍历整个数组,影响速度。
- 必要情况下应该替换成computed属性。
- 把 v-if 改成 v-show
从输入URL到页面加载完成期间经历了什么?
- DNS域名解析
- 发起TCP连接(三次握手)
- 发送HTTP请求,接受HTTP响应
- 断开TCP连接(四次挥手)
- 浏览器解析HTML代码,请求js,css等资源,最后进行页面渲染,呈现给用户
vuex数据状态持久化
刷新页面以后vuex里面存储的state就会被浏览器释放掉,所以我们通过 vuex-persistedstate这个插件,来实现将数据存储到本地,默认存储localStorage,可配置sessionStorage
1.npm install vuex-persistedstate
2.import createPersistedState from 'vuex-persistedstate'
const store = new Vuex.Store({
modules: {
app,
user
},
getters,
plugins: [createPersistedState()] //加上这个就可以了
})
权限控制
面向对象(OOP)
三大特征分别为:封装,继承和多态
封装:封装的优势在于定义只可以在类内部进行对属性的操作,外部无法对这些属性指手画脚,要想修改,也只能通过你定义的封装方法;
继承:继承减少了代码的冗余,省略了很多重复代码,开发者可以从父类底层定义所有子类必须有的属性和方法,以达到耦合的目的;
多态:多态实现了方法的个性化,不同的子类根据具体状况可以实现不同的方法,光有父类定义的方法不够灵活,遇见特殊状况就捉襟见肘了
Vue2.0和Vue3.0的区别
- 项目目录结构
vue-cli2.0与3.0在目录结构方面,有明显的不同,vue-cli3.0移除了配置文件目录,config 和 build 文件夹,同时移除了 static 静态文件夹,新增了 public 文件夹,打开层级目录还会发现, index.html 移动到 public 中 - 配置项
3.0 config文件已经被移除,但是多了.env.production和env.development文件,除了文件位置,实际配置起来和2.0没什么不同,没了config文件,跨域需要配置域名时,从config/index.js 挪到了vue.config.js中,配置方法不变 - 渲染
Vue2.x使用的Virtual Dom实现的渲染
Vue3.0不论是原生的html标签还是vue组件,他们都会通过h函数来判断,如果是原生html标签,在运行时直接通过Virtual Dom来直接渲染,同样如果是组件会直接生成组件代码 - 数据监听
Vue2.x大家都知道使用的是es5的object.defineproperties中getter和setter实现的,而vue3.0的版本,是基于Proxy进行监听的,其实基于proxy监听就是所谓的lazy by default,什么意思呢,就是只要你用到了才会监听,可以理解为‘按需监听’,官方给出的诠释是:速度加倍,同时内存占用还减半。 - 按需引入
Vue2.x中new出的实例对象,所有的东西都在这个vue对象上,这样其实无论你用到还是没用到,都会跑一变。而vue3.0中可以用ES module imports按需引入,如:keep-alive内置组件、v-model指令,等等。
setData(Object data, Function callback)
data: object,必填,表示这次要改变的数据
callback: function,非必填,setData引起的界面更新渲染完毕后的回调函数