文章目录
- 1、常见的数据类型?
- 2、typeof 运算符?
- 3、 什么可以准确的判断数据的类型
- 4、手写深拷贝
- 5、类型转换的坑?
- 6、如何使用class实现继承?
- 7、作用域
- 8、作用域链
- 9、什么是闭包?闭包会用在哪里?
- 10、 this指向
- 12、在class方法中的调用
- 13、箭头函数的指向
- 14、箭头函数有什么缺点?
- 15、什么时候不能使用箭头函数?
- 16、 event loop 轮询
- 17、宏任务和微任务
- 18、Promise 有哪三种状态?
- 19、async / await
- 20 for...of
- for..in 和 for...of 的区别?
- 21、如何优化DOM操作的性能?
- 22、事件
- Ajax 、fetch、axios 的区别?
- 23、使用XMLHttpRequest 实现 ajax
- 24、同源策略
- 25、跨域
- 26、HTTP协议的主要特点
- 27、HTTP报文的组成部分
- 28、HTTP方法
- 30、什么是持久连接?
- 31、 什么是管线化?
- 32、http的常见状态码?
- 33、,method的方法有哪些?
- 34、http有哪些常见的header
- 35、强缓存和协商缓存
- 36、刷新页面对http缓存有什么影响?
- 37、什么是DOCTYPE及作用?
- 38、浏览器的渲染过程
- 39、重排 reflow 和重绘 repaint
- 40、输入url到渲染出页面的整个过程?
- 41、防抖
- 42、节流
- 43、显示(强)类型转换和隐士类型转换
- 44、创建对象的方式?
- 45、constructor、原型、原型链?
- 46 instanceof
- 47、 JS 严格模式有什么特点?
- 3、 JS如何检测内存泄漏?
- 48、 内存泄漏的场景有哪些?(VUE为列)
- 49、for 和 forEach 谁快一些?为啥?
- 50、DOM 事件级别
- 51、DOM事件模型(捕获和冒泡)
- 52、DOM 事件流
- 53、描述DOM事件捕获的具体流程
- 54、Event 事件的常见应用
- 55、自定义事件
- 56、类的声明和实例化
- 57、继承方式
- 58、错误监控?
- 59、mvvm 框架
- 60、双向绑定的原理
- 61、代码发布流程原理?
1、常见的数据类型?
基本数据类型(7种): undefined 、null、string、number、boolean 、es6的Symbol 和 es10的BigInt
- 虽然使用typeof null 返回的是Object,但是null不是对象,是历史遗留下来的问题,是一个基本数据类型。
- Symbol类型定义的数据是唯一的,BigInt是一个整数
- 基本数据类型存储在栈中
引用数据类型:Object、Array 、function 、Data等
- function是一个比较特殊的应用类型,不用于存储数据,定义直接调用
- 引用数据类型的值存储在堆中,地址存储在栈中。当我们想把对象赋值给另一个变量时,复制的是内存地址,指向堆中的值(对象)。当其中一个变量的对象改变时,另一个变量也会改变。
1.1基本数据类型和引用数据类型的区别?
(1):声明变量时不同的内存分配
- 原始值:存储在栈中,因为基本数据类型占据的空间是固定的,所以可将它们存储在较小的内存栈中,这样存储便于徐速查询变量的值。
- 引用值:存储在堆中的对象,存储在栈中的内存地址是一个指针,指向堆中存储的对象。引用值的大小会改变,所以不能直接将对象放入栈中,否则会降低变量查询的速度(性能降低),相反,放在变量的栈空间中的值是该对象在堆中的地址,地址的大小是固定的,所以把内存地址存储在栈中对变量性能无任何负面影响。
(2):不同的内存分配机制也带来了不同的访问机制 - 在js中是不允许直接访问保存在堆内存中的对象的,所以在访问一个对象时,首先得到的是这个对象在堆中的地址,然后再按照这个地址去获取这个对象的值,这就是传说中的按引用访问,而原始类型的值则可以直接访问。
(3)复制变量时的不同 - 原始值:再将一个保存有原始值的变量复制给另一个变量,会将原始值的副本赋值给新变量,此后这两个变量是完全独立的,他们只是拥有相同的value值而已。
- 引用值:在将一个保存着对象内存地址的变量复制给另一个变量时,会把这个内存地址赋值给新变量,此时这两个变量都指向了同一个堆内存中的对象,如果他们任何一个改变对象属性,则另外一个也会跟着改变。
(4):参数传递的不同(把实参复制给形参的过程) - 原始值:只是把变量里的值传递给参数,之后参数和这个变量互不影响。
- 引用值: 对象变量它里面的值时这个对象在堆内存中的内存地址,这个一点要时刻铭记?因此它传递的值也就是这个内存地址,也就是为什么函数内部对这个参数做修改会影响到外部的原因了,因为他们都指向同一个对象。
2、typeof 运算符?
-
识别所有的基本数据类型
-
识别函数
-
判断是否是引用类型(不可再细分),返回object就是引用类型,当然null 除外
-
null 使用typeof 检测也是为object ,但它不是一个引用类型
3、 什么可以准确的判断数据的类型
1、使用Object.prototype.toString.call()
Object.prototype.toString.call(bsc)
// "[object String]"
Object.prototype.toString.call(null)
// "[object Null]"
Object.prototype.toString.call(123)
// "[object Number]"
Object.prototype.toString.call({a:''})
// "[object Object]"
Object.prototype.toString.call(function(){})
// "[object Function]"
Object.prototype.toString.call(undefined)
// "[object Undefined]"
3.1使用interface可以准确的判断一个对象的类型
判断一个构造函数的prototype属性所指向的对象是否存在另一个要检测的对象的原型链上。
[] instanceof Array // true 代表是数组类型
'124' instanceof String // false
let str = new String('123')
str instanceof String // true
4、手写深拷贝
// 深拷贝
function deepClone (obj) {
// 首先判断obj是否是对象或者null,如果不是,说明它是一个原始值或者null,原始值就是赋值两变量互不影响,则直接返回
if (typeof obj !== 'object' || obj == null) {
return obj
}
// 使用instanceof 判断是Array 还是 Object
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (const key in obj) {
// 使用hasOwnProperty判断对象是否含有自身的属性,不包含继承的属性,返回值给true和false
// 主要的作用就是保证key不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归主要是遍历含有多层对象的情况,而且把每个对象的值当成一个原始值再次赋值给属性名,这样子两个对象之间就影响了
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
const obj1 = {
name: '张三',
age: 28,
paly: {
name: '王者荣耀',
grade: '钻石'
},
code: [1, 2, 4, 5, 6]
}
// 使用深拷贝
const obj2 = deepClone(obj1)
obj2.name = '李四'
console.log('obj1的name值:', obj1.name) // 张三
obj2.paly.grade = '黄金'
console.log('obj1的grade值:', obj1.paly.grade) // 钻石
// 不使用深拷贝
const obj3 = obj1
obj3.name = '王五'
console.log('obj1的name值:', obj1.name) // 王五
obj3.paly.grade = '黄金'
console.log('obj1的grade值:', obj1.paly.grade) // 黄金
只要把以上代码看明白就知道,深拷贝的原理了。原理我觉得就是把引用类型转变为原始值进行赋值,这样子新对象的属性就跟原本对象的属性没有任何联系,只是值相同而已。
5、类型转换的坑?
- 字符串拼接
const a = 100 + 10 // 110
const b = 100 + '10' // 10010
const c = 100 + false // 100false
// 如果想要字符串相加
const d = 100+parseInt('10') // 110
- == 运算符
100 == '100' // true
'' == false // true
0 == '' // true
0 == false // true
null == undefined // true
// 这些都是会经过类型自动转换
// 所以我们要想得到我们想要的答案,类型和值都相等的话,就使用=== ,但是除了null使用 == ,其他都是用===
// 因为你要判断一个对象的属性是否是null或者undefined的时候,直接使用 == 更便捷,
if(obj.a == null ){}
// 代码同以下是一样的
if(obj.a === null || obj.a === undefined){}
-
if语句
首先我们要知道truly变量和falsely变量是啥?
(1)truly变量:就是经过两部非运算得出的为true ,例如 !! ‘str’ === true(2)falsely变量:就是经过两部非运算得出的为false ,例如 !! ’ ’ === false
// 以下都是falsely变量,除此之外都是truly变量
!! 0 === false
!!false === false
!!' ' === false
!!NaN === false
!!null === false
!!undefined === false
其实在if语句里面判断的不是true和false,判断的是truly 变量和falsely变量
// 这是一个truly变量
let a = 'abc'
if(a){
// 能够进来
}
// 这是一个falsely变量,进不去判断里
let b = ''
if(b){
}
- 逻辑运算
(1)&&:只要&&前面为false,无论后面是true还是false,都会返回&&前面的值;
只要&& 前面的值为true,无论后面是true还是false,都会返回&&后面的值
// 前面为true,都返回后面的值
10 && 0 // 0
10 && 2 // 2
// 前面为false ,都返回前面的值
null && 123 // null
null && 0 // null
(2) ||: 只要||前面的值为false,不管后面的值为true和false,都会返回|| 后面的值;
只要|| 前面的值为true ,不管后面的值为true还是false,都会返回前面的值
// 前面为false
0 || '10' // '10'
0 || false // false
// 前面为true
'10' || 0 // '10'
'10' || true // '10’
6、如何使用class实现继承?
- calss 实现继承主要通过extends 关键字来实现,然后通过constructor 上的super关键来继承里面的属性
代码如下:
7、作用域
- 作用域是变量、函数和对象可访问的范围。有全局作用域和局部作用域和块级作用域
- 全局作用域
在代码中任何地方都能访问到的变量、函数、对象拥有全局作用域。- 在js最外层定义的函数和变量拥有全局作用域
- 所有未定义直接赋值的变量拥有全局作用域
- 所有window对象的属性拥有全局作用域
缺点: 容易引起命名冲突;
- 局部(函数)作用域
声明在函数内部的变量,该变量只能在函数内部被访问,函数外不可访问函数内的变量,拥有局部作用域。 - 块级作用域
通过命令let \const 声明的变量,所声明的变量在指定块的作用域外无法被访问。创建块级作用域: - 函数内部
- 在一个代码块(由一对花括号包裹)内部
let/const 和var 的区别?
- let 声明的变量不可以重复声明
- var 定义的变量存在变量提升,let 声明的变量不存在变量提升。
- 在全局作用域中使用 var 声明的变量会成为 window 对象的属性,let 和 const 声明的变量则不会
- let 声明的变量属于块级作用域,var声明的变量属于函数作用域
8、作用域链
- 什么是自由变量? 就是当前作用域中没有定义的变量
var a = 100
function fn() {
var b = 200
console.log(a) // 这里的a在这里就是一个自由变量
console.log(b)
}
fn()
- 当前作用域没有找到改变量,就向父级作用域查找,如果全局作用域还没有找到,就全部放弃
作用域和执行上下文的区别?
- 执行上下文是在代码运行时决定的,作用域是在定义时确定的,并且不会改变;
9、什么是闭包?闭包会用在哪里?
闭包的定义:内部函数可访问到外部函数的作用域;能够读取其他函数内部变量的函数;闭包让你可以在一个内层函数中访问到其外层函数的作用域;闭包是指有权访问另外一个函数作用域中的变量的函数。
闭包有两种:
- 函数作为参数
// 函数作为参数
let a = 100
function fn(){
console.log(a)
}
function print(fn){
let a =200
fn()
}
print(fn) // 100
- 函数作为返回值被返回
// 函数最为返回值被返回
function fun (){
let a =100
return function (){
console.log(a)
}
}
const newFun = fun()
let a = 200
newFun() // 100
备注:闭包的自由变量查找,是在函数定义的地方向上级作用域查找,不是在执行的地方;
闭包的应用场景:
- 封装一个私有变量,外部不能直接访问函数内部的变量,只提供一个方法修改等
- 函数防抖、函数节流
10、 this指向
this的指向是在函数执行的时候确定的,不是在定义的时候。
作为普通函数调用 :
// 普通函数
function fn (){
console.log(this)
}
fn() // window
使用call 、apply 、bind 去调用 :this指向他们的参数对象
function fn (){
console.log(this)
}
// 立即执行,使用call :接受的是若干个参数的列表,fn.call(this,arg1,arg2,agr3)
fn.call({a:'hahah '}) // this指向:{a:'hahah'}
// 立即执行,使用apply:接受一个包含多个参数的数组,fn.apply(this,[arg1,arg2...])
fn.apply({c:'嘻嘻'}) // this指向 {c:'嘻嘻'}
// 使用bind返回的是一个函数,只有当需要调用的时候才执行
const fn1 = fn.bind({b:'嘿嘿'})
fn1() // this指向: {b:'嘿嘿'}
作为对象方法被调用
const people = {
name:"张三",
play(){
console.log(this) // this指向people
},
wait(){
setTimeout(function(){
console.log(this) // this 指向window
},100)
},
wait2(){
setTimeout(()=>{
console.log(this) // this 指向 people
},100)
}
}
people.paly() // this指向people
people.wait() // this 指向window
12、在class方法中的调用
class Pelople {
constructor(name){
this.name = name
}
say (){
console.log(this)
}
}
const zhangsan = new Pelople('张三')
zhangsan.say() // this指向 zhangsan
13、箭头函数的指向
(1)箭头函数不绑定this,箭头函数中的this查值行为与普通变量相同,在作用域中逐级寻找,
(2)箭头函数中的this 无法通过bind、call、aply 来直接修改(可以间接修改)
(3)改变作用域中的this指向就可以改变箭头函数的this
(4)箭头函数的取值取他上级作用域的值
const people = {
wait2(){
setTimeout(()=>{
console.log(this) // this 指向 people
},100)
}
}
14、箭头函数有什么缺点?
- 没有arguments
- 无法通过call、apply、bind 改变this
15、什么时候不能使用箭头函数?
- 对象方法里不可以用
const obj = {
name:'zhangsan',
getName:()=>{
return this.name // 获取不到name
}
}
- 原型方法不可用
const obj = {
name:'zhangsan',
}
obj.__proto__.getName = ()=>{
return this.name // 也获取不到
}
- 构造函数中不可用
const Obj = () =>{
this.name = name;
this.age= age
}
const obj1 = new Obj('张三',12) // 报错
- 动态上下文的回调函数
btn.addEventListener('click',()=>{
// this指向的是window,不是btn
this.innerHtml = 'clickss'
})
- vue 生命周期和method
16、 event loop 轮询
- 首先代码一行一行的执行,遇到同步代码就放入执行栈(call stack)中执行,执行完后执行栈就清空该代码
- 遇到异步代码,如果是微任务就放入微任务队列中等待,遇到宏任务就放入web apis 记录下,等待时机(比如定时、网络请求等)
- 宏任务等到时机到了(就是定时时间到了,或者请求结束之后),就把放到回调队列(calback queue)中,等待执行
- 如果执行栈中为空,就会去执行微任务队列代码,执行完之后尝试去渲染DOM ,如果没有DOM渲染,event loop 就开始工作了
- 轮询查找回调队列是否有代码可执行,如果有的话就放入执行栈中执行,如果没有继续轮询查找,像永动机一样的
17、宏任务和微任务
- 宏任务:setTimeout、setInterval 、ajax、Dom 事件
- 微任务: promise 、async/await
- 微任务执行比宏任务要早
console.log('11')
// 宏任务
setTimeout(()=>{
console.log('22')
})
// 微任务
Promise.resolve().then(()=>{
console.log('333')
})
console.log('5555')
// 打印结果 '11' '5555' '333' '22'
微任务和宏任务区别?
- 宏任务是Dom渲染后触发,如setTimeout
- 微任务是Dom渲染前触发,如Promise
- 最根本的区别在于微任务是es6语法规定的,宏任务是浏览器规定的
// 向页面中添加三个dom
const p1= $('<p>一段文字</p>')
const p2= $('<p>一段文字</p>')
const p3= $('<p>一段文字</p>')
$('#continer').append(p1).append(p2).append(p3)
// 微任务
Promise.resolce().then(()=>{
console.log('length1',$('#continer').children().length)
alert('promise then ') // Dom 渲染了吗 -- 没有
})
// 宏任务
setTimeout(()=>{
console.log('length1',$('#continer').children().length)
alert('setTimeout') // Dom 渲染了吗 -- 渲染了
})
18、Promise 有哪三种状态?
1:pending:在过程中,还没有结果 ;它不会触发then和catch函数
2:resolved: 成功 ; 它会触发then回调函数
3:rejected: 失败;它会触发catch 回调函数
它们之间的变化(2种),两种是不可逆:初始化promise时传入的函数会被立即执行
- pending => resolved (过程到成功)
- pending=> rejected (过程到失败)
then 和 catch 改变状态
- then 正常返回resolved 状态,如果then回调里有报错就返回rejected状态
const p = Promise.resolve().then(()=>{
return 100
})
console.log(p) // 状态是resolved // 后续触发then回调
const p1 = Promise.resolve().then(()=>{
throw new Error('报错')
})
console.log(p1) // rejected 状态,后续会触发catch 回调
p1.then((res)=>{
conosle.log('res',res) // 不进这里
}).catch((err)=>{
conosle.log('err',err) // p1会执行这里
})
catch 正常返回一个resolved 状态,如果里面有报错则返回rejected状态
const p = Promise.reject().catch(()=>{
console.log('aaa')
})
console.log(p) // 返回一个resolved的状态,因为没报错,会执行then回调
p.then(()=>{
console.log('被执行')
})
const p1 = Promise.reject().catch(()=>{
throw new Error('error')
})
console.log(p1) // 此时返回的是一个rejected 状态,因为报错了,所以会执行后面的catch回调
p1.then(()=>{
console.log('不执行这里')
}).catch((err)=>{
console.log(err) // 执行
})
// 上面p1.then().catch()又是一个resolved 的状态
19、async / await
- 解决异步回调地狱的问题,使用同步的语法,做异步的事情,只是一个语法糖,本质还是不能消灭异步、回调,js单线程
async/await 和promise 的关系,
- 执行async函数,返回的是promise对象
- await 相当于promise 的then回调
- try…catch 可捕获异常,代替promise 的catch
- 两者不排斥,相辅相成
20 for…of
- for … in (forEach \ for) 都是用于常规的同步遍历
- for … of 常用于异步的遍历
看一个列表,使用同步的遍历和使用for…of有什么区别
// 只是一个数字相乘的函数
function muti(num) {
return new Promise(resolve=>{
setTime(()=>{
resolve(num*num)
})
})
}
const arr = [1,2,3]
// 使用forEach 循环
arr.forEach(async (i)=>{
const res = await muti(i)
console.log(res) // 隔一秒之后,打印出1,4,9
})
// 使用for.. of :
!(async function(){
for(let i of arr){
const res = await muti(i)
console.log(res) // 每隔一秒打印出结果
}
})()
两者为什么打印的效果不同呢,因为for…of 是遍历每项的时候都要将里面的代码执行完再执行下一次循环,而forEach 是把每项循环完之后再去一次性执行异步的操作。
for…in 和 for…of 的区别?
- for…in 获取的是key;用于可枚举的数据,例如对象、数组、字符串
- for… of 获取的是value;用户可迭代的数据,例如数组、字符串、Map,Set
- 遍历对象: for…in 可以;for…of 不行
- 遍历Map、Set : for…of 可以,for…in 不行
- 遍历generate: for…of 可以, for…in不可以
21、如何优化DOM操作的性能?
-
对DOM查询做缓存
-
将频繁操作改为一次操作
22、事件
- 阻止默认行为 event.preventDefault()
- 阻止事件冒泡:event.stopPropagation()
Ajax 、fetch、axios 的区别?
都是用于网络请求,但是维度不同。
- ajax (asyncronous javasrcipt and xml)是一种技术的统称,是一个概念模型,它囊括了很多技术,并不特指某一技术,它很重要的特性之一就是让页面实现局部刷新。
- XMLHttpRequest 是实现ajax的一种方式而已。缺点:如果请求内部又包含请求,以此循环,就会出现回调地狱,后才出来fetch升级版。
- fetch: 是浏览器原生api, es6新出来,是基于promise,语法更加简洁、易用
fetch(url).then(res=>{
console.log(res);
})
- axios: 是一个基于 promise 封装的网络请求库,它是基于 XHR 进行二次封装。
// 发送 POST 请求
axios({
method: 'post',
url: '/user/12345',
data: {
name: 'name',
age: 12
}
})
23、使用XMLHttpRequest 实现 ajax
24、同源策略
- 同源: 协议(http、https) 、域名(例如:baidu.com)、端口(例如:8080)
无视同源策略的标签 - 图片:
<img src=跨域的图片地址 />
<link href= 跨域的css地址 />
<scrpit src = 跨域的js地址/>
,可实现JSONP
25、跨域
- 所有的跨域都必须经过服务器端的允许和配合
- 未经服务器端的允许就是先跨域,说明浏览器有漏洞
JSONP的原理:
// 实现代码
<script>
// 在window上定义一个回调函数,接收跨域地址返回的数据
window.callBack = function(data){
console.log('跨域得到的数据',data)
}
</script>
<script src="http://modd.cn/getData.json"></script>
// 跨域的网址http://modd.cn/getData.json,这个文件返回一个callBack函数执行,我们就能获取它里面的数据了
callBack({name:'aaa'})
// 前端回调函数可以通过跨域地址传给后端,后端获取回调函数名之后,更改好名字之后返回,也一样可以获取到跨域的数据
<script src="http://modd.cn/getData.json?callBackName = aaa"></script>
// 那么后端获取到callBackName之后得知前端的回调名为aaa,就会把之前的名字改成aaa({name:'aaa'})返回给前端,前端就获取到了数据
-
nginx 代理 ,配置代理地址
-
正向代理:服务器不知到用户是谁
-
反向代理: 用户不知道服务器是谁
-
node 代理,在webpack配置修改
-
解决跨域方法 CORS- 服务器设置http header(允许哪个域名访问等) 来解决跨域问题,一般后端设置之后前端就不需要处理跨域问题了
19-1 跨域请求时为啥要发送options 请求
- options 请求是跨域请求之前的预检查
- 浏览器自行发起时,无需我们干预
- 不会影响实际功能
26、HTTP协议的主要特点
- 简单快速:请求方式简单,客户端向服务器请求时,只需指定请求方法(post,get,delete,head)和路径,每种方法指定了服务器提供的不同类型的服务。快速: 由于http协议简单,因此http服务器的规模小,通讯速度快;
- 灵活:http协议允许传输任意类型的数据
- 无链接:连接一次断掉一次,即每次连接只处理一次请求,服务器处理完客服的请求并收到客户应答后,就断开连接,这样做是为了节省传输时间。
- 无状态: http协议是无状态协议,例如客户端向服务端请求一个图片,http建立连接,然后客服端拿到之后,断开连接,然后再请求同一个图片,服务端没法区分这两次请求是同一个身份的。
27、HTTP报文的组成部分
- 请求报文
请求行(请求方法和版本)、请求头、空行、请求体 - 响应报文
状态行(版本、状态)、响应头、空行、响应 体
28、HTTP方法
- POST : 传输资源
- GET:获取资源
- PUT: 更新资源
- DELTET: 删除资源
- HEAD:获得报文首部
29、POST 和GET的区别?
- get请求在URL中传送的参是有长度限制的,而post没有限制
- get没有post安全,因为参数直接暴露在url上,所以不能用来传递敏感信息
- get请求会被浏览器主动缓存,而post不会,除非手动设置
- get请求参数会被完整保留爱浏览器历史记录中,而post中的参数不会被保留
- get参数通过URL传递,post参数放在Request body中
- get产生的URL地址可以被收藏,而post不可以
- 对参数的数据类型,get只能接受ASCLL字符,而post没有限制
30、什么是持久连接?
- 在http1.1版本中,支持持久连接;当使用keep-alive (持久连接)模式时,使客户端到服务端的连接持续有效,当出现服务端的后续请求时,就不用建立或重新建立连接了。
31、 什么是管线化?
- 在使用持久连接的情况下,普通连接是请求一次响应一次;管线化是一次性请求多次,然后服务器把一次性响应打包回来。
32、http的常见状态码?
- 1xx 服务器收到请求,但是还没返回
- 2xx 请求成功 ,例如 200 请求成功
- 3xx重定向
(1) 301 永久重定向,后端如果给你返回一个301,location里是要跳转的地址,浏览器会自动帮你跳转location上的地址,等你下一次请求的时候就不需要调接口浏览器直接帮你跳转到上次的地址去
(2)302 临时重定向,后端如果给你返回一个302,location里是要跳转的地址,浏览器会自动帮你跳转location上的地址。等你下一次请求的时候还是需要调接口看返回给你的loaction是什么地址,浏览器再跳转。(类似百度上的链接)
(3)304 资源未被修改,可继续使用本地的缓存 - 4xx 客户端错误
(1)404 资源未找到
(2)403 没有权限 - 5xx服务端错误
(1)500 服务器错误,可能是写出bug了
(2)504 服务器超时
33、,method的方法有哪些?
- post 新增
- get 获取
- put/patch 编辑
- delete 删除
34、http有哪些常见的header
35、强缓存和协商缓存
强缓存
强缓存:如果缓存资源有效,不会向服务器发送请求,直接从缓存中读取资源。
在chrome控制台的Network选项中可以看到该请求返回200的状态码,并且Size显示from disk cache或from memory cache。
强缓存可以通过设置两种 HTTP Header 实现:Expires 和 Cache-Control。
- Expires:缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点。
- Cache-Control: 比如:Cache-Control:max-age=300 时,则代表在这个请求正确返回的5分钟内再次加载资源,就会命中强缓存。
- 两者的区别: 区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;在某些不支持HTTP1.1的环境下,Expires就会发挥用处。所以Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。
强缓存判断是否缓存的依据来自于是否超出某个时间或者某个时间段,而不关心服务器端文件是否已经更新,这可能会导致加载文件不是服务器端最新的内容,那我们如何获知服务器端内容是否已经发生了更新呢?此时我们需要用到协商缓存策略。
- 协商缓存: 主要是服务器来判断缓存是否可用,主要有以下两种情况:
-
协商缓存生效,返回304和Not Modified
-
协商缓存失效,返回200和请求结果
-
协商缓存可以通过设置两种 HTTP Header 实现:Last-Modified 和 ETag 。
区别:
精度:Etag 要优于 Last-Modified。
性能:Last-Modified 要优于 Etag。
优先级:服务器校验优先考虑Etag
- cache-control 的值有哪些?
1、 max-age :设置过期时间
2、no-cache : 不用缓存,让服务器处理
3、no-store : 不用缓存,直接放服务器返回资源
4、public:
buto
36、刷新页面对http缓存有什么影响?
37、什么是DOCTYPE及作用?
- 是用来声明文档类型和DTD规范的,主要的用途是文件的合法性验证,如果文件代码不合法,浏览器解析时就会出现一些差错。
- DTD规范是一系列的语法规则,用来定义XML和或(x)HTML的文件类型。浏览器会使用它来判断文档类型,决定使用何种协议来解析,以及切换浏览器模式。
hrml5
<!DOCTYPE hmtl>
38、浏览器的渲染过程
- 根据html代码生成DOM树
- 根据css代码生成cssom
- 将DOM树和CSSOM 整合成一个渲染树
- 浏览器根据渲染树渲染页面
- 遇到script 标签就暂停渲染,加载完js文件之后,再继续渲染页面
- 直到把渲染树渲染完成
39、重排 reflow 和重绘 repaint
-
重排:因为元素的规模尺寸,布局,隐藏等改变而需要重新构建,页面第一次加载的时候也会触发重排。
- 触发重排: 增加、删除、修改、DOM节点时
- 移动DOM的位置
- 修改DOM的宽高样式
- 修改网页的默认字体
-
重绘: 一个元素的外观被改变所触发的浏览器行为,浏览器会根据新的属性重新绘制,使元素呈现新的外观。
- 样式: color 、background、font-size等
DOM发生改变的时候触发重排,使DOM重新排列,重绘不一定会重排,但重排一定会发生重绘,重绘和重排都会耗费浏览器的性能,尽量避免。
40、输入url到渲染出页面的整个过程?
过程:
- 首先浏览器对地址进行DNS解析,将域名解析成IP地址
- 浏览器根据ip地址向服务器发起http请求
- 服务器处理http请求,并返回给浏览器
渲染过程: - 根据html代码生成DOM树
- 根据css代码生成cssom
- 将DOM树和CSSOM 整合成一个渲染树
- 浏览器根据渲染树渲染页面
- 遇到script 标签就暂停渲染,加载完js文件之后,再继续渲染页面
- 直到把渲染树渲染完成
41、防抖
- 一个需要频繁触发的函数,在设置的时间之内,只有最后一次生效,前面触发的不生效。例如一个输入框,等输入停之后,再调接口。
- 场景: 模糊搜索
function debounce(fn,delay=500){
// 定义一个定时器
let timer = null
return function (){
if(timer){ // 如果有定时器,就清空上一次的timer
clearTimeout(timer)
}
// 如果在定时器内进行下一次操作就不会触发里面的代码,只有超过定时器设置的时候执行下一次操作才算
timer = setTimeout(()=>{
fn.allpy(this,arguments)
timer=null
},delay)
}
}
debounce(()=>{
// 写上业务代码
console.log('业务代码')
}),600)
42、节流
- 一个需要频繁触发的函数,每隔设置的时间内触发一次,有规律的调用执行。
- 场景: 如DOM 元素的拖拽功能实现;监听滚动事件判断是否到页面底部自动加载更多;
function throttle (fn,delay=100){
// 定义一个定时器
let timer = null
return function (){
if(timer){ // 如果有定时器,就直接返回,不执行业务代码
return
}
// 如果定时器没有,就隔delay毫秒之后执行我们的操作
timer = setTimeout(()=>{
fn()
timer=null
},delay)
}
}
throttle(()=>{
// 写上业务代码
console.log('业务代码')
}),600)
43、显示(强)类型转换和隐士类型转换
显示(强)类型:通过Number()、String()、Boolean() 函数转换
-
Number() :
例如 Number(‘123’) // 123; Number(‘hs2’) // NaN; Number(‘’) // 0
Number(null) — 0; Number(undefined)---- NaN
Number({a: 1})------ NaN
如果转换的是个对象,则先进行valueOf,如果返回的还是对象,则再对其进行toString();
-
String()
String(true) ---- ‘true’
如果转换的是对象,先调对象的toString,如果返回的是一个对象,继续调对象的valueOf;
String({a:1}) ------- ‘[object object]’ -
Boolean()
如果遇到 undefined null -0 0 ‘’ NaN --------》 false,其他都是true
隐式类型:
- 四则运算:+ 、-、==等
- 判断语句 if 、三元运算符、
- Native调用: console.log 、alter 都转换成字符串类型
44、创建对象的方式?
// 使用创建字面量的方式
const obj1= {}
// 构造函数方式
function obj (){}
const obj2 = new obj()
// object.create(),此时属性挂载在原型上
const obj3 = Object.create({name:'hello'})
45、constructor、原型、原型链?
(1)、constructor
- 构造函数:使用new关键字来定义,所有函数用new来定义的就是构造函数,浏览器会自动给它一家protoType属性。该属性指向原型对象
(2)、原型: 实例.proto === 构造函数.protoType;原型的construcor执向构造函数;所有的函数(函数是对象,函数对象有prototype,普通对象没有)都有一个名为prototype(原型)的属性,这个属性是一个指针,指向一个原型对象,而这个对象的用途是包含可以有特定类型的所有实例共享的属性和方法。(并且有一个constructors(构造器)属性,指回原来的构造函数)
(3)、原型链:当调用一个对象的某个方法时,首先对象会查找本身有没有设置这个属性,如果找不到的话,那么它会去它的隐式原型 proto(也就是它的构造函数的显式原型 prototype)中寻找:如果原型还找不到,就这样沿着__proto__一直找下去,这就构成了js的原型链。
创建对象的几种方法
// 第一种字面量
let obj1 = {name: '张三'}
let obj2 = new Object({name: '张三'});
// 第二种 构造函数
let M = funcrtion (){
this.name = '张三'
}
var obj3 = new M();
// 第三种
let p= {name:'张三'}
let obj4 = Object .create(p)
46 instanceof
- 是用来判断构造函数的prototype属性是否出现在某个实例对象的原型链上。
47、 JS 严格模式有什么特点?
- 开启严格模式
// 全局开启 'use strict' // 在函数中开启 function fn(){ 'use strict' }
- 特点
- 全局变量必须严格先声明
'use strict'
n =10 // 报错,n is not defined
- 禁止用with,使代码不易阅读
'use strict'
const obj = {name:'aa',age:21}
with(obj){
console.log(name,age);
}
- 创建eval作用域
'use strict'
const num=20;
eval(`let num=10;console.log(num)`); // 10
console.log(num) // 20
- 禁止this指向window
'use strict'
function fn () {
console.log(this) // undefined
}
fn()
- 函数参数不能重名
'use strict'
function fn (x,x,y) {
}
fn(10,10,20) // 报错
3、 JS如何检测内存泄漏?
- 使用浏览器控制台下的Performance 下的Memory,如果HEAP的曲线是一直递增的则表示是内存泄漏,如果是锯齿状的则是正常的。
-
48、 内存泄漏的场景有哪些?(VUE为列)
- 全局变量、定时器引用、自定义事件引用,组件销毁时未清除
weakMap 和weakSet 是弱引用,
- 不存在内存泄漏问题,他们的第一个参数必须是引用类型,值什么类型都可以。
49、for 和 forEach 谁快一些?为啥?
- for 快一些,虽然他们的时间复杂度都是O(n);
- 因为forEach中每次都要创建一个函数来调用,但是for不用创建函数。函数需要独立的作用域,会有额外的开销。
50、DOM 事件级别
- DOM0 主要是element.Onclick = funciton (){}
- DOM2 element.addEventListener(‘click’,funtion(){},false)
- DOM3 element.addEventListener(‘keyup’,function(){},false)
51、DOM事件模型(捕获和冒泡)
52、DOM 事件流
DOM事件流是从页面中就收事件的顺序。
事件流分三个阶段
- 第一阶段: 捕获阶段
- 第二阶段:目标阶段(事件通过捕获到达目标元素)
- 第三阶段: 冒泡阶段:(目标元素上传到window对象,也就是冒泡过程。)
53、描述DOM事件捕获的具体流程
54、Event 事件的常见应用
- event.preventDefault() :阻止默认事件
- event.stopPropagation(): 阻止冒泡
- event.stoplmmediatePropagation():事件响应优先级
场景: 一个按钮绑定2个click事件,想让第一个事件先执行,不再执行第二个事件,可在第一个要执行的事件中加上这个即可实现该功能。 - event.currentTarget(): 当前所绑定的事件
- event.target(): 当前被点击的元素,事件代理可用这个来识别当前点击的是哪个元素。
55、自定义事件
// 只可自定义事件名,不可传参数
let eve1 = new Event('custome')
ev.addEventListener('custome',function(){})
ev.dispatchEvent(eve1);
// 可自定义事件名和传参数
let eve2 = new CustomEvent('custome',params)
ev.addEventListener('custome',function(){})
ev.dispatchEvent(eve2);
56、类的声明和实例化
// 类的声明 ES5
function Preson (
this.name = 'name'
)
// class 声明 es6
class Preson2 {
constructor(){
this.name='name'
}
}
// 实例
console.log(new Preson(),new Preson2())
57、继承方式
继承的本质是原型链
- 借助构造函数实现继承,主要是用到call,将父类的this指向子类的this,让自己this中包含父级的属性
缺点: 中继承了父类构造方法中的属性,没有继承父类原型上的属性和方法。
funcion Person (){
this.name='name'
}
function Son (){
Person.call(this)
this.age = 12
}
const obj = new Son();
console.log(obj) // {name:'name',age:12}
- 借助原型链实现继承,
缺点: 如果任意实例改变了父类的属性值,其他实例的该属性值也跟着变化,因为他们引用的是同一个地址。
function Person(){
this.name='name'
this.arr=[1,23]
}
function Son(){
this.age = 12
}
Son.protoType = new Person();
console.log(new son())
// 缺点
let s1 = new Son()
let s2 = new Son()
s1.arr.push(22)
s1.arr // [1,33,22]
s2.arr // [1,33,22]
- 组合方式(构造函数和原型链)
- 优点,能够解决用构造函数实现的继承不足,不能够继承原型上的属性;以及解决用原型链继承出现的改变父类属性,所有实例的该属性值都会改变的问题。
function Person (){
this.name = 'name'
}
function Son(){
Person.call(this);
this.age = 12
}
Son.protoType = new Person();
// 优化写法1 以上的不足:子类实例的构造函数指向的是父类的构函数
Son.protoType = Person.protoType;
// 优化2
Son.protoType = Object.create(Person.protoType)
Son.protoType.constructor = Son;
let s1 = new Son()
58、错误监控?
-
前端错误的分类
- 代码错误
- 资源加载错误
-
错误的捕获方式
- 代码错误捕获方式:
- try … catch
- window.onerror
- 资源加载错误捕获方式:
- object.onerror
- performance.getEntries()
- Error事件捕获
跨域的js运行错误可以捕获吗?错误提示是什么?应该怎么处理?
- 可以,错误提示是: script error,不能够具体知道是哪里报错了。这个时候可是使用如下:
- 在script 标签增加crossorigin 属性
- 设置js资源响应头Access-Control-Allow-Origin : *
- 代码错误捕获方式:
上报错误的基本原理
- 采用ajax 通信 方式上报
- 利用image对象上报 -基本都用这个
(new Image()) .src = '上报地址'
59、mvvm 框架
了解mvvm框架吗?
- react
- vue
- angular
mvvm 的定义
- m: model
- v:view 视图,页面
- vm: viewModel
60、双向绑定的原理
- MVVM 是观察者模式
61、代码发布流程原理?
- 将本地分支代码(test分支)push到gitlab远程分支,gitlab会自动执行代码中的gitlab-ci.yml文件,有三个步骤需要执行;
- 第一步骤:install 安装依赖,到指定的gitlab-runner(将Runner注册到你部署的Gitlab上)
install_packages: stage: install cache: key: tee paths: - node_modules/ tags: - runner2 only: refs: - test - master changes: - package.json - package-lock.json script: - cnpm install --silent
- 第二步骤: build 打包,将文件打包到指定的gitlab-runner的路径下,
build_project: stage: build cache: key: paths: - node_modules/ policy: pull tags: - runner2 only: refs: - test - master script: - export 服务器IP地址 - npm run build: artifacts: name: expire_in: 60 mins paths: - dist
- 第三步骤:deploy 发布,将html文件打包到服务器路径下,把打包后的文件都发送到静态资源服务器中(0ss)
deploy_test: stage: deploy tags: - runner2 only: refs: - test script: - rsync -rzav --delete dist/*.html --rsync-path="路径"*/ - sudo chown -R gitlab-runner:gitlab-runner dist/ - ~/ossutil64 cp -rf dist/ oss:地址
以上就是打包发布的过程 ,下面是发布成功,访问页面就是生效的代码
- 用户输入url地址,首先浏览器对地址进行DNS解析,将域名解析成IP地址
- 浏览器根据ip地址向服务器发起http请求
- 服务器处理http请求,会先执行nginx中的配置,找到对应的域名指向的资源地址,去执行放在服务器中的.hmtl文件,文件中的js,css等资源地址指向的是放在oss中的文件,然后把html文件返回给浏览器,浏览器开始解析,呈现出界面。这就是整个发布和预览的流程。