前端面试总结(自用)
(总结收集来源于网络)
数据类型
- 基础数据类型
- number:整数和小数
- string:文本
- boolean:表示真伪的两个特殊值
- null:表示空值
- undefined:表示未定义或不存在
- ES6新增:symbol和bigInt
- 复杂数据类型
- Object
- Function
- Array
输入网址后的过程
- 解析域名(DNS域名解析)
- 浏览器DNS缓存
- 计算机DNS缓存
- 路由器DNS缓存
- 网络运营商DNS缓存
- 递归查询
- 建立TCP连接(TCP三次握手)
- 客户端发送服务器,告知准备好了,请确认
- 服务器发送客户端,告知也准备好了,请确认
- 客户端发送服务器,确认完毕
- 发请求到服务器(发送请求报文)
- 服务器响应返回(返回响应报文)
- 浏览器解析渲染页面
- 遇到HTML,调用HTML解析器,解析成DOM树
- 遇到css,调用css解析器,解析成CSSOM树
- 遇到JS,调用JS解析器,解析JS代码
- 可能要修改的元素节点,重新调用HTML解析器,解析成新的DOM树
- 可能要修改样式,重新调用css解析器,解析成新的CSSOM树
- 断开TCP连接(TCP四次挥手)
- 客户端发送服务器,告知请求发送完毕,可以断开了
- 服务器发送客户端,告知请求接收完毕
- 服务器发送客户端,告知响应发送完毕,可以断开了
- 客户端发送服务器,告知响应接收完毕
检测数据类型
- typeof
- 注意:
- 使用typeof检测一个未声明的值,不会报错,而是返回undefined
- 检测Array返回Object,因为Array是特殊的对象
- 检测null返回Object
- 注意:
- instanceof
- Object.prototype.tostring
原型
- 每个函数都有一个显式原型属性:prototype
- 每个实例都有一个隐式原型属性:.proto
- 实例的__proto__与对应函数的prototype都指向原型对象
- 原型对象上有一个constructor属性指向对应的构造函数
如何区分构造函数和普通函数
- 通过是否调用new来区分一个函数是不是构造函数 ps:
- 无论是函数名还是变量名,统称为标识
- 构造函数具有显式原型属性和对应的原型对象。那么,普通函数也有显式原型属性和对应的原型对象
new关键字做了什么?
- 创建一个空对象,如果有实参,就将实参传给形参
- 该对象的隐式原型属性会指向构造函数的原型对象
this.__proto__ = Person.prototype
- 声明了this,这个新对象会绑定到函数调用的this
- 会默认返回当前的实例对象this
- 如果返回值不是对象数据类型,会自动返回this
- 如果返回值是对象数据类型,会返回该对象,不会返回this
原型链
- 从对象的_proto_开始,连接的所有对象,就是原型链,也叫隐式原型链
- 查找对象属性,先找自身,找不到就沿着原型链查找,还找不到就返回undefined
查找对象属性的基本流程
- 现在对象自身上找,如果有,直接返回
- 如果没有,根据__proto__在原型对象上找,找到就返回
- 还是没有就一直沿着原型链找,直到找到为止
- 找到了就返回,找到最后__proto__为null时,返回undefined
面试题——表达式a.b的解析流程
- 先在作用域链查看a
- 不存在:报错
- 存在:得到a的值
- 基本类型
- null/undefined => 报错
- number/string/boolean => 创建一个此值的包装对象
- 地址值 => 解析b => 查找b属性
- 先找自身,找到返回,如果没找到
- 原型链查找
- 找到返回
- 没找到返回undefined
- 基本类型
理解:A是不是B创建的?(A instanceof B 的原理)
实际上就是递归查找B的原型对象到底有没有出现在A的原型链上
A.__proto__ === B.prototype
A.__proto__.__proto__ === B.prototype
终极原型链
- 万物皆对象(除基本数据类型)
- 所有的对象都是Object的实例对象(直接或间接)
- 所有的函数都是Function的实例对象(包括Function自身)
- 函数时特殊的对象
注意:
1.到底是函数创建了对象,还是对象创建了函数? (其实JS底层使用C语言写了三个东西)
-
Object
-
Function
-
Function.prototype 2.在JS终极原型链中,有四条是作者自己加上去的
-
Function的隐式原型属性和Function的显式原型属性指向同一个原型对象
- 由于Function自己本身就是一个函数,所以它也要能使用所有函数共享的方法
-
Object的隐式原型属性和Function的显式原型属性指向同一个原型对象
-
Function的原型对象的隐式原型属性和Object的显式原型属性指向相同
- 因为在JS设计的时候,Function就是一个特殊的对象,那么说明Object的原型对象要出现在Function的原型链上
-
Object的原型对象的隐式原型属性为null
- 因为所有的对象都是Object的实例对象,会导致Object的原型对象的隐式原型指向自己,产生死循环
作用域(就是变量的作用范围,某块范围之内,所有具有的变量)
- 全局作用域(程序的最外层作用域,一直存在)
- 函数作用域(函数定义时创建)
- 块级作用域(ES6新增):const/let ({}内的就是)
预解析
- 变量提升 -> 只提升声明部分,赋值语句还会保留在原地(在变量定义语句之前,就可以访问到这个变量)
- 函数提升 -> 整体提升(在函数定义语句之前,就执行该函数)
- 函数表达式 -> 在JS引擎眼里,他就是变量提升(只提升声明部分) 注意:提升至当前作用域的最前面
作用域确认时间:函数声明的时候
扩展:
- 在编程界,具有动态作用域和词法作用域(静态作用域),但是JS中只有静态作用域
- 函数表达式,本质是变量声明,所以预解析环节,函数表达式只会把变量提升
作用域链
- 多个嵌套的作用域形成的由内向外的结构,用于查找变量
闭包
- 什么是闭包?
- 闭包是一个对象(js容器),内部存放要使用到的变量,闭包就是意外存活的变量对象
- 如何产生闭包?
- 函数嵌套函数,内部函数使用到外部函数变量的时候,会产生闭包
- 闭包的原理
- 闭包的实现原理,其实是利用了作用域链的特性,我们都知道作用域链就是在当前执行环境下访问某个变量时,如果不存在就一直向外层寻找,最终寻找到最外层也就是全局作用域,这样就形成了一个链条。
- 闭包的形式
- 闭包是一个函数,而且存在于另一个函数当中
- 闭包可以访问到父级函数的变量,且该变量不会销毁
- 闭包是什么时候产生的?
- 外部函数被调用的时候
- 内部函数声明的时候
- 闭包的好处
- 延长局部变量的生命周期
- 模块化(减少全局的污染)
- 闭包的坏处
- 由于缓存下来的变量数据还会占用内存,所以会导致内存泄露,如果内存泄露过多,导致内存溢出,程序会宕机
- 如何解决闭包的副作用
- 由于垃圾回收机制是根据是否还有被引用来决定是否回收内存的,而闭包不会销毁的原因,是因为有函数正在依赖它,而函数不会被销毁的原因,是因为有变量正在指向它。所以,我们只需将指向该函数的所有标识(函数名和变量名)全部移除,没人在使用它,那么函数就会被销毁,闭包也会被销毁
- 闭包的应用
-
需求:实现a的自增
- 通过全局变量,可以实现,但会污染其他程序
var a = 10; function Add(){ a++; console.log(a); } Add(); Add(); Add();- 定义一个局部变量,不污染全局,但实现不了递增
var a = 10; function Add2(){ var a = 10; a++; console.log(a); } Add2(); Add2(); Add2(); console.log(a)- 通过闭包,可以实现函数内部局部变量递增,不会影响全部变量
var a = 10; function Add3(){ var a = 10; return function(){ a++; return a; }; }; var cc = Add3(); console.log(cc()); console.log(cc()); console.log(cc()); console.log(a);
-
执行上下文
- 每次函数调用的时候,会在内存中开辟的一块空间
- 组成部分
- 运行当前函数所需要的内存
- 变量对象(用于收集当前函数中声明的所有变量)
- 创建时间:函数调用
- 销毁时间:函数执行结束
this
this 就是一个指针,指向调用函数的对象
常见的this指向
- 普通调用 A() -> this指向window
- 构造调用 new A() -> this指向当前函数创建的实例对象
- 隐式调用 obj.A() -> this指向前面的实例对象
- 显式调用 A.call(obj) -> this指向call中的第一个实参
- 箭头函数 -> this指向外层作用域的this
- 事件回调函数 -> this指向事件源
- 定时器的回调函数 -> this指向window
- vue组件中method函数 -> this指向vue组件实例对象
call和apply的区别
- 相同点
- 改变当前借调函数的this指向(仅当前这次)
- 都会同步指向借调函数
- 第一个参数都是用于修改当前本次执行的this,如果传入null或undefined,就会指向window
- 不同点
传递参数不同
- A.call(obj,a,b,c) -> 传入实参以逗号隔开,数量从0到无限
- A.apply(obj,[a,b,c]) -> 传入实参以数组存储,数量为0到2
手写call()
- 不使用ES6语法
Function.prototype.myCall = function(obj){
//判断,当传入参数为null或undefined时,this指向window
if(obj === null||obj === undefined ){
obj = window;
}
var args = []; //获取传入的参数
for(var i = 1;i<arguments.length;i++){
args.push(arguments[i]);
}
obj._b = this; //改变this指向
var res = eval("obj._b("+args.toString()+")");
delete obj._b; //改完指向就删除
return res;
}
- 使用ES6语法
Function.prototype.myCall = function(Obj,...args){
//判断,当传入参数为null或undefined时,this指向window
if(Obj === null||Obj === undefined ){
Obj = window;
}
Obj._b = this; //改变this指向
var res = Obj._b(...args);
delete Obj.-b;
return res;
手写bind()
Function.prototype.myBind = function (obj,...args){
//判断,当传入参数为null或undefined时,this指向window
if(obj === null||obj === undefined ){
obj = window;
}
return (...args) => {
this.apply(obj,[...args,...args1])
}
}
Promise
- ES6推出的新的更好的异步编程解决方案
- 可以在异步操作启动后或完成后,再指定回调函数得到异步结果数据
- 解决嵌套回调的回调地狱问题——promise的链式调用
- promise对象的三种状态
- pending -> 初始状态
- resolved/fulfilled -> 成功的状态
- rejected -> 失败的状态
- promise状态的两种变化(变化是不可逆的)
- pending -> resolved
- pending -> rejected
注意:
- Promise是一个构造函数,用于创建promise实例对象(初始化状态时pending)
- Promise构造函数必须接受一个实参,也就是执行器函数,该执行器函数会被同步执行
- 执行器函数接收2个实参
- resolve -> 可以将当前的promise状态改为成功
- reject -> 可以将当前的promise状态改为失败
- 执行器函数接收2个实参
promise.then
- 该函数最多接受2个实参,数据类型:函数
- 如果前面的promise的状态变为成功,触发第一个回调
- 如果前面的promise的状态变为失败,触发第二个回调
- 总是返回一个新的promise
- 新的promise的结果有then指定的回调函数指向的结果决定
- 抛出错误
- 返回成功的promise
- 返回失败的promise 扩展:
- 如何让.than的回调函数返回成功
- .than的回调函数中返回值不是一个promise对象
- .than返回成功的promise
- 如何让.than的回调函数返回失败
- .than的回调函数返回一个失败的promise对象
- 报错
promise.all([promise1,promise2,promise3])
- 批量/一次性发生多个异步请求
- 全部成功,返回的promise才为成功
- 只要有一个失败,返回的promse就失败
promise.race([promise1,promise2,promise3])
- 一次性发送多个异步请求,谁先回来用谁
async/await
- async/await和promise的关系
- async/await是消灭异步回调的终极武器
- 作用:简化promise对象的使用,不用在使用then/catch来指定回调函数
- 和promise并不排斥,反而相辅相成
- 执行async函数,返回promise对象
- await相当于promise的then
- try...catch可捕获异常,相当于promise的catch
- await之后的代码都会被放在promise的then的成功回调中,一定会返回一个全新的promise对象
- 如何控制该promise的状态变化
- 变为成功:当前函数中所有代码都执行结束,如果当前函数返回非promise对象的数据,会作为async函数返回promise的结果
- 变为失败:当前函数执行过程中报错 注意:如果async函数中出现错误,后续代码将不会继续执行
缓存
cookie
- 服务器发送到浏览器的一小块数据,浏览器会进行存储,并与下一个请求一起发送到服务器
- 可以设置有效时长,没设置的话,会话关闭时失效
- 存储大小4kb左右
- 自动携带在请求头中,过多使用cookie来保存数据会导致性能问题
- 主要用途
- 会话管理:登陆,购物车,游戏得分或者服务器应该记住的其他内容
- 个性化:用户偏好,主题或其他设置
- 追踪:记录和分析用户行为
- 使用方式
- 保存cookie的值
var dataCookie = '110' document.cookie = 'token'+'='+dataCookie- 获取指定的cookie
function getCookie(name) { let result = document.cookie.match("(^|[^;]+)\\s*" + name + "\\s*=\\s*([^;]+)") return result ? result.pop() : "" }
session
- 客户端请求服务端,服务端会为这次请求开辟一块内存空间,这个对象便是session对象
- 服务器可以利用session存储客户端在同一个会话期间的一些操作记录
- 仅保存当前会话,会话关闭则失效
localStorage和sessionStorage
- 区别
- localStorage:本地缓存,没有时间限制,将一直缓存在本地
- sessionStorage:会话缓存,即浏览器关闭时清楚缓存数据
- 容量:存储大小5mb左右
- 使用方法
- 设置缓存数据:localStorage.setItem(key,value)
- 获取缓存数据:localStorage.getItem(key)
- 获取全部:localStorage.valueOf()
- 获取指定下标的key键值:localStorage.key(n)
- 删除缓存数据:localStorage.removeItem(key)
- 清空全部:localStorage.clear()
同步和异步
- 同步:前一个任务结束后执行后一个任务
- 异步:做一个任务的同时还能做其他任务
- 同步任务都在主线程上执行,形成一个执行栈
- 异步任务是通过回调函数实现的
数组的方法
- push() :向数组的末尾添加一项或多项,返回更新后数组的长度
- pop() :删除数组的最后一项,返回被删除的项
- unshift() :向数组开头添加一项或多项,返回更新后数组的长度
- shift() :删除数组第一项,返回被删除的项
- splice() : 删除,插入,替换
- 参数1:索引值
- 参数2:删除数量
- 参数3:替换值
- reverse() :翻转数组
- sort() :数组排序
- concat() :合并数组
- slice() :复制创建新数组
- 参数1:开始的索引值
- 参数2:结束的索引值
- indexof() :查找元素在数组的索引值,找到返回索引值,否则返回-1
- lastindexof() :倒着查找元素在数组的索引值,找到返回索引值,否则返回-1
H5的新特性
- 语义化标签
- header
- footer
- section
- nav
- aside
- article
- input新属性
- color:颜色选择器
- number:数字
- Email:邮件格式
- Date:日期
- 音视频
- video
- audio
- canvas画布
- getCurrentPostion():获取用户地理位置
- 选择器
- first-Child
- last-Child
- nth-Child
- 伪类
- a:link
- a:visited
- a:hover
- a:active
- border-radius圆角
- box-shadow阴影
- websocket
- ::before ::after
- flex
- transition动画
CSS3的新特性
- 选择器
- E:first-of-type: 选择属于其父元素的首个E元素的每个E元素
- E:last-of-type: 选择属于其父元素的最后E元素的每个E元素
- E:only-of-type: 选择属于其父元素唯一的E元素的每个E元素
- E:only-child: 选择属于其父元素的唯一子元素的每个E元素
- E:nth-child(n): 选择属于其父元素的第n个子元素的每个E元素
- E:nth-last-child(n): 选择属于其父元素的倒数第n个子元素的每个E元素
- E:nth-of-type(n): 选择属于其父元素第n个E元素的每个E元素
- E:nth-last-of-type(n): 选择属于其父元素倒数第n个E元素的每个E元素
- E:last-child: 选择属于其父元素最后一个子元素每个E元素
- 动画
- transition
- Animation
- transform
- 边框
- border-radius
- border-shadow
- border-image
- 背景
- background-clip
- background-origin
- background-size
- background-break
- 文字效果
- word-wrap :允许文本强制文本进行换行
- text-overflow :设置或检索当当前行超过指定容器的边界时如何显示
- clip
- ellipsis
- text-shadow:可向文本应用阴影
- 用户界面
- box-sizing
- content-box:
- padding和border不被包含在定义的width和height之内
- border-box:
- padding和border被包含在定义的width和height之内。对象的实际宽度就等于设置的width值
- content-box:
- box-sizing
文本溢出隐藏
- 单行文本溢出隐藏
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
- 多行文本溢出隐藏
overflow:hidden;
text-overflow:ellipsis;
display:-webkit-box;
-webkit-line-clamp:2;
-webkit-box-orient:vertical;
ES6的新特性
const/let
- const定义常量
- let定义变量
- 与var的区别(var有变量提升,有初始化提升,值可变)
- 有块作用域
- 没有变量提升
- 不会添加到window上
- 不能重复声明
ps:暂时性死区问题说明:其实const/let是有变量提升的,但没有初始化提升
默认参数
- 原来写法
```js
function fn(name,age){
var name = name || 'lihua'
}
```
- ES6
```js
function fn(name='lihua',age = 18){}
```
扩展运算符...
- 原来拼接数组
arr1.concat(arr2) - ES7
[...arr1,...arr2]
剩余参数
function fn(name,...params){
console.log(name)
console.log(params)
}
fn('lihua',1,2,3,4) // lihua [1,2,3,4]
模版字符串
- 原来写法
```js
console.log(name+'今年'+age+'岁啦')
```
- ES6
```js
console.log(`${name}今年${age}岁啦`)
```
Object.keys
- 可以用来获取对象的key的集合,进而可以获取对应key的value
const Obj = {
name:'lihua',
age:22,
gender:'男'
}
console.log(Object.keys(Obj)) //['name','age','gender']
箭头函数
- 与普通函数的区别
- 箭头函数不可作为构造函数,不能使用new
- 箭头函数没有自己的this
- 箭头函数没有arguments对象
- 箭头函数没有原型对象
解构赋值
- 解构对象
const {name,age,gender} = obj - 解构数组
const [a,b,c] = arr - 解构重名
const {name:newname} = obj - 嵌套解构
const {doing:{evening}} = obj - 默认赋值
const {name,age,gender} = obj - 乱序解构
const [a,b,c=1] = arr - 形参解构
add({id,title}){} - 引入模块解构
import {getList} from '@/api'
ES6一些数组的方法
- Array.reduce
- 对数组元素从左到右依次执行reducer函数,返回一个累计的值
- 第一个参数是callback函数:pre为上次return的值,next为本次数组遍历的项
- 第二个参数为初始值,也就是第一个pre
//计算1+2+3+4+5的值
const arr = [1,2,3,4,5];
const sum = arr.reduce((pre,next)=>{
return pre+next;
},0) // 15
//统计元素出现的次数
const arr = ['我','我','你','我','他','你'];
const obj = arr.reduce((pre,next)=>{
if(pre[next]){
pre[next]++
}else{
pre[next] = 1;
}
return pre
},{})
- Array.forEach
- forEach方法按升序为数组中含有效值的每一项执行一次callback函数
- 总是返回undefined值,并且不可链式调用
- forEach的参数
arr.forEach((self,index,arr) =>{},this)**self:** 数组当前遍历的元素,默认从左往右依次获取数组元素\ **index:** 数组当前元素的索引,第一个元素索引为0,依次类推\ **arr:** 当前遍历的数组\ **this:** 回调函数中this指向
const arr = [1,2,3,4,5]
arr.forEach((item,index,arr)=>{
console.log(item,index,arr)
})
-
Array.map
- 常用于返回一个处理过后的新数组
- 按照原始数组元素顺序依次处理元素
- 不会对空数组进行检测
- 不会改变原始数组
const arr = [1,2,3,4,5]; console.log(arr.map((num,index,arr)=>2*num)) //[2,4,6,8,10] -
Array.filter - 用来过滤数组
const arr = [1,2,3,4,5]; const arr2 = arr.filter((num,index,arr)=> num >3) //[4,5] -
Array.some
- 一真即真
const arr = [1,2,3,4,5];
console.log(arr.some(val=>val = 5)) // true
- Array.every - 一假即假
const arr = [1,2,3,4,5];
console.log(arr.every(val=>val = 5)) //false
对象属性同名简写
- 原来写法
const name = 'lihua';
const age = 18;
const obj = {
name:name,
age:age
}
- ES6
const name = 'lihua';
const age = 18;
const obj = {
name,
age
}
find和findIndex
- find:找到返回被找元素,找不到返回undefined
- findIndex:找到返回被找元素索引,找不到返回-1
for of和for in
- for in:遍历方法,可遍历对象和数组
const obj = {name:'lihua',age:18,sex:'man'};
const arr = [1,2,3,4,5];
//for in 遍历对象
for(let key in obj){
console.log(key) // name age sex
}
//for in 遍历数组
for(let index in arr){
console.log(index) //0,1,2,3,4
}
- for of:遍历方法,可遍历数组,不能遍历对象
const arr = [1,2,3,4,5];
for(let item of arr){
console.log(item) //1,2,3,4,5
}
Promise
async函数
await表达式
类语法class
- 本质上也是function,class是function的语法糖
class Person{
constructor(name){
this.name = name;
}
sayName(){
console.log(this.name)
}
}
const lihua = new Person('李华')
lihua.sayName() // 李华
- static定义的属性和方法只能class自己使用,实例无法使用
- extend继承
class Animal{
constructor(name,age){
this.name = name;
this.age = age;
}
}
class Cat extends Animal{
say(){
console.log(this.name,this.age);
}
}
const cat = new Cat('大橘',5)
cat.say() // 大橘 5
新容器语法
Set
-
Set对象允许你存储任何类型的值,无论是原始值或者是对象引用。它类似于数组,但是成员的值都是唯一的,没有重复的值
-
Set本身是一个构造函数,用来生成Set数据结构。Set函数可以接受一个数组(或者具有 iterable 接口的其他数据结构)作为参数,用来初始化。
-
Set实例对象的属性
- size:返回Set实例的成员总数
var q = new Set([1,2,3,4,5]) console.log(q.size) // 5 -
Set实例对象的方法
- add:添加某个值,返回 Set 结构本身(可以链式调用)
//不传数组 const set1 = new Set(); set1.add(1); set1.add(2); console.log(set1) // Set(2) 展开 {1,2} //传数组 const set2 = new Set([1,2,3]); set2.add(4); set2.add('qwe'); console.log(set2) //Set(5) 展开 {1,2,3,4,'qwe'}- delete:删除某个值,删除成功返回true,否则返回false
set2.delete(2); console.log(set2)//Set(4) 展开 {1,3,4,'qwe'}- has:返回一个布尔值,表示该值是否为Set的成员
console.log(set2.has(3) //true- clear:清除所有成员,没有返回值
set2.clear() console.log(set2) //Set(0) -
利用Set的不重复性,实现去重
const arr1 = [1,1,2,2,3,3]; const arr2 = [...new Set(arr1)] //[1,2,3]
Map
- 对比Object最大的好处就是key不受类型限制
ES7
- includes:传入元素,如果数组中能找到,则返回true,否则返回false
const arr = [1,2,NaN];
console.log(arr.indexOf(NaN) // -1 找不到NaN
console.log(arr.includes(NaN) //true
ES8
- Object.values:获取对象的value集合
const obj = {name:'lihua',age:22}
const value = Object.values(obj) // ['lihua',22]
- Object.entries:获取对象的键值对集合
const values2 = Object.entries(obj) // [['name','lihua'],['age',22]]
AJAX
- 是一种用于创建快速动态网页的技术
- 可以使网页实现异步更新,无需加载整个网页,对某部分进行更新
- ajax请求与一般HTTP请求的区别
- ajax请求是一种特别的http请求
- 对服务端来说,没有区别,区别在浏览器端
- 只有xhr和fetch发出的才是ajax请求
- 浏览器接收请求
- 一般请求:浏览器一般会直接显示响应体数据,也就是刷新/跳转页面
- ajax请求:浏览器不会对界面进行任何更新操作,只是调用监视的回调函数并传入响应相关数据
- 优点
- 页面无需刷新
- 异步通信,更快的响应能力
- 缺点
- 不能后退
- 不能用url访问
- 存在安全问题
- 破坏程序的异常机制
- 封装一个简易的ajax异步请求函数
function ajax(url){
return new Promise((resolve,reject)=>{
const xhr = new XMLHttpRequest()//创建一个xhr对象
xhr.open('GET',url,true) //初始化异步请求
xhr.send(null)//发送请求
//处理响应
xhr.onReadystatechange = function(){
if(xhr.readystate !==4){ //响应值不为4,直接结束
return
)
if(xhr.status>=200&&xhr.status<300){
resolve(JSON.parse(xhr.responseText))
}else{
reject(new Error('request error status'+request.status))
}
}
})
}
AJAX面试题
- 什么是ajax?作用是什么?
- AJAX = 异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器 进行少量数据交换,AJAX可以使网页实现异步更新
- 为什么要用ajax?
- AJAX应用程序的优势在于
- 通过异步模式,提升了用户体验
- 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用
- AJAX引擎在客户端运行,承担一部分本来由服务器承担的工作,从而减少了大用户量下的服务器负载
- AJAX应用程序的优势在于
- ajax的特点
- AJAX可以实现动态不刷新(局部刷新)
- 就是在不更新整个页面的前提下维护数据。这使得web应用程序更为迅捷地回应用户动作,并避免了在网络上发送那些没有改变过的信息
- AJAX可以实现动态不刷新(局部刷新)
- ajax技术体系的组成部分有哪些?
- HTML
- CSS
- DOM
- XML
- XMPHttpRequest
- JavaScript
- 请介绍一下XMLHttpRequest对象 AJAX的核心是JavaScript对象XMLHttpRequest。它是一种支持异步请求的技术。简而言之,XMLHttpRequest使您可以使用JavaScript向服务器提出请求并处理响应,而不阻塞用户。通过XMLHttpRequest对象,开发人员可以在页面加载以后进行页面的局部刷新
- ajax有几种请求方式?优缺点?
- 常用的有
- post
- get
- delete
- put
- 代码上的区别
- get通过url传递参数
- post设置请求头规定请求数据类型
- 使用上的区别
- post比get安全,因为post参数在请求体中,get参数在url上面
- get传输速度比post快,根据传参决定,因为post通过请求体传参,后台通过数据流接收,速度稍微慢一点。而get通过url传参可以直接获取
- post传输文件理论上没有限制,get传输文件大概7-8k,ie是4k左右
- get获取数据,post上传数据,上传的数据比较多,而且上传数据都是重要数据。所以不论安全性还是数据量级post是最好的选择
- 什么情况造成跨域?
- 同源策略限制,不同源会造成跨域
- 协议,域名和端口号,只要有一个不相同就是非同源
- 跨域解决方案
- JsonP
- 原理:动态创建一个script标签。利用script标签的src属性不受同源策略限制。因为src和href属性都不受同源策略限制
- CORS跨域资源共享
- 原理:服务器设置Access-Control-Allow-OriginHTTP响应头之后,浏览器将会允许跨域请求
- nginx反向代理(项目上线时)
- webpack配置代理服务器
- 在vue.config.js中配置devServer
- JsonP
- http创建的状态码有哪些?
- 2开头(表示成功处理请求的状态码)
- 200 服务器以成功处理了请求
- 3开头(表示要完成请求,需要进一步操作,通常用来重定向)
- 304 (未修改)自从上次请求后,请求的网页未修改过,服务器返回此响应时不会返回网页内容
- 4开头(表示请求可能出错,妨碍了服务器的处理)
- 400 (错误请求)服务器不理解请求的语法
- 403 (禁止)服务器拒绝请求
- 404 (未找到)服务器找不到请求的网页
- 5开头(表示服务器错误)
- 2开头(表示成功处理请求的状态码)
VUE组件间通信
-
props
- 父组件中,给子组件的标签上添加标签属性,子组件声明props进行接收
// 父组件 <template> <div class="section"> <person :persons="personList"></person> </div> </template> <script> import person from './test/person.vue' export default { name: 'HelloWorld', components: { person }, data() { return { personList: ['小明', '小华', '小红'] } } } </script>// 子组件 <template> <div> <span v-for="(item, index) in persons" :key="index">{{item}}</span> </div> </template> <script> export default { props: ['persons'] } </script> -
$emit
- $emit绑定一个自定义事件, 当这个语句被执行时, 就会将参数arg传递给父组件,父组件通过v-on监听并接收参数
- 参数1:自定义事件
- 参数2:传递到参数
-
provide/inject
- 祖孙组件之间通信
- 祖先组件在配置对象中,创建属性provide,给他提供一个对象,该对象内的属性可以被后代组建获取到
- 后代组件在配置对象中,创建属性inject,写法与props数组版本相同,只需声明自己想获取到属性名即可
-
$children/$parent- $children -> 用于获取子组件组成的数组
- $parent -> 用于获取父组件实例对象
-
$refs
- 给标签添加,找到的是真实DOM
- 给组件添加,找到组件实例对象
-
$attrs/$listeners- $attr:用于接受当前组件没有接收的标签属性
- $listeners:用于接收当前组件上所有的自定义事件
-
事件总线(eventBus)
- 通过注册一个新的vue实例,通过调用这个实例的on来监听和触发这个实例的事件,通过传入参数实现全局通信
- 它是一个不具备DOM的组件,有的仅仅只是它的实例方法而已,因此非常轻便
-
Vuex
- vue的一个扩展包,用于集中管理多组件之间的共享数据,进行多组件间的通信
- 核心概念
- store:用于管理state,mutation,action,getter等数据,并向外提供一些方法的对象
- state:用于存储共享数据
- mutation:用于直接修改store中state数据,一般将同步代码写入mutation
- action:用于间接修改state数据,一般将异步代码写入action
- getter:相当于vue的computed
- dispatch:用于分发action
- modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护
VUE中computed与methods和watch的区别
- computed
- 支持缓存,多次读取,只执行一次计算,只有依赖的数据发生改变,才会重新计算
- 不支持异步,computed中有异步操作时无效,无法监听数据的变化
- computed默认走缓冲,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
- 如果一个属性是其他计算而来的,这个属性依赖其他属性,一般用computed
- 如果computed属性属性值是函数,默认走get方法,函数的返回值就是属性的属性值
- methods
- 没有缓存,多次读取,多次调用
- watch
- 支持异步
- 监听的函数接收2个参数,一个数新值,一个是旧值
- 监听数据必须是响应式数据
VUE的优点
- 声明式,响应式的数据绑定
- 组件化的开发,写组件的目的在于可复用或可组合
- 虚拟DOM
MVVM和MVC
- MVVM
- M:model数据层:保存数据
- V:view视图层:展示页面
- VM:ViewModel
- 可以通过vm将model层数据展示在view层
- 通过vm可以将view层用户输入的数据,来更新model层数据
- MVC
- M:model数据层:保存数据
- V:view视图层:展示页面
- C:controller层:负责将model数据流向view层
VUE的生命周期
- 初始化阶段
- beforCreate:
- 创建之前初始化生命周期或自定义事件,注入数据代理和数据劫持。这个阶段vue实例刚刚在内存中创建,此时data和methods都还没初始化好
- created:
- 这个阶段vue实例在内存中已经创建好了,data和methods也能获取到了,但是模版还没编译。此时可以发送请求,发送时机更早,数据回来更快。注意不要做复杂耗时到操作,否则由于js引擎解析的特点,会导致页面的解析和渲染延迟
- beforeMount:
- 这个阶段完成了模版的编译,但还没挂载到页面上,挂载之前准备挂载该组件,并且将template字符串转换成render函数,通过render函数生成虚拟DOM,再通过虚拟DOM生成真实DOM
- mounted:
- 这个阶段,模版编译好了,也挂载到页面中了,页面显示。可以发请求,首屏展示更快,先展示初始页面,再发请求。可以操作真实DOM,比如创建swiper实例,实现轮播图。可以绑定事件总线,绑定自定义事件
- beforCreate:
- 更新阶段
- beforeUpdate:
- 更新之前执行此函数,此时data中数据的状态值是最新的,但页面上显示的数据还是原始的,还没有重新开始渲染DOM树
- updated:
- 更新之后执行此函数,此时data中数据的状态值是最新的,而且页面上显示的数据也是最新的,DOM节点已经被重新渲染了
- beforeUpdate:
- 卸载阶段
- beforeDestroy
- vue实例被销毁之前,这个阶段vue实例还能用。需解绑事件监听:通过vue绑定的不用管,自己通过原生DOM绑定的事件回调函数需解绑
- destroyed
- vue实例销毁后调用,此时所有实例指示的所有东西都会解绑,事件监听器也移除,子实例也被销毁
- beforeDestroy
- 配合keep-alive进行组件缓存的生命周期
- activated激活
- deactivated失活
- 捕获后代组件错误
- errorCaptured
- 处理ssr的函数
- serverPrefetch
VUE响应式原理
- 数据代理
- 将data数据代理到实例对象上,遍历所有data数据,通过OBject.defineProperty()方法将data中的数据定义在实例对象上
- 内部通过get定义属性读取的方法,实际读取的是原数据data
- 通过set定义属性设置的方法,实际设置的是原数据data
- 数据劫持
- 遍历所有data数据,进行重新定义,将其定义成响应式
- 通过Object.defineProperty()方法,重新定义get和set
- 此时会通过闭包保存一个dep对象
- get将来通过dep就能建立dep和watcher的联系
- set将来通过dep就能通知所有watcher去更新用户界面
- 模版解析
- 会创建watch实例,出发get方法收集所有dep和watcher之间的关系
- 将元素节点转换成文档碎片节点
- 编译模版
双向数据绑定原理
- 通过v-model实现
- v-model会给元素绑定value属性和绑定input事件,当用户输入数据,会触发input事件,在input事件中更新data数据
VUE的核心点
- 数据驱动:即viewModel,指视图是由数据驱动生成的,我们对视图的修改,不会直接操作DOM,而是通过修改数据,保证了数据和视图的一致性
- 组件系统:就是为了解决页面布局等问题,而vue中组件分为两种,全局和局部,提供了强大的页面布局功能
vue的修饰符(未施工)
diff算法
- 页面初次渲染不会进行diff算法,diff算法只存在于数据更新后,为了优化性能
- 在数据更新后会生成新的虚拟DOM,于旧的虚拟DOM比较
- 只会同级比较
- 比较标签名
- 新前vs旧前
- 新后vs旧前
- 新后vs旧后
- 新前vs旧后
- 最后遍历整个列表查找key值
- 补充:
- v-for遍历的列表数据更新会根据key值决定是否重新渲染
nextTick
- 是为了开发者在操作数据后,立即需要操作DOM,提供的API
- nextTick在数据更新的时候会开启一个队列,缓冲同一事件中对所有数据的变更,我们定义的回调函数也会推入这个队列
- 在确保DOM操作完成后执行回调
后台权限管理
- 如何知道账号的权限
- 用户登录之后通过token获取用户的详细信息
- 如何控制路由跳转
- 项目启动时,只注册常量路由
- 获取用户信息之后,对应的权限信息更新到vuex中
- 生成当前账号专用的路由数组
- 将最新的路由数组动态注册到项目的VueRouter中
深浅拷贝
- 浅拷贝:仅复制了引用,彼此之间的操作会互相影响
- 深拷贝:在堆中重新分配内存,不同的地址,相同的值,互不影响
- 实现深拷贝
- JSON.stringify()和JSON.parse()配合使用
- 利用递归,对属性中所有引用类型的值进行遍历,直到是基本类型值为止
小程序的生命周期
- 进入页面
- onload:页面加载,相当于vue的created
- onshow:页面显示,相当于vue的activated
- onReady:渲染结束
- 离开页面
- onHide:页面隐藏,相当于vue的deactivated
- onUnload:页面卸载,相当于vue的beforeDestroy 注意:
- 离开页面时,如果使用wx.navigateTo跳转
- 保留当前页,从而触发onHide,不会出发onUnload
- 点击回退,可以重新展示上一个
- 如果使用wx.redirectTo跳转
- 关闭当前页,触发onUnload,不会触发onHide
- 这种方法离开没有回退按钮,所以只能重新进入该页面,所有生命周期都重新触发
小程序的事件绑定
- 冒泡事件
bind+'事件名' = '回调函数'
- 非冒泡事件
catch+'事件名' = '回调函数'
- 捕获事件
capture-bind+':'+'事件名' = '回调函数'
- 非捕获事件
capture-catch+':'+'事件名' = '回调函数'
小程序的路由跳转
- 声明式导航:通过navigate组件实现声明式导航
- 编程式导航:
- wx.navigateTo({}) (类似vue中的push)
- 保留当前页
- url必传
- 会保留上一页
- 注意:小程序页面栈最多10层,旧版本5层,超出上限无法跳转
- wx.redirectTp({}) (类似vue中的replace)
- 关闭当前页(销毁当前页面的实例对象)
- url必传
- 销毁上一页,如果想要展示需要重新挂载
- 注意:由于会销毁上一页,所以无需考虑页面栈的问题
- wx.switchTab({})
- 该API专门用于跳转tabBar页面
- wx.navigateTo({}) (类似vue中的push)
路由
- 前端路由
- 优点:访问新页面近变换一下路径,没有网络延迟,用户体验好
- 缺点:使用浏览器的前进,后退会重新发送请求,没有合理利用缓存,不利于SEO
- 2种实现方案
- hash
- 优点:兼容性好,后端不需要做任何的配置
- 缺点:地址栏有#,不能使用锚点功能
- 原理:
- 控制地址栏变化
- 通过
window.location.hash="/about"操作浏览器的地址栏,并且页面不会重新请求
- 通过
- 监听地址栏变化
- 通过window身上的
hashchange事件监听变化
- 通过window身上的
- 控制地址栏变化
- history
- 优点:不会影响锚点功能
- 缺点:兼容性较差,项目上线时,后端需要进行专门的配置
- 原理:
- 通过
window.history.pushState({},"","/about"),可以操作浏览器的地址栏,并且保证页面不会重新请求 - 监视地址栏变化
- 通过window身上的
popstate事件,可以监视地址栏变化,但是只能监视浏览器的前进后退
- 通过
- hash
webpack
- 只认识JS和JSON
- 核心概念
- 入口entry:入口文件地址
- output:一个对象,包含编译之后的文件名和保存地址
- loader:帮助webpack编译他不认识的文件或代码(模块转换器)
- plugin:扩展插件
- mode:告知webpack使用相应模式的内展优化
- development 默认
- production 开启树摇
- 树摇:不打包没有用到的代码
- 开启Terserplugin插件:将mode设置为production
- 多进程打包(threda-loader)
- rules中哪个rule对象需要多进程打包,就在他loader最前面加上thread-loader
- 代码分割
Optimization:{
splitChunks:{
chunks:"all",//分割类型
minSize:1 //分割体积
}
}