目录
HTML
标签语意化
HTML5新特性
SEO
input元素的类型
iframe的特点
CSS
Flex
BFC
重排重绘
CSS优先级
CSS3新特性
清除浮动的方法
盒模型的理解
响应式布局
移动适配方案
三栏布局
圣杯布局和双飞翼布局
JS
JS为何是单线程
JS数据类型
js判断数据类型
js中的length属性
判断空对象
判断空数组
ES6新特性
ES6 Module 和 CommonJS 的区别
箭头函数和普通函数区别
new一个对象做了什么事情
构造函数与普通函数的区别
防抖和节流
深拷贝浅拷贝
数组去重
原型链
call、bind、apply的含义和区别
Class
Class和构造函数的区别
js继承的几种方式及其优缺点
闭包
浏览器的缓存机制
任务队列(宏/微任务)
事件委托
请求
请求的方式有哪些,应用场景是什么
什么是promise,解决了什么问题
谈谈对async/await的理解
跨越问题如何解决
输入网站url地址后发生了什么
ajax工作流程及原理
axios 是什么、怎么使用
Localstorage、sessionStorage、cookie 的区别
后端接口没写出来,前端如何进行开发
post请求和get请求的区别
Session,Token,Cookie在身份认证方面的区别
常见的状态码
Vue2
Vue是什么
MVVM
观察者模式和发布订阅者模式
SPA(单页应用)的理解
响应式原理
Vue2生命周期
data为什么是函数的存在
Vue组件通信的方式
keep-alive原理及缓存策略
computed原理
watch原理
computed和methods、watch的区别
vue-router原理
Vue3
Vue2和Vue3响应式原理的区别
Vue3生命周期
选项式API和组合式API
VueX和Pinia的区别、优劣
Vite好在哪为什么
TS
对ts的理解
ts的数据类型
never和void的区别
枚举的理解
泛型
项目
token超时处理
登录页面安全问题
发布通知功能怎么实现
项目刚上线出现bug
用什么进行版本管理
Echarts在Vue里怎么使用
Echarts里的配置项不够用怎么办
团队中有的人用vue2有的人用vue3怎么办
项目优化
PC端兼容问题
img下的留白
如果图片加a标签在IE9-中会有边框
移动端问题
移动端页面滚动滞涩感
修改移动端的点击高亮效果
滚动穿透问题
在ios和andriod中,audio元素和video元素在无法自动播放
iOS 系统中文输入法输入英文时,字母之间可能会出现一个六分之一空格
IOS移动端click事件300ms的延迟响应
阻止旋转屏幕时自动调整字体大小
图片模糊问题
移动端某些情况下input的placeholder会出现文本位置偏上的现象
h5底部输入框被键盘遮挡问题
移动端如何做真机测试
H5和app的区别
HTML
标签语意化
简单来说:就是用正确的标签做正确的事。比如:
-
头部:header
-
导航:nav
-
主体内容:main
-
标题:h1 ~ h6
-
段落:p
-
侧边栏:aside
-
页脚:footer
这样,整篇HTML结构非常清晰,好看。
HTML语义化有什么好处呢?
-
网页加载慢导致CSS文件还未加载时(没有CSS),页面仍然清晰、可读、好看。
-
提升用户体验,例如title、alt可用于解释名词或解释图片信息。
-
有利于SEO:和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息。
-
方便其他设备(如屏幕阅读器、盲人阅读器、移动设备)更好的解析页面。
-
使代码更具可读性,便于团队开发和维护。
HTML5新特性
-
更加语义化的元素。 article、footer、header、nav、section
-
本地化储存。 localStorage 和 sessionStorage
-
拖曳以及释放的api。 Drag and drop(draggable="true";当拖拽一个项目到 HTML 元素中时,浏览器默认不会有任何响应。想要让一个元素变成可释放区域,该元素必须设置 ondragover和 ondrop 事件。)
-
媒体播放。 video 和 audio
-
增强表单控件类型。date、time、number、email、color、search
-
全双工通信协议。 websocket
-
跨域资源共享(CORS) Access-Control-Allow-Origin
补充:Web Storage 的概念和 cookie 相似,区别是它是为了更大容量存储设计的。Cookie 的大小是受限的,并且每次你请求一个新的页面的时候 Cookie 都会被发送过去,这样无形中浪费了带宽,另外 cookie 还需要指定作用域,不可以跨域调用。
1.localStorage: 用于持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的。
2.sessionStorage: 用于本地存储一个会话(session)中的数据,这些数据只有在同一个会话中的页面才能访问并且当会话结束后数据也随之销毁。因此 sessionStorage 不是一种持久化的本地存储,仅仅是会话级别的存储。
Web Storage 拥有 setItem,getItem,removeItem,clear 等方法,不像 cookie 需要前端开发者自己封装 setCookie,getCookie(安装依赖js-cookie、Cookies.set()、Cookies.get()、Cookies.remove)
SEO
SEO(Search Engine Optimization),即搜索引擎优化。SEO的存在就是为了提升网站在搜索引擎中的权重,增加对搜索引擎的友好度,使得用户在访问网站时能排在前面。
-
突出重要内容---合理的设计
title
、description
和keywords
-
语义化书写HTML代码,符合W3C标准
-
图片
img
标签添加alt
和title
属性 -
链接
<a>
页内标签添加title
属性 -
使用h1标签自带权重
input元素的类型
① button ② checkbox ③ file ④ hidden ⑤ image ⑥ password ⑦ radio ⑧ reset ⑨ submit ⑩ Text ⑪ Date
iframe的特点
-
优点:
-
iframe能够原封不动的把嵌入的网页展现出来。
-
如果有多个网页引用iframe,那么你只需要修改iframe的内容,就可以实现调用的每一个页面内容的更改,方便快捷。
-
网页如果为了统一风格,头部和版本都是一样的,就可以写成一个页面,用iframe来嵌套,可以增加代码的可重用。
-
如果遇到加载缓慢的第三方内容如图标和广告,这些问题可以由iframe来解决。
-
缺点:
-
iframe会阻塞主页面的onload事件;
-
iframe和主页面共享连接池,而浏览器对相同域的连接有限制,所以会影响页面的并行加载。,会产生很多页面,不容易管理。
-
iframe框架结构有时会让人感到迷惑,如果框架个数多的话,可能会出现上下、左右滚动条,会分散访问者的注意力,用户体验度差。
-
代码复杂,无法被一些搜索引擎索引到,这一点很关键,现在的搜索引擎爬虫还不能很好的处理iframe中的内容,所以使用iframe会不利于搜索引擎优化(SEO)。
-
很多的移动设备无法完全显示框架,设备兼容性差。
-
iframe框架页面会增加服务器的http请求,对于大型网站是不可取的。
CSS
Flex
-
哪些属性作用在父元素上?
justify-content:space-around space-between space-evently center flex-end
align-items:flex-star flex-end
flex-direction :row row-reverse column column-reverse
flex-wrap :nowrap(如果子孩子,的宽度超过父盒子,会进行总动的伸缩) wrap(不去管)
-
哪些属性作用在子元素上?
order (项目排列的顺序 越小越靠前)
align-self(单个属性的操作 flex-end center flex-star)
flex-grow(如果剩余有宽度是否进行扩张方法,默认是0,)
flex-shrink (空间不足的时候进行缩小默认是1 进行缩小)
BFC
定义:
块级格式化上下文。BFC
是一个完全独立的空间(布局环境),让空间里的子元素不会影响到外面的布局。用于对块级元素排版。默认情况下只有根元素(body)一个块级上下文。
布局规则:
-
内部的盒子会在垂直方向,一个个地放置;
-
盒子垂直方向的距离由
margin
决定,属于同一个BFC
的两个相邻盒子的上下margin
会发生重叠; -
每一个元素的左边,与包含块的左边相接触(对于从右往左的布局,则相反),即使存在浮动也是如此;
-
BFC
的区域不会与float
重叠;(应用:三栏布局) -
BFC
就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此; -
计算
BFC
的高度时,浮动元素也參与计算。(运用:清除浮动) -
当一个元素设置了新的
BFC
后,就和这个元素外部的BFC
没有关系了,这个元素只会去约束自己内部的子元素。(应用:外边距塌陷)
怎么触发BFC:
-
overflow:不为
visible
; -
float: 不为
none
; -
display: 为
inline-block
、table
、table-cell
、table-caption
、flex
、inline-flex
、grid
、inline-grid
、flow-root
; -
position: 为
absolute
或者fixed
;
重排重绘
页面生成的过程:
1.HTML 被 HTML 解析器解析成 DOM 树;
2.CSS 被 CSS 解析器解析成 CSSOM 树;
3.结合 DOM 树和 CSSOM 树,生成一棵渲染树(Render Tree),这一过程称为 Attachment;
4.生成布局(flow),浏览器在屏幕上“画”出渲染树中的所有节点;
5.将布局绘制(paint)在屏幕上,显示出整个页面。
第四步和第五步是最耗时的部分,这两步合起来,就是我们通常所说的渲染。
-
重绘:某些元素的外观被改变,例如:元素的填充颜色
-
重排:重新生成布局,重新排列元素。
单单改变元素的外观,肯定不会引起网页重新生成布局,但当浏览器完成重排之后,将会重新绘制受到此次重排影响的部分。比如改变元素高度,这个元素乃至周边dom都需要重新绘制。
也就是说:重绘不一定导致重排,但重排一定会导致重绘。
CSS优先级
!important > style > id > class > 标签 > 通配符 > 默认 > 继承
CSS3新特性
-
新增的选择器(各种伪类选择器)
-
圆角(border-radius)
-
阴影(box-shadow)
-
动画(animation)
-
过渡(transition)
-
翻转(transform)
-
渐变
-
媒体查询 @media
-
弹性盒子(flex)
-
rgba
清除浮动的方法
为什么要清除浮动?
如果子元素浮动,此时子元素不能撑开标准流的块级父元素。
方法:
1、直接给父元素设置高度
-
优点:简单、方便
-
缺点:有很多布局不能固定父元素高度:比如长列表、推荐模块,是无法确定子项内容有多少的
2、额外标签法
-
实现方式:
-
给父元素内容的最后添加一个块级元素
-
给添加的块级元素设置
clear: both;
(可以认为,设置了clear:both
的当前元素会把前边元素中设有浮动属性的元素,当做没设浮动一样来看待,以此来消除其对自己的影响)
-
-
缺点:
-
会添加进去额外的标签,让HTML结构变得复杂
-
3、单伪元素清除法
用伪元素替代额外标签
4、双伪元素清除法
除了可以清除浮动的影响,还可以解决外边距折叠的塌陷现象(原理:里面的"display:table;"触发BFC)
5、为父元素设置overflow: hidden
盒模型的理解
分标准盒模型和怪异盒模型。
标准盒模型采用的W3C标准,盒子的content内容部分由width宽度和height高度决定,添加padding内边距或border外边框后,宽高都会进行相应增长。
怪异盒模型也称为IE盒模型,是IE浏览器设计元素时遵循的规则。怪异盒模型的宽高在div盒子初次设置时就已经规定,添加padding或者border,会从中减少content内容的占据区域,来为padding和border制造空间,宽高不会相对应的进行增长。
盒模型转换用box-sizing:border-box,默认是标准盒模型。
响应式布局
-
bootstrop框架
-
媒体查询
移动适配方案
-
rem(用
flexible
方案,flexible.js帮我们计算出1rem 等于多少px) -
vw和vh
三栏布局
-
flex
-
定位
-
浮动
-
-
两边浮动中间margin + overflow: hidden(触发BFC)
-
两边浮动,中间calc()函数
-
三个部分都浮动(圣杯和双飞翼布局),然后结合margin和定位调整
-
-
display:table;
-
grid布局
圣杯布局和双飞翼布局
最后我们来总结一下,双飞翼布局其实和圣杯布局的精髓是一样的,都是在三个部分都是浮动的情况下,左右的部分因为父盒子宽度不够被挤下来的问题,通过设置负margin来实现元素的排布。
-
不同的就是html结构,双飞翼是在middle元素内部又设置了一个milddle-inner并设置它的左右margin,而非圣杯布局的padding,来排除两边元素的覆盖,最后把盒子定位到两侧。
-
双飞翼布局可以多了一个html结构,但是可以不用设置left,right元素的定位。
JS
JS为何是单线程
JavaScript的单线程,与它的用途有关。作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成这门语言的核心特征,将来也不会改变。
注:所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个。
JS数据类型
基本类型分为以下六种:
-
string(字符串)
-
boolean(布尔值)
-
number(数字)
-
symbol(符号)
-
null(空值)
-
undefined(未定义)
-
BigInt(是ES6中新引入的数据类型,它是一种内置对象,它提供了一种方法来表示表示任意大的整数。即使这个数已经超出了JavaScript构造函数 Number 能够表示的安全整数范围。)
引用数据类型:
-
数组
-
对象(函数也是对象)
区别:
值类型的变量会保存在 栈内存 中,如果在一个函数中声明一个值类型的变量,那么这个变量当函数执行结束之后会 自动销毁。
引用类型的变量名会保存在 栈内存 中,但是变量值会存储在 堆内存 中,引用类型的变量不会自动销毁,当没有引用变量引用它时,系统的 垃圾回收机制 会回收它。
js判断数据类型
判断数据类型方法有很多,实际使用需要根据自己的需求使用最适合自己的方法
1、使用 typeof
测试简单数据类型。对于
null
及数组、对象,typeof均检测出为object,不能进一步判断它们的类型。
2、使用 obj instanceof Object
测试复杂数据类型,因为instanceof 是用来判断数据是否是某个对象的实例
所以对于
null
和undefined
这两个家伙就检测不了因为原型链继承的关系,instanceof 会把数组都识别为 Object 对象,所有引用类型的祖先都是 Object 对象
3、使用Object.prototype.toString.call
Object.prototype.toString.call() 区分的数据类型适用范围更大,但是无法区分自定义对象类型,区分自定义对象类型使用 instanceof 操作符。
Object.prototype.toString()本身是允许被修改的,而我们目前所讨论的关于Object.prototype.toString()这个方法的应用都是假设toString()方法未被修改为前提的。
因为实例对象有可能会自定义toString()方法,会覆盖Object.prototype.toString(), 所以在使用时,最好加上call()。
4、使用.constructor
constructor不能判断undefined和null,并且使用它是不安全的,因为contructor的指向是可以改变的
js中的length属性
1、length属性常见于字符串和数组,来判断长度。
2、length属性还可以用于函数,来判断函数的长度,即函数中形参的个数。
注意:
-
...args
不计入形参个数 -
设置了默认值的参数及其之后的所有参数都不计入形参个数,之前的仍旧计入
判断空对象
所谓空对象,就是数组的长度等于0
let obj = {name : '你好'}
//是true为空对象,是false则不是空对象
console.log(JSON.stringify(obj) === '{}');//false
let obj = {}
let fn = (obj) => {
for(let key in obj) {
return false
}
return true
}
//返回false代表不为空,返回true则为空对象
console.log(fn(obj));//true
let obj = {name : '1'}
//Object.getOwnPropertyNames()获取到对象中的全部属性名,存到一个数组中
let s = Object.getOwnPropertyNames(obj)
console.log(s); //['name'] //为[],代表空对象
let obj = {name : '1'}
//Object.keys()获取给定对象的所有可枚举属性的字符串数组
let s = Object.keys(obj)
console.log(s);//[ 'name' ] //若为[],则为空对象
let obj = {name : '1'}
// 注意for...in 会将对象原型链上的属性也枚举出来,所以要借hasOwnProperty()方法来判断是不是对象本身的属性
// 如果存在返回true,不存在返回false
let fn = (s) => {
for(let key in s) {
if(s.hasOwnProperty(key)) {
return false
}
return true
}
}
console.log(fn(obj));//false //若是反回true则就是空对象
判断空数组
和判断空对象类似的,我们只要能验证这个对象的keys
长度是0,那就是个空对象了。
Array.isArray(arr) & arr.length === 0
ES6新特性
-
新的定义变量的方式 let const var 的区别
-
模板字符串
-
class
-
Promise
-
async/await
-
箭头函数
-
新的数组方法 filter some map every forEach reduce Array.from() find findIndex includes flat
-
拓展运算符
-
赋值解构运算符
-
Proxy
-
对象新增方法 Object.assign() Object.keys()
-
es6的模块化
-
Set
-
Map
ES6 Module 和 CommonJS 的区别
CommonJS 是对模块的浅拷贝;ES6 Module 是对模块的引用,即 ES6。
CommonJS是动态编译,可以放在代码里动态执行;ES6 Module 是静态编译,引用只能放在最前面。
箭头函数和普通函数区别
-
this指向(普通函数指向调用者,this指向父级作用域的this)
-
不可以被当做构造函数
-
不可以使用arguments对象,该对象在函数体内不存在,如果要用就用剩余参数替代
-
没有prototype属性
new一个对象做了什么事情
-
new构建函数可以在内存中创建一个空的对象
-
this会指向刚才创建的空对象
-
执行构造函数的代码给这个空对象添加属性和方法
-
返回这个对象(所以构造函数不需要return)
构造函数与普通函数的区别
1.构造函数就是一个普通的函数,创建方法和普通函数没有区, 不同的是构造函数习惯上首字母大写。
2.构造函数与普通函数的区别就是调用方式不同,普通函数直接调用,而构造函数使用new关键字调用。
防抖和节流
-
防抖debounce
定义:触发高频事件后n秒内函数只会执行最后一次,如果n秒内高频事件再次被触发,则重新计算时间。
原理:每次触发事件时都取消之前的延时调用方法
function debounce(fn) { let timer = null; // 创建一个标记用来存放定时器的返回值 return function () { // 执行这个函数之前先清掉定时器 clearTimeout(timer); timer = setTimeout(() => { // 绑定this的原因是为了让this指向正确 // 绑定arguments的原因是为了正确使用函数参数位置的事件对象e fn.apply(this, arguments); }, 500); }; }
应用场景:
-
搜索框输入查询,如果用户一直在输入中,没有必要不停地调用去请求服务端接口,等用户停止输入的时候,再调用,设置一个合适的时间间隔,有效减轻服务端压力。
-
表单验证
-
按钮提交事件。
-
浏览器窗口缩放,resize事件(如窗口停止改变大小之后重新计算布局)等
-
-
节流throttle
定义:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率
原理:每次触发事件时都判断当前是否有等待执行的延时函数
方法一: function throttle(func, wait) { var prev = 0; return function() { let now = Date.now(); if (now - prev > wait) { func(); // 重置起始时间 prev = now; } } } 方法二: function throttle(fn) { // 闭包保存是否可以开启定时器,默认是开启的 let canRun = true; return function () { // 如果没有开启就直接返回 if (!canRun) return; // 开启了就立即关闭定时器入口,然后开启定时器 canRun = false; setTimeout(() => { fn.apply(this, arguments); // 定时器运行后,下次定时器才可开启 canRun = true; }, 500); }; }
应用场景:
-
按钮点击事件
-
拖拽事件
-
onScoll
-
计算鼠标移动的距离(mousemove)
-
深拷贝浅拷贝
-
浅拷贝
-
展开运算符...
const array = [{ type: '😀' }, '😀', '😀'] const copyArray = [...array] array[0].type = '🤑' // 修改原数组 console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '🤑' }, '😀', '😀']
-
Object.assign()
const array = [{ type: '😀' }, '😀', '😀'] const copyArray = Object.assign([], array) array[0].type = '😋' // 修改原数组 console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '😋' }, '😀', '😀']
-
Array.prototype.concat()
const array = [{ type: '😀' }, '😀', '😀'] const copyArray = array.concat([]) array[0].type = '🤪' // 修改原数组 console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '🤪' }, '😀', '😀']
-
Array.prototype.slice()
const array = [{ type: '😀' }, '😀', '😀'] const copyArray = array.slice() array[0].type = '😍' // 修改原数组 console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '😍' }, '😀', '😀']
-
Array.from()
const array = [{ type: '😀' }, '😀', '😀'] const copyArray = Array.from(array) array[0].type = '😎' // 修改原数组 console.log(array, copyArray) // 原数组和复制的数组都变成了[{ type: '😎' }, '😀', '😀']
-
-
深拷贝
1、JSON.parse(JSON.stringify(obj))
-
会忽略undefined Symbol
-
不能序列化函数
-
不能解决循环引用的对象
-
不能正确处理 new Date()
-
不能处理正则
2、浅拷贝+递归
module.exports = function clone(target) { if (typeof target === 'object') { let cloneTarget = Array.isArray(target) ? [] : {}; for (const key in target) { cloneTarget[key] = clone(target[key]); } return cloneTarget; } else { return target; } };
-
数组去重
对于 JS 数组去重来说,其实万变不离其中,我简单的总结了以下 4 种去重类型
1、数组元素比较型:
该方法是将数组的值取出与其他值比较并修改数组
-
双层 for 循环
取出一个元素,将其与其后所有元素比较,若在其后发现相等元素,则将后者删掉
function uniq(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1);
j--;
}
}
}
return arr;
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN, NaN]
// 与 lodash 结果相比 NaN 没有去掉
复制代码
由于 NaN === NaN 等于 false,所以重复的 NaN 并没有被去掉
-
排序并进行相邻比较
先对数组内元素进行排序,再对数组内相邻的元素两两之间进行比较,经测试,该方法受限于 sort
的排序能力,所以若数组不存在比较复杂的对象(复杂对象难以排序),则可尝试此方法
function uniq(arr) {
arr.sort();
for (let i = 0; i < arr.length - 1; i++) {
arr[i] === arr[i + 1] && arr.splice(i + 1, 1) && i--;
}
return arr;
}
// 运行结果
//[[], [], 1, "1", [1], NaN, NaN, {}, {}, { a: 1 }, {}, { a: 1 }, "a", null, undefined];
// 与 lodash 结果相比 NaN 没有去掉,且对象的去重出现问题
同样由于 NaN === NaN
等于 false
,所以重复的 NaN 并没有被去掉,并且由于 sort
没有将对象很好的排序,在对象部分,会出现一些去重失效
2、查找数组元素位置型:
该类型即针对每个数组元素进行一次查找其在数组内的第一次出现的位置,若第一次出现的位置等于该元素此时的索引,即收集此元素
-
indexOf
以 indexOf
来查找元素在数组内第一次出现的位置,若位置等于当前元素的位置,则收集
function uniq(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (arr.indexOf(arr[i]) === i) {
res.push(arr[i]);
}
}
return res;
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null];
// 与 lodash 结果相比 少了 NaN
indexOf
采用与 ===
相同的值相等判断规则,所以在数组内没有元素与 NaN 相等,包括它自己,所以 NaN 一个都不会被留下
-
findIndex
以 findIndex
方法来查找元素在数组内第一次出现的位置
function uniq(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (arr.findIndex(item => item === arr[i]) === i) {
res.push(arr[i]);
}
}
return res;
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null];
// 与 lodash 结果相比 少了 NaN
结果原理和 indexOf
相同,因为用了 ===
的规则来判断元素是否相等,但此方法相当于双层 for
循环
3、查找元素是否存在型:
该方法基本依托 includes
方法来判断对应元素是否在新数组内存在,若不存在则收集
function uniq(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
if (!res.includes(arr[i])) {
res.push(arr[i]);
}
}
return res;
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 与 lodash 结果相同
includes
方法采用 SameValueZero 判断规则,所以可以判断出并去重 NaN
4、依托数据类型特性型:
该方案依托于数据类型的不重复特性,以达到去重效果
-
Map
Map 类型的数据可以像 Object 一样,在设定元素键值对的时候可以保证键的唯一,并且将键的类型拓展到了基本所有元素,包括对象,在设定好一个唯一键的 Map 数据类型后,再用其自带的 Map.prototype.keys()
方法取到相应的键类数组,最后将类数组进行一次转化即可
function uniq(arr) {
let map = new Map();
for (let i = 0; i < arr.length; i++) {
!map.has(arr[i]) && map.set(arr[i], true);
}
return [...map.keys()];
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 与 lodash 结果相同
-
Set
与 Map 类似,但是它里面每一项的值是唯一的,没有重复的值,Set是一个构造函数,用来生成set的数据结构。运用数据类型的特性完成去重,这个方法也是最热门的方法。
function uniq(arr) {
return [...new Set(arr)];
}
// 运行结果
// [1, "1", "a", {}, {}, { a: 1 }, {}, { a: 1 }, [], [], [1], undefined, null, NaN];
// 与 lodash 结果相同
原型链
基于原型对象的继承使得不同构造函数的原型对象关联在一起,并且这种关联的关系是一种链状的结构,我们将原型对象的链状结构关系称为原型链。
简单地说:原型链就是查找规则,__proto__对象原型的意义就在于为对象成员查找机制提供一个方向,或者说一条路线。
call、bind、apply的含义和区别
-
区别 : 传参不一样
call()
-
改变this指向
-
可以调用函数
-
参数: 接受的是若干个参数的列表 call(this,1,2,3)
apply()
-
改变this指向
-
可以调用函数
-
参数: 参数的数组 apply(this,[1,2,3])
bind()
-
只改变this指向,不调用函数,比如用来改变定时器内部的this指向
Class
-
class本质还是function
-
类的所有方法都定义在类的 prototype属性上
-
类创建的实例 里面也有 __ proto __ 指向类的 prototype 原型对象
-
所以 ES6 的类 他的绝大部分功能 es5 都可以做到 新的 calss 写法 只是让对象原型的写法 更加清晰 更像面向对象编程的语法而已
-
所以 ES6 的类 其实就是语法糖
Class和构造函数的区别
-
类的内部所有定义的方法,都是不可枚举的(但是在es5中prototype的方法是可以进行枚举的)
-
类的构造函数,不使用new是没法调用的,会报错。这是它跟普通构造函数的一个主要区别,后者可以直接调用
-
Class不存在变量提升(hoist),这一点与ES5完全不同
-
ES5的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))。ES6的继承机制完全不同,实质是先创造父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this。
js继承的几种方式及其优缺点
原型链继承
function Parent() {
this.name = 'kevin';
}
Parent.prototype.getName = function () {
console.log(this.name);
}
function Child() {
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child();
console.log(child1.getName()) // kevin
缺点:
-
在创建 Child 的实例时,不能向Parent传参
-
引用类型的属性被所有实例共享
借用构造函数继承
function Parent() {
this.names = ['kevin', 'daisy'];
}
function Child() {
Parent.call(this);
}
var child1 = new Child();
child1.names.push('yayu');
console.log(child1.names); // ["kevin", "daisy", "yayu"]
var child2 = new Child();
console.log(child2.names); // ["kevin", "daisy"]
优点:
-
可以在 Child 中向 Parent 传参
-
避免了引用类型的属性被所有实例共享
缺点:
-
只能继承父类的实例属性和方法,不能继承原型属性/方法
-
无法实现复用,每个子类都有父类实例函数的副本,影响性能
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child('kevin', '18');
child1.colors.push('black');
console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
var child2 = new Child('daisy', '20');
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]
缺点:
构建原型链的时候,Child.prototype的原型上面会有name、colors两个属性;在创建child1的时候它的实例上也会有name、colors。实例对象child1上的两个属性就屏蔽了其原型对象Child.prototype的两个同名属性。
所以,组合模式的缺点就是在使用子类创建实例对象时,会调用两次父构造函数,其原型链中会存在两份相同的属性/方法。
原型式继承
略。不重要可不说
寄生式继承
略。不重要可不说
寄生组合式继承⭐
对于组合式继承的缺点,思考一下,如何不调用两次父构造函数,不使用 Child.prototype = new Parent() ,而是间接的让 Child.prototype 访问到 Parent.prototype 呢?
function Parent(name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// 关键的三步
var F = function () {};
F.prototype = Parent.prototype;
// Child.prototype = new F();
let f = new F()
f.constructor = Child
Child.prototype = f
var child1 = new Child('kevin', '18');
console.log(child1);
最后我们把第三步的方法封装一下
function object(o) {
function F() {}
F.prototype = o;
return new F();
}
function prototype(child, parent) {
var prototype = object(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
// 当我们使用的时候:
prototype(Child, Parent);
这种方式的高效率体现它只调用了一次 Parent 构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
ES6类继承extends⭐
ES6继承的原理跟寄生组合式继承是一样的。
闭包
定义
-
闭包就是能够读取其他函数内部变量的函数。例如在
javascript
中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成定义在一个函数内部的函数。在本质上,闭包就是将函数内部和函数外部连接起来的桥梁。
优缺点:
-
能够访问局部变量
-
保护局部变量
-
由于闭包
会将它的外部函数的作用域也保存在内存中
,因此会比其他函数更占用内存,这样的话,如果过度使用闭包,就会有内存泄露的威胁。解决方法——使用完变量后,手动将它赋值为null。
应用:
-
构造函数的私有属性。由于javascript中天然没有类的实现,某些不希望被外部修改的
私有属性
可以通过闭包的方式实现 -
函数防抖、节流
浏览器的缓存机制
浏览器与服务器通信的方式为应答模式,即是:浏览器发起HTTP请求 – 服务器响应该请求。
-
浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识
-
浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中
根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分别是强制缓存和协商缓存。
-
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程
-
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存,协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存。
任务队列(宏/微任务)
首先我们需要明白以下几件事情:
-
JS分为同步任务和异步任务
-
同步任务都在主线程上执行,形成一个执行栈
-
所谓 "异步",简单说就是一个任务不是连续完成的,先执行第一段,等做好了准备,再回过头执行第二段,第二段也被叫做回调;同步则是连贯完成的。
-
主线程之外,有一个任务队列,只要异步任务有了运行结果,就在任务队列之中放置一个事件。
-
一旦执行栈中的所有同步任务执行完毕(此时JS引擎空闲),系统就会读取任务队列,将可运行的异步任务添加到可执行栈中,开始执行。
宏任务
可以理解是每次执行栈执行的代码就是一个宏任务(包括每次从事件队列中获取一个事件回调并放到执行栈中执行)。
主要包含:script(整体代码)、setTimeout、setInterval、DOM事件
微任务
可以理解是在当前(主线程)任务执行结束后立即执行的任务。也就是说,在当前任务任务后,下一个任务之前,在渲染之前。
所以它的响应速度相比setTimeout(setTimeout是task)会更快,因为无需等渲染。也就是说,在某一个macrotask执行完后,就会将在它执行期间产生的所有microtask都执行完毕(在渲染前)。
主要包含:Promise.then
运行机制(时间循环)
在事件循环中,每进行一次循环操作称为 tick,每一次 tick 的任务处理模型是比较复杂的,但关键步骤如下:
-
执行一个宏任务(全局Script脚本)
-
产生的的宏任务和微任务进入各自的队列中
-
宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
-
当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
-
渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
事件委托
利用事件冒泡的原理,将原本绑定给子元素的事件,绑定给父元素,子元素触发的事件冒泡给父元素,让父元素触发事件,执行函数。
事件委托好处:1. 效率好;2. 对于新增的子元素事件依然生效
请求
请求的方式有哪些,应用场景是什么
-
GET
GET请求是向服务端请求获取某个或某些资源(resource),比如查询数据库单个或list数据,服务端成功的话,一般状态码返回200。
-
POST
POST请求是用来向服务端请求新增资源(resource),处理成功的话,服务端一般返回状态码201。
-
PUT
PUT请求一般是用来向服务端请求修改某个已存在的资源(resource),服务端一般返回状态码200,204等。
-
DELETE
DELETE请求一般是用来向服务端请求删除某个已存在的资源(resource),服务端一般返回200,202等。
-
PATCH
PATCH请求一般是对某个资源做局部修改,如个别字段
什么是promise,解决了什么问题
Promise
Promise 是异步编程的一种解决方案:从语法上讲,promise是一个对象,从它可以获取异步操作的消息
-
promise有三种状态: pending(等待态),fulfiled(成功态),rejected(失败态);
-
状态一旦改变,就不会再变。创造promise实例后,它会立即执行。
-
-
resolve reject都是函数, resolve用来处理成功的状态,reject用来处理失败的状态
-
如何访问promise实例中的数据
-
promise提供了两个方法 .then()处理成功 .catch()处理失败(不会同时存在2中状态)
-
promise基本概念
-
Promise 是一个构造函数
-
promise.prototype上包含一个 .then 方法
-
.then()方法 可以用来预先指定失败的回调函数
-
p.then(成功的回调函数 ,失败的回调函数) p.then(result=>{},error=>{})
-
调用p.then()方法时,成功的函数是必选的 失败的回调函数是可选的
谈谈对async/await的理解
async和await
-
await必须结合async使用,async通常不会单独使用
-
async表示这个函数是一个异步函数,它的返回值如果是一个普通数据类型,默认会用Promise将数据包裹
-
await 等待?等待什么?等待的是一个异步的结果
-
异步和同步从上往下的顺序执行
相对于promise的优点:
-
解决了地狱回调问题
-
更简洁 更容易调试 有更好的错误处理 有条件分支处理
-
处理中间值更简单 更清楚异常堆栈来自哪里
跨越问题如何解决
-
同源:2个页面的协议/域名/端口均相同为同源
-
同源策略 :A网站的js不允许和非同源的网站进行资源交互
-
跨域 :协议/域名/端口不一致时(会被浏览器拦截)
-
解决方案 :
-
JSONP 和 CORS和代理服务器
-
CORS:出现的较晚,它是 W3C 标准,属于跨域 Ajax 请求的根本解决方案。支持 GET 和 POST 请求。缺点是不兼容某些低版本的浏览器
-
原理: 设置允许的响应头(需要浏览器和服务器同时支持).后台里面设置
-
-
JSONP:出现的早,兼容性好(兼容低版本IE)。是前端程序员为了解决跨域问题,被迫想出来的一种临时解决方案。
-
原理:根据< script>标签的src属性不受浏览器同源策略的限制
-
动态创建< script>标签,结合它的src属性传递可查询参数
-
参数的值是一个函数名(函数名最好随机生成,防止变量污染)
-
-
服务器返回的是一段函数执行代码
缺点是只支持 GET 请求,不支持 POST 请求。
-
实现过程:
-
script标签里的src属性动态创建和移除
-
在Jsonp请求时,动态向<head>中append(添加)一个<script>标签
-
在Jsonp请求成功后,动态从<head>中移除刚才append的<script>标签
由于浏览器同源策略的限制,网页中无法通过 Ajax 请求非同源的接口数据。但是 <script> 标签不受浏览器同 源策略的影响,可以通过 src 属性,请求非同源的 js 脚本。 因此,JSONP 的实现原理,就是通过 <script> 标签的 src 属性,请求跨域的数据接口,并通过函数调用的形式, 接收跨域接口响应回来的数据
-
-
代理服务器
-
原理: 服务器和服务器之前发送请求不受浏览器同源策略的影响
-
在vue.config.js 文件中配置
-
-
输入网站url地址后发生了什么
-
URL 解析
-
DNS解析,寻找服务器(DNS (Domain Name System) 是一个协议,主要用途是将一个域名解析成 IP 地址,这个过程叫做域名解析 (Name resolution))
-
TCP连接(传输控制协议(TCP,Transmission Control Protocol)),三次握手
-
第一次:浏览器向服务器发送请求(SYN=1),等待服务器确认;
-
第二次:服务器收到请求并确认,回复一个指令(SYN=1,ACK=1);
-
第三次:客户端收到服务器的回复指令,并返回确认(ACK=1);
-
-
发送http请求,找到相应的资源库
-
返回http响应(返回数据)
-
浏览器解析渲染页面
-
断开连接
ajax工作流程及原理
-
Ajax的应用的五个步骤:
1、建立XMLHttpRequest对象;
2、设置回调函数;
3、使用open方法与服务器建立连接;
4、向服务器端发送数据;
5、在回调函数针对不同响应状态进行处理;
-
原理:
ajax的工作原理就是通过XmlHttpRequest对象来向服务器发出异步请求,从服务器中获得数据,然后用Javascript来操作DOM从而更新局部页面
axios 是什么、怎么使用
axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它本身具有以下特征:
-
Ajax 是页面无刷新请求数据操作
-
从浏览器中创建 XMLHttpRequest
-
从 node.js 发出 http 请求
-
支持 Promise API
-
拦截请求和响应
-
转换请求和响应数据
-
取消请求
-
自动转换JSON数据
-
客户端支持防止 CSRF/XSRF
Localstorage、sessionStorage、cookie 的区别
共同点:都是保存在浏览器端、且同源的
区别:
-
请求不同
-
cookie 数据始终在同源的 http 请求中携带(即使不需要)
-
sessionStorage 和 localStorage不会自动把数据发送给服务器,仅在本地保存
-
-
存储大小限制不同
-
cookie 数据不能超过 4K(只适合保存很小的数据,如会话标识)
-
localStorage和sessionStorage 达到 5M
-
-
数据有效期不同
-
cookie只在设置的cookie 过期时间之前有效,即使窗口关闭或浏览器关闭
-
localStorage: 始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据
-
sessionStorage : 仅在当前浏览器窗口关闭之前有效
-
-
作用域不同
-
cookie: 所有同源窗口中都是共享的
-
localStorage: 在所有同源窗口中都是共享的
-
sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面
-
-
安全性问题
-
如果 cookie 被人拦截了,那人就可以取得所有的 session 信息。即使加密也与事无补,因为拦截者并不需要知道 cookie 的意义,他只要原样转发 cookie 就可以达到目的了
-
后端接口没写出来,前端如何进行开发
-
mock(模拟接口返回的信息)
-
单纯的前端mock可以通过抓包工具Fiddler,Charles实现
-
通过修改代理返回的数据,实现多种场景的测试。这里在抓包工具之中会解释
post请求和get请求的区别
-
get 传送的数据长度有限制,post 没有
-
get 通过 url 传递(查询字符串),在浏览器地址栏可见,post 是在请求中传递
-
适用场景
-
post 一般用于表单提交
-
get 一般用于简单的数据查询,严格要求不是那么高的场景
-
Session,Token,Cookie在身份认证方面的区别
-
为什么要使用身份认证:
-
因为http协议是无状态性的,每一次http请求都是独立的
-
连续多个请求之间无直接关系,服务器不会主动保存每次http请求状态
-
-
Session
-
session数据放在服务器上。
-
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能
-
将登陆信息等重要信息存放为session
-
-
Cookie
-
cookie数据存放在客户端上(所以安全性不高)
-
所以每个域的cookie数量是有限的(比较小4kb)
-
每次发送请求会自动携带Cookie
-
-
Token
-
token是服务端生成用于验证用户登录状态的加密数据,和session验证差不多。
-
token验证服务端不需要存储用户会话的配置数据,只是加密的字符串
-
然后用Token设置请求头
-
Token的目的是为了减轻服务器的压力,减少频繁的查询数据库
-
常见的状态码
-
200:请求已成功
-
401:没有权限
-
402: 参数传递错误
-
403:服务器已经理解请求,但是拒绝执行它。
-
404:请求失败,请求所希望得到的资源未被在服务器上发现。(未找到页面)
-
500:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这个问题都会在服务器端的源代码出现错误时出现。
Vue2
Vue是什么
是一个用于创建用户界面的开源JavaScript框架,也是一个创建单页应用的Web应用框架。
它的核心特征是数据驱动(MVVM)
MVVM
MVVM表示的是Model-View-ViewModel
-
Model:模型层,负责处理业务逻辑以及和服务器端进行交互
-
View:视图层:负责将数据模型转化为UI展示出来,可以简单的理解为HTML页面
-
ViewModel:视图模型层,用来连接Model和View,是Model和View之间的通信桥梁
MVVM就是一种结构设计模式。
-
什么是设计模式 : 设计模式,是对软件设计中普遍存在的各种问题所提出的解决方案
-
mvvm的好处 : 减少DOM操作,提高开发效率
-
MVVM设计模式 : M-model(数据模型) V-View(视图) VM-viewmodel(连接数据模型和视图的对象,它可以监听数据的变化,将数据变化映射到视图,反过来也成立)
观察者模式和发布订阅者模式
1、观察者模式优缺点
优点:模式简单,双方直接通信。
缺点:
-
如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
-
虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。
2、发布订阅者模式优缺点
优点:
完全解耦特性:消息的发布者和消息的订阅者在开发的时候完全不需要事先知道对方的存在,可以独立地进行开发。
订阅者可以只订阅自己想要的消息
缺点:发布订阅模式的缺点正是来源于它的优点。通过从订阅者中解耦发布者,它有时会很难保证应用程序的特定部分按照我们期望的运行。倘若我们假设订阅者需要记录或输出一些与应用程序处理有关的错误。如果订阅者执行日志崩溃了(或处于某种原因无法正常运行),由于系统的解耦特性,发布者就不会看到这一点。
SPA(单页应用)的理解
single-page application,它通过动态重写当前页面来与用户交互,整个运用一个页面。
单页面应用(SPA) | 多页面应用(MPA) | |
---|---|---|
组成 | 一个主页面和多个页面片段 | 多个主页面 |
刷新方式 | 局部刷新 | 整页刷新 |
url模式 | 哈希模式 | 历史模式 |
SEO搜索引擎优化 | 难实现,可使用SSR方式改善 | 容易实现 |
数据传递 | 容易 | 通过url、cookie、localStorage等传递 |
页面切换 | 速度快,用户体验良好 | 切换加载资源,速度慢,用户体验差 |
维护成本 | 相对容易 | 相对复杂 |
单页应用优缺点
优点:
-
具有桌面应用的即时性、网站的可移植性和可访问性
-
用户体验好、快,内容的改变不需要重新加载整个页面
-
良好的前后端分离,分工更明确
缺点:
-
不利于搜索引擎的抓取
-
首次渲染速度相对较慢
响应式原理
vue.js 是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter, getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
具体步骤:
第一步:需要 observe 的数据对象进行递归遍历,包括子属性对象的属性,都加上 setter 和 getter 这样的话。给这个对象的某个值赋值,就会触发setter,那么就能监听到了数据变化。
第二步:compile解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,收到通知,更新视图
第三步:Watcher订阅者是Observer和Compile之间通信的桥梁,主要做的事情是: 1、在自身实例化时往属性订阅器(dep)里面添加自己 2、自身必须有一个update()方法 3、待属性变动dep.notice()通知时,能调用自身的 update()方法,并触发Compile中绑定的回调,则功成身退。
第四步:MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的 model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁达到数据变化.> 视图更新:视图交互变化(input)-> 数据model变更的双向绑定效果.
Vue2生命周期
Vue生命周期总共可以分为8个阶段:创建前后, 载入前后,更新前后,销毁前销毁后,以及一些特殊场景的生命周期
生命周期 | 描述 |
---|---|
beforeCreate | 组件实例被创建之初(初始化vue 实例,进行数据观测) |
created | 组件实例已经完全创建(完成数据观测,属性与方法的运算,watch 、event 事件回调的配置) |
beforeMount | 组件挂载之前(处理vm.el) |
mounted | 组件挂载到实例上去之后(挂载el变成dom) |
beforeUpdate | 组件数据发生变化,更新之前 |
updated | 组件数据更新之后 |
beforeDestroy | 组件实例销毁之前 |
destroyed | 组件实例销毁之后 |
activated | keep-alive 缓存的组件激活时 |
deactivated | keep-alive 缓存的组件停用时调用 |
errorCaptured | 捕获一个来自子孙组件的错误时被调用 |
data为什么是函数的存在
-
产生块级作用域--->防止变量污染
-
让各个组件实例维护各自的数据
-
如果单纯的写成对象形式
-
就使得所有组件实例共用了一份 data
-
就会造成一个变了全都会变的结果
-
Vue组件通信的方式
-
通过 props 传递
-
通过 $emit 触发自定义事件
-
使用 ref
-
EventBus
-
$parent 或$root
-
attrs 与 listeners
-
Provide 与 Inject
-
Vuex
-
vue2.6新增的Vue.observable。一个小型的状态管理器
keep-alive原理及缓存策略
-
Vue.js内部在creatd钩子里将DOM节点抽象成了一个个的VNode节点,keep-alive组件的缓存也是基于VNode节点的而不是直接存储DOM结构。
-
它将满足条件(pruneCache与pruneCache)的组件在cache对象中缓存起来,在需要重新渲染的时候再将vnode节点从cache对象中取出并渲染。
-
destroyed钩子则在组件被销毁的时候清除cache缓存中的所有组件实例。
思考题:缓存后如何获取数据?
-
beforeRouteEnter
-
actived
computed原理
computed 也是响应式的,给 computed 设置的 get 和 set 函数,会跟 Object.defineProperty 关联起来。所以 Vue 能捕捉到 读取computed 和 赋值computed 的操作。
计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变时才会重新求值。
首先 computed 计算后,会把计算得到的值保存到一个变量中。读取 computed 时便直接返回这个变量。当使用缓存时,就直接返回这个变量。当 computed 更新时,就会重新赋值更新这个变量。
computed 控制缓存的重要一点是 【脏数据标志位 dirty】,dirty 是 watcher 的一个属性
当 dirty 为 true 时,读取 computed 会重新计算
当 dirty 为 false 时,读取 computed 会使用缓存
1一开始每个 computed 新建自己的watcher时,会设置 watcher.dirty = true,以便于computed 被使用时,会计算得到值
2当 依赖的数据变化了,通知 computed 时,会设置 watcher.dirty = true,以便于其他地方重新渲染,从而重新读取 computed 时,此时 computed 重新计算
3computed 计算完成之后,会设置 watcher.dirty = false,以便于其他地方再次读取时,使用缓存,免于计算
watch原理
根据 watch 的 api,我们需要了解三个地方:
1、监听的数据改变的时,watch 如何工作
watch 在一开始初始化的时候,会 读取 一遍 监听的数据的值,于是,此时 那个数据就收集到 watch 的 watcher 了
然后 你给 watch 设置的 handler ,watch 会放入 watcher 的更新函数中
当 数据改变时,通知 watch 的 watcher 进行更新,于是 你设置的 handler 就被调用了
2、设置 immediate 时,watch 如何工作
当你设置了 immediate 时,就不需要在 数据改变的时候 才会触发。
而是在 初始化 watch 时,在读取了 监听的数据的值 之后,便立即调用一遍你设置的监听回调,然后传入刚读取的值
3、设置了 deep 时,watch 如何工作
-
没有设置 deep
因为读取了监听的 data 的属性,watch 的 watcher 被收集在 这个属性的 收集器中
-
设置了 deep
因为读取了监听的data 的属性,watch 的 watcher 被收集在 这个属性的 收集器中
在读取 data 属性的时候,发现设置了 deep 而且值是一个对象,会递归遍历这个值,把内部所有属性逐个读取一遍,于是 属性和 它的对象值内每一个属性 都会收集到 watch 的 watcher
于是,无论对象嵌套多深的属性,只要改变了,会通知 相应的 watch 的 watcher 去更新,于是 你设置的 watch 回调就被触发了
computed和methods、watch的区别
-
computed是计算属性,methods是方法,都可以实现对 data 中的数据加工后再输出。数据量大,需要缓存的时候用 computed ;每次确实需要重新加载,不需要缓存时用 methods 。
-
Watch是一个侦听的动作,用来观察和响应Vue实例上的数据变动。它们都是vue对监听器的实现,只不过computed主要用于对同步数据的处理,watch则主要用于观测某个值的变化去完成一段开销较大的复杂业务逻辑。
vue-router原理
VueRouter核心是,通过Vue.use注册插件,在插件的install方法中获取用户配置的router对象。当浏览器地址发生变化的时候,根据router对象匹配相应路由,获取组件,并将组件渲染到视图上。
主要有三个重要点:
-
如何在install方法中获取vue实例上的router属性。
可以利用Vue.mixin混入声明周期函数beforeCreate,在beforeCreate函数中可以获取到Vue实例上的属性并赋值到Vue原型链上。
_Vue.mixin({
beforeCreate () {
if (this.$options.router) {
_Vue.prototype.$router = this.$options.router
}
}
})
-
如何触发更新
hash模式下:
-
通过location.hash修改hash值,触发更新。
-
通过监听hashchange事件监听浏览器前进或者后退,触发更新。
history模式下:
-
通过history对象方法修改浏览器地址,触发更新。
-
通过监听popstate事件监听浏览器前进或者后退,触发更新。
-
如何渲染router-view组件
-
通过Vue.observable在router实例上创建一个保存当前路由的监控对象current。
-
当浏览器地址变化的时候,修改监控对象current。
-
在router-view组件中监听监控对象current的变化,当current变化后,获取用户注册的相应component,并利用h函数将component渲染成vnodes,进而更新页面视图。
Vue3
Vue2和Vue3响应式原理的区别
主要就是Object.defineProperty和Proxy的区别
Vue3生命周期
选项式API和组合式API
-
组合式 API 的目的是增强,不是取代选项式 API , vue3 对两种 API 都支持
-
简单的场景使用选项式 API 更加简单方便
-
需要强烈支持 TS 的项目首选组合式 API
-
需要大量逻辑复用的场景首选组合式 API
VueX和Pinia的区别、优劣
Pinia的优点
-
完整的 TypeScript 支持:与在 Vuex 中添加 TypeScript 相比,添加 TypeScript 更容易
-
极其轻巧(体积约 1KB)
-
store 的 action 被调度为常规的函数调用,而不是使用 dispatch 方法或 MapAction 辅助函数,这在 Vuex 中很常见
-
支持多个Store
-
支持 Vue devtools、SSR 和 webpack 代码拆分
Pinia的缺点
-
不支持时间旅行和编辑等调试功能
Vuex的优点
-
支持调试功能,如时间旅行和编辑
-
适用于大型、高复杂度的Vue.js项目
Vuex的缺点
-
从 Vue 3 开始,getter 的结果不会像计算属性那样缓存
-
Vuex 4有一些与类型安全相关的问题
Vite好在哪为什么
vite优点:
-
webpack服务器启动速度比vite慢
由于vite启动的时候不需要打包,也就无需分析模块依赖、编译,所以启动速度非常快。当浏览器请求需要的模块时,再对模块进行编译,这种按需动态编译的模式,极大缩短了编译时间,当项目越大,文件越多时,vite的开发时优势越明显
-
vite热更新比webpack快
vite在HRM方面,当某个模块内容改变时,让浏览器去重新请求该模块即可,而不是像webpack重新将该模块的所有依赖重新编译;
-
vite使用esbuild(Go 编写) 预构建依赖,而webpack基于nodejs, 比node快 10-100 倍
vite缺点:
-
生态不及webpack,加载器、插件不够丰富
-
打包到生产环境时,vite使用传统的rollup进行打包,生产环境esbuild构建对于css和代码分割不够友好。所以,vite的优势是体现在开发阶段
-
没被大规模重度使用,会隐藏一些问题
-
项目的开发浏览器要支持esmodule
TS
对ts的理解
-
定义:
添加了类型系统的 JavaScript,适用于任何规模的项目。类型系统按照「类型检查的时机」来分类,可以分为动态类型和静态类型。
动态类型是指在运行时才会进行类型检查,这种语言的类型错误往往会导致运行时错误。JavaScript 是一门解释型语言,没有编译阶段,所以它是动态类型。
静态类型是指编译阶段就能确定每个变量的类型,这种语言的类型错误往往会导致语法错误。TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 TypeScript 是静态类型
-
好处:
项目带来更高的可维护性,以及更少的 bug。
ts的数据类型
基础:
布尔值、数值、字符串、null
、undefined
以及 ES6 中的新类型 Symbol 和 ES10 中的新类型 BigInt。
任意类型:
any、unknown
其他:
元祖、枚举、void、联合类型、类
never和void的区别
void表示没有任何类型,该类型用作函数的返回值类型。若一个函数没有返回值,那么该函数的返回值类型为void类型。
never表示不包含任何值,用永远没有值
拥有void返回值类型的函数可以正常运行,而never的则无法执行,也无法终止,会抛出异常。
枚举的理解
枚举是一种数据类型,允许我们定义一组命名常量。这样可以提高代码的可读性,便于后续的维护。枚举的使用是通过enum
关键字进行定义。
类型可以分成:
-
数字枚举
当我们声明一个枚举类型是,虽然没有给它们赋值,但是它们的值其实是默认的数字类型,而且默认从0开始依次累加
如果我们将第一个值进行赋值后,后面的值也会根据前一个值进行累加1
-
字符串枚举
-
异构枚举
数字枚举和字符串枚举结合起来混合起来使用
案例:后端的code、0-6对应的日期
泛型
泛型程序设计(generic programming)是程序设计语言的一种风格或范式。
泛型允许我们在强类型程序设计语言中编写代码时使用一些以后才指定的类型,在实例化时作为参数指明这些类型 在typescript
中,定义函数,接口或者类的时候,不预先定义好具体的类型,而在使用的时候在指定类型的一种特性。
泛型通过<>
的形式进行表述,可以声明:
-
函数
-
接口
-
类
正确使用泛型可以达到多类型约束的目的。
项目
token超时处理
一般有三种处理方式: 前端处理 后端处理 双token
-
前端处理
-
前端自己设置一个token有效期
-
在获得token的时候利用cookie-js自带的方法存入一个时间戳
-
在请求拦截器里面做判断现在的时间减去存入token的时间是否超时了
-
超时我的处理的是重新登录(返回登陆页面)
-
-
后端处理
-
如果token超时,后端会返回一个状态码
-
在响应拦截器error中做判断,看看返回的状态码是否登录后端返回的这个token超时状态码,等于的话就做处理(返回登录页面)
-
-
双token处理
-
有的时候登陆成功时后端会返回2个token给我们,一个是现在用的,一个是超时过后用的
-
如果开始用的token超时了,那么做处理,调用第二个token设置到请求头里面
-
如果第二个也超时了,做处理(返回登录页面)
-
登录页面安全问题
使用md5
插件对密码加密
发布通知功能怎么实现
websoket
项目刚上线出现bug
版本回退+拉分支出来修改bug
用什么进行版本管理
git
Echarts在Vue里怎么使用
插件v-echarts,注册组件使用
Echarts里的配置项不够用怎么办
换个插件,比如蚂蚁的AntV
团队中有的人用vue2有的人用vue3怎么办
技术栈选型就确认了。下载vue3的版本,在 vue3 中也可以支持 vue2 选项API 写法。然后在不同的单页面里使用不同的语法。
项目优化
-
vue优化:
-
Vue-Router路由懒加载:Vue异步组件、Webpack的require.ensure()、ES6的import、
-
按需加载UI库
-
如果首屏为登录页,可以做成多入口
-
页面使用骨架屏Skeleton Screen
-
服务端渲染SSR(将组件或页面通过服务器生成html字符串,再发送到浏览器,最后将静态标记"混合"为客户端上完全交互的应用程序)
-
网络优化:
-
减少 HTTP 请求数量
-
利用浏览器缓存,减小
cookie
大小,尽量用localStorage
代替 -
CDN加速,托管静态文件
-
开启 Gzip 压缩:Nginx的gzip缓存压缩、Webpack开启gzip压缩
-
js优化:
-
节流、防抖
-
动态加载、分页加载(大数据渲染)
-
图片懒加载(data-src)减少占用网络带宽
-
使用闭包时,在函数结尾手动删除不需要的局部变量,尤其在缓存dom节点的情况下
-
异步加载js,async解析dom树同时加载js,加载完暂停解析执行js、defer解析dom树同时加载js,解析完执行js
-
css优化:
-
不使用css表达式
-
减少回流(重排)
-
避免使用css文件嵌套过深
-
SEO优化
PC端兼容问题
img下的留白
解决方案:给img设定display:block。
如果图片加a标签在IE9-中会有边框
解决方案:给img设定border:none。
移动端问题
移动端页面滚动滞涩感
css在body元素上添加-webkit-overflow-scrolling: touch;
修改移动端的点击高亮效果
* {-webkit-tap-highlight-color:rgba(0,0,0,0);}
滚动穿透问题
方案一:在html和body上设置overflow:hidden禁止滚动
方案二:阻止touchmove默认事件
var modal = document.getElementById('modal'); // 弹窗dom对象
modal.addEventListener('touchmove', function(e) {
e.preventDefault();
}, false);
方案三:position: fixed
方案一和三,如果需要保持滚动条的位置需要用 js 保存滚动条位置关闭的时候还原滚动位置
在ios和andriod中,audio元素和video元素在无法自动播放
应对方案:触屏即播
$('html').one('touchstart',function(){
audio.play()
})
iOS 系统中文输入法输入英文时,字母之间可能会出现一个六分之一空格
可以通过正则去掉: this.value = this.value.replace(/\u2006/g, '')
IOS移动端click事件300ms的延迟响应
引入第三方插件fastclick可以解决在手机上点击事件的300ms延迟
阻止旋转屏幕时自动调整字体大小
html, body, form, fieldset, p, div, h1, h2, h3, h4, h5, h6 {-webkit-text-size-adjust:none;}
图片模糊问题
根据不一样的像素比,准备不一样的图片,正常来说是1px图片像素 对应1px物理像素,图片的显示就不会模糊啦,但是这样的情况不多,不是非常重要,特殊需求的图,我们不这么做。
.avatar{
background-image: url(conardLi_1x.png);
}
@media only screen and (-webkit-min-device-pixel-ratio:2){
.avatar{
background-image: url(conardLi_2x.png);
}
}
@media only screen and (-webkit-min-device-pixel-ratio:3){
.avatar{
background-image: url(conardLi_3x.png);
}
}
移动端某些情况下input的placeholder会出现文本位置偏上的现象
PC端设置line-height等于height能够对齐,而移动端仍然是偏上,解决是设置line-height:normal
h5底部输入框被键盘遮挡问题
可以通过监听移动端软键盘弹起,Element.scrollIntoView() 方法让当前的元素滚动到浏览器窗口的可视区域内。
window.addEventListener('resize', function() {
if (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA') {
setTimeout(function() {
if ('scrollIntoView' in document.activeElement) {
document.activeElement.scrollIntoView(false)
} else {
document.activeElement.scrollIntoViewIfNeeded(false)
}
}, 0)
}
})
移动端如何做真机测试
方式1、公司有测试服务器,代码放到测试服务器上,进行手机访问测试
方式2、自己电脑上搭服务器,保证自己手机和电脑处于同一个局域网,然后用手机进行访问测试
H5和app的区别
-
H5是通过链接进行访问,而APP是通过应用程序进行访问
-
H5在应用商店里面没有,而APP是有的
-
H5不需要审核就可以上线,而APP是需要审核的
-
H5的响应速度没有APP快文章来源:https://www.toymoban.com/news/detail-481485.html
-
H5的开发成本比APP低文章来源地址https://www.toymoban.com/news/detail-481485.html
到了这里,关于2023前端超全面试题,全是金三银四面试真题整理!附答案。的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!