一、JS
JavaScript垃圾回收机制。
1.标记清除: 原理:是当变量进入环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。
垃圾回收器,在运行的时候会给存储在内存中的所有变量都加上标记。 去掉环境中的变量以及被环境中的变量引用的变量的标记。 再被加上标记的会被视为准备删除的变量。 垃圾回收器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
2.引用计数 原理:跟踪记录每个值被引用的次数。
声明了一个变量并将一个引用类型的值赋值给这个变量,这个引用类型值的引用次数就是1。 同一个值又被赋值给另一个变量,这个引用类型值的引用次数加1。 当包含这个引用类型值的变量又被赋值成另一个值了,那么这个引用类型值的引用次数减1。 当引用次数变成0时,说明没办法访问这个值了。 当垃圾收集器下一次运行时,它就会释放引用次数是0的值所占的内存。 ————————————————
释放内存 把声明的对象赋值为 null
let obj = {} // 开辟内存
obj.name = 'haha' // 标记内存
obj = null // 释放内存
闭包
let obj = {
name:'哈哈哈',
fn:function(){
return this.name
}
}
obj.fn();
// 释放obj的内存
obj = null;
优点:可以隔离作用域,不造成全局污染,可以让垃圾回收机制不会回收这个变量,让他能一直被标记为被引用。
缺点:由于闭包长期驻留内存,则会导致大量内存永远不会被释放。
如何释放内存:将暴露外部的闭包变量置为 null。
this指向
function fn1(){
console.log(this)
}
fn1(); // window
// window.fn1();
let obj = {
name:'哈哈哈',
fn:function(){
return this.name
}
}
obj.fn() // obj
<div id="dom">你好</div>
let dom = document.getElementById('dom')
dom.addEventListener('click'function(){
console.log(this) // dom
}
)
setTimeout(()=>{
console.log(this) // window
})
setInterval(()=>{
console.log(this) // window
})
var num = 2
var obj = {
num:1,
fn:function(){
console.log(this.num); // 1
}
}
// 如果是箭头函数,那么会把this指向他的父级作用域
{
var num = 2
var obj = {
num:1,
fn:() => {
console.log(this.num); // 2
}
}
}
箭头函数的特点
1、没有构造函数,不能new实例对象
2、本身没有this指向,this指向父级
3、不能使用argumetns
4、没有原型
// 箭头函数使表达更加简洁,隐式返回值
// 正常的写法
const isEven = function(n){
return n % 2 === 0;
}
const square = function(n){
return n * n;
}
const isEven = n => n % 2 === 0;
const square = n => n * n;
// 箭头函数的一个用处是简化回调函数
// 正常函数写法
[1,2,3].map(function (x) {
return x * x;
});
// 箭头函数写法
[1,2,3].map(x => x * x); //[1,4,9]
var let 和 const 区别
// var 的变量会挂载到 window上
var a = 1 // window.a
console.log(a); // 1
console.log(window.a); // 1
let b = 2; //
console.log(b); // 2
console.log(window.b); // undefined
const c = 3; //
console.log(b); // 3
console.log(window.b); // undefined
// var 可以被重复声明,let const 不可以,如果重复声明会报错
var a = 1;
var a = 100;
console.log(a) // 100
let b = 2 ;
let b = 200 ;
// Uncaught SyntaxError: Identifier 'b' has already been declared
let const = 3 ;
let const = 300 ;
// Uncaught SyntaxError: Identifier 'c' has already been declared
// var有变量提升, let 和 const 没有
console.log(a) // undefined
var a = 10;
console.log(b)
let b = 20;
//Uncaught ReferenceError: can't access lexical declaration 'b' before initialization
console.log(c)
let c = 20;
//Uncaught ReferenceError: can't access lexical declaration 'c' before initialization
// let const 生成块级作用域
if(1){
var a = 100;
let b = 10
console.log(b)
}
function fn(){
return {
a:100
}
}
fn()
console.log(a);
console.log(b)
// let const 会形成一个暂时性死区
var a = 100
function fn(){
console.log(a);
}
fn();
//
var a = 100
function fn(){
console.log(a);
let a = 10;
}
fn(); // 暂时性死区
// 1、一旦声明必须赋值。
// 2、声明后不能再修改
// 3、如果声明的是复合类型数据,可以修改其属性
const a = 10;
a = 100; // 报错
const arr = [];
arr = [1,2] // 报错,改变了内存指针的指向
arr.push(1); // [1],只是修改指针指向的内存地址里的属性
let obj = {}
obj = {name:'tan'} // 报错
obj.name = 'tan' // 不报错,内存指针未变,只改变了内存里的属性
js内存存储机制
基本数据类型:bigInt、string、number、boolean、null、undefined、symbol
引用数据类型:Object、Function、Array
基本数据类型放在栈内存里面
引用数据类型有一个占位牌在栈内存里,真实的数据在堆内存中,并且占位牌有一个指针指向对应的内存空间。
这是为什么const 可以对原引用数据类型进行修改,而不能直接重新赋值一个新对象的原因,第一种指针未发生改变,第二种指针指向新的内存地址所以会报错。
js的值传递和引用传递
function setAge(i) {
alert(i); // 24
i = 18;
alert(i);//18,i的改变不会影响外面的age
};
let age = 24;
setAge(age);
alert(age); // 24
js的全局变量和局部变量
var a = 10;
function fn(){
var a = 20;
}
console.log(a);
------------------------
var a = 10;
function fn(){
console.log(a) // undefined
var a = 20;
}
fn();
console.log(a); // 10
------------------------
var a = 10;
function fn(){
// Uncaught ReferenceError: can't access lexical declaration 'a' before initialization
console.log(a)
let a = 20;
}
fn();
console.log(a);
------------------------
var a = 10;
function fn(){
a = 20;
}
fn();
console.log(a); // 20
------------------------
var a = 10;
function fn(a){
a = 20;
}
fn();
console.log(a); // 10
------------------------
Promise的使用
有两个接口,第二接口的数据依赖于第一个接口
console.log(1)
console.log(2)
// 1 -> 2
get(/list),then(res=>{
this.id = res.id
}).catch(err=>{
})
get(/list/user).then(res2=>{
if(this.id){
console.log(res2)
}
})
// 回调地狱
get(/list),then(res=>{
get(/list/user).then(res2=>{
if(res.id){
console.log(res2)
}
})
}).catch(err=>{
})
----------------------------
console.log(1)
let p = new Promise ((resolve,reject)=>{
setTimeout(()=>{
console.log(2)
resolve(true)
},1500);
})
p.then(res=>{
if(res){
console.log(3);
}
})
----------------------------
console.log(1)
let p = new Promise ((resolve,reject)=>{
setTimeout(()=>{
console.log(2)
resolve(1) // then
// reject(2) // catch
},1500);
})
p.then(res=>{
console.log(res)
if(res){
console.log(3);
}
}).catch(err=>{
console.log(err);
}).finally(res=>{
console.log('finally')
})
---------------------------- Promise.all
返回多个promise的结果,形成一个数组
let p1 = new Promise ((resolve,reject)=>{
setTimeout(()=>{
resolve(1)
},1500);
})
let p2 = new Promise ((resolve,reject)=>{
setTimeout(()=>{
resolve(2)
},0);
})
Promise.all([p1,p2]).then(res=>{
console.log(res)
})
---------------------------- Promise.race
返回多个promise中最快的那一个,其他不返回
let p1 = new Promise ((resolve,reject)=>{
setTimeout(()=>{
resolve(1)
},1500);
})
let p2 = new Promise ((resolve,reject)=>{
setTimeout(()=>{
resolve(2)
},0);
})
Promise.race([p1,p2]).then(res=>{
console.log(res)
})
---------------------------- Promise 联用
new Promise((resolve,reject)=>{
resolve(1);
}).then(res =>{
// console.log(res);
return res
}).then(res=>{
console.log(res);
return res
})
async 和 await
async function fn(){
return new Promise((resolve,reject)=>{
resolve(1)
})
}
async function test(){
let p = await fn();
console.log(p)
}
test();
----------------------------
let p1 = new Promise((resolve,reject)=>{
resolve(1)
})
let p2 = new Promise((resolve,reject)=>{
resolve(2)
})
let p3 = new Promise((resolve,reject)=>{
resolve(3)
})
async function test(){
let p = await Promise.all([p1,p2,p3]);
console.log(p)
}
async function test(){
let p = await Promise.all([p1,p2,p3]);
console.log(p)
if(p[0]===1&&p[1]===2){
console.log(p[2]);
}
}
test();
----------------------------
async function test(){
let pone = await p1;
// console.log(pone)
let ptwo = await p2;
// console.log(ptwo);
if(pone===1&&ptwo===2){
let pthree = await p3;
console.log(pthree)
}
}
什么是深拷贝?深拷贝和浅拷贝有什么区别?
浅拷贝是指只复制第一层对象,但是当对象的属性是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。
实现一个深拷贝:
function deepClone(obj) { //递归拷贝
if(obj === null) return null; //null 的情况
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
if(typeof obj !== 'object') {
//如果不是复杂数据类型,直接返回
return obj;
}
/**
* 如果obj是数组,那么 obj.constructor 是 [Function: Array]
* 如果obj是对象,那么 obj.constructor 是 [Function: Object]
*/
let ty = new obj.constructor();
for(let key in obj) {
//如果 obj[key] 是复杂数据类型,递归
t[key] = deepClone(obj[key]);
}
return ty;
}
事件循环机制 EventLoop
是js的一个底层运行原理,js是单线程的,但是也有一些耗时任务, 会影响执行效率.代码都在主线程中执行,当遇见你像ajax请求.setTimeout 定时器时候,会 单独开启异步线程.异步线程耗时之后会推入异步队列中等待执行.然后当主线程执行完毕 之后.会到异步队列中取出到主线程中执行.然后再去异步队列中取第二个.这个来回取的过程就是事件循环(eventLoop)吧
数组去重
实现代码如下:
function uniqueArr(arr) {
return [...new Set(arr)];
}
js的数据类型以及储存方式
基本数据类型 String、Number、Boolean、null、undefind、Symbol、BigInt 基本数据类型存储在栈内存中的,因为基础的数据类型占用空间比较小,大小值是固定的,通过值来访问,属于被频繁使用的数据 注意:Symbol和BigInt是ES6引入的新的数据类型
引用数据类型(复杂数据类型) Object、Date、Function、Array等等 引用的数据类型存储在堆内存中,因为引用的数据类型占空间比较大,占用内存大小也不固定。如果存储在栈中,则会影响到程序运行的性能,引用数据类型在栈中存储了指针,改指针指向堆中的起始地址,当我们需要引用值时,就会检索其在栈中的地址,取得地址后再从堆中获得值
js内存分为栈内存和堆内存
栈内存:是一种特殊的线性表,具有后进先出的特性,存储着基本数据类型 堆内存:存放引用的类型,在栈内存中会存储一个基本类型值保存对象在堆内存的地址,方便引用这个对象
可以通过typeof 检测基本数据类型和复杂数据类型 注意:typeof 对于基本类型,除了 null 都可以显示正确的类型,这是一个很久的bug,可以上网查资料
var a = 0; //number
var b = 'this is str'; //string
var c = null; //object
var d = [1,2,3]; //object
var e = {m:20}; //object
var f = undefined; //undefind
还可以进一步使用instanceof确认复杂数据类型
JavaScript 如何判断一个变量是否为数组类型
- 使用 instanceof 运算符, 该运算符左边是我们想要判断的变量, 右边则是我们想要判断的对象的类, 例如:
let arr = [1, 2, 3]
console.log(arr instanceof Array)
// true 返回true,说明变量arr是数组类型
- 利用构造函数来判断他的原型是否为Array, 用法:
变量.constructor === 变量类型
let arr = [1, 2, 3]
console.log(arr.constructor === Array)
// true 返回true,说明变量arr是数组类型
- 第三种方法利用的一个专门的方法 isArray(), 用法:
Array.isArray(变量),返回true,则说明该变量是数组类型;反之,说明该变量不是数组类型
let arr = [1, 2, 3]
console.log(Array.isArray(arr))
// true 返回true,说明变量arr是数组类型
- 第四种方法是调用
Object.prototype.toString.call(),返回true,则说明该变量是数组类型;反之,说明该变量不是数组类型
let arr = [1, 2, 3]
console.log(Object.prototype.toString.call(arr) === '[object Array]')
// true 返回true,说明变量arr是数组类型
- 第五种方式是通过对象的原型方式来判断,直接来看例子
let arr = [1, 2, 3]
console.log(arr.__proto__ === Array.prototype)
// true 返回true,说明变量arr是数组类型
- 第六种方式是通过
Object.getPrototypeOf()来判断是否为数组类型,例如
let arr = [1, 2, 3]
console.log(Object.getPrototypeOf(arr) === Array.prototype)
// true 返回true,说明变量arr是数组类型
- 第七种方式是通过
isPrototypeOf()方法来判断是否为数组类型,例如
let arr = [1, 2, 3]
console.log(Array.prototype.isPrototypeOf(arr))
// true 返回true,说明变量arr是数组类型
call-apply-bind三者的作用以及区别
1. call-apply-bind三者的作用
在JavaScript中,call-apply-bind是Function对象自带的三种方法,它们的作用都是用来改变函数内的this指向,传递的第一个参数都是this所要指向的对象,而且它们三个都可以传递参数(后续传值)
2. call-apply-bind三者的区别
- call:调用一个对象的一个方法,用另一个对象替换当前对象,在传递参数的时候需要一个一个进行传递,而且定义完立即执行,返回值为调用方法的返回值,如果调用的方法没有返回值,则返回undefined
obj.call(obj1,arg1,arg2,arg3....);
- apply:和call很类似,都是立即执行,返回值为调用方法的返回值,如果调用方法没有返回值,则返回undefined,唯一不同的是,apply在传递参数的时候需要以数组的形式
obj.apply(obj1,[arg1,arg2,arg3.....]);
- bind:除了返回值是函数以外,它在传递参数的时候和call一样,都是一个一个传递,它在调用之后返回一个新的函数,不会立即执行
obj.bind(obj1,arg1,arg2,arg3...)();
parseInt()、Number()的区别
parseInt()、Number()这两个函数用到最多的地方就是把一个字符串转换成数据类型,那么他们都有哪些区别?
1. parsseInt()
parseInt()函数将给定的字符串以指定的基数解析为整数。
parseInt(string,radix)
第二个参数表示使用的进制,我们一般使用10进制,也可能会有到8或者16进制。为了避免对“0”和“0x”开头的字符串解析错误,各种javascript编程规范都规定必须要明确给出第二个参数的值,如parseInt(“123”,10).
parseInt('16', 8) = 14
parseInt('10', 8) = 8
parseInt('16', 10) = 16
parseInt('10', 10) = 10
parseInt('16', 16) = 22
parseInt('10', 16) = 16
parseInt从头解析string为整数,在遇到不能解析的字符时就返回已经解析的整数部分,如果第一个字符就不能解析,就直接返回NaN。
2. Number()
Number()在不用new操作符时,可以用来执行类型转换。如果无法转换为数字,就返回NaN。像“123a”,parseInt()返回是123,Number()返回是NaN。
==和===区别
- ==, 两边值类型不同的时候,要先进行类型转换,再比较
- ===,不做类型转换,类型不同的一定不等。
==类型转换过程:
- 如果类型不同,进行类型转换
- 判断比较的是否是 null 或者是 undefined, 如果是, 返回 true .
- 判断两者类型是否为 string 和 number, 如果是, 将字符串转换成 number
- 判断其中一方是否为 boolean, 如果是, 将 boolean 转为 number 再进行判断
- 判断其中一方是否为 object 且另一方为 string、number 或者 symbol , 如果是, 将 object 转为原始类型再进行判断
经典面试题:[] == ![] 为什么是true
转化步骤:
- !运算符优先级最高,
![]会被转为为false,因此表达式变成了:[] == false - 根据上面第(4)条规则,如果有一方是boolean,就把boolean转为number,因此表达式变成了:
[] == 0 - 根据上面第(5)条规则,把数组转为原始类型,调用数组的toString()方法,
[]转为空字符串,因此表达式变成了:'' == 0 - 根据上面第(3)条规则,两边数据类型为string和number,把空字符串转为0,因此表达式变成了:
0 == 0 - 两边数据类型相同,0==0为true
阻止事件冒泡,阻止默认事件,event.stopPropagation()和event.preventDefault(),return false的区别
1.event.stopPropagation()方法
这是阻止事件的冒泡方法,不让事件向documen上蔓延,但是默认事件任然会执行,当你掉用这个方法的时候,如果点击一个连接,这个连接仍然会被打开,
2.event.preventDefault()方法
这是阻止默认事件的方法,调用此方法是,连接不会被打开,但是会发生冒泡,冒泡会传递到上一层的父元素;
3.return false ;
这个方法比较暴力,他会同事阻止事件冒泡也会阻止默认事件;写上此代码,连接不会被打开,事件也不会传递到上一层的父元素;可以理解为return false就等于同时调用了event.stopPropagation()和event.preventDefault()
二 CSS
盒模型的理解(包括 IE 和 w3c 标准盒 模型)
盒模型其实就是浏览器把一个个标签都看一个形象中的盒子,那每个 盒子(即标签)都会有内容(width,height),边框(border),以及内容和边框 中间的缝隙(即内间距 padding),还有盒子与盒子之间的外间距(即margin), 用图表示为:
当然盒模型包括两种:IE 盒模型和 w3c 标准盒模型
IE盒模型=设置的 width 宽度+margin 的值 , 其中,width 值包含了 padding 和 border;
标准盒模型=width+padding+border+margin
那如何在 IE 盒模型宽度和标准盒模型总宽度之间切换呢,可以通过box-sizing:border-box 或设置成 content-box 来切换 其中:box-sizing:border-box //IE 盒模型 box-sizing:content-box //w3c 盒模型
如何实现一个 div 垂直居中
1. 定位有三种方法
通过给 div 设置绝对定位,并且 left,right,top,bottom 设置为 0,margin:auto 即可以水平垂直居中
通过给 div 设置绝对定位,left 为 50%,top 为 50%,再给 div 设置距左是自身的一半即:margin-left:自身宽度/2,margin-top:自身高 度/2。
通过给 div 设置绝对定位,left 为 50%,top 为 50%,再给 div 设置跨左和跟上是自身的一半:
transform:translate3d(-50%,-50%,0)
2. flex布局
有两个div,父级div和子级div,给父级div设置
display:flex, 并且设置父级 div 的水平居中justify-content:center,并且给父级 div 设置 垂直居中align-items:center即可
让元素消失
visibility:hidden、display:none、z-index=-1、opacity:0
- opacity:0,该元素隐藏起来了,但不会改变页面布局,并且,如果该元素已经绑定了一些事件,如click事件也能触发
- visibility:hidden,该元素隐藏起来了,但不会改变页面布局,但是不会触发该元素已经绑定的事件
- display:none, 把元素隐藏起来,并且会改变页面布局,可以理解成在页面中把该元素删掉
- z-index=-1置于其他元素下面
实现一个简单的三角形
首先来看在为元素添加border时,border的样子;假设有如下代码:
<div></div>
div {
width: 50px;
height: 50px;
border: 2px solid orange;
}
效果图:
这是我们平常使用border最普遍的情况——往往只给border一个较小的宽度(通常为1-2px);然而这样的日常用法就会容易让大家对border的形成方式产生误解,即认为元素的border是由四个矩形边框拼接而成。
然而事实并不是这样。实际上,元素的border是由三角形组合而成,为了说明这个问题,我们可以增大border的宽度,并为各border边设置不同的颜色:
div {
width: 50px;
height: 50px;
border: 40px solid;
border-color: orange blue red green;
}
效果图:
既然如此,那么更进一步,把元素的内容尺寸设置为0会发生什么情况呢?
div {
width: 0;
height: 0;
border: 40px solid;
border-color: orange blue red green;
}
效果图:
我们将惊奇地发现,此时元素由上下左右4个三角形“拼接”而成;那么,为了实现最终的效果,即保留最下方的三角形,还应该怎么做?很简单,我们只需要把其它border边的颜色设置为白色或透明色:
div {
width: 0;
height: 0;
border: 40px solid;
border-color: transparent transparent red;
}
效果图:
Duang~ 最终的简单三角形就绘制出来了。同理,如果想要得到其它边上的三角形,只需要将剩余的border边颜色设置为白色或透明色即可。
不过,被“隐藏”的上border仍然占据着空间,要想使得绘制出的三角形尺寸最小化,还需要将上border的宽度设置为0(其它情况同理):
div {
width: 0;
height: 0;
border-width: 0 40px 40px;
border-style: solid;
border-color: transparent transparent red;
}
css优先级 的 6大分类
通常可以将css的优先级由高到低分为6组:
-
第一优先级:无条件优先的属性只需要在属性后面使用!important。它会覆盖页面内任何位置定义的元素样式。ie6不支持该属性。
-
第二优先级:在html中给元素标签加style,即内联样式。该方法会造成css难以管理,所以不推荐使用。
-
第三优先级:由一个或多个id选择器来定义。例如,#id{margin:0;}会覆盖.classname{margin:3pxl}
-
第四优先级:由一个或多个类选择器、属性选择器、伪类选择器定义。如.classname{margin:3px}会覆盖div{margin:6px;}
-
第五优先级:由一个或多个类型选择器定义。如div{marigin:6px;}覆盖*{margin:10px;}
-
第六优先级:通配选择器,如*{marigin:6px;}
css优先级 的 优先顺序
行内样式(style="…")>ID 选择器(#box{…})>类选择器(.con{…})>标签选择器(dic{…})>通用选择器(*{…})
三、vue
vue3和vue2的原理区别
我们知道Vue2 是响应式原理基于
Object.defineProperty方法重定义对象的 getter 与 setter,vue3 则基于Proxy代理对象,拦截对象属性的访问与赋值过程。
mvc和mvvm理解
MVC
MVC即Model View Controller,简单来说就是通过controller的控制去操作model层的数据,并且返回给view层展示。
- View 接受用户交互请求
- View 将请求转交给Controller处理
- Controller 操作Model进行数据更新保存
- 数据更新保存之后,Model会通知View更新
- View 更新变化数据使用户得到反馈
MVVM
MVVM即Model-View-ViewModel,将其中的 View 的状态和行为抽象化,让我们可以将UI和业务逻辑分开。MVVM的优点是低耦合、可重用性、独立开发。
- View 接收用户交互请求
- View 将请求转交给ViewModel
- ViewModel 操作Model数据更新
- Model 更新完数据,通知ViewModel数据发生变化
- ViewModel 更新View数据
MVVM模式和MVC有些类似,但有以下不同
- ViewModel 替换了 Controller,在UI层之下
- ViewModel 向 View 暴露它所需要的数据和指令对象
- ViewModel 接收来自 Model 的数据
概括起来,MVVM是由MVC发展而来,通过在Model之上而在View之下增加一个非视觉的组件将来自Model的数据映射到View中。
响应原理
vue采用数据劫持结合发布者-订阅者模式的方式,通过
Object.defineProperty劫持data属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
生命周期函数
- beforeCreate(创建前) vue实例的挂载元素$el和数据对象 data都是undefined, 还未初始化
- created(创建后) 完成了 data数据初始化, el还未初始化
- beforeMount(载入前) vue实例的$el和data都初始化了, 相关的render函数首次被调用
- mounted(载入后) 此过程中进行ajax交互
- beforeUpdate(更新前)
- updated(更新后)
- beforeDestroy(销毁前)
- destroyed(销毁后)
组件data为什么返回函数
组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data。如果单纯的写成对象形式,就使得所有组件实例共用了一份data,造成了数据污染。
vue给对象新增属性页面没有响应
由于Vue会在初始化实例时对属性执行getter/setter转化,所以属性必须在data对象上存在才能让Vue将它转换为响应式的。Vue提供了$set方法用来触发视图更新。
export default {
data(){
return {
obj: {
name: 'fei'
}
}
},
mounted(){
this.$set(this.obj, 'sex', 'man')
}
}
v-if和v-show区别
v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建;也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
v-show 就简单得多——不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 的 “display” 属性进行切换。
所以,v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景;v-show 则适用于需要非常频繁切换条件的场景。
scoped属性作用
在Vue文件中的style标签上有一个特殊的属性,scoped。当一个style标签拥有scoped属性时候,它的css样式只能用于当前的Vue组件,可以使组件的样式不相互污染。如果一个项目的所有style标签都加上了scoped属性,相当于实现了样式的模块化。
scoped属性的实现原理是给每一个dom元素添加了一个独一无二的动态属性,给css选择器额外添加一个对应的属性选择器,来选择组件中的dom。
<template>
<div class="box">dom</div>
</template>
<style lang="scss" scoped>
.box{
background:red;
}
</style>
vue将代码转译成如下:
.box[data-v-11c6864c]{
background:red;
}
<template>
<div class="box" data-v-11c6864c>dom</div>
</template>
scoped样式穿透
scoped虽然避免了组件间样式污染,但是很多时候我们需要修改组件中的某个样式,但是又不想去除scoped属性。
- 使用/deep/
//Parent
<template>
<div class="wrap">
<Child />
</div>
</template>
<style lang="scss" scoped>
.wrap /deep/ .box{
background: red;
}
</style>
//Child
<template>
<div class="box"></div>
</template>
- 使用两个style标签
//Parent
<template>
<div class="wrap">
<Child />
</div>
</template>
<style lang="scss" scoped>
//其他样式
</style>
<style lang="scss">
.wrap .box{
background: red;
}
</style>
//Child
<template>
<div class="box"></div>
</template>
ref的作用
- 获取dom元素
this.$refs.box - 获取子组件中的data
this.$refs.box.msg - 调用子组件中的方法
this.$refs.box.open()
vue 中 methods,computed,watch 的区别
methods 中都是封装好的函数,无论是否有变化只要触发就会执行 computed:是vue独有的特性计算属性,可以对data中的依赖项再重新计算, 得到一个新值,应用到视图中,和 methods 本质区别是 computed 是可缓存的, 也就是说computed 中的依赖项没有变化,则computed中的值就不会重新计算, 而 methods 中的函数是没有缓存的。Watch 是监听 data 和计算属性中的新旧变 化。
2.watch用于观察和监听页面上的vue实例,如果要在数据变化的同时进行异步操作或者是比较大的开销,那么watch为最佳选择。
vue路由有几种模式
- hash模式
即地址栏URL中的#符号,它的特点在于:hash 虽然出现URL中,但不会被包含在HTTP请求中,对后端完全没有影响,不需要后台进行配置,因此改变hash不会重新加载页面。
- history模式
利用了HTML5 History Interface 中新增的pushState() 和replaceState() 方法(需要特定浏览器支持)。history模式改变了路由地址,因为需要后台配置地址。
组件之间的传值通信
- 父组件给子组件传值通过props
- 子组件给父组件传值通过$emit触发回调
- 兄弟组件通信,通过实例一个vue实例eventBus作为媒介,要相互通信的兄弟组件之中,都引入eventBus
//main.js
import Vue from 'vue'
export const eventBus = new Vue()
//brother1.vue
import eventBus from '@/main.js'
export default{
methods: {
toBus () {
eventBus.$emit('greet', 'hi brother')
}
}
}
//brother2
import eventBus from '@/main.js'
export default{
mounted(){
eventBus.$on('greet', (msg)=>{
this.msg = msg
})
}
}
axios拦截器怎么配
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
四、浏览器
浏览器的缓存机制
- 什么是浏览器缓存机制
浏览器的缓存机制就是把一个请求过的web资源(例如:html页面、图片、js、数据等)拷贝一份副本储存在浏览器中;缓存会根据进来的请求保存输出内容的副本,当下一个请求到来的时候,如果是相同的URL,缓存会根据缓存机制决定是否直接使用副本响应访问请求,还是向源服务器再次发送请求。比较常见的就是浏览器会缓存访问过的页面,当再次访问这个URL地址的时候,如果网页没有更新就不会再次下载网页,而是直接使用本地缓存的网页;只有当网站明确标识已更新,浏览器才会再次下载网页;
- 浏览器缓存机制的好处
减少网络宽带产生较小的流量,减轻服务器的压力,渲染缓存的页面,就减少了对源服务器的访问,提高响应页面的速度,用户再次打开不用等待时间过长;
HTTP状态码
1xx表示临时响应并需要请求者继续执行操作的状态代码
2xx表示成功的请求
- 200表示OK,正常返回信息
- 201表示请求成功且服务器创建了新的资源
- 202表示服务器已经接受了请求,但还未处理
3xx表示重定向
- 301表示永久重定向,请求的网页已经永久移动到新位置
- 302表示临时重定向
- 304表示自从上一次请求以来,页面的内容没有改变过
4xx表示客户端错误
- 401表示服务器无法理解请求的格式
- 402表示请求未授权
- 403表示禁止访问
- 404表示请求的资源不存在,一般是路径写错了
5xx表示服务器错误
- 500表示最常见的服务器错误
- 503表示服务器暂时无法处理请求
常见的跨域方式
1. JSONP
jsonp 实现原理:主要是利用动态创建 script 标签请求后端接口 地址,然后传递 callback 参数,后端接收 callback,后端经过 数据处理,返回 callback 函数调用的形式,callback 中的参数 就是 json 2. 通过代理方式 前端代理我在 vue 中使用那个 vue.config.js 里面配置一个 proxy,里面有个 target 属性指向跨域链接.修改完重启项目就可 以了.实际上就是启动了一个代理服务器.绕开同源策略,在请求 的时候,通过代理服务器获取到数据再转给浏览器 3. CORS CORS 全称叫跨域资源共享,主要是后台工程师设置后端代码来达 到前端跨域请求的
什么是重绘和回流
重绘、回流 DOM性能 浏览器的性能大部分都是被这两个问题所消耗
回流必将引起重绘,重绘不一定会引起回流。
重绘
当页面中元素样式的改变并不影响它在文档流中的位置时(例如:
color、background-color、visibility等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘。 回流 当Render Tree中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流。 因为重绘和回流的存在导致真实DOM性能不佳,所以VUE和recat还有angular等框架增加了虚拟DOM技术,就是为了减少DOM的重绘和回流从而减少浏览器性能消耗,这就是虚拟DOM的好处。
什么是虚拟DOM
virtual DOM 虚拟DOM,用普通js对象来描述DOM结构,因为不是真实DOM,所以称之为虚拟DOM。
虚拟 dom 是相对于浏览器所渲染出来的真实 dom而言的,在react,vue等技术出现之前,我们要改变页面展示的内容只能通过遍历查询 dom 树的方式找到需要修改的 dom 然后修改样式行为或者结构,来达到更新 ui 的目的。
这种方式相当消耗计算资源,因为每次查询 dom 几乎都需要遍历整颗 dom 树,如果建立一个与 dom 树对应的虚拟 dom 对象( js 对象),以对象嵌套的方式来表示 dom 树及其层级结构,那么每次 dom 的更改就变成了对 js 对象的属性的增删改查,这样一来查找 js 对象的属性变化要比查询 dom 树的性能开销小。