v-if和v-show的区别
v-if控制元素是否渲染
v-show控制样式的display:none;
如果有元素频繁的展示隐藏,就需要v-show,因为渲染是很耗费浏览器性能的,v-if确定要渲染或不渲染就只有一次更好。
v-for和v-if的优先级
v-for比v-if优先级更高,每次使用v-for都会执行一次v-if,十分消耗性能,降低代码质量。
在v-if不依赖v-for中的某个值的前提下,即可能只是用来判断这整个循环列表是否需要显示,这种情况下,我们可以在循环外层加一个template标签,在template标签里做一个v-if的判断,可以让v-if判断优先
当v-if的判断条件依赖于v-for的内容时,即需要对每个循环项做判断,符合条件的才展示,可以在computed计算属性里先做好判断,过滤出符合条件的元素重新组成新的数组,在dom里循环这个新的数组,就不需要加v-if的判断了,已经在计算属性里把符合条件的项取出来了,在computed里过滤的成本比v-if的成本低。
判断对象为空
JSON.stringify():将json对象序列化为json字符串
var obj = {}
if(JSON.stringify(obj) == '{}')//对于值为函数的对象是无法判断准确的
Object.keys()将对象中所有可枚举属性取出,以数组形式返回
if(Object.keys(obj).length == 0)//在比较对象键为符号对象和枚举就不方便
for...in 方法:一般用于循环一个对象的属性
var isEmptyObj = function(obj){
for(let key in obj){//进入循环说明有属性,最终返回false
return false;
}
return true;
}
白屏时间和首屏时间
首屏时间 = 首屏内容渲染完成的时间点-开始请求的时间
白屏时间 = 页面开始展示的时间 - 开始请求的时间
首屏时间 = 白屏时间 + 首屏(开始渲染到)完全渲染完成的时间
数组降维
递归
var testArr = [ [1,2,3],4,5,[6],7,[[8],[9]]];
var res = [];
var toArr = function(arr){
arr.foreach(item =>{
if(Array.isArray(item)){
toArr(item);
}else{
res.push(item);
}
}
return res;
}
var result = toArray(testArr);
console.log(result);
join + split : 将原数组转化为字符串,再将字符串转为数组
var str = testArr.join(",");
//join()方法用于把数组中的所有元素放入一个字符串中
var arr = str.split(',');
//split()方法用于把一个字符串分割为字符串
var res = arr.map(Number);将arr字符串数组装化为数字数组
console.log(res);
toString+split : 将原数组转化为字符串,再将字符串转为数组
var str = testArr.toString();
var arr = str.split(',');
var res = arr.map(Number);
console.log(res);
随机输出数组中的值
给定一个数组,每次随机输出数组的值
随机输出一个索引
arr[Math.floor(Math.random()*arr.length)];
/*
1.Math.round():根据“round”的字面意思“附近、周围”,可以猜测该函数是求一个附近的整数,看下面几个例子就明白。
小数点后第一位<5
正数:Math.round(11.46)=11
负数:Math.round(-11.46)=-11
小数点后第一位>5
正数:Math.round(11.68)=12
负数:Math.round(-11.68)=-12
小数点后第一位=5
正数:Math.round(11.5)=12
负数:Math.round(-11.5)=-11
总结:(小数点后第一位)大于五全部加,等于五正数加,小于五全不加。
2.Math.ceil():根据“ceil”的字面意思“天花板”去理解;
例如:
Math.ceil(11.46)=Math.ceil(11.68)=Math.ceil(11.5)=12
Math.ceil(-11.46)=Math.ceil(-11.68)=Math.ceil(-11.5)=-11
3.Math.floor():根据“floor”的字面意思“地板”去理解;
例如:
Math.floor(11.46)=Math.floor(11.68)=Math.floor(11.5)=11
Math.floor(-11.46)=Math.floor(-11.68)=Math.floor(-11.5)=-12
*/
TCP和UDP的区别
TCP和UDP都是通信协议,也都是传输层协议。
UDP是无连接的,发送数据前不需要建立连接;TCP是面向连接的传输数据,tcp是三次握手。
UDP是不可靠传输,TCP是可靠传输,因为tcp拥有拥塞控制等机制,能够保证数据无差错传递,而udp没有相关机制,不保证数据的可靠交付
UDP是面向报文传输,TCP将数据看成一串无结构的字节流,是面向字节流传输的。
UDP是支持一对一、一对多、多对多的交互通信;TCP是一对一的两点服务,即一条连接只有两个端点。
TCP是可靠的传输协议,但是效率慢;而UDP是不可靠的传输协议,但效率快。
TCP适用场景:用于对通信数据的完整性和准确性要求较高的情况,如:重要传输,邮件发送等。
UDP适用场景:用于对通讯速度速度要求较高,对数据信息的安全性和准确性较低,如:网络电话、视频会议、直播等实时通信。
TCP三次握手定理
a:首先客户端先向服务器端发送一个TCP报文
标记位为SYN,表示“请求建立新连接”;
序号为Seq=X(X一般为1)(传输信息的时候每个数据包的序号);
随后客户端进入SYN-SENT阶段(请求连接的阶段)。
b:服务器端收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段报文
标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);
序号为Seq=y;(返回一个收到信息的数据包 并给其标序号为y)
确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值(两端配对 接收到消息 并反馈的过程;随后服务器端进入SYN-RCVD阶段。
ACK:代表确认收到消息
c:客户端接收到来自服务器确认收到数据的TCP报文后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段,并返回一段TCP报文
标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);
序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;
确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;
随后客户端进入ESTABLISHED阶段。(即成功建立了连接)
d:关于确认号Ack和数据包的序号Seq值得变化
Ack确认号:就是确认收到消息后 返回给 发送端的 序号(Ack = 发起方的Seq + 1) 即就是下次发送的端的seq序号。
ACK确认序号(Seq)有效:确认发送的数据包的成功到达
Seq:序号:给每个数据包一个序号,保证接受端可以按序收到数据包(首次握手的时候 Seq = 上次握手的时候的Ack值,如果没有 则可以是任意值)
在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续"握手",以此确保了"三次握手"的顺利完成
为什么要三次握手?
- a:为了防止服务器端开启一些无用的连接增加服务器开销
第一次握手客户端发送的TCP报文,服务端成功接收;然后第二次握手,服务端返回一个确认收到消息的TCP报文,但这个报文因为某些原因丢失了,那么客户端就一直收不到这个TCP报文的。
客户端设置了一个超时时间,超过了就重新发送一个TCP连接请求,那么如果没有第三次握手的话,服务端是不知道客户端是否收到服务返回的信息的,这样没有给服务器端一个创建还是关闭连接端口的请求,服务器端的端口就一直开着,等到客户端因超时重新发出请求时,服务器就会重新开启一个端口连接。那么服务器端上没有接收到请求数据的上一个端口就一直开着,长此以往,这样的端口多了,就会造成服务器端开销的严重浪费。
- b:防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
已经失效的客户端发出的请求信息,由于某种原因传输到了服务器端,服务器端以为是客户端发出的有效请求,接收后产生错误。
- c:总结
也可以这样理解:“第三次握手”是客户端向服务器端发送数据,这个数据就是要告诉服务器,客户端有没有收到服务器“第二次握手”时传过去的数据。
若发送的这个数据是“收到了”的信息,接收后服务器就正常建立TCP连接,否则建立TCP连接失败,服务器关闭连接端口。由此减少服务器开销和接收到失效请求发生的错误。(如果第三次握手失败的话,那服务端就关闭连接)
四次挥手
a:首先客户端想要释放连接,向服务器端发送一段TCP报文,其中
标记位为FIN,表示“请求释放连接“;
序号为Seq=U;
随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。
注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。
b:服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:
标记位为ACK,表示“接收到客户端发送的释放连接的请求”;
序号为Seq=V;
确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;
随后服务器端开始准备释放服务器端到客户端方向上的连接。
客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段前"两次挥手"既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了
c:服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:
(第二次挥手 服务器端回复 确认收到 客户端想要释放连接的请求 返回ACK,但是 服务端还有些数据的处理,所以不能马上断开连接,所以需要第三次握手 即服务端发送 FIN 释放连接的标志位)
标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。
序号为Seq=W;
确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。
随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。
d:客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:
标记位为ACK,表示“接收到服务器准备好释放连接的信号”。
序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。
确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。随后客户端开始在TIME-WAIT阶段等待2MSL。
服务器端收到从客户端发出的TCP报文之后结束LAST-ACK阶段,进入CLOSED阶段。由此正式确认关闭服务器端到客户端方向上的连接。
客户端等待完2MSL之后,结束TIME-WAIT阶段,进入CLOSED阶段,由此完成“四次挥手”。
e:总结
后“两次挥手”既让客户端知道了服务器端准备好释放连接了,也让服务器端知道了客户端了解了自己准备好释放连接了。于是,可以确认关闭服务器端到客户端方向上的连接了,由此完成“四次挥手”。
为什么握手是三次,挥手要四次?
-
a:握手三次(即在一次握手就可以搞定ACK+SYN)
TCP建立连接时之所以只需要"三次握手",是因为在第二次"握手"过程中,服务器端发送给客户端的TCP报文是以SYN与ACK作为标志位的。SYN是请求连接标志,表示服务器端同意建立连接;ACK是确认报文,表示告诉客户端,服务器端收到了它的请求报文。
即SYN建立连接报文与ACK确认接收报文是在同一次"握手"当中传输的,所以"三次握手"不多也不少,正好让双方明确彼此信息互通。 -
b:挥手四次
TCP释放连接时之所以需要“四次挥手”,是因为FIN释放连接报文与ACK确认接收报文是分别由第二次和第三次"握手"传输的。
b1:为何建立连接时一起传输,释放连接时却要分开传输?
建立连接时,被动方服务器端结束CLOSED阶段进入“握手”阶段并不需要任何准备,可以直接返回SYN和ACK报文,开始建立连接。
释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文。
css隐藏元素方式
display:none;//会让元素在页面上彻底消失,元素本来占有的空间也会被其他元素占有,会导致浏览器重排和重绘
visibility:hidden;//元素在页面消失后,其占据空间依旧会保留,不会重排和重绘
opacity:0;//完全透明,视觉隐藏
transform:scale(0);//缩放为0
height:0;//设置高度(宽度)为0
clip-path:circle(0);
z-index失效情况
z-index是用来设置元素堆叠 顺序的,值越大越在上层。
- 元素本身position为static,或没有设置position
- 父元素position为非static,且z-index非常小
- 元素设置了float,但是没有设置定位
- 父元素相对定位时,子层级会受到父元素的层级影响。父元素的层级判断比子元素层级判断优先级高。
find和filter的方法有什么区别
find和filter都不会改变数组。
find是查找数组中第一个符合条件的值并返回当前这个元素,就不会再向后遍历。
filter是过滤所有符合条件的值,并返回所有符合条件的项组成一个新数组。
const list =[
{name:'xiaohong' , 'department':'xingzheng'},
{name:'xiaolv' , 'department':'xiaoshou'},
{name:'xiaozhou' , 'department':'xingzheng'},
{name:'xiaogou' , 'department':'xiaoshou},
]
let list2 = list.find(item => item.department === 'xingzheng');//xiaohong
let list3 = list.filter(item=> item.department === 'xingzheng');//xiaohong,xiaozhou
数组方法every 和 some 的区别
every:遍历数组元素,全部符合条件才返回true,遍历过程中只要一个不符合条件,直接返回false,不再向后遍历。
some:遍历数组元素,碰到一个符合条件的元素就直接返回true,不用向后遍历,全部都不符合才会返回false。
const list =[
{name:'xiaohong' , 'department':'xingzheng'},
{name:'xiaolv' , 'department':'xiaoshou'},
{name:'xiaozhou' , 'department':'xingzheng'},
{name:'xiaogou' , 'department':'xiaoshou},
]
let list2 = list.every(item => item.department === 'xingzheng');//false
let list3 = list.some(item=> item.department === 'xingzheng');//true
计算属性somputed和监听属性watch的区别**
computed用于数据计算,初始化时会自动计算一次
computed会读取缓存数据
watch监控某个数据,被监控的数据发生更改,则watch执行
watch初始化的时候不会自动执行一次
const num = 1;
const num2 = computed(()=>{
console.log("走进computed")
return num.value+10
})
const num3 = ref("未成年");
watch(num,(newVal,oldVal)=>{
console.log("走进watch")
if(newVal >= 18){
num3.value = "已成年";
}
}
vuex理解和核心内容
vuex实现了组件全局状态(数据)管理的一种机制,通过创建了一个集中的数据存储,达到组件之间数据共享的效果,状态管理工具或数据管理工具,将数据存放在一个公共地方。
核心属性
- state:存公共数据的地方,类似组件中的data;
- mutations : 存数据修改的逻辑,也是唯一修改state数据的地方;且必须是同步的。
- actions : 用于改变数据,但是无法直接修改数据,需要提交mutations,在mutations里修改数据; 且支持异步操作。
- getters : 从基本数据(state)派生的数据,相当于组件里的计算属性computed;
- modules : 模块化vuex,可以让每一个模块拥有自己的state、mutation、action、getters使得结构清晰,方便管理
//index.js
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex)
export default new Vuex.Store({
state:{//数据
username:'123',
age:'18'
},
mutation:{
changeName(state,val){
state.username = val;
}
changeAge(state,val){
state.age = val;
}
},
action:{
changeAgeSync({commit},val){
commit('changeAge',val)
}
},
getters:{}
})
//main.js引入
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'//进行引入
Vue.config.productionTip = false
new Vue({
el:'#app',
router,
store,//声明
components:{App},
template:'App'
})
//子组件a
<template>
<div class = 'a'>
<h1> 我是子组件<h1>
<ul>
<li>我的名字是:{{this.$store.state.username}}</li>
<li>我的名字是:{{this.$store.state.age}}</li>
</ul>
<button @click = "changeage">changeage</button>
</div>
</template>
<script>
export default{
name :'a',
methods:{
changeage(){
this.$store.dispatch('changeAgeSync',22);//dispatch分发到actions中
}
}
}
</script>
//子组件b
<template>
<div class = 'b'>
<h1> 我是子组件<h1>
<ul>
<li>我的名字是:{{this.$store.state.username}}</li>
<li>我的名字是:{{this.$store.state.age}}</li>
</ul>
<button @click = "changeName">changeName</button>
</div>
</template>
<script>
export default{
name::'b',
methods:{
changeName(){
this.$store.commit('changeName','789');//提交至mutation,更改数据
}
}
}
</script>
SPA单页面应用
SPA单页面应用就是只有一个页面的应用,优点是切换快,用户体验好;缺点是首屏加载慢,不利于SEO搜索引擎的优化。
只在web页面初始化时加载相应的html,js,css等,一旦页面加载完成就不会因为用户的操作而进行页面的程序加载或调整,看上去只有一个web页面,通过路由机制,监听路由的变化实现html内容的变化,从而动态实现UI与用户交互,没有去请求一个新的html动作,而是通过变化的路由,去找到对应路由对应的页面。
优点:页面初始化时项目就依赖资源统一加载了,后面切换页面就不用向服务器的请求了,切换速度快且流畅,用户体验性好。一定程度上也会减小服务器的压力。
前后端职责清晰,前端就负责页面相关以及调后端接口拿数据的工作,后端则负责数据相关的处理。
缺点:
首屏加载慢,因为这是单页面应用,初次加载时资源会统一全部加载。
由于所有的内容都是在一个页面中动态替换显示,所以不利于SEO搜索引擎优化。
JS怎么判断数字是否是小数,小数后几位
x.toString().split(“,”)[1].length;
echarts图表随窗口自适应大小
纯CSS画三角形
div{
height: 0px;
width: 0px ;
border-top: 30px solid transparent;
border-bottom: 30px solid green;
border-left: 30px solid transparent;
border-right: 30px solid transparent;
}
vue组件给子组件传值,子组件不更新的原因
vue组件传值
父传子:props
父组件通过标签属性进行数据传递
子组件通过defineprops获取父组件传过来的数据
//index.vue
//父组件
<script>
import PageHeader from "./components/header.vue"
import {ref} from "Vue"
const msg = ref("init 数据")
</script>
<template>
<div>
<h1>我是父组件</h1>
<!-- 父组件通过标签属性进行数据传递 -->
<PageHeader :msg="msg"/>
</div>
</template>
//components/header.vue
//子组件
<script>
//defineProPS获取父组件传递过来的数据
const props = defineProps(["msg"])
</script>
<template>
<div>
<hr/>
<h5>我是子组件</h5>
<p>{{props.msg}}</p>
</div>
</template>
子传父:emits
子组件不允许更改父组件的数据
应该让父组件自己更改
在父组件声明一个自定义事件,emits触发组件的自定义事件
//index.vue
//父组件
<script>
import PageHeader from "./components/header.vue"
import {ref} from "Vue"
const msg = ref("init 数据")
const getSonVal=(val)=>{
console.log(val);
msg.value = val;
}
</script>
<template>
<div>
<h1>我是父组件</h1>
<!-- 父组件通过标签属性进行数据传递 -->
<PageHeader :msg="msg" @getVal="getSonVal"/>
</div>
</template>
//components/header.vue
//子组件
<script>
//defineProPS获取父组件传递过来的数据
const props = defineProps(["msg"])
const emits = defineEmits(["getVal"])
const sonMsg = ref("");
const handleChange = ()=>{
//sonMsg.value给到父组件,通过emit触发
emits("getVal",sonMsg.val);
}
</script>
<template>
<div>
<hr/>
<h5>我是子组件</h5>
<p>{{props.msg}}</p>
<hr>
<input type = "text" v-model = "sonMsg">
<button @click="handleChange">传递给父组件</button>
</div>
</template>
兄弟组件传值:事件总线BUS vue3中使用mitt插件
mitt安装:cnpm i mitt -S
事件总线:相当于全局的事件管理,
使用:
mitt.emit(“方法名”,参数);//触发某个方法
mitt.on(“对应方法名”,callback);//监听某个方法
mitt.off(“移除对应方法”);//移除某个方法,一般放在Unmounted事件中
//index.vue
//父组件
<script>
import PageHeader from "./components/header.vue"
import PageFooter from "./components/footer.vue"
import {ref} from "Vue"
const msg = ref("init 数据")
const getSonVal=(val)=>{
console.log(val);
msg.value = val;
}
</script>
<template>
<div>
<h1>我是父组件</h1>
<!-- 父组件通过标签属性进行数据传递 -->
<PageHeader :msg="msg" @getVal="getSonVal"/>
<PageFooter />
</div>
</template>
//components/header.vue
//子组件
<script>
import bus from "../utils/bus.js
//defineProPS获取父组件传递过来的数据
const props = defineProps(["msg"])
const emits = defineEmits(["getVal"])
const sonMsg = ref("");
const handleChange = ()=>{
//sonMsg.value给到父组件,通过emit触发
emits("getVal",sonMsg.val);
}
const handleChange2=()=>{
bus.emit("broutherData,sonMsg.val);
}
</script>
<template>
<div>
<hr/>
<h5>我是子组件</h5>
<p>{{props.msg}}</p>
<hr>
<input type = "text" v-model = "sonMsg">
<button @click="handleChange">传递给父组件</button>
<button @click="handleChange2">传递给兄弟组件</button>
</div>
</template>
//components/footer.vue
//子组件
<script>
import bus from "../utils/bus.js"
import {Unmounted} from "vue"
bus.on(("broutherData,(val)=>{
console.log(val);
});
onUnmounted(()=>{
bus.off("broutherData");
})
</script>
<template>
<div>
<hr/>
<h5>我是footer组件</h5>
</template>
//utils/bus.js
export mitt from "mitt"
export default new mitt()
JS数组去重
const arr = [1, 2, 2, 'abc', 'abc', true, true, false, false, undefined, undefined, NaN, NaN];
//1.利用Set()+Array.from()
const res1 = Array.from(new Set(arr));
//2.利用两层循环+数组的splice方法
function removeDuplicate(arr) {
let len = arr.length
for (let i = 0; i < len; i++) {
for (let j = i + 1; j < len; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1)
len-- // 减少循环次数提高性能
j-- // 保证j的值自加后不变
}
}
}
return arr
}
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN, NaN ]
//3.利用数组的indexOf方法
function removeDuplicate(arr) {
const newArr = []
arr.forEach(item => {
if (newArr.indexOf(item) === -1) {
newArr.push(item)
}
})
return newArr // 返回一个新数组
}
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN, NaN ]
//4.利用数组的includes方法
function removeDuplicate(arr) {
const newArr = []
arr.forEach(item => {
if (!newArr.includes(item)) {
newArr.push(item)
}
})
return newArr
}
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN ]
//5.利用数组的filter()+indexOf()
//filter方法会对满足条件的元素存放到一个新数组中,结合indexOf方法进行判断。
function removeDuplicate(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item) === index
})
}
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined ]
//6.利用Map(),Map对象是JavaScript提供的一种数据结构,结构为键值对形式,
//将数组元素作为map的键存入,然后结合has()和set()方法判断键是否重复。
function removeDuplicate(arr) {
const map = new Map()
const newArr = []
arr.forEach(item => {
if (!map.has(item)) { // has()用于判断map是否包为item的属性值
map.set(item, true) // 使用set()将item设置到map中,并设置其属性值为true
newArr.push(item)
}
})
return newArr
}
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN ]
//7.利用对象,其实现思想和Map()是差不多的,
//主要是利用了对象的属性名不可重复这一特性。
function removeDuplicate(arr) {
const newArr = []
const obj = {}
arr.forEach(item => {
if (!obj[item]) {
newArr.push(item)
obj[item] = true
}
})
return newArr
}
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined, NaN ]
vue 路由跳转
router-link//标签,to到,类似a标签
this.$router.push()//调用路由,跳转到指定url路径,向history栈中添加一条记录,点击后退就会返回到上一个页面
this.$router.replace()//跳转到指定url路径,实现代替历史记录,回到的是上上条路径
this.$router.go(n)//可以利用这个方法向前或者向后跳转n个页面
script中defer和async的区别
当浏览器在解析原文件html时,遇见外部script,解析过程就会被暂停,这时会对Script进行加载、执行,会阻塞html解析只有script完全下载并执行后才会继续执行dom解析。
通过defer和async可以加载script,而不阻塞html,从而将script的下载阶段变成异步执行,就是和html解析同步执行。
script加了defer属性,它会在全部html页面解析完毕后再去执行。
script加async属性,它会异步的加载和执行脚本,不会因为加载脚本而阻塞页面的加载,但是一旦加载后就会立刻执行,此时如果html没有解析完成,就会阻塞解析。
总结:defer和saync都是异步加载,区别就是脚本下载完成之后,defer是延迟执行,等到html解析完再执行js,多个defer属性脚本,就会按顺序执行;而async是当前JS文件加载完成之后,不管html是否解析完成,都会立即执行js.,多个async脚本,加载完就执行,不按顺序。
BFC
https://blog.csdn.net/sqLeiQ/article/details/125261564
block formatting context
块级格式化上下文
-
BFC是一个块级元素,块级元素会在垂直方向,一个接一个的排列
-
BFC是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签
-
BFC区域不会与浮动的容器发生重叠
-
属于同一个BFC的两个相邻元素的外边距会发生重叠,垂直方向的距离会由两个元素中margin较大值决定
-
计算BFC高度时,浮动元素也会参与计算
如何触发BFC
可以通过添加一些css属性
overflow除了visible以外的值,如hidden
position可以设置为absoluted或者fixed
display可以设置为inline-block\flex
解决的问题
- 可以阻止元素被浮动元素覆盖,如两栏布局,左边div宽度固定,同时设置左浮,右边的div自适应,此时由于浮动的元素脱离文档流了,不占据空间,右侧的div到了最左边,同时左侧浮动的div还会覆盖在上面,可以通过把右侧的div元素设置为一个bfc,添加display:flex属性,可以解决右侧被左侧覆盖的问题。
- 能够解决父元素没有高度,子元素设置成浮动元素式,产生的父元素高度塌陷问题,比如一个容器内的两个div都是浮动元素,此时我们给父元素添加一个红色的背景色,会没有任何效果,此时父元素高度为0,可以添加一个可以触发父元素BFC功能的属性,因为计算BFC高度时,浮动元素也会参与计算,此时父元素会被撑开,会产生清除浮动的效果。
- 可以解决margin边距重叠问题,一个容器里面有两个div,这两个div分别设置了自己margin,一个设置10px,另一个20px,两个盒子边距为20px
get和post的区别
- GET 请求是用来获取资源的,向服务器获取数据时用,也就是进行查询操作的,而 POST 请求是用来传输实体对象的,向服务器提交数据时用,因此会使用 POST 来进行添加、修改和删除等操作。
- get请求如果带参数,参数会拼接在地址栏url上暴露出来,而post请求,请求参数会放在请求体中,用post传输敏感参数会更安全一点如传输密码,。
- get请求可以被缓存,post请求不会被缓存,http缓存只适用于不改变服务端数据的请求,比如查询类的请求, GET 请求是用来获取资源的,向服务器获取数据时用,也就是进行查询操作的,get符合http缓存。
- get请求由于参数是跟在url后面,导致该请求下传输的数据长度,受限于url长度,get请求的传输数据长度有限;post请求理论上没有长度限制,因为请求数据是放在body中的,不是获取数据只能用get,是通常get用于获取数据。
为什么不推荐用Style行内样式
- 多个元素样式相同时,style会让代码大量重复,十分冗余,且样式修改时,需要每一个都进行修改不利于维护,而用class的多个元素很方便的就可以实现共用,代码简洁,改样式也只需要一次
- style在实际业务中,有时候需要预留给js去控制,做交互使用,因为它的优先级更高,可以按需覆盖原有的class样式,
- 样式和内容分离,分层思想,如果混杂在一起,严重耦合,结构不清晰,也不利于维护,而用class能做到部分的结构样式分离,让整体结构看起来更加简洁明了,更符合前端规范可读性更强
- 更多的内联样式还会影响到最终的打包体积
- 单独写一个样式文件是可以在浏览器建立缓存的,二次访问时加载速度更快,用户体验更好。
如何让chrome支持小于12px的文字(默认最小为12px)
- zoom属性,可以改变页面上元素的尺寸(存在兼容问题)
zoom:0.5//6px文字
- transform属性,可以改变页面上元素的尺寸,
transform:scale(0.5)//6px文字
-webkit-transform:scale(0.5)//scale只对有定义宽高的元素生效,
//而行内元素是没有宽高的,span行内元素要转化为行内块级元素//
//通过加一个display:inline-block实现
- -webkit-text-size-adjust属性,
-webkit-text-size-adjust:none;
token的作用
token的意思是令牌,是用户第一次登陆时,服务器生成的一段加密字符串,然后返回给客户端;后面客户端每次向服务端请求资源时,只需要带上token,不用再带着用户名和密码去请求。
为什么要带token
因为用户登陆成功后,后续需要反复到服务器获取数据,服务器对每一次请求,都要去验证哪位用户发送的,用户是否合法,对数据库造成过大的压力。
当后续请求都带上token后,服务器直接解密token,就可以知道用户的相关信息省去了查询数据库的操作,能够减轻数据库的压力。
在请求拦截器中统一封装,让每个请求都能带上token。
伪数组和数组有什么区别?
- 伪数组的数据类型是对象Object,而真实的数组是一个Array
- 伪数组拥有length属性,通过.length获取长度,同时也可以通过index获取某个元素,但是不具有数组所具有的方法如forEach、
- 伪数组的长度是不可变的,真数组的长度可以变
- 伪数组因为是对象,所以用for…in遍历,数组通常用for…of遍历在javascript内置对象中,常见的伪数组有arguments函数的参数,还有原生JS获取DOM如document.querySelector(‘div’)获取到的列表也是个伪数组
伪数组如何装化为数组
Vue的双向绑定原理
v-model
原理是通过数据劫持和发布订阅模式实现的
- 首先vue通过Object.defineProperty()方法对数据进行劫持,监听数据的变化,并通过getter和setter方法对数据进行读写,
- 其次vue通过发布订阅者模式维护了一个订阅者数组,
-
- 当数据发生变化时,vue 会通知所有订阅者进行更新,因此当用户在页面上进行修改时,vue会更新对应的数据,并通知所有订阅者更新视图,
-
- 同时当数据发生变化时,vue也会更新对应的视图,通过这样的机制,vue实现了双向数据绑定,使得数据和视图的变化可以相互影响。
- 订阅者是vue的模式,用于管理更新视图对象,当数据发生变化时,vue会通知所有的订阅者进行更新,在vue中每一个挂载到视图上的组件或则每一个watcher都可以被看做是一个订阅者,他们订阅了某一个数据的变化,并等待数据发生变化时进行更新,订阅者是vue实现双向数据绑定的关键组成部分,管理着数据和视图之间的关系,保证了数据的变化。
请求是放在mounted还是在created中?
created创建完成,已经初始化了某些属性值,vue实例中的Data和methods已经可以使用了,但是还没挂载到页面上。
mounted挂载完成,这时初始化页面完成,页面已经渲染出来了,可以进行dom操作。
因为created和mounted是同步的,请求时异步的,所以它不会阻塞页面渲染的主线程,而且也不能控制请求回来的时间,都可以用。但是如果需要操作dom相关的请求,就要放在mounted中执行,因为页面才挂载完成,才可以进行dom操作,mounted阶段不保证所有子组件都被挂载完成,如果我们希望等到整个视图都渲染完成,再做操作,那就需要使用this.$nextTick方法。
为什么说 vue是一个渐进式框架
渐进式框架时可以只使用框架的某些部分或功能,而不需要立即全面的采用整个框架,可以根据自己的需求和项目的特点,逐步应用框架的功能,这种方式可以采用新框架的过程更加平滑,降低学习成本,同时也能更好的控制项目的开发进度。
vue可以根据使用其模板语法、组件系统、状态管理、路由等不同的特性,如果不需要某些特性,就可以先不使用它,更加灵活。vuex提供状态管理工具,不需要就不用,符合vue渐进式框架的概念。(想用就用,不用也行)
keep-alive标签作用和使用场景
在vue中keep-alive标签可以用来缓存组件,当一个组件被包裹在keep-alive标签中时,离开当前页面时,这个组件不会被销毁,而是被缓存起来,当这个组价再次被使用时,vue会从缓存中提取组件实例,并重新挂载,而不是重新渲染,这个功能可以提高应用的性能,特别是在需要频繁切换组件的场景下,就比如tab切换或者路由切换,因为不需要每一次切换时都重新创建和销毁组件,而是直接从缓存中获取,这样可以避免重复的初始化和渲染,从而提高应用的响应速度和性能,比如:有个员工列表,点击某条数据,查看员工详情后,再返回员工列表,这时希望这个列表能够保持刚才的状态,这时可以使用keep-alive标签把这个列表所在的组件包裹。
性能优化和代码优化
性能优化
- 使用cdn将静态资源如图片、css、JS文件等托管到cdn上提高资源加载速度
- 使用浏览器缓存,通过设置合适的HTTP响应头,可以让浏览器缓存静态资源减少HTTP请求和带宽的消耗,
- 使用压缩,对静态资源进行压缩,可以减少资源的大小从而提高加载速度,减少htttp请求,通过合并内联和按需加载等技术,可以减少http的请求数量,从而提高页面的响应速度
- 通过图片的懒加载,延迟加载图片,可以减少页面的加载时间和带宽消耗,提高用户的体验
代码优化 - 减少DOM的操作,DOM操作比较消耗资源可以减少,可以通过减少DOM操作的次数,和使用合适的选择器等技术,来减少DOM的操作次数,尽量减少HTTP的请求次数,可以通过合并内联,按需加载等技术实现,再一个是避免重复的计算,对一些计算结果比较耗时的操作,可以通过使用缓存结果,或使用记忆化函数等技术避免重复的计算
- 使用原生的JS、API 、而不是使用框架或者库中的API,可以提高代码的性能和可读性,
- 将代码分成多个模块,可以提高代码的可维护性和可读性,从而提高开发效率和代码质量
es6的新特性
- 变量的声明,引入了let和const声明,可以帮助我们更好的管理作用域,let声明只在其所在的代码块有效,而const声明的常量不能再次被赋值
- 箭头函数,es6提供了简洁的箭头函数语法,可以更简单的定义函数,还可以更好的处理this关键字,使用箭头函数要关注this作用域
- 模板字符串,es6引入了模板字符串语法,可以方便的进行字符串的差值和多行字符串的定义
- 结构赋值,方便对象和数组进行结构
- 新的循环语句,for…of和for…in语句,可以更方便对对象和数组进行遍历
- 对象扩展语法,可以方便的合并多个对象,并简化创建新的对象的代码。
- 模块系统,将代码分成几个独立的模块,以便于维护和重用,还可以帮助我们管理代码的依赖关系,
- 异步编程,提供了async和await两个关键字可以简化异步编程,可以帮助我们编写更简洁
- 类的语法,方便的定义对象的构造函数和方法,可以帮助我们更好的组织代码,并方便的复制对象,
10.迭代、generator、Symbols
JS中常见的安全问题
XSS跨脚本攻击,它指的是恶意用户将恶意代码插入的正常网页中,从而在用户访问网页时对它造成攻击,这种攻击可能导致敏感信息泄露,计算机病毒的感染等。
CSRF霸占请求的伪造是指恶意用户用了用户已经登录的网站,通过伪造请求对网站进行攻击,这种攻击可能导致用户的账号被盗,财产损失等。
解决措施:
- 过滤输入数据,对于所有的输入数据,需要进行过滤,以避免恶意代码的插入。
- 对数据进行编码,对于所有的输出数据,进行编码以避免XSS的供给,
- 增加验证机制,在服务端增加验证机制,以识别和拦截有效的恶意请求,
- 使用https,以加密数据通信,防止数据被窃取,
- 利用cors,使用跨域资源共享,以控制网页与服务端的通信,避免CSRF的供给
- 限制请求的方法,对于特定的请求方法,可以限制他们的使用,以防止恶意的请求
- 利用签名机制,利用数字签名机制,以确保请求的真实性和完整性。
- 定期扫描网站,以发现漏洞以及时修复。
JS实现异步的数据交互
异步数据交互是指不主射页面渲染的情况下,通过JS发送HTTP请求,并处理响应数据,
//XMLHttpRequest,浏览器端与服务端进行异步通讯的技术,
//可以发送和接收HTTP请求
var xhr = new XMLHttpRequest();
xhr.open("GET","url",true);
xhr.send();//发送请求
xhr.onreadystatechange = function(){
if(xhr.readyState === XMLHttpRequest.DONE){
if(xhr.status === 200){
var reponse = xhr.responseText;
.....
}
}
}
//Fetch API先引入,有些浏览器的兼容性不太好,基于promise实现
//语法简洁,使用方便,理想异步数据的交互方案
fetch("url").then(res => res.json()).then(data =>{
//....
});
fetch("url",{
method:"POST",
headers:{
"Content-Type":"application/json"
},
body:JSON.stringify({
key:value})
}).then(res => res.json()).then(data =>{
//....
});
JS处理浏览器事件
onclick:点击事件
ondblclick:双击事件
onmousedown:当用户按下鼠标按键时触发
onmouseup:当用户释放鼠标按键时触发
onmousemove:当用户移动鼠标时触发
原型链
prototype这个属性只有函数对象才有,(构造)原型对象函数
__proto__所有对象都有此属性,总是指向对应的(构造)函数的原型对象,总是指向prototype
constructor:__proto__下面的constructor指向构造函数本身,用于判断对象的原型是否是某个对象
对象访问属性的时候,在自身属性查找,找不到再去___proto__原型链上找,直到找不到为止,返回undefined
var obj = {a:1}
console.log(Object.prototype === obj.__proto__)//true
//对象总是指向对应的(构造)函数的原型对象,总是指向prototype
console.log(obj.__proto__.constructor === Object);//true
function Vue(){
this.name = "小红";
}
const app = new Vue();
console.log(app.__proto__.__proto__ === Object.prototype);//true
闭包
函数嵌套函数,内部函数可以访问外部函数的变量,并把内部函数return ,主要是跨域的一个访问变量,一级级向上找。
闭包形成的条件
1.函数嵌套
2.内部函数引用外部函数
作用:封装变量,收敛权限
闭包的优点:
延长外部函数局部变量的生命周期
闭包的缺点:
优点也是缺点,本应被销毁的变量,因为闭包的原因没有被销毁,长期存在的话,容易造成内存泄漏
注意点:
1.合理使用闭包
2.用完闭包要及时清除(销毁),避免内存泄露
var list = [];
var studentsSyns=function(){
return {
search(name){
if(list.includes(name)){
console.log("已报名");
}
},
insert(name){
if(list.push(name)){
console.log("报名成功");
}
},
console(){
console.log(list);
},
}
}
studentSyns.insert("小红");
异步解决方案
- callback回调函数
- promise
- async/await