1. jsonp
jsonp 是我们实现跨域请求的手段,是把我们之前的东西组合在一起使用的技术手段而已
利用的是 script 标签的src来实现
2. Promise编写
有了Promise对象,就可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
此外,Promise对象提供统一的接口,使得控制异步操作更加容易。
Promise将 回调 转换成 链式调用 来解决 回调地狱
const p1= new Promise(function (resolve, reject) {
// resolve() 表示成功的回调
// reject() 表示失败的回调
})
p1.then(function (res) {
// 成功的函数
}).catch(function (err) {
// 失败的函数
})
例:
let p1 = new Promise((resolve, reject) => {
var num = Math.random()
console.log(num)
if(num>0.5){
resolve()
}else{
reject()
}
});
p1.then(function(){
console.log("成功了")
}).catch(function(){
console.log("失败了")
})
3. Promise状态
promise 就是一个语法,有三种状态:
1. pending(进行中)
2. fulfilled(已成功)
3. rejected(已失败)
promise 意思是表示“承诺”,表示其他手段无法改变。
一旦状态确定,就不会再变,任何时候都可以得到这个结果。
Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为 resolved(已定型)。
4. Promise的方法
原型方法
添加在 Promise对象原型上的方法
- Promise.prototype.then( )
- 为 Promise 实例添加状态改变时的回调函数,有两个参数,都是函数,1成功,2失败
- Promise.prototype.catch( )
- 用于指定发生错误时的回调函数
- Promise.prototype.finally( )
- 用于指定不管 Promise 对象最后状态如何,都会执行的操作
静态方法(函数方法)
- Promise.resolve( )
- 成功状态
- Promise.reject( )
- 失败状态
- Promise.all( )
- 接受一个数组作为参数,并发请求多个,全部成功才算成功,有一个失败就失败
- Promise.race( )
- 接受一个数组作为参数,并发请求多个,取最快返回的结果
- Promise.allSettled( )
- 接受一个数组作为参数,并发请求多个,不论成功失败结果都一并返回成功
- Promise.any( )
- 接受一个数组作为参数,并发请求多个,一个成功就是成功,全部失败就失败
5. 跨域
协议、域名、端口有一个不一样,就会引起跨域
6. 跨域解决方案
1.浏览器插件
Allow Cros插件
一定不能使用file协议打开文件,使用open with live server,保证在appache的环境下运行html
2.JSONP(需要服务器)
- jsonp 是我们实现跨域请求的手段,是把我们之前的东西组合在一起使用的技术手段而已
- 利用的是 script 标签来实现
script 标签的本质
- 浏览器给我们提供了一个 script 标签
- 它的本质就是请求一个外部资源,是不受到同源策略的影响的
- 同时 script 标签的 src 属性,也是一种请求,也能被服务器接收到
- 并且:
script标签的src属性请求回来的东西是一个字符串,浏览器会把这个字符串当作 js 代码来执行
- 所以我们就可以利用这个 script 标签的 src 属性来进行跨域请求了
3.后端响应头
access-control-allow-origin
7. 闭包概念
函数中返回一个函数,函数运行可以访问到上一个函数作用域中的变量,这就是闭包
闭包的使用场景
1. 柯里化函数,将接收多个参数的函数转化为单一参数,将之前的参数缓存起来,等到符合条件就一起执行的方法
2. 比如一些变量不想污染全局,函数运行返回函数,执行时访问闭包中的变量,比如防抖和节流中的开关和定时器
闭包的三个特性
函数嵌套函数
闭包就是能够读取其他函数内部变量的函数
参数和变量不会被垃圾回收机制回收
闭包的好处和坏处
好处: 保护函数内的变量安全,实现封装,防止变量流入其他环境发生命名冲突
在内存中维持一个变量,可以做缓存(但使用多了同时也是一项缺点,消耗内存)
匿名自执行函数可以减少内存消耗
坏处:
其中一点上面已经有体现了,就是被引用的私有变量不能被销毁,增大了内存消耗,造成内存泄漏,解决方法是可
以在使用完变量后手动为它赋值为null;
其次由于闭包涉及跨域访问,所以会
8. async,await
async和await实际上是generator的语法糖,他能解决promise的过多then的问题,使代码更具有线性思维,更容易书写和阅读
其原理是使用gennerator状态机,当执行到yield的时候,就返回一个promise对象,等promise改变状态的时候调用其next方法,以此类推
9. 判断数组有哪些方式
方法1:通过语法 Array.isArray()
方法2:instanceof
方法3:原型链(constructor)
Array.isArray([1, 2 ,3]) // true
[1, 2 ,3] instanceof Array // true
[1, 2 ,3].constructor === Array // true
Array.isArray('aa') // false
'aa' instanceof Array // false
'aa'.constructor === Array // false
10. 深浅拷贝概念
1.浅拷贝
浅拷贝主要是对指针的拷贝,拷贝后两个指针指向同一个内存空间
2.深拷贝
深拷贝不但对指针进行拷贝,并对指针指向的内容进行拷贝,经过深拷贝后的指针是指向两个不同地址的指针。
11. 深浅拷贝实现方式
浅拷贝的实现方式:
1.展开运算符
2.Object.assign()
深拷贝的实现方式:
1. 递归 (闻'归'色变)
- 前置知识点 1.递归的基本语法 2.遍历对象的方法(for in,Object.keys,Object.values)
- Object.keys 可以获取对象中所有的key(属性名),返回一个数组
- Object.values 可以返回对象中所有的value(属性值),返回一个数组
2. 第三方的库 lodash
- lodash地址:https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js
- _.cloneDeep()方法
3.inmutable库
4.JSON.stringify JSON.parse
12. json方法深拷贝缺陷
1. 如果obj里面存在时间对象,JSON.parse(JSON.stringify(obj))之后,时间对象变成了字符串。
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. 如果对象中存在循环引用的情况也无法正确实现深拷贝。
13. new一个构造函数发生了什么
-
开辟一个内存,创建一个实例
-
构造函数的this指向当前的实例
-
执行构造函数中的代码
-
实例的constructor指向构造函数
// let Sym = fn
function Sym() {
this.a = 1;
this.b = 2;
this.c = 3;
}
// 第一种使用方式:当变量赋值用
let data1 = Sym;
console.log(data1); // 打印:函数本身
// 第二种使用方法:当函数用
let data2 = Sym();
console.log(data2); // 打印:undefined 但是window多了a、b、c三个属性
// 第三种使用方法:new关键词
let data3 = new Sym();
console.log("new关键词:", data3); // 打印:对象
/*
栈 堆
data1: 地址1 地址1:function Sym() { this.a = 1; this.b = 2; this.c = 3; }
data2:undefined 地址2:{a:1,b:2,c:3, __proto__:}
data3:地址2
*/
function _new(当前new的构造函数) {
// let obj = {};
// obj.__proto__ = 当前new的构造函数.prototype;
let obj = Object.create(当前new的构造函数.prototype); // !!!!
// obj.a = 1;
// obj.b = 2;
// obj.c = 3;
当前new的构造函数.call(obj);
return obj;
}
let data4 = _new(Sym);
// 需求:封装_new普通函数仿写new关键词效果
// 分析:_new普通函数里面做哪些时
// - 必须返回一个对象
console.log("通过_new仿写new:", data4);
14. 原型的作用及原型链
原型:js给每个函数分配的公共空间,减少内存占用
原型链:多个原型的集合,当调用对象的属性或方法时,先自身找,找不到去原型链上找,一直找到Object构造函数得原型链
15. 改变this指向的方法
call / apply / bind
1.call() 方法
- call 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向
- 语法: 函数名.call(要改变的 this 指向,要给函数传递的参数1,要给函数传递的参数2, ...)
var obj = { name: 'Jack' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)
fn.call(obj, 1, 2)
fn() 的时候,函数内部的 this 指向 window
- fn.call(obj, 1, 2) 的时候,函数内部的 this 就指向了 obj 这个对象
- 使用 call 方法的时候
- 会立即执行函数
- 第一个参数是你要改变的函数内部的 this 指向
- 第二个参数开始,依次是向函数传递参数
2.apply() 方法
- apply 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向
- 语法: 函数名.apply(要改变的 this 指向,[要给函数传递的参数1, 要给函数传递的参数2, ...])
var obj = { name: 'Jack' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)
fn.apply(obj, [1, 2])
fn() 的时候,函数内部的 this 指向 window
- fn.apply(obj, [1, 2]) 的时候,函数内部的 this 就指向了 obj 这个对象
- 使用 apply 方法的时候
- 会立即执行函数
- 第一个参数是你要改变的函数内部的 this 指向
- 第二个参数是一个数组,数组里面的每一项依次是向函数传递的参数
3.bind() 方法
- bind 方法是附加在函数调用后面使用,可以忽略函数本身的 this 指向
- 和 call / apply 有一些不一样,就是不会立即执行函数,而是返回一个已经改变了 this 指向的函数
- 语法: var newFn = 函数名.bind(要改变的 this 指向); newFn(传递参数)
var obj = { name: 'Jack' }
function fn(a, b) {
console.log(this)
console.log(a)
console.log(b)
}
fn(1, 2)
var newFn = fn.bind(obj)
newFn(1, 2)
- bind 调用的时候,不会执行 fn 这个函数,而是返回一个新的函数
- 这个新的函数就是一个改变了 this 指向以后的 fn 函数
- fn(1, 2) 的时候 this 指向 window
- newFn(1, 2) 的时候执行的是一个和 fn 一摸一样的函数,只不过里面的 this 指向改成了 obj
注意:bind() 方法可以用来改变 定时器的 this指向
var obj1 = {
usname: "hello",
fn: function(){
setInterval(function(){
console.log(this.usname); // "hello"
}.bind(obj1),1000)
}
}
obj1.fn()
16. 继承的方法
1.原型继承
- 原型继承,就是在本身的原型链上加一层结构
2.借用构造函数继承
- 把父类构造函数体借用过来使用一下而已
3.组合继承
- 就是把 `原型继承` 和 `借用构造函数继承` 两个方式组合在一起
4.es6的类继承,extends
17. 元字符
. : 匹配非换行的任意字符
\ : 转译符号,把有意义的 **符号** 转换成没有意义的字符,把没有意义的字符转换成有意义的符号
\n 换行
\t 缩进
\b 空格 ...
\s : 匹配空白字符(空格/制表符/...)
\S : 匹配非空白字符
\d : 匹配数字
\D : 匹配非数字
\w : 匹配数字字母下划线
\W : 匹配非数字字母下划线
18. 限定符
* : 前一个内容重复至少0次,也就是可以出现0~正无穷次
+ : 前一个内容重复至少1次,也就是可以出现1 ~ 正无穷次
? : 前一个内容重复 0 或者 1 次,也就是可以出现0~ 1次
{n} : 前一个内容重复 n 次,也就是必须出现n次
{n,} : 前一个内容至少出现 n 次,也就是出现n ~ 正无穷次
{n,m} : 前一个内容至少出现 n 次至多出现 m 次,也就是出现 n ~ m 次
19. 边界符
^ : 表示开头
$ : 表示结尾
20. 标识符/修饰符
i : 表示忽略大小写
这个 i 是写在正则的最后面的
/\w/i
就是在正则匹配的时候不去区分大小写
g : 表示全局匹配
这个 g 是写在正则的最后面的
/\w/g
就是全局匹配字母数字下划线
21. git命令
2.git clone :克隆代码,将远程仓库的代码克隆到本地;
3.git clone -b 指定的分支名 :拉取远程指定分支
4.git add . :将所有变更的代码 提交到仓库门口(暂存区)
5.git commit -m :提交代码到仓库中 用于提交暂存区的文件
6.git pull :拉取代码合并
7.git push :把本地仓库的代码,推送到远程的仓库
8.git remote -v :查看远程源
9.git remote add origin :添加远程源
10.git remote rm origin :删除远程源
11.git diff :对比不同 查看变更内容
12.git status :查看提交状态
13.git rm :删除文件
14.git rm --cached :停止追踪但不删除 删除暂存区的文件
15.git commit --amend :修改描述,只能修改最近一次
16.git log :查看版本日志 显示所有commit日志
17.git branch :查看分支
18.git branch -d 分支名 :删除分支
19.git checkout 分支名 :切换分支
20. git checkout -b xx origin/xx :作用是checkout远程的xx分支,在本地起名为xx分支,并切换到本地的xx分支
21.git merge :合并分支
22. git branch -a :查看远程分支,远程分支会用红色表示出来
22. git版本回退
git ls -a 查看是否有.git文件
git log 查看日志,复制最新提交的版本号
按q退出
git reset --hard 版本号
23. git stash
git stash save 文件名(缓存文件)
git stash pop(恢复缓存文件)
git stash list(查看现有stash的文件)
git stash drop 需要恢复的文件名(选择需要恢复的文件)
24. git解决冲突
当文件出现冲突时,通过git diff查看变更内容,将冲突文件拉到vscode,搜索<<<,根据具体需求选择保留原文件、现文件还是都保留,冲突解决后,再次使用git add ./git commit -m/git push
25. git提交代码流程
1.进入gitee页面,注册账号,登入,点击右上角"+"创建新仓库 一定要有一个.git文件夹 另外一定要有一个叫做readme.md的文件
2.git config --global user.name "填写用户名 " 添加git配置--全局用户名
3.git config --global user.email "填写邮箱" 添加git配置--全局用户邮箱
4.git init 初始化一个git
5.touch README.md 创建一个README.md 文件 非常重要,如果没有readme.md是无法推送的
6.git add XXXX文件 添加文件到仓库门口
7.git commit -m "first commit" 提交版本号
8.git remote add origin 输入仓库地址XXXXXX 添加远程仓库
9.git push -u origin master 将代码推到远程仓库
10.vim xxxxx文件 可以打开文件更改内容 i 进入书写 Esc + :wq 退出
将dev拉取到本地
26. http状态码
1** 信息,服务器收到请求,需要请求者继续执行操作
2** 成功,操作被成功接收并处理
3** 重定向,需要进一步的操作以完成请求
4** 客户端错误,请求包含语法错误或无法完成请求
5** 服务器错误,服务器在处理请求的过程中发生了错误
301 (Moved Permanently) 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
302 (Found) 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
303 (See Other) 查看其它地址。与301类似。使用GET和POST请求查看
304 (Not Modified) 未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源
400 (Bad Request) 客户端请求的语法错误,服务器无法理解401 (Unauthorized) 请求要求用户的身份认证
402 (Payment Required) 保留,将来使用
403 (Forbidden) 服务器理解客户端的请求,但是拒绝执行此请求
404 (Not Found) 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
500 (Internal Server Error) 服务器内部错误,无法完成请求
501 (Not Implemented) 服务器不支持请求的功能,无法完成请求
502 (Bad Gateway) 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应
27. commonjs
commonjs就是为了实现javascript模块化,而制定的一套规范,只能应用在node环境之中,传统浏览器不支持
导入:require()
导出:module.exports
28.es6和commonjs的区别
1.commonjs是动态引入,运行时加载,并且是同步加载,是对内容拷贝(复制数据类型浅拷贝)
2.es6模块化是编译时加载,是对内容的只读引用
3.commonjs有加载缓存,es6模块没有加载缓存
4.es6模块引入的时候,需要放在代码的最顶部,commonjs不需要
29.响应式原理
1. Vue 内部使用了 Object.defineProperty() 来实现双向绑定,通过这个方法可以给对应的属性添加get和set方法,访问属性会触发get,设置属性会触发set
2. vue的内部声明了一个叫做Observer的函数,这个函数的作用是用来劫持vue的data属性,但是由于data中又会存在复杂数据类型,需要深度递归劫持,在刚刚的Obser基础上,再声明一个函数,defineReactive用于递归劫持子属性
3. 通过上面两个方法劫持到了属性的get和set以后,需要给数据添加发布订阅流程,内部声明了一个叫做watcher的类,用于订阅属性的变化,在这个watcher中,添加update更新的方法,实例化整个watcher,订阅需要变化的属性
4. 声明watcher之后,和劫持的数据耦合依赖度比较高,所以还需要声明一个叫做Dep的类,用于解发布和订阅者的耦合,Dep中声明subs属性,储存订阅者实例,声明addSub来添加订阅者,notify用于在数据变化的时候,通知订阅者,在notify中调用Update实例的,以后被劫持的属性赋值的时候,会触发set方法的调用,set中调用notify,订阅属性的时候,会触发get方法,在get方法中,调用Dep的addSub方法添加订阅者
30.key的作用
区分组件的唯一性,提高diff的效率
31.视图不响应怎么处理
调用方法:vue.$set( target, key, value )
target:要更改的数据源(可以是对象或者数组)
key:要更改的具体数据
value :重新赋的值
32.自定义指令的作用
在vue里面如果需要对dom进行底层操作的时候,就需要用上自定义指令了
33.自定义指令钩子函数
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:只调用一次,被绑定元素插入父节点时调用
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
34.自定义指令钩子的参数
el:指令所绑定的元素,可以用来直接操作 DOM 。
binding:一个对象,包含以下 property:
name:指令名,不包括 v- 前缀。
value:指令的绑定值
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
expression:字符串形式的指令表达式。。
arg:传给指令的参数,可选。
modifiers:一个包含修饰符的对象
vnode:Vue 编译生成的虚拟节点
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用
35.按钮鉴权的思路
1. 注册自定义指令,我们取的指令名字叫做has
2. 登入之后,会获取当前角色所具备的按钮权限,得到的是一个数组[add,read,edit,delete]
3. 将v-has绑定在模板中的每个按钮上,并且通过指令参数,描述当前按钮所需要具备的权限 (<button v-has:delete>删除</button>)
4. 再给自定义指令的表达式赋值,将角色的权限数组赋值给指令表达式 v-has:delete="permission",permission就是后台返回的权限数据
5. 没有权限的角色,按钮不显示 核心逻辑,通过角色所有的权限数组,查看是否包含按钮所需要的权限字段,如果没有权限,那么el.parent.removeChild(el),把自己这个按钮干掉
36.什么是虚拟dom,为什么需要虚拟dom
使用js在内存中,模拟真实的dom树的嵌套关系,更新的时候都在内存中更新,更新完成之后,替换页面上的真实dom.
为了实现页面的高效更新
37.watch和computed区别
watch: 一次可以监听多个,不支持缓存,支持异步
Computed: 一次只能监听一个,支持缓存,不支持异步
38.watch立即调用
immediate:true;
39.watch深度侦听
deep:true
40.组件通信(传参)
父传子
props,$parent,$root
子传父
$emit,ref
兄弟组件传值
eventBus
跨组件传值
provide,inject
41.data为什么是一个函数
为了让每个组件都能维护一份被返回对象的独立拷贝,避免组件和组件之间数据相互污染的情况
42.如何复用组件逻辑
使用mixins将公共逻辑提取进行复用
43.混入的合并策略
1.数据对象在内部会进行递归合并,并在发生冲突时以组件数据优先。
2.同名钩子函数将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。
3.值为对象的选项,例如 methods、components和directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对。
混入的缺点:可读性差,难维护
44.nextTick的作用
vue中的nextTick主要用于处理数据动态变化后,DOM还未及时更新的问题,用nextTick就可以获取数据更新后最新DOM的变化
45.keep-alive的作用、钩子、属性、ref
1. keep-alive作用
缓存组件的状态,避免组件重复渲染导致的性能问题
2. 两个生命周期钩子函数 deactivated activated 被缓存的时候 和 被激活的时候触发的钩子函数
属性Props:
include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
max - 数字。最多可以缓存多少组件实例
ref:
预期:stringref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
46.Provide inject特点,怎么处理
父组件通过provide提供数据,其他组件可以使用inject注入数据。
特点:
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
vue通过Provide传递下去的属性是不响应的,是有意为之的
如果想要它响应,就要传递一个对象
47.路由的传参方式、区别
query:刷新页面不会丢失
params:不能通过路径跳转,只能通过name跳转
localStorage
sessionStorage
window
48.路由的模式、区别
1. hash有#号,#号后面的url不会向后端发起请求
2. hash路由使用onhashchange监听 history使用onpopstate监听
3. history使用的是H5的api pushState replaceState
4. history容易造成404,原因是将地址当成了接口,向通过http发起了请求,但是没有服务响应
5. abstract模式是在没有浏览器api的情况下自动启用,abstract模式在切换页面的时候,路径是不会发生变化的
49.编程式导航的方式、区别
go,back,push,replace:replace会替换掉上一个历史记录,forword
50.给vue实例添加一个响应式属性
vue.$set
51.vuex 核心属性
state(保存全局的状态),getters(相当于计算属性),mutation(保存了用于唯一修改state的方法),action(保存了用于处理异步的方法),modules(用大项目的模块化)
52.vuex刷新问题
刷新时数据会丢失,安装持久化存储插件createpersistedstate
53.vuex模块化
当应用较为复杂,需要共享的数据较多时,state对象以及store对象都会变得很臃肿,不利于代码维护。 将store分割成模块。每个模块拥有自己的state、mutation、action、getter并且可以嵌套了模块
54.vuex持久化存储方案
将其状态保存到localStorage或者sessionStorage中
然后在给每个状态默认值的时候就从localStorage或sessionStorage中取就可以了也就是咱们直接手写实现,另外一种方式就是使用第三方插件(vuexpersistedstate或者vuex-persist)
55.axios fetch ajax的区别
和ajax相比:
1. 不存在回调地狱的问题
2. 基于promise 支持链式调用
3. 配合async await可以更优雅的处理异步逻辑
4. 内置拦截器,可以更方便的发送请求,携带数据
5. 使用简单
和fetch相比:
1. fetch是es6的原生语法,浏览器默认支持,不需要下载3方库
2. axios是一个基于promise的三方的库,是需要下载安装的,浏览器中不带这个库
3. fetch需要自己手动实现拦截器,但是axios不需要,已经内置了
56.vue3新特性和vue2的区别
vue3与vue2的区别
1. 底层数据劫持的api发生了变化 在vue2里面使用的是Object.defineProperty,缺点是不能够劫持数组,vue3里面使用的是ES6的proxy,reflect可以完美响应数组变化,解决了vue2不能劫持数组的问题
2. 使用层面上来说,在vue3里面有'应用的概念',使用新的api createApp 用于解决配置项污染的问题
3. 增加了组合式的api,组合式(composition)的api可以解决vue2中选项式代码量多的时候,焦点丢失,上下反复横跳的问题
4. 可以有多个节点了,vue2里面只能有一个根节点,vue3里面可以有多个节点
5. 除了对props可以校验以外,vue3里面的事件也可以校验了
6. 新增了setup钩子,新增语法糖 script setup 编写逻辑更加简单方便,直接省略了选项
7. 移除了过滤器 移除了eventBus
8. style中也可以使用v-bind绑定数据了....
57.ref和reactive的区别
1. reactive在使用的时候,会自动解包不需要像ref那样去.value
2. reactive不能对简单数据类型进行响应,需要在reactive中传入引用数据类型
如果直接返回reactive对应的变量,那么会失去响应,正确的写法是将reactive的属性放到toRefs方法中,保持其响应式
58.vue3钩子函数
- created:在绑定元素的 attribute 或事件监听器被应用之前调用
- beforeMount:当指令第一次绑定到元素并且在挂载父组件之前调用。
- mounted:在绑定元素的父组件被挂载后调用。
- beforeUpdate:在更新包含组件的 VNode 之前调用。
- updated:在包含组件的 VNode 及其子组件的 VNode 更新后调用。
- beforeUnmount:在卸载绑定元素的父组件之前调用
- unmounted:当指令与元素解除绑定且父组件已卸载时,只调用一次。
59.怎么取消promise
在promise内部直接抛出异常(throw_error,new DOWExpection)
实现核心思路:重写resolve
60.watch watchEffect
watch特点
watch 监听函数可以添加配置项,也可以配置为空,配置项为空的情况下,watch的特点为:
有惰性:运行的时候,不会立即执行;
更加具体:需要添加监听的属性;
可访问属性之前的值:回调函数内会返回最新值和修改之前的值;
可配置:配置项可补充 watch 特点上的不足: immediate:配置 watch 属性是否立即执行,值为 true 时,一旦运行就会立即执行,值为 false 时,保持惰性。 deep:配置 watch 是否深度监听,值为 true 时,可以监听对象所有属性,值为 false 时保持更加具体特性,必须指定到具体的属性上。
watchEffect特点
非惰性:一旦运行就会立即执行;
更加抽象:使用时不需要具体指定监听的谁,回调函数内直接使用就可以;
不可访问之前的值:只能访问当前最新的值,访问不到修改之前的值
61.vuex异步流程
1. 使用action定义一个类型,处理异步函数
2. 在action中获得异步结果之后,通过commit提交mutation,并且将结果通过payload传递给mutaion
3. 在mutation中接受payload 并且更改state的值
62.get请求Post请求的区别
安全角度:get相对没有post安全,因为如果登录账号密码会在历史记录中找到
数据角度:get相对没有post多,get不同浏览器限制地址栏长度不同,post根据后端服务器配置普遍2M~8M
get只能传递字符串,post可以传递任何类型
63.query和params的区别
64.路由的钩子
全局前置守卫beforeEach,
全局后置钩子afterEach,
全局解析守卫beforeResolve,
路由独享的守卫beforeEnter
65.为什么mutation必须是同步函数
因为vuex调试工具就是在mutation这个环节来捕获状态变更的,如果在这里操作异步,那么很难捕获到变更之后的状态,因为无法确定这个异步回调什么时候执行
66.箭头函数为什么不能是构造函数
因为箭头函数里面没有constructor
67.箭头函数与普通函数的区别
1. 语法更加简洁、清晰
2. 箭头函数不会创建自己的this
3. 箭头函数继承而来的this指向永远不变
4. .call()/.apply()/.bind()无法改变箭头函数中this的指向
5. 箭头函数不能作为构造函数使用
6. 箭头函数没有自己的arguments
7. 箭头函数没有原型prototype
8. 箭头函数不能用作Generator函数,不能使用yeild关键字
68.js是单线程?
js是单线程,但是在H5的规范中是可以开启多线程的,线程之间互相不干扰,可以在子线程进行运算,将结果返回给主线程,多线程注重的是同性
如何创建多线程?
1. 创建子线程 使用 new Worker()
2. 接收子线程数据 onmessage
3. 传递数据给子线程 postMessage
4. 关闭子线程 使用 terminate
69.事件循环
同步和异步任务分别进入不同的执行环境,同步的进入主线程,即主执行栈,异步的进入任务队列。主线程内的任务执行完毕为空,会去任务队列读取对应的任务,推入主线程执行。 上述过程的不断重复就是我们说的 Event Loop (事件循环)。
70.微任务 micro task queue (背诵)
Process.nextTick(Node独有)、Promise.then、Object.observe(废弃)、MutationObserver
71.宏任务 macro task queue (背诵)
script全部代码、setTimeout、setInterval、setImmediate(浏览器暂时不支持,只有IE10,Node支持,具体可见MDN)、I/O、UI Rendering。
72.想要知道数据实时变化(面试问的时候)
可以使用轮询或websocket
73.轮询和websocket区别
轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器,但是这样,会带来占用流量和服务器性能浪费的问题。
websocket 是双向通信的,只要 websocket 连接建立起来,可以由客户端给服务端发送数据,也可以由服务端主动给客户端发送数据,这样能更好的节省服务器资源和带宽,并且能够更实时地进行通讯
74.v-if和v-show区别
相同点:都是控制隐藏或显示
不同点:v-if控制DOM、v-show控制样式,v-show可以减少性能消耗
如何选:频繁切换使用v-show
vue2中先v-for,后v-if
vue3中先v-if,后v-for
75.回流和重绘
回流:重新布局(width、top、margin、display:none)
重绘:重新绘制样式不影响布局(color、background、visibility)
1、相同点:display和visibility都有讲元素隐藏的意思
2、不同点:display是元素隐藏,隐藏的元素不占文档流
76.单页应用
一个项目只有一个html页面,所有的页面跳转依赖于路由进行
优点:
用户体验好,切换速度快,不需要刷新整个页面
缺点:
不利用SEO不利于服务端渲染,首页加载速度比较慢
spa就是单页面应用,是一种网页类型,他只有一个主页面,有多个页面片段,他是通过两种路由形式hash、history路由变化实现页面动态渲染改变内容
spa只有一个主页面,所以对于seo优化非常不方便,但是能显著提升用户的体验,虽然第一次加载需要加载大量的资源,但是后续的内容交互只需要加载以缓存的内容不需要再向服务器发送请求,所以页面会非常迅速。
由于是使用单页面,所有的交互问题只需要在前端完成就好,想要要数据就向后端发送请求,有利于前后端分离,更容易维护和迭代
如何给spa做seo优化
- 使用ssr服务器渲染
- 使用phantomjs针对爬虫处理
如何使spa单页面首屏加快
- 缩小入口文件的大小
- 静态文件本地缓存
- ui框架按需加载
- css、js、图片资源压缩处理
- 使用矢量图或者icon图标用字体
77.React与vue的区别
1. 组件化方面,react用的是jsx,一个js文件就是一个组件,而vue使用的是单文件组件
2. 数据流方面,react完全遵循单向数据流的原则,vue里面,理论上也是单向数据流,没有react严格,vue是可以双向数据绑定的
3. vue是响应式编程,react是函数式编程
4. 两者的diff算法不同 react用的算法是fiber (vue3.0)算法比react快
5. 在react中,有纯函数的概念
6. vue是一个渐进式的架构
7. react的元素(对象)创建出来是不允许改变的,只能通过新的值来覆盖
78.节流和防抖
防抖:多次执行,只取最后一次结果
核心方法是使用定时器
核心思想:清空上一个定时器,保留最后一个定时器
节流:多次执行一段逻辑的时候,每隔一段时间,执行一次
核心思想:设定定时器,定时器执行完成之后才设定下一个定时器
79.pinia和vuex的区别
1. mutations 不再存在。他们经常被认为是非常冗长。他们最初带来了 devtools 集成,但这不再是问题。
2. 无需创建自定义复杂包装器来支持 TypeScript,所有内容都是类型化的,并且 API 的设计方式尽可能利用 TS 类型推断。
3. 不再需要注入、导入函数、调用函数、享受自动完成功能!
4. 无需动态添加 Store,默认情况下它们都是动态的,您甚至都不会注意到。请注意,您仍然可以随时手动使用 Store 进行注册,但因为它是自动的,您无需担心。
5. 不再有 modules 的嵌套结构。您仍然可以通过在另一个 Store 中导入和使用来隐式嵌套 Store,但 Pinia 通过设计提供平面结构,同时仍然支持 Store 之间的交叉组合方式。您甚至可以拥有 Store 的循环依赖关系。
6. 没有命名空间模块。鉴于 Store 的扁平架构,“命名空间” Store 是其定义方式所固有的,您可以说所有 Store 都是命名空间的。
80.react里的钩子函数
哪个钩子可以操作dom
componentdidMount
哪里发送异步请求
componentdidMount
定时器在哪里卸载,持续性的事件监听
componentWillUnmount
子组件更新的时候会不会触发父组件的更新钩子?不会
父子组件嵌套执行顺序:
父:getDerivedStateFromProps render
子:getDerivedStateFromProops render componentDidMount
父:componentDidMount
// 16.4以前的钩子,16.4以后即将被废弃
componentWillMount
componentWillReceiveProps
componentWillUpdate
// 16.4x以后的钩子
// 新的
getDerivedStateFromProps
render
// 组件挂载完毕
componentDidMount
// 性能优化的钩子
shouldComponentUpdate
// 在更新之前,获取快照
// 新的
getSnapshotBeforeUpdate
// 更新之后
componentDidUpdate
// 卸载
componentWillUnmount
81.ts新增的类型
元组,枚举,空值void,any,never
82.常见的hook有哪些
useState,useEffect,useMemo,useCallback,useRef,forwardRef,useImperativeHandle,useContext
83.浏览器解析过程
1. 对输入的域名进行dns解析,再通过解析到的ip访问服务器
2. 访问服务器开始建立tcp连接
3. 三次握手 1.客户端发送一个 syn([synchronize](https://cn.bing.com/dict/search?q=synchronize&FORM=BDVSP2&qpvt=Synchronize+)) seq=1(sequence同步码) ack(acknowledgment确认码)=0发送给服务端 2.服务端接收到之后 返回seq=2 ack = seq+1 3.客户端收到之后 再次将ack = seq + 1返回给服务端
4. 服务端返回页面数据,以二进制数据流的方式发送回来
5. 客户端收到数据之后,浏览器引擎开始解析
6. 创建DOM tree(dom树)
7. 创建Style Rules(样式规则)
8. 构建Render tree(渲染树)
9. 布局Layout
10. 绘制Painting
11. 显示Display
所有浏览器的引擎工作流程都差不多,如上图大致分5步:创建DOM tree –> 创建Style Rules -> 构建Render tree -> 布局Layout –> 绘制Painting
84.正确地使用状态
1.不要直接更新状态
2.状态更新可能是异步的。因为 this.props 和 this.state 可能是异步更新的,你不应该依靠它们的值来计算下一个状态。
3.状态更新合并。React 可以将多个setState() 调用合并成一个调用来提高性能。
85.状态提升
将多个组件共享的状态提升至离他们最近的父组件这种操作,称之为状态提升
86.react组件复用
render props:
1.创建要复用的组件,在组件中提供状态和操作状态的方法
2.将要复用的状态作为props.render(state)的参数暴露出去
注意:提供的render函数必须有返回值,另外如果将jsx写为组件的子节点,则父组件可以用this.props.children的形式
高阶组件(HOC):
高阶组件是一个函数,在函数中有可复用的类组件,通过props将可复用的状态传给被包装组件
使用步骤:
1.创建一个函数,一般使用with开头便于区分
2.给函数传入一个参数,该参数为将要渲染的组件,因此命名应该以大写字母开头
3.在这个函数中创建一个类组件,提供需要复用的状态和操作状态的方法并返回
4.在该组件中渲染作为参数传入的组件,并通过展开运算符的方式将复用的状态作为参数组件的属性
5.调用该高阶组件函数并传入需要增强的组件,即可返回添加复用参数和方法后的组件
87.React中给元素绑定onClick后的工作原理
1. react会对事件先进行注册,将事件统一注册到document上
2. 根据组件唯一的标识key来对事件函数进行存储
3. 统一的指定dispatchEvent回调函数
4. 储存事件回调:react会将click这个事件统一存到一个对象中,回调函数的存储采用键值对(key/value)的方式存储在对象中,key 是组件的唯一标识 id,value 对应的就是事件的回调函数,通过组件的key就能回调到相应的函数了
defaultProps
如果父组件在调用子组件时,没有给子组件传值,子组件使用的就是defaultProps里定义的默认值。
用法:
- 在定义组件
Children之后,在export之前 组件名.defaultProps = 对象- 对象里
键为对应的要指定初始值的属性名,值就为要定义的默认值。
例子:
Parent
import React, {Component} from 'react'
import Children from './Children'
export default class Parent extends Component {
constructor(props) {
super(props)
this.state = {
name: '我是父组件',
msg: '父组件传值给子组件',
}
}
render() {
return (
<div>
<h2>{ this.state.name }</h2>
<h3>我要引入子组件了:</h3>
<hr/>
<Children />
</div>
)
}
}
Children
import React, {Component} from 'react'
class Children extends Component {
constructor(props) {
super(props)
this.state = {
name: '我是子组件',
msg: '子组件传值给父组件'
}
}
render() {
return (
<div>
<h2>{ this.state.name }</h2>
<h3>子组件的msg为:{ this.state.msg }</h3>
<h3>父组件传来的msg为:{ this.props.msg }</h3>
</div>
)
}
}
// defaultProps
Children.defaultProps = {
msg: '默认父组件传来的值'
}
export default Children
propTypes
用来验证父组件传值的合法性。
步骤:
- 父组件的state加一个
num: '123' - 父组件调用子组件时把值传过去
<Children num={ this.state.num } /> - 子组件中引入PropTypes
import propTypes from 'prop-types' - 在定义组件
Children之后,export之前加入:组件名.propTypes = { 属性名: propTypes.要求的类型 }如Children.propTypes = { num: propTypes.number }
例子: Parent
import React, {Component} from 'react'
import Children from './Children'
export default class Parent extends Component {
constructor(props) {
super(props)
this.state = {
name: '我是父组件',
msg: '父组件传值给子组件',
num: '123'
}
}
render() {
return (
<div>
<h2>{ this.state.name }</h2>
<h3>我要引入子组件了:</h3>
<hr/>
<Children num={ this.state.num } />
</div>
)
}
}
Children
import React, {Component} from 'react'
import propTypes from 'prop-types'
class Children extends Component {
constructor(props) {
super(props)
this.state = {
name: '我是子组件',
msg: '子组件传值给父组件'
}
}
render() {
return (
<div>
<h2>{this.state.name}</h2>
<h3>子组件的msg为:{this.state.msg}</h3>
<h3>父组件传来的msg为:{this.props.msg}</h3>
<h3>父组件传来的num为:{this.props.num}</h3>
</div>
)
}
}
// defaultProps
Children.defaultProps = {
msg: '默认父组件传来的值'
}
// PropTypes
Children.propTypes = {
num: propTypes.number
}
export default Children
88.BFC机制的理解
BFC即块级格式化上下文,它是页面中的渲染区域,并且有一套属于自己的渲染机制
-
内部的盒子会垂直方向一个一个放置
-
对于同一个BFC两个相邻盒子margin会重叠,与方向无关
-
BFC区域不会与浮动元素重合,早期用于做两栏布局
-
计算BFC元素高度时,浮动元素也会参与计算
-
容器中与容器外不会互相打扰
BFC元素的触发条件
-
根元素
-
浮动元素
-
overflow值不为visible
-
display:为table、flex等
-
定位的元素
应用场景
-
防止margin重叠,两个盒子分别触发BFC机制
-
清除浮动
-
清除高度塌陷
-
两栏布局
89.元素水平垂直居中的方法
- 定位然后都设置为0,margin:auto
- 定位top、left 50% 然后transform
- flex布局
90.CSS画三角形
- 宽高都设置为0
- 设置border-width
- 设置某个方向的border-color
91.为什么要使用hooks
- 类组件this指向很难获得,因为类在运行的时候会产生自己的实例,自己内部可以实现状态变化,于是很难维护
- 类组件在类的内部很难做到优化,
hooks使用限制
- 不要在循环、条件、嵌套函数中调用hook
- 不要在react函数组件之外调用hook
92.React的类组件与函数组件有什么区别
类组件和函数组件两者的设计模式不同
相较于类组件:函数组件更易于测试 如果在不适用Recompose或者hooks的情况下,如需使用生命周期,那么就使用类组件,类组件可以实现继承, 性能优化依靠shouldComponentUpdate函数去阻断渲染 函数组件依靠useMemo的方法
在使用方式上,没有什么区别,他们都能正确渲染出页面 但是他们的模型不一样
- 类组件是面向对象编程
- 函数组件是函数式编程 使用场景上
- 如果使用生命周期,使用继承就使用类组件
- 如果想要简单易用,那么就主推函数组件 未来趋势是hooks 在优化上
- 类组件使用shouldComponentUpdate阻断渲染
- 函数组件使用memo的方式缓存结果,优化渲染
93.react是如何实现传参的
第一种情况是父与子之间的通信
通过props由父组件向子组件传参,由子组件执行父组件传入的函数,实现状态提升从而达到子父传参
第二种情况是兄弟之间的通信
通过他们共同的父组件作为容器跳板,同时使用父子传参从而实现兄弟之间的传参
第三种情况是多层级的组件通信
从顶层向下层传递数据,比如路由的参数,全局共同的参数还有国际化语言包可以使用context Api中的 Provider和Consumer
如果是两个毫不相关的组件就可以使用状态管理框架,比如redux,flux,mobx
94.react-router路由
实现原理:基础原理 hash路由是依靠hash值变化网页不发生跳转的原理实现的,依靠hashchange这个api拿到当前的hash值,匹配是否正确,然后跳转到相应的页面 history:路由是依靠html5新增的一个api history Api 两个方法pushstate和replacestate实现跳转,popstate实现对页面路径的监听,实现路由跳转,使用这个api的时候,需要完成后端对historyApiFallback的配置
95.存储的方式和区别
- cookie默认在关闭浏览器时失效,但如果设置了失效时间,就按照时间,cookie会在发送请求的时候自动设置在请求头中,服务端也可以使用setCookie设置客户端的cookie
- sessionStoreage默认是在关闭浏览器的时候失效,与cookie相比并不能设置过期时间,也不能随着请求被发送到后端
- localStoreage默认是永久不失效,除非手动删除,一般大小为5M 也不能随着请求被发送到后端 还有一个是indexDB 可以满足存储的一切方法
96.vue如何封装组件
-
第一种是声明式的,需要引入组件,然后在components中注册,就算父子组件,这里需要注意的是子组件必须要考虑插槽、传参、复用、还有使用listener实现父组件监听子组件,常用的封装是对于ui组件的再封装,比如我觉得ui组件的样式不好看,但是我还是想要使用他的功能,那么就通过再封装的方式。 封装的组件示例 封装过提交组件,因为有时候页面中新增和修改用的是同一套弹窗,有可能没有图片,有可能图片,所以我对这个做了封装,只需要使用插槽将需要提交的文本框传入就好,交互就用props和emit 封装过点击修改文字的插件,就是在文字后面有个修改按钮,当点击的时候,文字自动切换为输入框,然后修改完成之后只需要点击确认就可以实现文字的修改,slot插槽传入文字,props和emit实现点击提交和点击修改
-
第二种是函数调用方式的,使用extends这个api,Vue.extends(组件)就会返回一个构造函数,new 之后返回一个实例,执行$mount可以手动挂载生成el真实的dom节点
封装的组件示例 封装全局登录的dialog,因为如果登录再跳转到首页的话有的东西不好保存,也影响体验,于是封装了一个全局登录的dialog,具体流程如下
- 首先按照往常,写vue的dialog,支持关闭,确定,取消,登录逻辑
- 封装dialog类,创建真实的dom节点作为容器,挂载在body下
- 使用Vue.extends执行生成构造函数,实例化并手动挂载,于是就有了组件的实例,在实例上找到el并append到容器中,通过操作属性就可实现关闭和开启
- 将关闭和开启的函数抽离出来并保存,再使用切片的方式执行传递进去的函数,然后再执行原本的开启关闭函数,这里需要注意this指向的问题
- 为了实现仿element-ui的promise写法,需要将方法返回promise,当点击取消或者取消就调用resolve和reject函数
class Dialog {
constructor() {
this.dialog = document.createElement('div')
document.body.appendChild(this.dialog)
this.initConfirm()
this.initToast()
}
initConfirm() {
const confirmComponent = Vue.extend(confirm)
this.$confirm = new confirmComponent()
this.$confirm.$mount()
this.dialog.appendChild(this.$confirm.$el)
}
initToast() {
const toastComponent = Vue.extend(toast)
this.$toast = new toastComponent()
this.$toast.$mount()
this.dialog.appendChild(this.$toast.$el)
}
confirm(options) {
// 打开弹窗
this.$confirm.$data['isShow'] = true
Object.keys(options).forEach((key) => {
if (!this.$confirm.$data[key]) return
this.$confirm.$data[key] = options[key]
})
return new Promise((resolve, reject) => {
// 返回一个promise 如果点击了取消就触发reject, 如果点击确定就触发resolve
// 使用切片编程
let offStartFn = this.$warn.offFn
let onStartFn = this.$warn.onFn
this.$warn.offFn = () => {
// 需要执行原本的内容
offStartFn.call(this.$warn)
reject()
}
this.$warn.onFn = () => {
onStartFn.call(this.$warn)
resolve()
}
})
}
success(text) {
// 成功的轻提示
this.$toast.setLi({ id: this.$toast.$data.num++, content: text, state: 'success' })
}
error(text) {
this.$toast.setLi({ id: this.$toast.$data.num++, content: text, state: 'error' })
}
warn(text) {
this.$toast.setLi({ id: this.$toast.$data.num++, content: text, state: 'warn' })
}
}
最后一步就是将实例挂载到vue的prototype上就可以实现
this.dialog.login(options).then()做一些操作了
97.Vuex的原理
原理其实和vue的双向绑定一样,当实例化的时候会将实例包装成watcher,给仓库的state添加getter和setter属性,于是访问的时候添加到dep中,更新的时候就拿出dep中所有的watcher执行update
98.http的三次握手和四次挥手
三次握手实际上是指建立tcp连接时,客户端和服务器发送三次请求,确认是否能连接正常
- 第一次握手:客户端向服务器发送连接报文,并向服务器发送指定的初始化序列号
- 第二次握手:服务器收到客户端的连接报文后,发送同意连接报文、返回自己的序列号、返回客户端的初始化序列号+1作为应答
- 第三次握手:客户点收到服务器的同意连接报文后,发送服务端的序列号+1表示开始连接
四次挥手就是终止并断开连接,为什么要四次呢,因为tcp有半关闭机制即关闭之后还会接收到内容
- 第一次挥手,客户端发送请求断开报文:FIN=1 SEQ=u,并停止发送数据,主动关闭于tcp的连接,进入终止等待状态
- 第二次挥手,服务器收到请求断开报文后,发送确认报文段:ACK=1,确认号ack=u+1,seq=v,表明已经收到断开连接的报文了
- 第三次挥手,服务器想要断开发送确认断开报文:FIN=1,ACK=1,序号ack = u + 1,服务器进入最后最后确认状态
- 第四次挥手,客户端发送确认关闭,然后自己断开监听,发送断开报文ACK=1,seq=u+1,ack=w+1,服务器收到后直接断开
为什么需要三次连接,两次不行吗 第一次握手,使服务器知道,客户端发送能力正常,服务器发送能力正常 第二次握手,使客户端知道服务器发送能力和接收能力正常,但是现在服务器还不知道客户端的接收能力是否是正常的 第三次握手,让服务器知道客户端发送是正常的 如果使用两次握手,那么服务器不知道客户端能否接收到自己的报文,直接开始发,发完了之后服务器问收到了吗?客户端???你倒是发啊
什么是半连接队列 第一次握手之后,客户端和服务器都没有建立连接,服务器会将此种状态下的请求连接放在一个队列中 全连接就是三次连接完成建立了连接 如果第三次握手丢失了,客户端会做如何处理 会根据tcp超时机制,第3,6,12秒重新发送包,如果重发次数后依然没有收到第三次握手,就自动关闭这个连接
为什么需要发送四次 因为第二次请求不一定发送完了,需要发送了一个确认收到,然后等内容发送完成之后再发送确认断开报文
99.弹性盒
是css3的一种布局模式,代替了浮动,由弹性容器(设置了display:flex的元素)和弹性子元素组成
有主轴(弹性子元素排列的方向)和侧轴
100.h5新特性
header,main,footer,aside,nav,mark,artical
101.vue的修饰符
- 表单修饰符 lazy,trim、number
- 事件修饰符 stop、prevent、self、once、capture、native
- 按键修饰符 left、right、keydown、enter 其他修饰符
- sync
forEach和map的区别
渲染时候污染了其它数据,怎么办?——进行深拷贝
项目优化——路由懒加载,组件懒加载,图片压缩,组件封装,keep-alive组件缓存
首屏加载过慢怎么办?
1 每次获取最新的 可见视口 + 滚动的高度 2 获取所有图片 3 遍历 4 判断:当前图片的.offsetTop < 步骤1(可见视口 + 滚动的高度) 不成立-不管 成立-修改图片的src地址 改成真是的
页面频繁切换——使用promise
开发流程——那个工作流程ppt
settimeout第二个值不写会怎么样?
js设计模型
弹性盒写”十“字——两个div盒子,一个盒子设置浮动,一个盒子压成横,一个盒子压成竖,形成十字
echart出现标签或者文字出现重叠,就使用avoidLabelOverlap:true(防止标签重叠)
如何让echarts根据窗口大小改变进行适配:1.侦听浏览器窗口发生大小改变的事件;2.调用echarts的resize方法:window.onresize = function(){barchart.resize()}
echarts如何适配?1.我们公司采用的适配方案是rem适配方案,主要使用的是lib-flexible和plugin-px2rem;2.原理就是动态的计算浏览器跟字体大小,使用rem来进行适配
102.axios封装
第一步,导入axios import axios from axios(下载axios) 第二步, 创建axios实例(配置axios:baseURL,timeout,是否携带数据) 第三步,请求拦截,响应拦截 第四步,默认导出request
103.清除浮动
clear:left(清除左浮动)/right(清除右浮动)/both(清除全部浮动)
104.table封装
- 通过
defineProps接收调用者传递过来的columns数组,columns数组里是一个个对象 对象里有label表头、prop数据字段、width列宽、align对齐方式(默认居中),接收到这个数据后通过v-for给el-table-column进行遍历,并且绑定好对应的表头和数据等等 defineProps接收tableData属性,这是表格的数据,绑定给table的:data上,并且接受一个控制表格加载动画开启和关闭的属性tableLoading- 每一列里面都设置了一个具名插槽,name名为columns里的
prop,使用者可以根据自己的需求自行选择每一列是否需要使用插槽,如果使用只需要让v-slot:具名xxx与columns里的props相同即可,组件内部的插槽位置会将表格数据的row(当前列的数据),通过插槽作用域:row="row"传递出去,使用者在定义插槽里的内容时,如果需要使用到这个row,可以给v-slot赋值,v-slot="{row}"解构传递出来的row数据 - 由于在每一列都用了插槽,el-table-column上的prop失去效果,无法将数据呈现在单元格上,所以也通过插值表达式专门定义了插槽的默认值,将columns里的prop当做k,
row[item.prop]即可拿到当前单元格的数据 - 如果
prop的字段是xxx[0].xxx这种格式的,row[item.prop]会匹配不到这种格式的数据,组件内部也进行了处理,通过正则判断到带有中括号[ ]这种字符,如果没有[]说明是普通的数据,直接用row[item.prop],如果有[ ]字符,就将拆开来的符合条件的字段挨个使用,并返回最终的值
105.dialog封装
- 通过
defineProps接收调用者传递过来的visible属性来控制dialog的显示和隐藏,组件内部通过watchEffect来监听传递过来的visible的值,监听到visible的变化后赋值给绑定在dialog上的v-model的属性 defineProps接收表单的数据来源tableData,通过:model绑定给dialog里面的form元素,tableData需要定义为一个响应式对象,对象里面有label和value,label控制表单元素的label描述,value为表单元素input输入框的值- form里通过
v-for遍历数据来源来确定表单元素的数量,并通过循环的每一项依次赋值给label和input上的v-model - 表单元素上设置
:rules来进行表单校验,它是一个对象,对象里填写required是否必填(true),trigger校验触发方式(blur失焦),和message校验不通过的提醒,并且需要绑定:prop作为判断的依据 - dialog有两个按钮 分别是取消和确认,通过vue的发布订阅机制 按钮点击后通过
defineEmits将不同按钮对应的自定义事件传递出去,从而让调用组件的地方能够通过这两个事件名的触发编写不同的逻辑,确定按钮事件为validateSuccess(回传一个form实例引用,点击按钮在表单校验通过后触发),取消按钮事件为dialogCancel,需要先在引入的组件标签上绑定自定义事件。 - dialog对话框关闭后也会有一个事件名
closed,调用者也可以通过这个事件编写dialog关闭之后对应的逻辑 - 在form的最上方预留了一个具名插槽
upload,使用者可以根据自己的业务需求自己添加其它组件 例如文件上传
106.bind、call、apply的区别
call是让函数执行,让函数的this指向call的第一个参数,然后将剩余的参数传入到函数中
apply也是让函数执行,然后this指向apply的第一个参数,不同的是传入的参数是以数组的方式
bind是返回一个被改变this的函数,传参方式和call相同
call的原理是什么
在传入的第一个参数上面定义一个函数,然后执行他
obj.$fn = this 这样this就能修改为obj了执行的时候将参数传入
107.什么是递归、应用场景是什么
递归就是自己调用自己,返回经过处理的内容 使用场景比如深拷贝、数据的树状类型转平面
108.typeof 和 instanceof的区别
typeof只能判断基本数据类型,如果遇到大多数引用数据类型就只能返回object instanceof用于检测实例是否由此构造函数创建,返回true / false 如果需要检测通用数据类型可以使用 Object.prototype.toString,调用此方法会统一返回 [object 类型] 封装函数就能识别数据类型
109.==和===的区别
双等于会有隐式转换的过程,内部会将类型转换成同一类型、然后再进行比较 双等于一般用于比较两者的布尔值、不太严谨 三等于一般是严格比较类型和值,一般都使用三等于
110.作用域和作用域链
全局作用域:就是全局的
函数作用域:函数执行时产生的作用域,这里需要注意的是函数在没有运行的时候是以字符串的方式保存在堆中的引用数据类型,执行之后才会产生函数作用域
块级作用域:定义let会产生块级作用域 作用域链:当我们需要访问变量的时候,如果本作用域中没有此变量,就会逐层向上查找,一直找到window,如果没有找到就会报not Fund的错误
111.vue2的生命周期钩子函数
beforeCreate:组件实例被创建之前使用,这时候是拿不到data的
created:组件实例被创建,这时候已经有了data,data中也支持双向绑定,可以对于data进行操作
beforeMount:组件被挂载之前调用,这时候是拿不到dom节点的
mounted:组件挂载完成,组件可以拿到真实的dom节点
beforeUpdate:更新dom节点之前执行
updated:dom节点更新之后
beforeDistory:组件实例销毁之前
distoryed:组件销毁之后,用于清除定时器
actived:keep-alive激活时
deactived:keep-alive组件被停用时
errorCaptured:子孙组件发生错误时,用于ui降级,不至于看到白屏
Vue3的生命周期
onBeforeMount:挂载之前
onMounted:组件挂载之后
onBeforeUpdate:更新之前
onUpdated:更新之后
onBeforeUnmount:卸载之前
onUnmounted:组件卸载之后
renderTracked:render被调用时触发,首次被调用是也触发
renderTriggerd:render被调用时触发,首次被调用时不会触发
112.Vue权限管理怎么做
权限管理分为
- 接口权限
- 按钮权限
- 菜单权限
- 路由权限 接口权限一般是使用上传token 的操作,就是在路由请求拦截中,传入token,为了安全我们还传入了时间戳和经过时间戳计算的token
time:xxxx,
token:xxxxx,
matchToken: token + time加密计算,可以实现动态计算,即使复制了token也是没有用的
按钮权限一般使用自定义全局指令做,使用时传入这个按钮所需要的权限,绑定的时候查看这个按钮权限够不够,够的话就保留,如果不够就删除 菜单权限一般是后端返回菜单的列表,然后前端通过动态计算获得 路由权限是 根据后端返回的菜单列表,在路由全局守卫里这么做
- 判断去的页面是否需要登录,如果不需要直接放行
- 需要登录,看看vuex中的登录状态
- true,登录状态
- false,没有登录去登录
- null,不知道有没有登录,就发送一次请求看看登录了没有
- 校验登录完成之后,后端会返回新的路由权限表,如果权限通过就直接通行,如果权限没通过就去登录 这里我们通过菜单绑定路由权限的方式,生成权限表的时候也动态计算了权限菜单,所以如果别的地方给你提高了权限,只需要刷新一下就能显示
113.Vue双向绑定原理
- 当state初始化的时候,会递归使用defineproperty为对象的属性都添加get和set属性,遇到数组就不适用defineproperty而是将数组的七个方法重写,使用切片编程的方式,先改变数组,然后调用render方法
- vue在取值之前会将自己包装成watcher,放在全局的Dep.target上,当取值的时候,属性会将watcher放在自己的dep上
- 当值发生改变的时候,调用dep上的更新方法,dep就通知所有的watcher执行更新操作,即render方法 vue3使用的是proxy + reflect方法,给对象添加代理,当然proxy也是不能监听深层次的内容的,还是需要递归
114.什么是vue
vue的核心以数据驱动的模型,即mvvm模型驱动数据,在以数据驱动模型 支持组件化思想,每个vue文件都可以看作一个组件,其优点是灵活高复用性,可以实现高内聚,低耦合,提高可维护性 使用模板template作为语法,引入很多诸如v-if、v-for、v-prev等指令,用于操作视图 vue是伪单向数据流,父子之间可以通过children获取到内容 vue是渐进式框架,官方对状态管理,路由等方面提供了一揽子解决方案,便于学习,如果项目中用到vue-router就用,不用也没关系
115.三个路由独享守卫
beforeRouteLeave:在失活组件离开时触发
beforeRouteUpdate:在重用组件
beforeRouteEnter:在进入对应组件创建之前调用
116.vue.$set是怎么实现的
为了实现给以前不存在的对象添加属性可以动态更新页面,对象只有在实例化的时候才会添加getter和setter属性,所以后添加的内容是检测不到的,
- Vue.set({},"name","val")
先判断对象是不是数组,而且是索引,那么就更改数组的长度,内部会调用splice方法 如果判断是对象,就手动赋值然后主动通知更新
117.websocket
websocket是html5的新协议,允许服务器向客户端主动发送信息,实现浏览器和客户端的双工通信 他的特点是
- websocket建立在tcp协议之上,端口号也是80和443不容易被屏蔽
- 性能开销小,通信高效
- 可以发送文本,也可以发送二进制数据
- 没有同源限制,客户端可以与任意服务器建立连接
- 协议标识符为ws,加密则为wws 说一下心跳机制 就是客户端每隔一段时间向服务器发送特定的心跳信息,每次服务器收到请求后只需要将消息返回此时如果二者保持连接,客户端一定会收到信息,如果没有保持连接,则说明连接被断开了,此时客户端重新发送连接请求 实现原理是在第一次连接的时候设置一个定时器,自动发送心跳信息,如果二者间保持了通讯,则重置定时器,重新计算时间,如果没有保持通讯就发送心跳信息,如果收到就重置定时器,如果没有收到就发起重新连接请求,如果还是没有收到,那么就认为是断开了连接
118.CSS选择器有哪些,优先级是什么
CSS选择器分为id选择器、类选择器、标签选择器、后代选择器、后代选择器、子选择器、相邻选择器 还有伪类选择器、属性选择器
优先级是 内联 > ID选择器 > 类选择器 > 标签选择器
119.CSS预编译语言
sass、less、stylus
120.mvc和mvvm区别
MVC是软件开发中常见的开发模式,主要应用于后端,将程序划分为M模型、V视图、C控制器从而便于团队协作开发,减少代码冗余
MVVM是Model-View-ViewModel缩写
相同点:都是软件开发常见的开发模式或者开发思想 不同点: 1- MVC后端居多,MVVM前端 2- MVC单向通信 目的将M和V代码分离,MVVM则是双向通信,不需要手动操作DOM
或
最初MVC最早出现在后端 M代表模型负责数据处理、V代表视图负责数据战士、C代表控制器负责调度 后来前端也有了MVC库,最早实现的就是backbone.js 但是V和M并没有很好的解耦 因此出现了MVVM模式, MVVM是Model-View-ViewModel缩写,也就是将MVC中的Controller演变成ViewModel Model层代表数据模型、 View层代表UI组件 ViewModel是Model、View层的桥梁,数据会绑定到ViewModel并自动将数据渲染到页面,视图变化会通知ViewModel层更新数据。
121.vue的单页和多页
单页:全称SPA单页面应用(SinglePage Web Application)。
单页应用将所有内容放在一个页面中,从而使整个页面更加流畅。就用户体验而言,单机导航可以定位锚点,快速定位相应的部分,并轻松上下滚动。单页面应用提供的信息和一些主要内容已经过筛选和控制,可以简单方便地阅读和浏览。
多页:全称MPA多页面应用(MultiPage Application)。
多页应用是指包含多个独立页面的应用,其中每个页面都必须重复加载JS,CSS等相关资源。多页应用在跳转时,需要刷新整页资源
区别:
1.刷新方式
SPA:相关组件切换,页面局部刷新或更改
MPA:整页刷新
2.路由模式
SPA:可以使用hash,也可以使用history
MPA:普通链接跳转
3.用户体验
SPA:页面片段间时间的切换快,用户体验良好,当初次加载文件过多时,需要做相关调优。
MPA:页面切换加载缓慢,流畅度不够,用户体验比较差,尤其网速慢的时候
4.转场动画
SPA:容易实现转场动画
MPA:无法实现转场动画
5.数据传递
SPA:容易实现数据传递,方法有很多(通过路由带参数传值,Vuex传值等等)
MPA:依赖url传参,cookie,本地存储
6.搜索引擎优化(SEO)
SPA:需要单独方案,实现较为困难,不利于SEO检索,可利用服务器端渲染(SSR)优化
MPA:实现方法容易
7.使用范围
SPA:高要求的体验度,追求界面流畅的应用
MPA:适用于追求高度支持搜索引擎的应用
8.开发成本
SPA:较高,长需要借助专业的框架
MPA:较低,但也页面代码重复的多
9.维护成本
SPA:相对容易
MPA:相对复杂
10.结构
SPA:一个主页面+许多模块的组件
MPA:许多完整的页面
11.资源文件
SPA:组件公用的资源只需要加载一次
MPA:每个页面都需要自己加载公用的资源
122.useEffect
useEffect就是指定一个副效应函数,组件每渲染一次,该函数就自动执行一次。组件首次在网页 DOM 加载后,
副效应函数也会执行。
参数:useEffect()有两个参数,第一个参数是要执行的函数,第二个参数是一个依赖项数组数组(根据需求第二个
参数可选是否填写),根据数组里的变量是否变化决定是否执行函数
第二个参数的用法:
1.若不写第二个参数,函数操作每次都会执行 useEffect(method)
2.若有第二个参数且数组里的变量不为空,则变量有变化时执行副作用操作,无变化则不执行. useEffect(()=>
{doSomeThing}, [a]), a 变化时执行(任意一个或全部变化)
3.有第二个参数但数组为空,则副作用仅在组件挂载和卸载时执行。useEffect( ()=>{doSomeThing}, [])
其他有依赖项数组的hook:useLayoutEffect、useCallback、useMemo
123.useEffect 和 useLayoutEffect
共同点:底层的函数签名都是一样的,都是用于处理副作用 不同点: useEffect适用于大部分场景,因为是异步调用的原因,可以处理一些异步的副作用 useLayoutEffect:适用于处理dom,调整样式,避免页面闪烁,因为他是在dom节点更新之前调用的
124.uniapp跳转方式
- uni.navigateTo: 【保留】当前页面,跳转到应用内的某个页面;
- uni.redirectTo: 【关闭】当前页面,跳转到应用内的某个页面;
- uni.reLaunch: 【关闭所有】页面,打开到应用内的某个页面。
- uni.switchTab: 跳转到tabBar页面,并关闭其他所有非tabBar页面。
- uni.navigateBack: 返回上一个页面,酷似vuerouter.to(-1),
- uni.preloadPage: 预加载页面,是一种性能优化技术。被预载的页面,在打开时速度更快。
125.大屏自适应
// 设计稿的宽度,这里我假设是1920px
var desginWidth = 1920;
var desginHeight = 920;
document.getElementById('scaleContainer').style.width = `${desginWidth}px`;
document.getElementById('scaleContainer').style.height = `${desginHeight}px`;
function setScale() {
// 这个是真实浏览器的宽度;手机上,电脑上,改变浏览器宽度都可以,就是最后浏览器展示内容的宽度
var trueWidth = window.innerWidth;
var trueHeight = window.innerHeight;
// 计算设计搞和真实展示宽度的,比例
var wScale = trueWidth / desginWidth;
var hScale = trueHeight / desginHeight;
var scale = Math.min(wScale, hScale);
document.getElementById('scaleContainer').style.transform = `scale(${scale}) translate(-50%, -50%)`;
}
window.addEventListener('resize', setScale);
setScale();
- 视口单位中的“视口”,PC端指的是浏览器的可视区域。
- vw:1vw 等于视口宽度的 1%。若电脑屏幕分辨率为 1920px,则分成了100份 vw, 每个 vw 的 px 值 为 19.2px;
- vh:1vh等于视口高度的1%。若电脑屏幕分辨率为 1080px,则分成了100份 vh, 每个 vh 的 px 值 为 10.8px;
- 这样我们就可以通过设计稿上面的 px 值得到 vw, vh 的值。如果设计稿上的一个 div 高为 360px, 宽为 120px, 则我们可以在 样式中写为如下
div{
width: 360/19.2vw; // 计算得出具体值,保留两位小数
height: 120/10.8vh; // 计算得出具体值,保留两位小数
}
promise的链式调用,为什么可以一直.then,.then下去-----每次访问返回的都是一个新的promise
126.useMemo和useCallback的用法,怎么做性能优化的
useMemo:
相当于是vue里面的计算属性
useMemo(fn,dependencies)
当成shouldComponentUpdate
可用于性能优化,接收2个参数,根据后面的依赖值是否变化执行函数
useCallback:
useCallback 传入一个函数,第二个参数是一个数组,里面是依赖值
useMemo 用于性能优化 处理正常的业务罗的时候,可以使用useMemo 第二个参数是一个依赖值,只有依赖值发生了变化,那么useMemo中的函数才会执行 避免不必要的更更新
useCallback的返回值是一个缓存的函数,如果依赖值没有发生改变,那么useCallback返回的函数的同一个,只有当依赖值发生变化的时候,返回的函数才会发生变化
127.小程序视图层
WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。
WXML不支持传统的html标签,应该使用weixin提供的组件比如 view text block..
WXSS(WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。
与 CSS 相比,WXSS 扩展的特性有:rpx(responsive pixel): 可以根据屏幕宽度进行自适应。
128.小程序生命周期
- onload 监听页面加载 只会执行一次
- onshow 监听页面显示 能够执行多次
- onready 监听页面渲染完成 只会执行一次
- onhide 监听页面隐藏 可执行多次
- onUnload 监听页面卸载 执行一次
129.小程序选择器
目前支持的选择器有:
| 选择器 | 样例 | 样例描述 |
|---|---|---|
| .class | .intro | 选择所有拥有 class="intro" 的组件 |
| #id | #firstname | 选择拥有 id="firstname" 的组件 |
| element, element | view, checkbox | 选择所有文档的 view 组件和所有的 checkbox 组件 |
| element | view | 选择所有 view 组件 |
| ::after | view::after | 在 view 组件后边插入内容 |
| ::before | view::before | 在 view 组件前边插入内容 |
在微信小程序中,需要使用setData的方式来修改data中的数据,否则视图层是不会响应的
130.小程序事件
使用以下事件的时候需要在事件名称前面加上bind 比如:bindtap
| 类型 | 触发条件 | 最低版本 |
|---|---|---|
| touchstart | 手指触摸动作开始 | |
| touchmove | 手指触摸后移动 | |
| touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 | |
| touchend | 手指触摸动作结束 | |
| tap | 手指触摸后马上离开 | |
| longpress | 手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发 | 1.5.0 |
| longtap | 手指触摸后,超过350ms再离开(推荐使用longpress事件代替) | |
| transitionend | 会在 WXSS transition 或 wx.createAnimation 动画结束后触发 | |
| animationstart | 会在一个 WXSS animation 动画开始时触发 | |
| animationiteration | 会在一个 WXSS animation 一次迭代结束时触发 | |
| animationend | 会在一个 WXSS animation 动画完成时触发 | |
| touchforcechange | 在支持 3D Touch 的 iPhone 设备,重按时会触发 | 1.9.90 |
131.什么是事件
- 事件是视图层到逻辑层的通讯方式。
- 事件可以将用户的行为反馈到逻辑层进行处理。
- 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。
- 事件对象可以携带额外信息,如 id, dataset, touches。
事件的使用方式:
在组件中绑定一个事件处理函数。
在相应的Page定义中写上相应的事件处理函数,参数是event。
132.小程序编程式导航
- wx.navigateTo(Object object)
参数
Object object
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|---|---|---|---|---|
| url | string | 是 | 需要跳转的应用内非 tabBar 的页面的路径, 路径后可以带参数。参数与路径之间使用 ? 分隔,参数键与参数值用 = 相连,不同参数用 & 分隔;如 'path?key=value&key3=value3' | |
| events | Object | 否 | 页面间通信接口,用于监听被打开页面发送到当前页面的数据。基础库 3.7.3 开始支持。 | |
| success | function | 否 | 接口调用成功的回调函数 | |
| fail | function | 否 | 接口调用失败的回调函数 | |
| complete | function | 否 | 接口调用结束的回调函数(调用成功、失败都会执行) |
- wx.redirectTo(Object object)
- wx.navigateBack(Object object)
| 属性 | 类型 | 默认值 | 必填 | 说明 |
|---|---|---|---|---|
| delta | number | 1 | 否 | 返回的页面数,如果 delta 大于现有页面数,则返回到首页。 |
| success | function | 否 | 接口调用成功的回调函数 |
- wx.switchTab(Object object)
- wx.reLaunch(Object object)
133.小程序子传父的流程
- 首先在组件的标签上,自定义一个事件名,比如toparent,书写的时候需要加上bind bindtoparent="事件处理函数"
- 事件处理函数需要写到父组件的方法中,并且接收一个e
- 子组件中,通过事件触发自定义事件方法 this.triggerEvent("自定义事件名",'传递的参数')
- 在父组件(页面)中,通过处理函数的e.detail来接收子组件传递的参数
134.小程序兄弟组件传参的流程
兄弟组件传参需要通过父组件来完成,比如我们现在有3个组件,分别是parent,child,child2
目的:将child2中的msg传递给child
流程:通过子传父的方式 child2=>parent 再由父传子过程 parent=>child
135.微信登入流程
- 调用wx.login获取code
- 发送请求,请求开发服务器,将code传递给开发服务器
- 获取开发服务器返回的结果,保存在storage即可