请问websocket
如何兼容低浏览器的
websocket
是H5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket
使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket
API
中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。对于低版本的浏览器我们可以使用下面几种方法对低版本浏览器进行兼容。
- 引入
SockJS
库,他是JavaScript
的一个库,支持websocket
的浏览器会优先使用原生的websorcket
,如果不支持,则会使用引用的库文件。- 引用
socket.IO
的库文件,这同样是基于时间的双向通信,如何不支持则会使用替代的方案。
Vue单页面的SEO解决方案
方法:用prerender-spa-plugin
结合vue-meta-info
来实现首页的seo
首先就是安装插件:
npm install prerender-spa-plugin vue-meta-info --save
然后在项目的目录下找到文件build/webpack.prod.conf.js
,添加如下代码:
const PrerenderSpaPlugin = require('prerender-spa-plugin')
new PrerenderSpaPlugin(
//将渲染的文件放到dist目录下
path.join(__dirname, '../dist'),
//需要预渲染的路由信息
[ '/index','/about' ],
{
//在一定时间后再捕获页面信息,使得页面数据信息加载完成
captureAfterTime: 50000,
//忽略打包错误
ignoreJSErrors: true,
phantomOptions: '--web-security=false',
maxAttempts: 10,
},
)
描述图片懒加载的方案,并对其原理进行简单的描述
图片懒加载是前端页面优化的一种方式,在页面中有很多图片的时候,图片加载就需要很多时间,很耗费服务器性能,不仅影响渲染速度还会浪费带宽,为了解决这个问题,提高用户体验,所以就出现了懒加载这种方式来减轻服务器的压力,优先加载可视区域的内容,其他部分等进入了可视区域再加载,从而提高性能。
思路:在图片没有进入可视区域时,先不给的src
赋值,这样浏览器就不会发送请求了,等到图片进入可视区域再给src
赋值。图片的真实地址需要存储在data-src
中。图片没有进入可视区域,也就是说图片的offsetTop需要小于页面的可视高度,但想一想,当图片在页面的下方的时候呢,需要页面滚动了一段距离之后才能看到图片,所以这里需要满足img.scrollTop < 页面的可视区域高度+页面滚动的高度,这里是实现图片懒加载的关键,接下来看具体代码的实现
<img src="loading.gif" data-src="1.jpg" alt="">
<img src="loading.gif" data-src="2.jpg" alt="">
<img src="loading.gif" data-src="3.jpg" alt="">
<img src="loading.gif" data-src="4.jpg" alt="">
<img src="loading.gif" data-src="5.jpg" alt="">
<img src="loading.gif" data-src="6.jpg" alt="">
<img src="loading.gif" data-src="7.jpg" alt="">
<img src="loading.gif" data-src="8.jpg" alt="">
<img src="loading.gif" data-src="9.jpg" alt="">
<img src="loading.gif" data-src="10.jpg" alt="">
......
*{
margin: 0;
padding: 0;
}
img{
vertical-align: top;
100%;
height: auto;
}
let img = document.getElementsByTagName('img');
let n = 0; //存储图片加载到的位置,避免每次都从第一张图片开始遍历
lazyload();
window.addEventListener('scroll',lazyload);
function lazyload(){ //监听页面滚动事件
var seeHeight = window.innerHeight; //可见区域高度
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for(let i = n; i < img.length; i++){
if(img[i].offsetTop < seeHeight + scrollTop){
if(img[i].getAttribute("src") == 'loading.gif'){
img[i].src = img[i].getAttribute("data-src");
}
n = i + 1;
}
}
}
请问浮动元素引起的问题和解决方法
浮动元素引起的问题:
- 由于浮动元素已脱离文档流,所以父元素无法被撑开,影响与父级元素同级的元素。
- 与浮动元素同级的非浮动元素(内联元素)会跟随其后,也是由于浮动元素脱离文档流,不占据文档流中额位置。
- 如果该浮动元素不是同级第一个浮动的元素,则它之前的元素也应该浮动,否则容易影响页面的结构显示。
如何清楚浮动:
- 使用css样式中的
clear:both
- 给父元素添加
clearfix
样式
.clearfix:after{
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
.clearfix{
display: inline-block;
} /* for IE/Mac */
跨域的原因与解决方式
跨域是指浏览器的不执行其他网站脚本的,由于浏览器的同源策略造成,是对JavaScript的一种安全限制.说白点理解,当你通过浏览器向其他服务器发送请求时,不是服务器不响应,而是服务器返回的结果被浏览器限制了.
跨域的解决方案
JSONP方式
缺点: get请求,前后端都要修改
优点:无兼容问题(js哪个浏览器不兼容 站出来)
思路:利用js保存全局变量来传递数据,因为js不受同源策略控制,script标签加载机制html
反向代理,ngixn
你在工作中如何优化项目代码,请举例说明.
深拷贝和浅拷贝的区别,请你实现深拷贝的几种方法?
深拷贝和浅拷贝最根本的区别在于是否真正获取一个对象的复制实体,而不是引用。
浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变。
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象。
实现深拷贝:
let obj={
name:'zs',
age:18
}
let deepCloneObj=JSON.parse(JSON.stringify(obj))
deepCloneObj.name=28;
console.log(obj.age);//18
优点:优点是方便快捷,性能相对比较好;
缺点:但是复杂的对象进行JSON转换有可能会丢失属性.
let obj={
name:'zs',
main:{
a:1,
b:2
},
fn:function(){},
friends:[1,3,5,7,9]
}
function copy(obj){
let newObj=null;
if(typeof(obj)=='object'&&obj!==null){
newObj=obj instanceof Array ? []:{};
for(var i in obj){
newObj[i]=copy(obj[i])
}
}else{
newObj=obj
}
return newObj
}
var obj2 = copy(obj)
obj2.name = '修改成功'
obj2.main.a = 100
console.log(obj,obj2)
请你实现快速排序算法
function quickSort(arr) {
/*
* 创建len保存数组的长度,每次获取数组的长度都要实时查询不利于性能;
* index作为保存取到的中间值;
* pivot保存比较参照物;
* left、right作为子数组的容器;
*/
var len = arr.length,
index,
pivot,
left=[],
right=[];
// 如果数组只有一位,就直接返回数组,递归的终止条件;
if (len <= 1) return arr;
//获取中间值的索引,使用Math.floor向下取整;
index = Math.floor(len / 2);
// 使用splice截取中间值,第一个参数为截取的索引,第二个参数为截取的长度;
// 如果此处使用pivot=arr[index]; 那么将会出现无限递归的错误;
// splice影响原数组,原数组长度减一;
pivot = arr.splice(index, 1);
len -= 1;
// 小于arr[pivot]的存到left数组里,大于arr[pivot]的存到right数组;
for (var i = 0; i < len; i++) {
if (pivot > arr[i]) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
// 不断把分割的左右子数组传入quickSort,直到分割的只有一位直接返回子数组本身,递归终止;
// 把每次分割的数组一层一层的用concat连接起来;
// 每一层left里的元素都小于对应的pivot,right里的元素都大于对应的pivot;
return quickSort(left).concat(pivot, quickSort(right));
}
如何判断JS变量的数据类型
typeof
1 // 特殊的基本数据类型判断
2 typeof null // 'object'
3 // 特殊的引入数据类型判断
4 typeof function () {} // 'function'
5 typeof new Function() // 'function'
instanceof
1 function Car () {} // 定义一个构造函数
2 console.log(new Car() instanceof Car)
3 console.log({} instanceof Object) // true
4 console.log([] instanceof Array) // true
5 console.log(new String('') instanceof String) // true
6 console.log('' instanceof String) // false
Object.prototype.toString.call(null) // [object Null]
Object.prototype.toString.call(undefined) // [object Undefined]
Object.prototype.toString.call(1) // [object Number]
Object.prototype.toString.call(new Number(1)) // [object Number]
Object.prototype.toString.call('a') // [object String]
Object.prototype.toString.call(new String('a')) // [object String]
Object.prototype.toString.call({}) // [object Object]
Object.prototype.toString.call([]) // [object Array]
Object.prototype.toString.call(new Date()) // [object Date]
Object.prototype.toString.call(/^d$/g) // [object RegExp]
Object.prototype.toString.call(() => {}) // [object Function]
Object.prototype.toString.call(new Function()) // [object Function]
function Car () {} // 定义一个构造函数
Object.prototype.toString.call(new Car()) // [object Object]
rem要如何配置
rem是只相对于根元素htm的font-size,即只需要设置根元素的font-size,@media
可以针对不同的屏幕尺寸设置不同的样式
例如:
/*dpi*/
/* for 1080+ px width screen */
/* for 1080 px width screen */
/* for 800 px width screen */
/* for 800 px width screen */
@media only screen and (min- 751px) { html, body { font-size: 31.25px; } }
/* for 800 px width screen */
@media only screen and (max- 750px) { html, body { font-size: 31.25px; } }
/* for 720 px width screen */
@media only screen and (max- 720px) { html, body { font-size: 30px; } }
/* for 640 px width screen */
@media only screen and (max- 640px) { html, body { font-size: 27px; } }
/* for 540 px width screen */
@media only screen and (max- 540px) { html, body { font-size: 22.5px; } }
/* for 480 px width screen */
@media only screen and (max- 480px) { html, body { font-size: 20px; } }
/* for 450 px width screen */
@media only screen and (max- 450px) { html, body { font-size: 18.9px; } }
/* for 414 px width screen */
@media only screen and (max- 414px) { html, body { font-size: 17.25px; } }
/* for 375 px width screen */
@media only screen and (max- 375px) { html, body { font-size: 15.625px; } }
/* for 320 px width screen */
@media only screen and (max- 320px) { html, body { font-size: 13.5px; } }
vue中key的作用
key值大多情况下使用在循环语句中,从本质来讲主要作用大概有以下两点:
- 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes,相当于唯一标识ID。
- Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染, 因此使用key值可以提高渲染效率,同理,改变某一元素的key值会使该元素重新被渲染。
至少写出三种减少页面加载时间的方法
- 重复的HTTP请求数量应尽量减少
- 减少调用其他页面,文件的数量
- 精灵图
- 压缩JavaScript css代码
- 在文件头部配置css样式的定义
- 在文件末尾放javascript脚本
- css,javascript改为外部调用
- 使用CDN网络加速
- 服务器启用Gzip压缩功能
- ajax采用缓存调用
- 缩减iframe的使用,如无必要尽量别使用
- 优化图片文件
路由导航(路由)守卫
全局导航守卫
- 全局前置守卫
router.beforeEach(fn)
- n中有三个参数
- to:表示要去哪里
- from:表示从哪里来
- next:表示通不通过
-
关于next的使用
- next() 等价于 next( true ) 表示可以从当前路由跳转到目标路由
- next( false ) 表示不通过, 表示从当前路由跳转不到目标路由
- next(’/login’) 等价于 next({path:’/login’}) 跳转指定的路由
- next(’/login’) 等价于 next({path:’/login’,params,query})
- next( fn ) 数据预载
// 全局前置路由守卫 router.beforeEach( (to,from,next) =>{ const name = localStorage.getItem('name');//查看本地存储上是否有name对象 if( name || to.path === '/login'){//短路逻辑,有就可以继续执行,没有就跳转到登录页面 next() }else{ next( '/login' )//跳转登录页面 } }) // 全局后置路由守卫 router.afterEach(fn),表示离开当前路由时执行 router.afterEach( (to,from,next)=> { if(to.path === '/user'){//当to的path值等于这个时 alert('确定进入user吗') } })
-
全局的后置守卫
afterEach(fn)
路由独享守卫
- 写在路由表中的守卫钩子
- 针对的是和当前路由相关的,那么其他与之不相关的路由,它是无法监听它的变化情况的
{
path: '/user',
component: User,
beforeEnter: ( to,from,next ) =>{
// console.log(to)
const name = localStorage.getItem('name');//判断本地存储有没有name对象
if( name){//存在就可以继续执行
next()
}else{
setTimeout(()=>{
alert('你还没有登录,点击跳转登录')//不存在就弹窗告诉没有登录,点击登录
next('/login')
},0)
}
}
},
DOM阻塞
DOM的阻塞会导致页面的加载时间延迟,阻塞的资源有html,css(包括web font),javascript
不是单页面能使用vuex么
看你个人需要了。单页应用为什么要用的?还不是他的逻辑太复杂了,混乱的不得了,不找个工具管理一下各种坑人。如果一个单页应用里面什么js都没有,就是html,那你引入vuex有什么用吗?那么好了,并不是说单页应用什么的,是你遇到了什么问题,用什么东西去解决好了附上vuex里面其实也写了,为了管理你的共享的东西,共享的状态。
swagger的了解
Swagger
是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful
风格的Web
服务。1、是一款让你更好的书写API文档的规范且完整框架。
2、提供描述、生产、消费和可视化
RESTful
Web Service
。3、是由庞大工具集合支撑的形式化规范。这个集合涵盖了从终端用户接口、底层代码库到商业API管理的方方面面。
面向对象的理解
在现实生活中任何物体都可以归为一类事物,而每一个个体都是一个事物的实例,面向对象的编程是以对象为中心.
其中面向对象的有三大特征,分别是封装,继承和多态
其中的多态表现为:方法的重载和方法的重写;
方法重载:重载是指不同的函数使用相同的函数名,但是函数的参数个数或类型不同。调用的时候根据函数的参数来区别不同的函数.
方法重写:重写(也叫覆盖)是指在派生类中重新对基类中的虚函数(注意是虚函数)重新实现。即函数名和参数都一样,只是函数的实现体不一样.
H5有哪些屏幕触碰事件
touchstart:触摸开始的时候触发
touchmove:手指在屏幕上滑动的时候触发
touchend:触摸结束的时候触发
而每个触摸事件都包括了三个触摸列表,每个列表里包含了对应的一系列触摸点(用来实现多点触控):
touches:当前位于屏幕上的所有手指的列表。
targetTouches:位于当前DOM元素上手指的列表。
changedTouches:涉及当前事件手指的列表。
每个触摸点由包含了如下触摸信息(常用):
identifier:一个数值,唯一标识触摸会话(touch session)中的当前手指。一般为从0开始的流水号(android4.1,uc)
target:DOM元素,是动作所针对的目标。
pageX/pageX/clientX/clientY/screenX/screenY:一个数值,动作在屏幕上发生的位置(page包含滚动距离,client不包含滚动距离,screen则以屏幕为基准)。
radiusX/radiusY/rotationAngle:画出大约相当于手指形状的椭圆形,分别为椭圆形的两个半径和旋转角度。初步测试浏览器不支持,好在功能不常用,欢迎大家反馈。
Label通常怎么用,用什么场景
label
标签为input元素定义标注(标记),它不会向用户呈现任何特殊效果,和span
标签类似。但label
标签和span
标签最大的区别就是它为鼠标用户改进了可用性,可以关联特定的表单控件。
主要的应用场景:
label
标签常用于与checkbox
或radio
关联,以实现点击文字也能选中/取消checkbox
或radio
css写一个三角形
<div></div>
<style>
div {
0;
height: 0;
border-top: 50px solid transparent; /*这行去掉也行*/
border-bottom: 50px solid yellow;
border-right: 50px solid transparent;
border-left: 50px solid transparent;
}
</style>
或者
<div></div>
<style>
div {
height:0px;
0px;
border-top:solid 100px red;
border-left:solid 100px green;
}
</style>
递归组件实现以及有什么需要注意的
所谓的递归组件就是组件里面自己调用自己
比如:
<template>
<ul>
<my-ul></my-ul>
<ul>
</template>
<script>
export default{
name:'myUl'
}
</script>
需要注意的是:
一般我们引入的时候都是import myUl from '../..xxx',然后通过component注册到组件上;
但是如果没有结束条件的情况直接递归的话会报错的,所以 针对递归组件我们需要加上结束条件.
link 的作用
link标签有两个作用:
- 定义文档和外部文件的关系;
- 是链接样式表
created和mounted的区别
created
是 在模板渲染之前调用的,他只是初始化好了一些数据,并没有到渲染视图的阶段触发;所以在此阶段不能获取DOM
节点;mounted
在模板渲染之后触发,通常此时页面初始化完成,可以获取DOM
节点.
垃圾回收机制怎么触发
垃圾回收有两种实现方式,分别是标记清除和引用计数
- 标记清除
当变量进入执行环境时标记为“进入环境”,当变量离开执行环境时则标记为“离开环境”,被标记为“进入环境”的变量是不能被回收的,因为它们正在被使用,而标记为“离开环境”的变量则可以被回收;
function func(){
const a=1
const b=2
//函数执行时,a,b分别标记,进入环境
}
func()//函数执行结束,a,b被标记,离开环境,被回收
-
引用计数
统计引用类型变量声明后被引用次数,当次数为0时,变量将被回收
vue中updated和destroyed我们能做什么
vue钩子函数updated,当事先设置好data里面的数据被改变后会有updeted函数.且视图更新完毕,所以可以在数据更新时我们做的一些操作写在updated钩子中;
destroyed钩子,当我们离开某个页面的时候,实例销毁,便会调用此钩子,我们可以将离开页面的某些操作放在此钩子中,例如清除某些定时器之类的...
vue2.0双向绑定原理的缺陷
vue2.0的双向绑定用的是Object.defineProperty()进行数据拦截的操作,其缺点是无法监控到数组下标的变化,导致通过数组下标添加元素不能实时响应.还有就是只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果属性是对象则要进行深度遍历.
nextTick
vue中的nextTick是将回调函数延迟在下一次DOM更新数据后调用,可以理解为,当数据更新了,视图更新后自动执行该函数;
<div id="app">
<div id="text" v-if="isShow">这是一段文本</div>
<button @click="getText">获取内容</button>
</div>
</body>
<script src="./node_modules/vue/dist/vue.min.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
isShow: false,
},
methods: {
getText() {
this.isShow = true;
this.$nextTick(() => {
let text = document.querySelector("#text").innerHTML;
console.log(text);
});
},
},
});
</script>
1累加到100,用递归实现
function Add() {
this.nul = 0
this.fn = function(num) {
if (num == 1) {
return this.nul = 1
}
return this.nul = num + this.fn(num - 1)
}
}
const add=new Add()
add.fn(100)
console.log(add.nul);//5050
vue中的e-charts是怎么用的,具体代码,后台返回的数据是怎么处理的,怎么渲染
首先安装echarts
来画图
yarn add echarts
准备具备宽高的容器装,用ref获取dom元素
<div ref="main" style=" 600px; height:400px;"></div>
导入echarts
import echarts from 'echarts'
初始化echarts实例
handleTabClick (tab) {
if (tab.name === 'img') {
// 使用echarts画图
// console.log('画图')
// 基于准备好的dom,初始化echarts实例
const myChart = echarts.init(this.$refs.main)
// 指定图表的配置项和数据
const option = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data: ['销量']
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
}
// 使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
}
}
TCP和UDP以及三次握手四次挥手
组件中的data为啥是一个函数
因为组件是用来复用的,且JS里对象是引用关系,如果组件中的data 是一个对象的话,那么这样作用域没有隔离,data会相互影响,data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响,而Vue实例是不会被复用的,所以不会存在引用对象的问题.
axios封装原理
axios
是一个轻量的 HTTP客户端
,它基于 XMLHttpRequest
服务来执行 HTTP 请求,支持丰富的配置,支持 Promise
,支持浏览器端和 Node.js
端。
data和props的区别
vue提供了两种不同的存储变量:props
和data
data
是每个组件的私有内存,可以在其中存储需要的任何变量。props
是将数据从父组件传递到子组件的方式。子组件中的data数据,不是通过父组件传递的是子组件私有的,是可读可写的。
子组件中的所有 props中的数据,都是通过父组件传递给子组件的,是只读的。
http请求有几种
http请求中的8种请求方法
1、opions
返回服务器针对特定资源所支持的HTML请求方法 或web服务器发送*测试服务器功能(允许客户端查看服务器性能)
2、Get
向特定资源发出请求(请求指定页面信息,并返回实体主体)
3、Post
向指定资源提交数据进行处理请求(提交表单、上传文件),又可能导致新的资源的建立或原有资源的修改
4、Put
向指定资源位置上上传其最新内容(从客户端向服务器传送的数据取代指定文档的内容)
5、Head
HEAD就像GET,只不过服务端接受到HEAD请求后只返回响应头,而不会发送响应内容。当我们只需要查看某个页面的状态的时候,使用HEAD是非常高效的,因为在传输的过程中省去了页面内容。
6、Delete
请求服务器删除request-URL所标示的资源*(请求服务器删除页面)
7、Trace
回显服务器收到的请求,主要用于测试和诊断6386
8、Connect
HTTP/1.1协议中能够将连接改为管道方式的代理服务器
- 如何优化await导致多个异步操作的延迟问题
- 预加载的优化方法
脚手架帮我们做了什么?
vue 的脚手架的作用是来自动一键生成vue+webpack的项目模板.包含依赖库,免去你手动安装各种插件.
脚手架中Babel插件帮我们做了js语法降级,兼容性较好;
还可以使用typescript的语法;
里面可以配好路由和vuex及多种css预处理器.
说一下flex布局,以及媒体查询
flex:弹性布局,用来为盒子模型提供最大的灵活性.任何容器都能指定为flex容器.
给容器加上display:flex后,他的子盒子会自动成为容器成员,默认情况下,所有的成员都排在同一条轴线上.
媒体查询的作用是通过监听屏幕的宽度来现实不停的内容的大小,从而能够在不同尺寸的设备上正常显示内容.
语法:
@media only screen and (max- 420px) {
body {
background-color: red;
}
}
手写继承
- 原型链继承:
将父类实例对象 赋值 给子类的原型对象。
function SuperClass(){
this.name = 'super';
this.list = [];
}
SuperClass.prototype.sayHi = function(){
console.log('i am is super');
}
function SubCalss(){
this.name = 'sub'
}
SubCalss.prototype = new SuperClass();
const sub = new SubCalss();
console.log(sub); // Object { name:"sub" }
console.log(sub.name); //sub
console.log(sub.__proto__.name); // super
sub.sayHi();// i am is super
const sub1 = new SubCalss();
sub1.list.push('eee')
console.log(sub.list); // [ "eee" ]
- 借用构造函数
调用父类的构造函数
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
// function Chinese(name,age,gender,skin){
// this.name = name;
// this.age = age;
// this.gender = gender;
// this.skin = skin;
// }
function Chinese(name,age,gender,skin){
Person.call(this,name,age,gender);
this.skin = skin
}
const person = new Chinese('zs',18,'nan','red')
console.log(person.name); // zs
- 组合继承
原型链继承 + 借用构造函数继承
function Person(name,age,gender){
this.name = name;
this.age = age;
this.gender = gender;
}
Person.prototype.sayHi = function(){
console.log(`hello ${this.name}`);
}
function Chinese(name,age,gender,skin){
Person.call(this,name,age,gender);
this.skin = skin
}
Chinese.prototype = new Person();
let zs = new Chinese('zs',18,'male','red')
zs.sayHi() // hello zs
手写节流防抖并说明区别
- 节流函数 throttle
规定在一个单位时间内,只能触发一次函数。如果这个单位时间内触发多次函数,只有一次生效。
let btn = document.querySelector('button')
let the = throttle(function(){
console.log(this);
},3000)
btn.onclick = the;
//------------------------------------
function throttle(fn, wait) {
// 当前时间
let prev = new Date();
// 返回方法调用
return function() {
// 获取当前时间
const now = new Date();
if (now - prev > wait) {
// 时间到了之后调用方法并且传入 this
fn.apply(this);
// 更新时间
prev = new Date();
}
}
}
- 防抖 debounce
延迟执行/在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时,重新出发定时器。
function throttle(fun,time){
// 定时器
let timeOut;
// 返回方法调用
return function(){
clearTimeout(timeOut)
// 没达到时间的开启一个定时器
timeOut = setTimeout(() => {
// 定时器时间到了之后调用方法
fun.call(this)
},time);
}
}
Ajax 二次封装
function axios(options){
// 创建 promise 对象
return new Promise((resolve,reject) => {
// 1 创建一个 xhr 对象
const xhr = new XMLHttpRequest()
// 2 设置请求方式和地址
xhr.open(options.methods,options.url)
// 3 设置请求体,发送请求
xhr.send(null)
// 4 监听结果 xhr 状态:4 才行
xhr.onreadystatechange = function(){
if(xhr.status === 4){
if(xhr.status === 200){
console.log('成功')
resolve(JSON.parse(xhr.responseText))
} else {
console.log('失败')
reject(new Error(xhr.responseText))
}
}
}
})
}
斐波那契数列以及优化思路
耗时:普通递归 > 改进递归 > for循环
- 递归
①普通递归:运行大了会卡死
function fibonacci(n) {
if (n == 1 || n == 2) {
return 1
};
return fibonacci(n - 2) + fibonacci(n - 1);
}
fibonacci(30)
改进:
②改进递归-前两位数字做成参数
function fibonacci(n) {
function fib(n, v1, v2) {
if (n == 1)
return v1;
if (n == 2)
return v2;
else
return fib(n - 1, v2, v1 + v2)
}
return fib(n, 1, 1)
}
fibonacci(30)
③改进递归-里用闭包把运算结果存储在数组里
var fibonacci = function () {
let memo = [0, 1];
let fib = function (n) {
if (memo[n] == undefined) {
memo[n] = fib(n - 2) + fib(n - 1)
}
return memo[n]
}
return fib;
}()
fibonacci(30)
③-1 改进递归-摘除存储计算结果的函数功能
var memoizer = function (func) {
let memo = [];
return function (n) {
if (memo[n] == undefined) {
memo[n] = func(n)
}
return memo[n]
}
};
var fibonacci=memoizer(function(n){
if (n == 1 || n == 2) {
return 1
};
return fibonacci(n - 2) + fibonacci(n - 1);
})
fibonacci(30)
- 循环
① for 循环
function fibonacci(n) {
var n1 = 1, n2 = 1, sum;
for (let i = 2; i < n; i++) {
sum = n1 + n2
n1 = n2
n2 = sum
}
return sum
}
fibonacci(30)
② for循环 + 解构赋值
var fibonacci = function (n) {
let n1 = 1; n2 = 1;
for (let i = 2; i < n; i++) {
[n1, n2] = [n2, n1 + n2]
}
return n2
}
fibonacci(30)
webpack 怎么实现兼容
就是开发模式和生产模式都给出独立的配置文件,两边互不干扰,本地调试调用开发模式的配置文件编译,最终发布包里的文件由编译环境调用生产模式的配置文件编译生成。
webpack 流程
1、建目录 dist , src/main.js
2、初始化:yarn init -y
3、安装包:yarn add webpack webpack-cli -D
4、在 package.json 中配置 script
"scripts":{ "build":"webpack ./src/main.js" }
打包 ./src/main.js 到 dist 目录中去
5、yarn build
webpack 会不会配置 -loaders的配置
webpack 默认只认识 js 文件和 json 文件,但是 webpack 可以使用 loader 来加载预处理文件。
- 处理 css 文件
// 1 安装依赖
yarn add style-loader css-loader -D
// 2 配置
modules:{
// loader 规则
rules:[
{
test:...,
use:['style-loader','css-loader']
}
]
}
- 分离 css 文件 mini-css-extract-plugin
// 1 安装依赖
yarn add mini-css-extract-plugin -D
// 2 在 webpack.config.js 中引入这个模块
// 引入分离 css 文件的 模块
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
// 3 配置loaders
// 模块加载
module: {
// loader的规则
rules: [
// 配置 css 文件的解析
{
test: /.css$/,
use: [ // 根据官方文档写的,注意'css-loader'的书写位置
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath:'../',
},
},
'css-loader'
]
},
],
}
// 4 插件的配置
plugins: [
new HtmlWebpackPlugin({ template: './public/index.html' }),
// 定义打包好的文件的存放路径和文件名
new MiniCssExtractPlugin({
filename:'css/index.css'
})
],
- 处理 less 文件
yarn add less less-loader -D
// 配置 less 文件的解析
{
test: /.less$/,
use: [
// 分离出 css 内容
{
loader: MiniCssExtractPlugin.loader,
options: {
publicPath:'../',
},
},
'css-loader',
'less-loader'
]
- 处理图片 url-loader
1 下载依赖
yarn add url-loader file-loader -D
2 配置 loader
{
test: /.(png|jpg|gif)$/i,
use: [
{
loader: 'url-loader',
options: {
// 超过 8k 就不转 base64, 小于 8k 才转
limit: 8 * 1024
}
}
]
}
- 使用 babel 处理高版本的 js
yarn add -D babel-loader @babel/core @babel/preset-env
// 配置规则
module: {
rules: [
{
test: /.js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
}
原型链
用 vue 写项目会有哪些问题
- 列表进入详情页的传参问题
例如商品列表页面前往商品详情页面,需要传一个商品id。比如在c页面的路径为 .../#/detail?id=1 ,可以看到传了一个参数 id=1 ,刷新页面还会存在,此时在c页面可以通过id来获取对应的详情数据,获取 id 的方式是
this.$route.query.id
vue传参方式有:query、params
+ 动态路由传参
区别:
1、query
通过path切换路由,params
通过name
切换路由
2、query
通过 this.$route.query
,params
通过this.$route.params
来传参。
3、query传参的 url 展现方式:/detail?id=1&user=123&identity=1&更多参数
4、params 动态路由传参,一定要在路由中定义参数,然后在路由跳转的时候必须加上参数,否则就是空白页
- 本地开发环境请求服务器接口跨域的问题
可以更改axios的默认配置axios.defaults.baseURL = '/api'
- axios 封装和 api 接口统一管理
axios封装主要是进行请求的拦截和响应的拦截,在请求的拦截中可以携带 userToken,post请求头、qs对post提交数据的序列化等。
-
UI库按需加载
-
只在当前页面中覆盖ui库组件中的样式
①给第三方组件写个class ---> 不优雅,存在全局污染和命名冲突的问题。
②通过深度选择器解决 /deep/
- Vue-Awesome-Swiper 基本能解决所有的轮播需求
安装
cnpm install vue-awesome-swiper --save
引入组件:import 'swiper/dist/css/swiper.css'
import { swiper,swiperSlide } from 'vue-awesome-swiper'
-
在config/index.js中设置
productionSourceMap:false
设置不生成 .map 文件 -
websocket 是啥
是html5出的一个持久化的协议,借用了http的协议来完成一部分握手,在握手阶段是一样的。
Vue.mixin的作用 (混入)
混入(mixin)是一种分发Vue组件中可复用功能的方式。混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被混入该组件本身的选项。
递归组件
就是组件里面自己调用自己
注意:如果没有结束条件的情况直接递归的话会报错的,所以需要加上结束条件
$attrs和$listeners
主要应用是实现多层嵌套传递
组件A与组件B通信一般都会使用组件B中转,即A传递给B,B再给C,但是如果A到C组件之间嵌套的组件过多,需要传递的事件和属性较多,会导致代码繁琐,代码维护困难。在vue2.4中,为了解决该需求,引入了$attrs
和$listeners
,新增了inheritAttrs
选项。
-
$attrs
一个组件在父组件中被引用,$attrs
就是组件标签上的静态值(attr)和动态属性值(:attr)的对象集合,这个集合不包含class
,style
和事件属性 -
与 $props 比较
$props
必须在组件中注册了props
才能拿到值,所以在嵌套层级比较深的组件中$attrs
拿值更加便捷 -
$listeners 的使用
包含了父作用域中的 (不含
.native
修饰器的)v-on
事件监听器。它可以通过v-on="$listeners"
传入内部组件。
两点:
1、$listeners
是组件的内置属性,它的值是父组件(不含.native
修饰器的)v-on
事件监听器。
2、组件可以通过在自己的子组件上使用v-on="$listeners"
,进一步把值传给自己的子组件。如果子组件已经绑定$listener
中同名的监听器,则两个监听器函数会以冒泡的方式先后执行。
浏览器渲染过程
在地址栏中输入后,按下 enter,会发生什么?
浏览器会判断输入的是一个地址,还是一个query。
1、浏览器会查询本地缓存的dns列表,以获取主机地址,若dns缓存未命中,则浏览器发出请求,进行查询,查询之后的结果缓存起来。
2、连接主机
3、发送请求
4、获得一个服务器的响应
5、进行渲染
- 过程
1、解析html,得到一个DOM tree
2、解析CSS,得到CSS tree
3、两者整合成渲染树,render tree
4、布局
5、绘制
Vuex 页面刷新数据丢失问题
1、使用localStorage实时保存vuex的数据
2、使用vuex-along插件
uni-app和element中自带的bug
- uni-app:
①
input
高度更改不了---->解决:在外面包一层view
②v-show
在打包后不起作用 ---->可用v-if
或者通过class
绑定来解决这个问题
③Data.parse()
时间转化为时间戳出现NaN
,但pc正常 ----> 将-
替换为/
④手机端uni.createSelectorQuery()
为空,但在PC正常 ----> 后面加一个.in(this)
,如果在函数内部还需要在函数外部重新声明一下this
⑤number.toFixed()
方法报错失效,但pc正常 ---->number
变量在真机运行的时候可能会变为string
数据类型.通过toFixed()
就无法进行运算了,所以使用parseInt()
或者parseFloat()
将number
变量变为数字类型
⑥真机测试null
会显示出来而在PC则没有关系 ---->在真机上加以判断让其不显示或者写一个三元运算即可
- element:
原生js轮播图思路
1 首先是轮播切换,就是一个定时器,定时的调用这个切换函数,并且这里需要注意num的值,因为num切换到最后一张,下一张就应该是第一张。
2 接下来是点击左右切换按钮,左切换按钮要留意切换到第一张的时候,需要将下一张定义为最后一张,右切换按钮需要留意最后一张,下一张是第一张。
3 最后是小圆点点击跳转到对应的图片。
Bus使用的弊端
必须先 on 监听才能进行 emit 触发 (用 vuex 解决)
原型链prototype和proto区别
prototype
是函数的一个属性(每个函数都有一个prototype属性),这个属性是一个指针,指向一个对象。它是显示修改对象的原型的属性。__proto__
是一个对象拥有的内置属性,__proto__
是js内部使用寻找原型链的属性。简单来说,在js中每个对象都会有一个__proto__
属性,当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去__proto__
里找这个属性,这个__proto__
又会有自己的__proto__
---->原型链
es6中遍历对象
1、for...in
2、Object.key()---返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含Symbol属性)
3、Object.getOwnPropertyNames(obj)---返回一个数组,包含对象自身的所有属性(不包含Symbol属性,包括不可枚举属性)
4、Reflect.ownKeys(obj)---返回一个数组,包含对象自身的所有属性,不管属性名是Symbol或字符串,也不管是否可枚举
数据类型 (8种)
ES5:
Number,String,Boolean,undefined,object,Null
ES6:Symbol (这种类型的对象永不相等,即始创建的时候传入相同的值,可以解决属性名冲突的问题,作为标记)BigInt
:操作大整数
检测数据类型
1、typeof 一般主要用来检测基本数据类型,因为它检测引用数据类型返回的都是object(检测null返回的也是object)
2、instanceof 这个方法主要是用来准确地检测引用数据类型(不能用来检测基本数据类型)
3、Object.prototype.toString() 可以用来准确地检测所有数据类型
updated 的使用场景
比如即时聊天项目,打开当前会话加载完消息后需要自动滚到窗口最底部,可以用settimeout解决,但是时间不好控制,太短页面还没渲染滚动的高度不准确影响用户体验,可用updated
深拷贝实现方法思路
1、Json.stringify和Json.parse
//通过js的内置对象JSON来进行数组对象的深拷贝
function deepClone(obj) {
let _obj = JSON.stringify(obj);
let objClone = JSON.parse(_obj);
return objClone;
}
2、Object.assign()拷贝
3、通过JQuery的extend方法实现深拷贝
let $ = require('jquery');
let obj1 = {
a: 1,
b: {
f: {
g: 1
}
},
c: [1, 2, 3]
};
let obj2 = $.extend(true, {}, obj1);
4、lodash.cloneDeep()实现深拷贝
let _ = require('lodash');
let obj1 = {
a: 1,
b: { f: { g: 1 } },
c: [1, 2, 3]
};
let obj2 = _.cloneDeep(obj1);
5、使用递归的方式实现深拷贝
function _deepClone(source) {
let target;
if (typeof source === 'object') {
target = Array.isArray(source) ? [] : {}
for (let key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] !== 'object') {
target[key] = source[key]
} else {
target[key] = _deepClone(source[key])
}
}
}
} else {
target = source
}
return target
}
路由懒加载
为了使首屏组件加载速度更快一些,解决白屏问题
常用方法:Vue异步组件和ES中的import
Vue异步组件:component:resolve=>(require(['需要加载的路由的地址']),resolve)
import方法(最常用):const HelloWorld = ()=>import('需要加载的模块地址')
token 存在哪里
state + localStorage
ajax 原理
简单来说通过
XmlHttpRequest
对象来向服务器发送异步请求,从服务器获得数据,然后用js来操作DOM而更新页面,其中最关键的是从服务器获得请求数据。
优点:
<1>.无刷新更新数据。
<2>.异步与服务器通信。
<3>.前端和后端负载平衡。
<4>.基于标准被广泛支持。
<5>.界面与应用分离。
缺点:
<1>.AJAX干掉了Back和History功能,即对浏览器机制的破坏。
<2>.AJAX的安全问题。
<3>.对搜索引擎支持较弱。
<4>.破坏程序的异常处理机制。
<5>.违背URL和资源定位的初衷。
<6>.AJAX不能很好支持移动设备。
<7>.客户端过肥,太多客户端代码造成开发上的成本。
axios中before形式
vue解决刷新页面 vuex数据、params参数消失的问题
关于刷新页面数据消失原因有两种,一是通过路由 params 传值,二是 vuex 传值
一、通过路由params传值
路由传值有两种方式,params和query,params传值刷新页面是要消失的,然而query却不会,两者的区别就在于query会把传递的参数显示在url地址中,就像这样:/adminUser/001001001?jum=001001001211,参数过多的话url地址会变得非常难看,如果你并不在意url地址难看与否,那么你可以不使用params,而是使用query,只是切换个单词而已,这是一种方法(不是最优办法)。还有一种方法,就是在定义路由的时候,给path设定参数。
二、使用vuex
需要一个全局的方法来,将store的数据存储在localStorage里
在App.vue中,created初始化生命周期中写入以下方法:
//在页面刷新时将vuex里的信息保存到localStorage里
window.addEventListener("beforeunload",()=>{
localStorage.setItem("messageStore",JSON.stringify(this.$store.state))
})
//在页面加载时读取localStorage里的状态信息
localStorage.getItem("messageStore") && this.$store.replaceState(Object.assign(this.$store.state,JSON.parse(localStorage.getItem("messageStore"))));
// replaceState 这个方法呢,查了api,就是替换 store 的根状态,然后通过对象赋值assign将localStorage进行赋值
// beforeunload这个方法是在页面刷新时触发的,将store中的信息存入localStorage
vue组件中的data为啥必须是函数
data是一个函数时,每个组件实例都有自己的作用域,每个实例相互独立,不会相互影响.