1、每个html元素都可以看作一个盒子,这个盒子由里到外,由这个元素的内容content、边框border、内边距padding、外边距margin组成。
2、盒子模型一般分为标准盒模型 和 怪异盒模型,怪异盒模型又叫做IE盒模型。这两种盒模型又有什么区别呢?
3、在标准盒模型下,浏览器的width属性,就是内容content的宽度,也就是说,如果我们给一个元素设置width属性,那么width属性就是内容的宽度,此时这个元素盒子的总宽度就是:width + 内边距 + 边框 + 外边距,高度也是这样。而怪异盒模型,指的是浏览器的width属性不是内容的宽度,是元素的内容 + 内边距 + 边框的宽度之和。
换句话说,如果我们给一个元素设置width属性,那么这个盒子的总宽度就是:width + 外边距 之和。因为width已经包含了内容、内边距、边框。
正常情况下默认是标准盒模型,但是我们可以通过box-sizing属性来指定盒模型,当它的值是border-box时,就是怪异盒模型。当值content-box时,就是标准盒模型,因为标准盒模型的width就是content。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
html * {
padding: 0;
margin: 0;
}
/* 重要:设置容器为网格布局,宽度为100% */
.layout.grid .left-center-right {
display: grid;
width: 100%;
grid-template-rows: 100px;
grid-template-columns: 300px auto 300px; /* 重要:设置网格为三列,并设置每列的宽度。即可。*/
}
.layout.grid .left {
background: red;
}
.layout.grid .center {
background: green;
}
.layout.grid .right {
background: blue;
}
</style>
</head>
<body>
<section class="layout grid">
<article class="left-center-right">
<div class="left">我是 left</div>
<div class="center">
我是 center
<h1>网格布局解决方案</h1>
</div>
<div class="right">我是 right</div>
</article>
</section>
</body>
</html>
补充:
1、flex-direction属性
取值:row(默认) | row-reverse | column | column-reverse
2、flex-wrap属性
取值:nowrap(默认) | wrap | wrap-reverse
3、justify-content属性
取值:flex-start(默认) | flex-end | center | space-between | space-around | space-evenly;
4、align-items属性
取值:flex-start | flex-end | center | baseline | stretch(默认)
不管页面发生了重绘还是重排,它们都会影响性能(最可怕的是重排 ,应尽量避免)
由于回流和重绘会带来很大的性能开销,所以在开发中我们要尽量避免或减少回流和重绘的次数来提高性能
其中解析HTML的时候会将HTML文档转换为DOM树,构建render树的时候会将DOM树转换为更加结构化的渲染树,布局和绘制render树的时候会将渲染树转换为可见的像素画面。
1. 通过定位,给父盒子相对定位,子盒子绝对定位,top、left为50%,再margin-left : -(子盒子宽的一半)px; margin-top: -(子盒子高的一半)px;
<style>
div {
position: relative;
height: 400px;
width: 400px;
background-color: pink;
}
span {
position: absolute;
top: 50%;
left: 50%;
margin-left: -50px;
margin-top: -50px;
display: block;
width: 100px;
height: 100px;
background-color: purple;
}
</style>
2. 不依赖通过计算子盒子的宽高进行定位,可以用transform: translate 移动自身的一半就行了。
<style>
div {
position: relative;
height: 400px;
width: 400px;
background-color: pink;
}
span {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: block;
width: 100px;
height: 100px;
background-color: purple;
}
</style>
3. 通过flex布局,设置垂直水平都居中。
<style>
div {
display: flex;
justify-content: center;
align-items: center;
height: 400px;
width: 400px;
background-color: pink;
}
span {
display: block;
width: 100px;
height: 100px;
background-color: purple;
}
</style>
module.exports = {
plugins: {
autoprefixer: {}, // 自动添加相应浏览器前缀,样式兼容,如-webkit-,-moz-等
"postcss-px-to-viewport": {
unitToConvert: "px", // (String)需要转换的单位,默认为"px"
viewportWidth: 750, // (Number)设计稿的视口宽度:可为设计稿的宽度,也可按需自行配置(PC)
unitPrecision: 6, // (Number)单位转换后保留的精度,即小数点位数
propList: ["*"], // (Array)指定转换的css属性的单位,*代表全部css属性的单位都进行转换
viewportUnit: "vw", // (String)指定需要转换成的视窗单位,默认vw
fontViewportUnit: "vw", // (String)指定字体需要转换成的视窗单位,默认vw
selectorBlackList: ["demo"], // (Array)需要忽略的CSS选择器,不转为视窗单位,使用原有单位,
minPixelValue: 1, // (Number)设置最小的转换数值,默认为1,只有大于1的值会被转换
mediaQuery: true, // (Boolean)媒体查询里的单位是否需要转换单位,默认false
replace: true, // (Boolean)是否直接更换属性值,而不添加备用属性
exclude: [/node_modules/], // (Array or Regexp)忽略某些文件夹下的文件或特定文件,用正则做目录名匹配
include: /Ignore.vue/, //(Array or Regexp)只有匹配到的文件才会被转换,那将只有匹配到的文件才会被转换
landscape: false, // (Boolean)是否添加根据 landscapeWidth 生成的媒体查询条件
landscapeUnit: 'vw', //(String)横屏时使用的单位
landscapeWidth: 700 //(Number)横屏时使用的视口宽度
}
}
};
const pxtoviewport = require("postcss-px-to-viewport");
module.exports = {
css: {
// 忽略 CSS order 顺序警告
extract: { ignoreOrder: true },
// 适配插件
loaderOptions: {
postcss: {
plugins: [
pxtoviewport({
// 配置视窗口尺寸
viewportWidth: 1700,
unitPrecision: 5,
viewportUnit: "vw",
fontViewportUnit: "vw",
selectorBlackList: [],
minPixelValue: 1,
mediaQuery: true,
}),
],
},
},
},
}
module.exports = {
plugins: {
'postcss-pxtorem': {
rootValue: 37.5,
propList: ['*']
}
}
}
【 什么是BFC?】BFC的全称是block-formatting-context, 对应其中文翻译就是块级格式上下文,它是一个独立的渲染区域,我们可以把BFC理解为,一个封闭的容器,内部的元素无论怎么变化都不会影响到外部,容器内的样式布局自然也不会受到外界的影响。
【BFC内部规则】1BFC它就是一个块级元素,块级元素会在垂直方向上一个接一个的往下排列,2BFC就是页面中的一个隔离的独立容器,容器里的标签不会影响到外部标签,3BFC区域不会与浮动的容器发生重叠,4属于同一个BFC的两个相邻元素的外边距会发生重叠,垂直方向的距离由两个元素中margin的较大的值决定,5计算BFC的高度时,浮动元素也会参与计算。
【如何触发BFC? 】通过添加CSS属性,就可以触发,overflow:hidden;除了visible以外的值,position:absolute/fixed;display:inline-block/flex;
【BFC到底解决什么问题】?
- 它可以阻止元素被浮动元素覆盖,例如,一个两栏布局,左边div宽度固定,同时设置左浮动,右边的div自适应,此时由于浮动元素脱离文档流了,不占据空间,那么就会导致右侧的div到了最左边,同时左侧浮动的div还会覆盖在上面,这时候我们就可以通过把右侧的div元素设置为一个BFC,比如可以给它添加display:flex;属性来触发,就可以解决右侧被左侧覆盖的问题。
- 能够解决父元素没有高度,子元素设置成浮动元素时,产生父元素高度塌陷问题,比如一个容器内的两个div都是浮动元素,此时我们给父元素添加一个红色的背景色,会发现没有任何效果,因为父元素高度塌陷,高度为0,这个时候我们就可以添加一个触发BFC功能的属性,因为BFC有个规则是计算BFC高度时,浮动元素也会参与计算,所以触发BFC后,父元素的高度就会被撑开,也就是会产生清除浮动的效果。
- 第三可以解决margin边距重叠的问题,比如一个容器里有两个div,一个div的下边距的margin设置的是10px,一个div的上边距设置的是20px,那这两个盒子之间的距离是20px,而不是30px,这就是margin塌陷问题,这个时候margin应为两个div之间较大的那个margin值,而不是两者相加,如果就想让他们之间的间距是30px,就需要触发一个div的BFC,它的内部就会遵循BFC规则,解决办法是为元素包裹一个盒子,形成一个完全独立的空间,做到里面的元素,不被外面的元素影响。
【区别】
伪类:当我们希望样式在某些特定状态下才被呈现到指定的元素时,换句话说就是,当某个元素状态改变时,我们期待给这个元素添加一些特殊效果,那么我们就可以往元素的选择器后面加上对应的伪类。比如: hover就能够指定当我们悬浮在某元素上时,期望该元素要显示的样式。
伪元素:则是创建了一些不在文档树中的元素,并为其添加样式,需要注意的是伪元素样式里必须要给它一个content届性。比如可以通过::before伪元素在个元素前增加一些文本,并为这些文本添加样式。这些文本实际上不在文档树中的,所以叫伪元素。
总结来看,伪类的操作对象是文档树中已有的元素,而伪元素则是创建文档权以外的元素并为其添加样式。所以二者最核心区别就在于,是否创造了“新的元素"。
<template>
<div>
<div class="div1">第一个字符是红色。</div>
<div class="div2">选择部分背景是黑色哟。</div>
<div class="div3">该内容前面插入内容123。</div>链接被访问后是红色,悬浮是绿色。<div>
<p>第一个p元素会是黄色</p>
<p>第一个p元素会是黄色</p>
</div>
</div>
</template>
<style scoped>
/*单冒号(是伪类,双冒号《::》是伪元素*/
.div1::first-letter{
color: red;
}
.div2::selection{
background-color: black;
}
.div3::before{
content:'123';
}
a:visited {
color: red;
}
a:hover{
color: limegreen;
}
p:first-child {
color:yellow;
}
<style>
但这种方式,虽然视觉上是实现了三角形,但实际上,隐藏的部分任然占据部分高度,需要将上方的宽度去掉。
.triangle {
width: 0px;
height: 0px;
border-top: 100px solid transparent;
border-bottom: 100px solid red;
border-left: 100px solid transparent;
border-right: 100px solid transparent;
}
<div class="triangle"></div>
#triangle {
margin: 100px;
/* width: 100px;
height: 100px; */
background-color: pink;
position: relative;
}
#triangle:before {
position: absolute;
content: "";
width: 0;
height: 0;
top: 0px;
left: 100px;
border-top: solid 50px transparent;
border-left: solid 50px pink;
border-bottom: solid 50px transparent;
}
<div id='triangle'></div>
// 1. 利用计时器,实现防抖。
// 2. 通过 setTimeout 的方式,在一定的时间间隔内,将多次触发变成一次触发。
function debounce(fn, delay) {
let timer;
return function () {
let args = arguments;
if (timer) {
clearTimeout(timer);
}
timer = setTimeout(()=> {
fn.apply(this, args);
}, delay);
};
}
function throttle(fn, delay) {
let timer;
return function () {
let _this = this;
let args = arguments;
if (timer) {
return;
}
timer = setTimeout(function () {
fn.apply(_this, args);
// 在delay后执行完fn之后清空timer,此时timer为假,throttle触发可以进入计时器
timer = null;
}, delay)
}
}
深拷贝的实现方式
(1) js内置对象的JSON对象的序列化和反对象化方法:JSON.parse + JSON.stringify
// 但是这个方法是有局限的,无法实现对对象中方法的深拷贝,取不到值为undefined的key等等之类的。
function cloneDeepJson(obj){
return JSON.parse(JSON.stringify(obj))
}
(2) 使用递归实现深拷贝。a. 传入的原对象,遍历其属性,每个属性需要判断它的值是否是object类,如果不是,说明是基本数据类型,可以直接赋值;b. 如果是object类,那么需要再具体判断这个数据是对象还是数组,是数组创建一个空数组[],是对象则创建一个空对象{},继续递归。
//递归实现深拷贝
function deepClone(origin, target){
var target = target || {}; //防止不传target
for(let key in origin){ //遍历origin中的属性
if(origin.hasOwnProperty(key)){ //判断自身是否有该属性而非原型链上的
if( origin[key] && typeof(origin[key]) == "object"){ //如果当前value是一个object类型,需要往下递归
target[key] = Array.isArray(origin[key]) ? [] : {}; //判断这个object类型,具体是数组还是对象
deepClone(origin[key], target[key]); //递归
}else{
target[key] = origin[key]; //如果当前value不是一个object类型,直接赋值即可
}
}
}
return target; //返回最终的拷贝对象
}
(3) loadash是一个很热门的函数库,我们引入这个库后,就可以直接使用这个方法了,但是如果项目本身没有引入这个库,就不要为了使用深拷贝专门引入整个库,这样有点得不偿失。
(2) Object.assign(target,…sources) ES6新增的方法可以实现深拷贝,target: 拷贝给谁。 sources: 拷贝的对象。
// 注意:只有当对象中没有嵌套对象时,才可以实现深拷贝
const foo = {
name: '张三',
age: 24
}
const newFoo = Object.assign({}, foo)
foo.age = 25
console.log(foo, newFoo) // {name: '张三', age: 25} {name: '张三', age: 24}
// 对象中有内嵌的对象时
const foo = {
name: '张三',
info: {
age: 24
}
}
const newFoo = Object.assign({}, foo)
foo.info.age = 25
console.log(foo, newFoo) // { name: '张三', info: { age: 25 } } { name: '张三', info: { age: 25 } }
(3) for ··· in ,使用for ··· in 遍历赋值太麻烦,不推荐。只要碰到某一个复杂数据类型(数组、对象),就再次进入这个复杂数据类型进行二次遍历,如果还有就再进入继续遍历。
(4) 通过 json 反序列化实现深拷贝。不管多复杂的数据类型,转换为json字符串以后就是基本数据类型,字符串的赋值就是基本数据类型的赋值,赋值以后再转换回来。
(6) ...
展开运算符。只深拷贝最外层
(7) arr.concat() 方法。只深拷贝最外层
(8) arr.slice() 方法。只深拷贝最外层
https://zhuanlan.zhihu.com/p/556923120 深入了解深浅拷贝
阻止冒泡:通过 event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件处理程序被执行。
// 写一个函数,每次调用,数字都要减1 --------- 全局变量,占用内存,任意函数(地方)都可以随便调用,会污染环境。
var a = 100;
function fn() {
a--;
console.log(a);
}
fn() // 99
fn() // 98
fn() // 97
// -----------每次调用,结果都是99。每次函数一调用完就会销毁。每次调用,b都是100开始,存不住。
function fn2() {
var b = 100;
b--;
console.log(b);
}
fn2() // 99
fn2() // 99
fn2() // 99
// ------------闭包的写法
function fn3() {
let c = 100;
function fn4() {
c--;
console.log(c);
}
return fn4;
}
var fn5 = fn3();
fn5() // 99
fn5() // 98
fn5() // 97
fn5 = null;
- vue-cli代理跨域:在 vue.config.js文件的devServer对象的 proxy中配置。
module.exports={
pages:{
index:{
//入口
entry:'src/main.js',
},
},
lintOnSave:false,//关闭语法检查
//开启代理服务器 方法一
// devServer:{
//paroxy:'http://localhost:5000',//5000服务器端口号,
// },
//方法二:
devServer:{
proxy:{
// 请求前缀/api,只有加了/api前缀的请求才会走代理(前端自定义)
'/api':{
target:'http://localhost:5000',
pathReweite:{'^/api':''},//重写
//ws:true,//用于支持websocket
changeOrigin:true,//用于控制请求头中的host值,默认true,react中默认false
}
}
}
}
在使用webpack或者vite构建工具时候,配置反向代理,把本地所有的axios请求的地址,(域名:端口)改成:/api/,本地发起请求的地址 /api/ 都会被自动替换成实际后端请求地址;这样就实现了当前请求的地址,就会被后端接收识别为和前端都一样的域名;保证了前后端域名一致,解决了跨域;
2. nginx里面也可以使用同样的方式,通过反向代理转发,来解决跨域问题。
3. JSONP解决跨域。需要前后端一起配合,利用同源策略对script标签不受限制,不过只支持get请求。
4. CORS跨域资源共享。cors跨域,只需要后端配置,服务端设置header(“Access-Control-Allow-Origin:*”);允许任何来源,header(“Access-Control-Allow-Origin:http://me.com”);只允许来自域名http://me.com的请求。
- 第一步创建一个空对象。
- 第二步将 this 指向空对象。
- 第三步动态给刚创建的对象添加成员属性。
- 第四步隐式返回 this。
1. 箭头函数比普通函数在写法上更加简洁。
(1)如果没有参数,就直接写一个空括号即可
(2) 如果只有一个参数,可以省去参数的括号
(3)如果有多个参数,用逗号分割
(4)如果函数体的返回值只有一句,可以省略大括号
2. 箭头函数没有自己的this
箭头函数不会创建自己的this, 所以它没有自己的this,它只会在自己作用域的上一层继承this。所以箭头函数中this的指向在它在定义时已经确定了,之后不会改变。
3. 箭头函数继承来的this指向永远不会改变,call()、apply()、bind()等方法不能改变箭头函数中this的指向 。
var id = 'GLOBAL';
var obj = {
id: 'OBJ',
a: function(){
console.log(this.id);
},
b: () => {
console.log(this.id);
}
};
obj.a(); // 'OBJ'
obj.b(); // 'GLOBAL'
new obj.a() // undefined
new obj.b() // Uncaught TypeError: obj.b is not a constructor
对象obj的方法b是使用箭头函数定义的,这个函数中的this就永远指向它定义时所处的全局执行环境中的this,即便这个函数是作为对象obj的方法调用,this依旧指向Window对象。需要注意,定义对象的大括号{ }是无法形成一个单独的执行环境的,它依旧是处于全局执行环境中。
4. 箭头函数不能作为构造函数使用
由于箭头函数时没有自己的this,且this指向外层的执行环境,且不能改变指向,所以不能当做构造函数使用。
5. 箭头函数没有自己的arguments
箭头函数没有自己的arguments对象。在箭头函数中访问arguments实际上获得的是它外层函数的arguments值。
6. 箭头函数没有prototype
7. 箭头函数的this指向哪⾥?
箭头函数不同于传统JavaScript中的函数,箭头函数并没有属于⾃⼰的this,它所谓的this是捕获其所在上下⽂的 this 值,作为⾃⼰的 this 值,并且由于没有属于⾃⼰的this,所以是不会被new调⽤的,这个所谓的this也不会被改变。
instanceof和typeof的区别:
instanceof:返回值为布尔值。instanceof 用于判断一个变量是否属于某个对象的实例。
typeof:返回值是一个字符串, 用来说明变量的数据类型。typeof 一般只能返回如下几个结果:number,boolean, string, function, object, undefined
1.最简单的set去重
var aa=[23,45,23,23,34,2,34,66,78];
var aa=new set(aa);
console.log(aa);
2.用indexOf()来去重
var sum=[ ]; //给它一个空的数组
for(var i=o;i<aa.Length;i++){
//如果没有找到字符串,则返回-1,检索是否存在。
if(sum.indexOf(aa[i]>=0){
continue;//结束本次循环
}else{
sum.push(aa[i]);
}
}
console.log(sum);
3.用sort排序后去重
function fn(arr){
let newArr = []
arr.sort((a,b)=>{
return a-b
})
arr.forEach((val,index)=>{
if(val != arr[index+1]){
newArr.push(val)
}
})
return newArr;
}
4.数组去重,根据里面相同的ID去重,键值
var str=[
{name:"张三",id:1},
{name:"李四",id:2},
{name:"王五",id:2},
{name:"小明",id:3},
{name:"小兰",id:1},
];
//声明一个数组
var result=[];
//声明一个对象
var obj={};
for(var i=0;i<str.Length;i++){
if(!obj[str[i].id]){
result.push(str[i]);
obj[str[i].id]=true;
}
}
console.log(result);
5.普通去重的方式
for(var i=0;i<aa.length;i++){
for(var j=i+1;j<aa.length;j++){
if(aa[i]==aa[j]){
//索引,删除长度,有点个参数则添加
aa.splice(j,1);
//删除两个或三个以上相同的值
j--;
}
}
}
console.log(aa);
// 1.使用JSON自带的.stringify万法来判断
if(Json.stringify(obj)=='{}'){
console.log('是空对象')
}
// 2.使用ES6新增的方法Object.keys()来判断
if(Object.keys(Obj).length<0){
console.log('是空对象')
}
总结:for…in循环主要是为了遍历对象而生,不适用遍历数组。 for…of 循环可以用来遍历数组、类数组对象、字符串、Set、Map以及Generator对象。
1.如果obj里面有时间对象,则JSON.stringify后再JSON.parse的结果,时间将是字符串的形式,而不是对象的形式。
2. 如果obj里有RegExp(正则表达式的缩写)、Error对象,则序列化的结果将只得到空对象。
3. 如果obj里有函数,undefined,则序列化的结果会把函数或 undefined丢失。
4. 如果obj里有NaN、Infinity和-Infinity,则序列化的结果会变成null。
5. JSON.stringify()只能序列化对象的可枚举的自有属性,例如 如果obj中的对象是有构造函数生成的, 则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢弃对象的constructor。
6. 如果对象中存在循环引用的情况也无法正确实现深拷贝。
const set = new Set();
const s1 = new Set(); // 空的Set数据结构
console.log(s1.size) // 0
const s2 = new Set(["a", "b"]);
console.log(s2.size) // 2
1.
const set = new Set([1,2,3,4,5,4,5]);
console.log(set) // {1, 2, 3, 4, 5}
2.
const s3 = new Set(["a","a","b","b"]);
console.log(s3.size) // 2
const ary = [...s3];
console.log(ary) // ["a","b"]
1. add(value): 添加某个值,返回Set结构本身。
const s4 = new Set();
// 向set结构中添加值 使用add方法
s4.add('a').add('b'); // 链式调用
console.log(s4.size) // 2
2. delete(value): 删除某个值,返回一个布尔值,表示删除是否成功。
const s4 = new Set(['1','2','c']);
const r1 = s4.delete('c');
console.log(s4.size) // 2
console.log(r1); // true
3. has(value): 返回一个布尔值,表示该值是否为Set的成员。
const s4 = new Set(['1','2','c']);
const r2 = s4.has('d');
console.log(r2) // false
4. clear(): 清除所有成员,没有返回值。
const s4 = new Set(['1','2','c']);
s4.clear();
console.log(s4.size); // 0
5. 遍历 (方法)
- Set结构的实例与数组一样,也拥有forEach方法,用于对每个成员执行某种操作,没有返回值。
// 遍历set数据结构,从中取值
const s5 = new Set(['a', 'b', 'c']);
s5.forEach(value => {
console.log(value)
})
// a
b
c
// 创建一个Map
const map = new Map();
1. size属性
- 返回Map对象中所包含的键值对的个数。
const map = new Map()
const a = map.set(1,'one')
console.log(a.size) // 1
2. set方法
- 向Map中添加新元素。
- 第一次set是添加数据,第二次添加相同的key时,会将第一次添加的值给覆盖掉。
set(key,value)
const map = new Map()
const a = map.set(1,'one')
console.log(a) // {1 => 'one'}
3. get方法
- 通过键,查找特定的值。
get(key)
4. delete方法
- 通过键,从Map中移除对应的数据。
delete(key)
5. has方法
- 判断Map对象中是否存在key,若有则返回true,否则返回false。
has(key)
6. clear方法
- 将这个Map中的所有元素删除。
clear(key)
1. keys(): 返回键名的遍历器
const map = new Map()
map.set(1,'one')
map.set(2,'two')
map.set(3,'three')
for(const item of map.keys()) {
console.log(item); // 1 2 3
}
2. values(): 返回键值的遍历器
const map = new Map()
map.set(1,'one')
map.set(2,'two')
map.set(3,'three')
for(const item of map.values()) {
console.log(item); // 'one' 'two' 'three'
}
3. entries(): 返回键值对的遍历器
- entry条目的意思,entries复数,意思指键值对。
- entries方法中的键值对是以数组的形式存在的。
const map = new Map()
map.set(1,'one')
map.set(2,'two')
map.set(3,'three')
for(const item of map.entries()) {
console.log(item); // 返回三个数组[1,'one'] , [2,'two'], [3,'three']
}
4. forEach(): 使用回调函数遍历每个成员
第一个参数:item,是Map的value值
第二个参数:index 是Map的key值
第三个参数: Map本身
阿帕奇(Apache)和 Nginx 都是常用的 Web 服务器软件,它们可以用来处理 HTTP 请求并向客户端发送响应。下面是它们的配置方法简述:
阿帕奇配置:
1. 修改 Apache 的配置文件 httpd.conf(或 .htaccess 文件),配置虚拟主机和监听端口等参数。
2. 配置 Apache 的模块,如 PHP 模块,通过修改 httpd.conf 文件或添加 .conf 文件。
3. 重启 Apache 服务器,使配置生效。
Nginx 配置:
1. 修改 Nginx 的配置文件 nginx.conf,配置虚拟主机、监听端口、反向代理等参数。
2. 配置 Nginx 的模块,如 Lua 模块、gzip 模块等,通过在 nginx.conf 文件中添加配置项。
3. 重启 Nginx 服务器,使配置生效。
需要注意的是,在配置阿帕奇和 Nginx 的过程中,需要根据实际情况选择不同的配置选项,并确保配置文件的正确性和安全性。另外,由于 Nginx 的性能优于阿帕奇,因此在高并发的情况下,可以考虑使用 Nginx 作为 Web 服务器。
前后端交互是指前端页面和后端服务器之间的数据传输和通信。前端通常使用 JavaScript 发送 HTTP 请求(如 GET、POST 请求等),后端服务器接收请求并处理请求,返回相应的数据给前端。
下面是前后端交互的基本流程:
1. 前端页面通过 JavaScript 构造 HTTP 请求,包括请求方法(GET、POST 等)、请求 URL、请求头、请求体等。
发送 HTTP 请求到后端服务器。可以使用浏览器的 XMLHttpRequest 对象、Fetch API,或者第三方库(如 axios)发送请求。
2. 后端服务器接收请求,根据请求 URL 和请求方法进行处理,并从数据库或其他资源中获取数据,对数据进行处理和计算。
后端服务器将处理后的数据封装成 HTTP 响应,包括响应状态码、响应头、响应体等。
3. 前端页面接收到 HTTP 响应,解析响应数据,并根据数据更新页面内容。
4. 需要注意的是,前后端交互需要遵循一定的协议和规范,例如 HTTP 协议、JSON 数据格式等。此外,为了提高交互效率和用户体验,可以使用一些技术和工具,如 Ajax、WebSocket、RESTful API 等。
- 开发前,和后端约定好接口协议,数据格式。(前端需要显示的数据内容和格式一般json比较多)
- 开发中,前端通过mock数据,完成本地开发和ui渲染
- 联调:后端给前端接口文档,前端可以通过postman调试,也可以把接口放在前端项目中调试,请求返回的接口数据有疑问就继续让后端调整就行。
通俗说就是前端去调后端的接口,把数据传给后端或者从后端那边取数据。作为前端,我们主要关注的是接口的地址是什么,前端调接口的时候要传什么参数给后端,后端又会返回什么样的数据给我们等等。这些信息我们都会从接口文档上找到答案, 接口文档一般由后端按规范编写后提供给我们,在开发中,如果发现实际收到的数据和接口文档上不一致,那么这时候就可以和后端进行沟通,前端不能擅自修改,如果有的数据格式,不是我们想要的无非也就两种情况,要么是和后端老师沟通让后台老师改,要么前端自己把数据处理成理想的状态。
为什么需要浏览器缓存?
1. 浏览器的缓存,主要针对的是前端的静态资源。最好的效果是,我们在发起请求后,拉去相应的静态资源,并且保存在本地。如果服务器的静态资源没有更新,那么在下次请求的时候,就直接从本地读取;如果服务器的静态资源已经更新,那么我们再次请求的时候,就到服务器拉取新的资源,并保存在本地。这样能减少请求的次数,提高网站的性能。
2. 所谓的浏览器缓存:指的是浏览器将用户请求过的静态资源,存储到电脑本地磁盘中,当浏览器再次访问的时候,就可以直接从本地加载,不需要再去服务端请求了。
3. 使用浏览器缓存,有以下优点:
(1)减少服务器的负担,提高了网站的性能。
(2)加快客户端请求网页的加载速度。
(3)减少了多余网络数据传输。
(1) 解析URL
(2) 缓存判断
(3) DNS解析
(4) 获取MAC地址
(5) TCP三次握手
(6) HTTPS握手
(7) 返回数据
(8) 页面渲染
(9) TCP四次挥手
tcp和udp两者都是通信协议,也都是传输层协议,但是他们的通信机制和应用场景不同。
因此,tcp的适用场景:用于对通信数据的完整性和准确性要求较高的情况。如: 重要文件传输,邮件发送等。
udp的适用场景:用于对通讯速度要求较高,对数据信息的安全性和完整性要求相对较低的情况。如: 网络电话、视频会议直播等实时通信。
最后,当整个三次握手结束过后,客户端和服务端都知道自己和对方具备发送和接收数据的能力,随后整个连接建立就完成了,可以进行后续数据的传输了。
水平越权常见场景
1、基于用户身份的ID
在使用某个功能时通过用户提交的身份ID (用户ID、账号、手机号、证件号等用户唯一标识)来访问或操作对应的数据。
2、基于对象ID
在使用某个功能时通过用户提交的对象ID (如订单号、记录号)来访问或操作对应的数据。
3、基于文件名
在使用某个功能时通过文件名直接访问文件,最常见于用户上传文件的场景。
垂直越权常见场景
1、未认证账户访问无需认证就能访问该功能
2、不具备某个功能权限的账户认证后成功访问该功能
在web安全领域中,XXS和CSRF是最常见的两种攻击方式。
XXS的英文全称是Cross Site Script,中文翻译过来是跨站脚本攻击。XSS(跨站脚本攻击)是指攻击者在返回的HTML中嵌入javascript脚本,为了防御XXS攻击,需要在HTTP头部配上,set-cookie:http-only ;这个属性使得脚本无法获取,它会禁止javascript脚本来访问cookie。也可以使用验证码登陆,这样避免脚本伪装成用户执行一些操作。
- 攻击者对客户端网页注入的恶意脚本一般包括 JavaScript,有时也会包含 HTML 和 Flash。有很多种方式进行 XSS 攻击,但它们的共同点为:将一些隐私数据像 cookie、session 发送给攻击者,将受害者重定向到一个由攻击者控制的网站,在受害者的机器上进行一些恶意操作。
- XSS攻击可以分为3类:存储型(持久型)、反射型(非持久型)、基于DOM。
CSRF的英文全称是Cross-site request forgery,所以又称为“跨站请求伪造”,是指黑客引诱用户打开黑客的网站,在黑客的网站中,利用用户的登录状态发起的跨站请求。简单来讲,CSRF攻击就是黑客利用了用户的登录状态,并通过第三方的站点来做一些坏事。
详细理解转: https://juejin.cn/post/6945277278347591688
(1)form表单提交前加上校验,防止xss攻击。为防止xss攻击,表单的每个字段提交需要做校验或者编码过滤。校验的话可以用正则,比如校验手机号或者邮箱之类的。编码过滤的话,提交前需要对提交的内容进行编码过滤,防止特殊的标签之类的提交到后台。比如用户输入 '’ 这类的脚本或者html标签之类的。要过滤掉,防止提交到后台。
(2)限制URL访问,越权访问。1在公共模块增加校验方式,查看是否具有对应权限。例如,每个客户只能查看和修改自己的信息,在url地址栏参数中,带的参数有序列号之类的,攻击者可能会想到,客户的序列号是按照顺序往下排的,要是按顺序加一减一是不是就可能访问到别人的账号(水平越权)。还有一种是不同级别的登陆者登陆所拥有的功能权限不同,低权限者可能访问高权限者的账号,从而使用原本它不具有的功能,这种也是越权漏洞,属于垂直越权。2监听路由跳转,在路由跳转之前,增加校验(路由导航守卫)。3和后台联调,将对应的信息存入cookie,在数据访问时进行对比。
(3)文件上传漏洞。例如用户上传任意类型的文件可能会让攻击者注入危险内容或恶意代码,并在服务器上运行。解决:1严格限制用户上传的文件后缀以及文件类型。2定义上传文件类型白名单,只允许白名单里面类型的文件上传。3文件上传目录禁止执行脚本解析,避免攻击者进行二次攻击。
1、父传子:父组件通过自定义属性传递给子组件,子组件中通过props接收父组件中的绑定的属性
2、子传父:子组件通过广播的方式 e m i t 发送自定义事件,将值传递给父组件,父组件监听事件,触发一个函数去接收子组件中传递过来的值。 3 、兄弟间传值: ( 1 ) 通过父组件中转来传值,即 A 和 B 是兄弟组件,可以 A 传给父组件,由父组件再传给 B ( 2 ) n e w 一个 B u s 实例,在需要发送数据的组件中自定义方法,通过 emit发送自定义事件,将值传递给父组件,父组件监听事件,触发一个函数去接收子组件中传递过来的值。 3、兄弟间传值: (1)通过父组件中转来传值,即A和B是兄弟组件,可以A传给父组件,由父组件再传给B (2) new一个Bus实例,在需要发送数据的组件中自定义方法,通过 emit发送自定义事件,将值传递给父组件,父组件监听事件,触发一个函数去接收子组件中传递过来的值。3、兄弟间传值:(1)通过父组件中转来传值,即A和B是兄弟组件,可以A传给父组件,由父组件再传给B(2)new一个Bus实例,在需要发送数据的组件中自定义方法,通过emit传递数据,在需要接收数据的组件生命周期created中,通过$on监听获取数据。
(3) 使用vuex状态管理,可以实现数据的随意存储和获取。
加载渲染过程:
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
子组件更新过程:
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父组件更新过程:
父 beforeUpdate -> 父 updated
销毁过程:
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
异步组件使用路由懒加载,方法如下:component:resolve=>(require([‘需要加载的路由的地址’]),resolve)
// 代码如下:
import Vue from 'vue'
import Router from 'vue-router'
/* 此处省去之前导入的HelloWorld模块 */
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: resolve=>(require(["@/components/HelloWorld"],resolve))
}
]
})
方法如下:const HelloWorld = ()=>import('需要加载的模块地址')(不加 { } ,表示直接return)
import Vue from 'vue'
import Router from 'vue-router'
Vue.use(Router)
const HelloWorld = ()=>import("@/components/HelloWorld")
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component:HelloWorld
}
]
})
在实际开发中请求不论放在created还是mounted大多时候是没有区别的,因为created和mounted都是同步的,而请求是异步的,不会堵塞页面渲染的主线程,我们也不能控制请求回来的时间。主要是看个人习惯吧。
但是如果是需要操作dom相关的请求,就要在mounted中执行,因为这时候页面才挂载完成,才可以进行dom操作。
另外需要补充一点,官方文档上给大家提的一个醒,就是mounted阶段不保证所有的子组件也都被挂载完成,这时候如果我们希望等到整个视图都渲染完毕再做操作,那就需要使用到this.$nextTick方法。
我们需要获取到滚动上去的高度,窗口的高度,文档的高度。
获取页面滚动上去的高度:document.documentElement.scrollTop/document.body.scrollTop
获取当前元素的宽度和高度:ele.offsetHeight/ele.offsetWidth
获取窗口的宽度和高度:window.innerHeight
获取文档的宽度和高度:document.documentElement.scrollHeight
<template>
<div :style="{height: `${contentHeight}px`}" class="content_box" @scroll="scroll">
<!--这层div是为了把高度撑开,让滚动条出现,height值为所有数据总高-->
<div :style="{'height': `${itemHeight*(listAll.length)}px`, 'position': 'relative'}">
<!--可视区域里所有数据的渲染区域-->
<div :style="{'position': 'absolute', 'top': `${top}px`}">
<!--单条数据渲染区域-->
<div v-for="(item,index) in showList" :key="index" class="item">
{{item}}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: "list",
data(){
return{
listAll: [], //所有数据
showList: [], //可视区域显示的数据
contentHeight: 500, //可视区域高度
itemHeight: 30, //每条数据所占高度
showNum: 0, //可是区域显示的最大条数
top: 0, //偏移量
scrollTop: 0, //卷起的高度
startIndex: 0, //可视区域第一条数据的索引
endIndex: 0, //可视区域最后一条数据后面那条数据的的索引,因为后面要用slice(start,end)方法取需要的数据,但是slice规定end对应数据不包含在里面
}
},
methods:{
//构造10万条数据
getList(){
for(let i=0;i<100000;i++){
this.listAll.push(`我是第${i}条数据呀`)
}
},
//计算可视区域数据
getShowList(){
this.showNum = Math.ceil(this.contentHeight/this.itemHeight); //可视区域最多出现的数据条数,值是小数的话往上取整,因为极端情况是第一条和最后一条都只显示一部分
this.startIndex = Math.floor(this.scrollTop/this.itemHeight); //可视区域第一条数据的索引
this.endIndex = this.startIndex + this.showNum; //可视区域最后一条数据的后面那条数据的索引
this.showList = this.listAll.slice(this.startIndex, this.endIndex) //可视区域显示的数据,即最后要渲染的数据。实际的数据索引是从this.startIndex到this.endIndex-1
const offsetY = this.scrollTop - (this.scrollTop % this.itemHeight); //在这需要获得一个可以被itemHeight整除的数来作为item的偏移量,这样随机滑动时第一条数据都是完整显示的
this.top = offsetY;
},
//监听滚动事件,实时计算scrollTop
scroll(){
this.scrollTop = document.querySelector('.content_box').scrollTop; //element.scrollTop方法可以获取到卷起的高度
this.getShowList();
}
},
mounted() {
this.getList();
this.scroll();
}
}
</script>
<style scoped>
.content_box{
overflow: auto; /*只有这行代码写了,内容超出高度才会出现滚动条*/
width: 700px;
border: 1px solid red;
}
/*每条数据的样式*/
.item{
height:30px;
padding: 5px;
color: #666;
box-sizing: border-box;
}
</style>
<div id="app">
// 子组件
<Child>
// 给插槽提供的内容
<template v-slot:default>
<button>按钮</button>
<a href="https://huawei.com">跳转华为</a>
</template>
<template v-slot:header>
<h1>标题</h1>
<p>内容,21231215456454</p>
</template>
</Child>
</div>
<Child>子组件的模板中:
<div>
<h1>这是子组件的内容</h1>
<slot></slot> // 给插槽提供的内容将会被放在这个位置,这是默认插槽
</div>
微前端是一种类似于微服务的架构,它将微服务的理念应用于浏览器端,即将 Web 应用由单一的单体应用转变为多个小型前端应用聚合为一的应用。各个前端应用可以独立运行、独立开发、独立部署。(建议先了解微服务)
(1)微服务就是一种架构风格
(2)微服务就是把一个项目拆分成独立的多个服务,并且多个服务是可以独立运行的,而每个服务都会占用线程。
正确解决方法:升级版本。
创建阶段: 只执行一次。
beforeCreate(开始进行一些数据和方法的初始化的操作, data 中的数据和 methods 中的方法还不能用)。
created(已经完成数据和方法的初始化, data 中的数据和 methods 中的方法可以使用了)。
挂载阶段:
beforeMount(开始渲染虚拟 DOM)。
mounted(已经完成了虚拟 DOM 的渲染, 可以操作 DOM 了, 只执行一次)。
更新阶段: 执行多次
beforeUpdate(data 中的数据即将被更新, 会执行多次)。
updated(data 中的数据已经更新完毕, 会执行多次)。
销毁阶段: 只执行一次
beforeDestroy(vue 实例即将销毁, 此时 data 中的数据和 methods 中的方法依然处于可用状态)。
destroyed(vue 实例已经销毁, 此时 data 中的数据和 methods 中的方法已经不可用)。
因为对象是一种引用数据类型,在内存中只有一份. 如果 data 的值直接是一个对象的话, 那么后期组件在不同的地方多次调用的时候, 会相互产生影响, 因为每一次调用操作的 data 对象是一样的。使用函数的方式返回对象, 可以保证组件的每一次调用都会创建一个新对象,这样组件的每一次调用不会相互产生影响。
// 修改数据
vm.msg ='Hello'// DOM 还没有更新
Vue.nextTick(function(){
// DOM 更新了})
this.$nextTick()
具体实现:在项目的app.vue文件中定义一个布尔类型的数据,通过v-if来控制 router-view是否展示,同时定义一个刷新函数,函数内部逻辑主要是将展示设为false,等nextTick执行后再将展示设为true,实现页面的重新加载;写好刷新函数后,在需要刷新的组件中通过inject注入刚刚app.vue中provide提供的依赖,也就是那个刷新函数,然后直接调用这个函数即可实现刷新。
总结刷新函数做的事情就是,想要刷新的时候,我们就调用刷新函数,结合v-if的作用,先将组件是否展示设为false让组件先销毁,再将是否展示设为true让组件创建。除了以上方式,还有this.$forceUpdate()等刷新方式。
【需求】全局页面背景色是白色,现需要更改某个页面的背景色为灰色。
【无效】尝试直接改body标签的样式,但是设置后,发现所有页面背景色都变成灰色了。
【原因】vue是一个单页面应用,只有一个index.html,牵一发而动全身。
【正确做法】在这个页面创建前,也就是beforeCreate生命周期函数里把body背景色改成我们想要的颜色,同时在这个页面销毁前,也就是beforeDestroy生命周期钩子中,移除我们刚加的背景色样式。这样跳到其他页面时,刚在那个页面加的body背景色就会移除,继续使用全局的那个背景样式。
具体代码是:
beforeCreate(){
document.querySelector('body').setAttribute('style','background-color:#fff')
},
beforeDestroy() {
document.querySelector('body').removeAttribute('style')
},
除了在需要修改的页面上里改body的背景样式外,我们也可以把这块逻辑封在路由守卫中,当进入需要更改的页面路由时,做刚刚的样式操作。离开这个路由时移除。
this.$refs.tes
t获取dom,然后做一些我们需要的操作。this.$refs.test
,就可以拿到一个VueComponent对象,这个对象里面有这个子组件的各个属性,打印出来会发现里面有个$el
属性,这就是这个子组件的dom对象。如果子组件的data中有个msg属性,那么在父组件内我们就可以通过this.$refs.test.msg
拿到子组件的这个msg值;再假设子组件有一个getData方法,那么父组件内通过this.$refs.test.getDgta()
也可以调用子组件的getData方法。【相同点】两个文件夹下都可以用于存储项目中所需的静态资源,像图片,样式文件等等。
【区别】assets下存放的静态资源文件在项目打包时,也就是执行 npm run build 指令时,会走webpack的打包流程,做压缩代码体积、代码格式化这种操作;放static中存放的资源文件不会走打包流程,而是直接复制进最终的dist目录里。所以如果找们把所有资源都放进static下,由于该文件夹下的资源不会走打包流程,所以在项目打包时会提高一定的效率,但是同时也有一个问题,就是由于不会进行压缩等操作,所以项目打包后的体积会比把资源都放进assets下来得大。
【总结】我们通过npm run build打包项目后,会生成一个dist文件夹,放在assets里面的资源会被webpack打包后放进dist文件夹中,而static里面的资源是直接复制进dist中,由于第三方类库资源一般都已经经过处理了,所以我们可以在static里放一些外部的第三方资源文件,assets放我们自己项目中的图片等资源,让这些资源走打包流程,减少最终包的体积。但是实际开发中情况肯定是多变的,还是要根据实际情况来看把静态资源文件放在哪里更合适。
this.$router
是VueRouter的一个实例,是一个全局路由对象,它可以用来操作路由,项目中比较常用的就是拿来做路由跳转,比如经常我们需要跳转到另一个页面时,就会写this.$router.push( )
,this.$route
是当前激活的路由对象,通过它我们可以拿到当前路由的一些信息比如path,,query,meta等属性。this.$router.push
来做跳转,并且我们可以给即将跳转到的详情路由对象的query对象里传个该条数据的id,为的是希望路由跳转后,通过这个id去获取数据的详情并展示,跳转后怎么拿id呢,我们就可以通过this.$route
先拿到当前路由的所有信息,然后去query对象里拿刚刚在上一页面跳转前放进去的id属性,这样拿到id后再去查详情数据。完整的就是this$route.query.id
。概括来说就是,route 是用来获取当前路由信息的,也就是读路由信息,而router是用来操作路由的,是写路由的。
this.$router.push()
方法,我们可以在某个函数里面要用路由的这个方法来实现跳转。this.$router.replace()
,this.$router.push()
跳转到指定url路径的同时也会像history栈中添加一条记录,点击后退就会返回到上一个页面,this.$router.replace()
方法跳转到指定URL 路径点击返回,他是跳转到你看到的上上个页面也就是说目标页面直接替换了,而不是添加一条记录,this.$router.go(n)
,我们可以利用这个方法向前或者向后跳转n个页面,比如说n是1,可以跳到下一页,如果是-1,则回退到上一页,0就是当前页面。【需求】浏览器窗口大小变化的时候,echarts图表要随着浏览器窗口变化而变化。
【解决】window.addEventListener方法监听窗口的变化,当窗口变化时,让需要自适应的echarts实例调用echarts官方给的自适应resize方法,可以在mounted钩子函数中写下面的代码,这样就可以实现自适应了。
window.addEventListener('resize', () => {
myEchart.resize();
});
<div>
<h6>图片懒加载</h6>
<img data-src="/static/images/login-bg-3.jpg" src="/static/images/login-bg-4.jpg"><br>
<img data-src="/static/images/login-bg-1.jpg" src="/static/images/login-bg-4.jpg"><br>
<img data-src="/static/images/login-bg.jpg" src="/static/images/login-bg-4.jpg"><br>
<img data-src="/static/images/login-bg-3.jpg" src="/static/images/login-bg-4.jpg"><br>
<img data-src="/static/images/login-bg-old.jpg" src="/static/images/login-bg-4.jpg"<br>
<img data-src="/static/images/login-bg-1.jpg" src="/static/images/login-bg-4.jpg"><br>
<img data-src="/static/images/login-bg.jpg" src="/static/images/login-bg-4.jpg"><br>
</div>
第一种:vue-lazyload 插件实现
(1) 安装插件
npm install vue-lazyload --save-dev
(2) 在main.js文件中引入并使用
import VueLazy from ‘vue-lazyload’
Vue.use(VueLazyload)
(3) 修改图片显示方式为懒加载即可。将:src=“xxx” 属性直接改为v-lazy=“xxx”
第二种:IntersectionObserver API 实现。
这个api可以自动"观察"元素是否可见,Chrome 51+ 已经支持。由于可见的本质是,目标元素与视口产生一个交叉区,所以这个 API 又叫做交叉观察器。
created() {
this.intersectionObserver();
},
methods:{
intersectionObserver(){
let images = document.getElementsByTagName('img');
const observer = new IntersectionObserver((imgs) => {
console.log('imgs===', imgs)
// imgs: 目标元素集合
imgs.forEach((img) => {
// img.isIntersecting代表目标元素可见,可见的时候给src赋值
if (img.isIntersecting) {
const item = img.target
item.src = item.dataset.src
observer.unobserve(item);
}
})
})
//定时器和Array.from的目的是让images可遍历
setTimeout(()=>{
Array.from(images).forEach(item => {
observer.observe(item);
})
},300)
}
}
// isIntersecting 为true 代表该目标元素可见,可以加载;target 即对应页面中的真实img。
元素的offsetTop API 实现。
判断进入可视区域的条件有变,通过下图,可以看出,这里进入可视区域的判断条件是某一元素e的 e.offsetTop < document.body.clientHeight + document.body.scrollTop
获取对象相对于版面或由 offsetTop 属性指定的父坐标的计算顶端位置 < 网页可视区域的高度 + 网页被卷去的高度