大家好,又见面了,我是你们的朋友全栈君。
更新啦!更新啦! 2022年面试题及答案
点进来之后你的噩梦就要来了,接下来你要面对上百道面试题,那么,如果你——
顺便,如果有错误的地方请各位一定要指出,免得误导更多人。 接下来的题我会根据重点程度使用⭐来标记,⭐越多标明越重点,满星是5颗星 ok,你准备好了吗?咱们开始吧!
面试官:JavaScript中什么是基本数据类型什么是引用数据类型?以及各个数据类型是如何存储的?⭐⭐⭐⭐⭐
答: 基本数据类型有
引用数据类型统称为Object类型,细分的话有
基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,在栈中保存数据的引用地址,这个引用地址指向的是对应的数据,以便快速查找到堆内存中的对象。
顺便提一句,栈内存是自动分配内存的。而堆内存是动态分配内存的,不会自动释放。所以每次使用完对象的时候都要把它设置为null,从而减少无用内存的消耗
面试官:在JS中为什么0.2+0.1>0.3?⭐⭐⭐⭐
答:
因为在JS中,浮点数是使用64位固定长度来表示的,其中的1位表示符号位,11位用来表示指数位,剩下的52位尾数位,由于只有52位表示尾数位。
而0.1
转为二进制是一个无限循环数0.0001100110011001100......
(1100循环)
小数的十进制转二进制方法:https://jingyan.baidu.com/article/425e69e6e93ca9be15fc1626.html 要知道,小数的十进制转二进制的方法是和整数不一样的,推荐看一看
由于只能存储52位尾数位,所以会出现精度缺失,把它存到内存中再取出来转换成十进制就不是原来的0.1
了,就变成了0.100000000000000005551115123126
,而为什么02+0.1是因为
// 0.1 和 0.2 都转化成二进制后再进行运算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111
// 转成十进制正好是 0.30000000000000004
面试官:那为什么0.2+0.3=0.5呢?⭐⭐⭐⭐
// 0.2 和 0.3 都转化为二进制后再进行计算
0.001100110011001100110011001100110011001100110011001101 +
0.0100110011001100110011001100110011001100110011001101 =
0.10000000000000000000000000000000000000000000000000001 //尾数为大于52位
// 而实际取值只取52位尾数位,就变成了
0.1000000000000000000000000000000000000000000000000000 //0.5
答:0.2
和0.3
分别转换为二进制进行计算:在内存中,它们的尾数位都是等于52位的,而他们相加必定大于52位,而他们相加又恰巧前52位尾数都是0
,截取后恰好是0.1000000000000000000000000000000000000000000000000000
也就是0.5
面试官:那既然0.1不是0.1了,为什么在console.log(0.1)的时候还是0.1呢?⭐⭐⭐
答:在console.log
的时候会二进制转换为十进制,十进制再会转为字符串的形式,在转换的过程中发生了取近似值,所以打印出来的是一个近似值的字符串
面试官:判断数据类型有几种方法⭐⭐⭐⭐⭐
答:
typeof
typeof null
的值为Object
,无法分辨是null
还是Object
instanceof
constructor
Object.prototype.toString.call()
Object.prototype.toString.call()
;它可以区分 null 、 string 、
boolean 、 number 、 undefined 、 array 、 function 、 object 、 date 、 math 数据类型。
// -----------------------------------------typeof
typeof undefined // 'undefined'
typeof '10' // 'String'
typeof 10 // 'Number'
typeof false // 'Boolean'
typeof Symbol() // 'Symbol'
typeof Function // ‘function'
typeof null // ‘Object’
typeof [] // 'Object'
typeof {
} // 'Object'
// -----------------------------------------instanceof
function Foo() {
}
var f1 = new Foo();
var d = new Number(1)
console.log(f1 instanceof Foo);// true
console.log(d instanceof Number); //true
console.log(123 instanceof Number); //false -->不能判断字面量的基本数据类型
// -----------------------------------------constructor
var d = new Number(1)
var e = 1
function fn() {
console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(e.constructor);//ƒ Number() { [native code] }
console.log(e.constructor.name);//Number
console.log(fn.constructor.name) // Function
console.log(date.constructor.name)// Date
console.log(arr.constructor.name) // Array
console.log(reg.constructor.name) // RegExp
//-----------------------------------------Object.prototype.toString.call()
console.log(Object.prototype.toString.call(undefined)); // "[object Undefined]"
console.log(Object.prototype.toString.call(null)); // "[object Null]"
console.log(Object.prototype.toString.call(123)); // "[object Number]"
console.log(Object.prototype.toString.call("abc")); // "[object String]"
console.log(Object.prototype.toString.call(true)); // "[object Boolean]"
function fn() {
console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(Object.prototype.toString.call(fn));// "[object Function]"
console.log(Object.prototype.toString.call(date));// "[object Date]"
console.log(Object.prototype.toString.call(arr)); // "[object Array]"
console.log(Object.prototype.toString.call(reg));// "[object RegExp]"
instanceof原理⭐⭐⭐⭐⭐
function myInstance(L, R) {
//L代表instanceof左边,R代表右边
var RP = R.prototype
var LP = L.__proto__
while (true) {
if(LP == null) {
return false
}
if(LP == RP) {
return true
}
LP = LP.__proto__
}
}
console.log(myInstance({
},Object));
面试官:为什么typeof null是Object⭐⭐⭐⭐
答:
因为在JavaScript
中,不同的对象都是使用二进制存储的,如果二进制前三位都是0的话,系统会判断为是Object
类型,而null的二进制全是0,自然也就判断为Object
这个bug是初版本的JavaScript中留下的,扩展一下其他五种标识位:
000
对象1
整型010
双精度类型100
字符串110
布尔类型 面试官:==
和===
有什么区别⭐⭐⭐⭐⭐
答:
===
是严格意义上的相等,会比较两边的数据类型和值大小
==
是非严格意义上的相等,
面试官:手写call、apply、bind⭐⭐⭐⭐⭐
答:
fn
设置为需要调用的函数fn
__proto__
指向_this
的prototype
Array.prototype.slice.call()
call:
Function.prototype.myCall = function (context) {
// 先判断调用myCall是不是一个函数
// 这里的this就是调用myCall的
if (typeof this !== 'function') {
throw new TypeError("Not a Function")
}
// 不传参数默认为window
context = context || window
// 保存this
context.fn = this
// 保存参数
let args = Array.from(arguments).slice(1) //Array.from 把伪数组对象转为数组
// 调用函数
let result = context.fn(...args)
delete context.fn
return result
}
apply
Function.prototype.myApply = function (context) {
// 判断this是不是函数
if (typeof this !== "function") {
throw new TypeError("Not a Function")
}
let result
// 默认是window
context = context || window
// 保存this
context.fn = this
// 是否传参
if (arguments[1]) {
result = context.fn(...arguments[1])
} else {
result = context.fn()
}
delete context.fn
return result
}
bind
Function.prototype.myBind = function(context){
// 判断是否是一个函数
if(typeof this !== "function") {
throw new TypeError("Not a Function")
}
// 保存调用bind的函数
const _this = this
// 保存参数
const args = Array.prototype.slice.call(arguments,1)
// 返回一个函数
return function F () {
// 判断是不是new出来的
if(this instanceof F) {
// 如果是new出来的
// 返回一个空对象,且使创建出来的实例的__proto__指向_this的prototype,且完成函数柯里化
return new _this(...args,...arguments)
}else{
// 如果不是new出来的改变this指向,且完成函数柯里化
return _this.apply(context,args.concat(...arguments))
}
}
}
面试官:字面量创建对象和new创建对象有什么区别,new内部都实现了什么,手写一个new⭐⭐⭐⭐⭐
答:
字面量:
new内部:
__proto__
指向原函数的prototype
手写new
// 手写一个new
function myNew(fn, ...args) {
// 创建一个空对象
let obj = {
}
// 使空对象的隐式原型指向原函数的显式原型
obj.__proto__ = fn.prototype
// this指向obj
let result = fn.apply(obj, args)
// 返回
return result instanceof Object ? result : obj
}
面试官:字面量new出来的对象和 Object.create(null)
创建出来的对象有什么区别⭐⭐⭐
答:
Object.create(null)
创建出来的对象原型为null,作为原型链的顶端,自然也没有继承Object的方法和属性
面试官:什么是作用域,什么是作用域链?⭐⭐⭐⭐
答:
面试官:什么是执行栈,什么是执行上下文?⭐⭐⭐⭐
答:
执行上下文分为:
执行栈:
很多人都吃不透js闭包,这里推荐一篇文章:彻底理解js中的闭包
面试官:什么是闭包?闭包的作用?闭包的应用?⭐⭐⭐⭐⭐
答:
函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护和保存的作用
作用:
应用:
缺点
面试官:什么是原型?什么是原型链?如何理解⭐⭐⭐⭐⭐
答:
原型: 原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。
原型链: 多个__proto__
组成的集合成为原型链
__proto__
都指向他们构造函数的prototype
prototype
都是对象,自然它的__proto__
指向的是Object()
的prototype
Function()
的显示原型面试官:说一说 JS 中的常用的继承方式有哪些?以及各个继承方式的优缺点。⭐⭐⭐⭐⭐
答:
原型继承、组合继承、寄生组合继承、ES6的extend
原型继承
// ----------------------方法一:原型继承
// 原型继承
// 把父类的实例作为子类的原型
// 缺点:子类的实例共享了父类构造函数的引用属性 不能传参
var person = {
friends: ["a", "b", "c", "d"]
}
var p1 = Object.create(person)
p1.friends.push("aaa")//缺点:子类的实例共享了父类构造函数的引用属性
console.log(p1);
console.log(person);//缺点:子类的实例共享了父类构造函数的引用属性
组合继承
// ----------------------方法二:组合继承
// 在子函数中运行父函数,但是要利用call把this改变一下,
// 再在子函数的prototype里面new Father() ,使Father的原型中的方法也得到继承,最后改变Son的原型中的constructor
// 缺点:调用了两次父类的构造函数,造成了不必要的消耗,父类方法可以复用
// 优点可传参,不共享父类引用属性
function Father(name) {
this.name = name
this.hobby = ["篮球", "足球", "乒乓球"]
}
Father.prototype.getName = function () {
console.log(this.name);
}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype = new Father()
Son.prototype.constructor = Son
var s = new Son("ming", 20)
console.log(s);
寄生组合继承
// ----------------------方法三:寄生组合继承
function Father(name) {
this.name = name
this.hobby = ["篮球", "足球", "乒乓球"]
}
Father.prototype.getName = function () {
console.log(this.name);
}
function Son(name, age) {
Father.call(this, name)
this.age = age
}
Son.prototype = Object.create(Father.prototype)
Son.prototype.constructor = Son
var s2 = new Son("ming", 18)
console.log(s2);
extend
// ----------------------方法四:ES6的extend(寄生组合继承的语法糖)
// 子类只要继承父类,可以不写 constructor ,一旦写了,则在 constructor 中的第一句话
// 必须是 super 。
class Son3 extends Father {
// Son.prototype.__proto__ = Father.prototype
constructor(y) {
super(200) // super(200) => Father.call(this,200)
this.y = y
}
}
面试官:什么是内存泄漏⭐⭐⭐⭐⭐
答:
内存泄露是指不再用的内存没有被及时释放出来,导致该段内存无法被使用就是内存泄漏
面试官:为什么会导致的内存泄漏⭐⭐⭐⭐⭐
答:
内存泄漏指我们无法在通过js访问某个对象,而垃圾回收机制却认为该对象还在被引用,因此垃圾回收机制不会释放该对象,导致该块内存永远无法释放,积少成多,系统会越来越卡以至于崩溃
面试官:垃圾回收机制都有哪些策略?⭐⭐⭐⭐⭐
答:
手写浅拷贝深拷贝⭐⭐⭐⭐⭐
// ----------------------------------------------浅拷贝
// 只是把对象的属性和属性值拷贝到另一个对象中
var obj1 = {
a: {
a1: {
a2: 1 },
a10: {
a11: 123, a111: {
a1111: 123123 } }
},
b: 123,
c: "123"
}
// 方式1
function shallowClone1(o) {
let obj = {
}
for (let i in o) {
obj[i] = o[i]
}
return obj
}
// 方式2
var shallowObj2 = {
...obj1 }
// 方式3
var shallowObj3 = Object.assign({
}, obj1)
let shallowObj = shallowClone1(obj1);
shallowObj.a.a1 = 999
shallowObj.b = true
console.log(obj1); //第一层的没有被改变,一层以下就被改变了
// ----------------------------------------------深拷贝
// 简易版
function deepClone(o) {
let obj = {
}
for (var i in o) {
// if(o.hasOwnProperty(i)){
if (typeof o[i] === "object") {
obj[i] = deepClone(o[i])
} else {
obj[i] = o[i]
}
// }
}
return obj
}
var myObj = {
a: {
a1: {
a2: 1 },
a10: {
a11: 123, a111: {
a1111: 123123 } }
},
b: 123,
c: "123"
}
var deepObj1 = deepClone(myObj)
deepObj1.a.a1 = 999
deepObj1.b = false
console.log(myObj);
// 简易版存在的问题:参数没有做检验,传入的可能是 Array、null、regExp、Date
function deepClone2(o) {
if (Object.prototype.toString.call(o) === "[object Object]") {
//检测是否为对象
let obj = {
}
for (var i in o) {
if (o.hasOwnProperty(i)) {
if (typeof o[i] === "object") {
obj[i] = deepClone(o[i])
} else {
obj[i] = o[i]
}
}
}
return obj
} else {
return o
}
}
function isObject(o) {
return Object.prototype.toString.call(o) === "[object Object]" || Object.prototype.toString.call(o) === "[object Array]"
}
// 继续升级,没有考虑到数组,以及ES6中的map、set、weakset、weakmap
function deepClone3(o) {
if (isObject(o)) {
//检测是否为对象或者数组
let obj = Array.isArray(o) ? [] : {
}
for (let i in o) {
if (isObject(o[i])) {
obj[i] = deepClone(o[i])
} else {
obj[i] = o[i]
}
}
return obj
} else {
return o
}
}
// 有可能碰到循环引用问题 var a = {}; a.a = a; clone(a);//会造成一个死循环
// 循环检测
// 继续升级
function deepClone4(o, hash = new map()) {
if (!isObject(o)) return o//检测是否为对象或者数组
if (hash.has(o)) return hash.get(o)
let obj = Array.isArray(o) ? [] : {
}
hash.set(o, obj)
for (let i in o) {
if (isObject(o[i])) {
obj[i] = deepClone4(o[i], hash)
} else {
obj[i] = o[i]
}
}
return obj
}
// 递归易出现爆栈问题
// 将递归改为循环,就不会出现爆栈问题了
var a1 = {
a: 1, b: 2, c: {
c1: 3, c2: {
c21: 4, c22: 5 } }, d: 'asd' };
var b1 = {
b: {
c: {
d: 1 } } }
function cloneLoop(x) {
const root = {
};
// 栈
const loopList = [ //->[]->[{parent:{a:1,b:2},key:c,data:{ c1: 3, c2: { c21: 4, c22: 5 } }}]
{
parent: root,
key: undefined,
data: x,
}
];
while (loopList.length) {
// 深度优先
const node = loopList.pop();
const parent = node.parent; //{} //{a:1,b:2}
const key = node.key; //undefined //c
const data = node.data; //{ a: 1, b: 2, c: { c1: 3, c2: { c21: 4, c22: 5 } }, d: 'asd' } //{ c1: 3, c2: { c21: 4, c22: 5 } }}
// 初始化赋值目标,key 为 undefined 则拷贝到父元素,否则拷贝到子元素
let res = parent; //{}->{a:1,b:2,d:'asd'} //{a:1,b:2}->{}
if (typeof key !== 'undefined') {
res = parent[key] = {
};
}
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k],
})
} else {
res[k] = data[k];
}
}
}
}
return root
}
function deepClone5(o) {
let result = {
}
let loopList = [
{
parent: result,
key: undefined,
data: o
}
]
while (loopList.length) {
let node = loopList.pop()
let {
parent, key, data } = node
let anoPar = parent
if (typeof key !== 'undefined') {
anoPar = parent[key] = {
}
}
for (let i in data) {
if (typeof data[i] === 'object') {
loopList.push({
parent: anoPar,
key: i,
data: data[i]
})
} else {
anoPar[i] = data[i]
}
}
}
return result
}
let cloneA1 = deepClone5(a1)
cloneA1.c.c2.c22 = 5555555
console.log(a1);
console.log(cloneA1);
// ------------------------------------------JSON.stringify()实现深拷贝
function cloneJson(o) {
return JSON.parse(JSON.stringify(o))
}
// let obj = { a: { c: 1 }, b: {} };
// obj.b = obj;
// console.log(JSON.parse(JSON.stringify(obj))) // 报错 // Converting circular structure to JSON
深拷贝能使用hash递归的方式写出来就可以了 不过技多不压身,推荐还是看一看使用while实现深拷贝方法
面试官:为什么JS是单线程的?⭐⭐⭐⭐⭐
**答:**因为JS里面有可视的Dom,如果是多线程的话,这个线程正在删除DOM节点,另一个线程正在编辑Dom节点,导致浏览器不知道该听谁的
面试官:如何实现异步编程?
**答:**回调函数
面试官:Generator是怎么样使用的以及各个阶段的变化如何?⭐⭐⭐
答:
done
,是个布尔类型,done为true说明生成器函数执行完毕,没有可返回的值了,
done
为true
后继续调用迭代器的next方法,返回值的value
为undefined
状态变化:
yield
属性的时候,都会返回一个对象yield
依次循环yield
了,就会返回一个结果对象done
为true
,value
为undefined
面试官:说说 Promise 的原理?你是如何理解 Promise 的?⭐⭐⭐⭐⭐
答:
class MyPromise2 {
constructor(executor) {
// 规定状态
this.state = "pending"
// 保存 `resolve(res)` 的res值
this.value = undefined
// 保存 `reject(err)` 的err值
this.reason = undefined
// 成功存放的数组
this.successCB = []
// 失败存放的数组
this.failCB = []
let resolve = (value) => {
if (this.state === "pending") {
this.state = "fulfilled"
this.value = value
this.successCB.forEach(f => f())
}
}
let reject = (reason) => {
if (this.state === "pending") {
this.state = "rejected"
this.value = value
this.failCB.forEach(f => f())
}
}
try {
// 执行
executor(resolve, reject)
} catch (error) {
// 若出错,直接调用reject
reject(error)
}
}
then(onFulfilled, onRejected) {
if (this.state === "fulfilled") {
onFulfilled(this.value)
}
if (this.state === "rejected") {
onRejected(this.value)
}
if (this.state === "pending") {
this.successCB.push(() => {
onFulfilled(this.value) })
this.failCB.push(() => {
onRejected(this.reason) })
}
}
}
Promise.all = function (promises) {
let list = []
let count = 0
function handle(i, data) {
list[i] = data
count++
if (count == promises.length) {
resolve(list)
}
}
return Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(res => {
handle(i, res)
}, err => reject(err))
}
})
}
面试官:以下代码的执行顺序是什么⭐⭐⭐⭐⭐
答:
async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
async1()
console.log('script start')
//执行到await时,如果返回的不是一个promise对象,await会阻塞下面代码(当前async代码块的代码),会先执行async外的同步代码(在这之前先看看await中函数的同步代码,先把同步代码执行完),等待同步代码执行完之后,再回到async内部继续执行
//执行到await时,如果返回的是一个promise对象,await会阻塞下面代码(当前async代码块的代码),会先执行async外的同步代码(在这之前先看看await中函数的同步代码,先把同步代码执行完),等待同步代码执行完之后,再回到async内部等promise状态达到fulfill的时候再继续执行下面的代码
//所以结果为
//async1 start
//async2
//script start
//async1 end
面试官:宏任务和微任务都有哪些⭐⭐⭐⭐⭐
答:
script
、setTimeOut
、setInterval
、setImmediate
promise.then
,process.nextTick
、Object.observe
、MutationObserver
面试官:宏任务和微任务都是怎样执行的⭐⭐⭐⭐⭐
答:
例题1
setTimeout(function(){
console.log('1')
});
new Promise(function(resolve){
console.log('2');
resolve();
}).then(function(){
console.log('3')
});
console.log('4');
new Promise(function(resolve){
console.log('5');
resolve();
}).then(function(){
console.log('6')
});
setTimeout(function(){
console.log('7')
});
function bar(){
console.log('8')
foo()
}
function foo(){
console.log('9')
}
console.log('10')
bar()
解析
例题2
setTimeout(() => {
console.log('1');
new Promise(function (resolve, reject) {
console.log('2');
setTimeout(() => {
console.log('3');
}, 0);
resolve();
}).then(function () {
console.log('4')
})
}, 0);
console.log('5'); //5 7 10 8 1 2 4 6 3
setTimeout(() => {
console.log('6');
}, 0);
new Promise(function (resolve, reject) {
console.log('7');
// reject();
resolve();
}).then(function () {
console.log('8')
}).catch(function () {
console.log('9')
})
console.log('10');
运行结果: 5 7 10 8 1 2 4 6 3
面试官:变量和函数怎么进行提升的?优先级是怎么样的?⭐⭐⭐⭐
答:
undefined
面试官:var let const 有什么区别⭐⭐⭐⭐⭐
答:
面试官:箭头函数和普通函数的区别?箭头函数可以当做构造函数 new 吗?⭐⭐⭐⭐⭐
rest
运算符yield
属性,不能作为生成器Generator使用_proto_
指向函数的prototype面试官:说说你对代理的理解⭐⭐⭐
get
和set
handler
对象。Proxy和上面两个的区别在于Proxy专门对对象的属性进行get和setObject.defineProperty
,vue3用的是proxy
面试官:为什么要使用模块化?都有哪几种方式可以实现模块化,各有什么特点?⭐⭐⭐
面试官:exports
和module.exports
有什么区别?⭐⭐⭐
exports.xxx='xxx'
module.export = {}
exports
是module.exports
的引用,两个指向的是用一个地址,而require能看到的只有module.exports
面试官:JS模块包装格式有哪些?⭐⭐⭐
AMD
CMD
面试官:ES6和commonjs的区别⭐⭐⭐
commonjs
模块输出的是值的拷贝,而ES6输出的值是值的引用commonjs
是在运行时加载,是一个对象,ES6是在编译时加载,是一个代码块commonjs
的this指向当前模块,ES6的this指向undefined哎呀呀呀,不简单,你竟然都看到这里了,看看进度条,已经达到一半了 不过——在这之前,先问问自己,前面的都掌握了吗?? 如果你还没有,赶紧滚回去看! 如果你掌握前面的了,那么准备迎接下一个boss——计算机网络
面试官:跨域的方式都有哪些?他们的特点是什么 ⭐⭐⭐⭐⭐
面试官:讲一讲三次握手四次挥手,为什么是三次握手四而不是两次握手?⭐⭐⭐⭐⭐
面试官:HTTP的结构⭐⭐⭐⭐
HTTP头都有哪些字段⭐⭐⭐⭐
面试官:说说你知道的状态码⭐⭐⭐⭐⭐
网络OSI七层模型都有哪些?TCP是哪一层的⭐⭐⭐⭐
面试官:http1.0和http1.1,还有http2有什么区别?⭐⭐⭐⭐
面试官:https和http有什么区别,https的实现原理?⭐⭐⭐⭐⭐
面试官:localStorage、SessionStorage、cookie、session 之间有什么区别⭐⭐⭐⭐⭐
localstorage存满了怎么办?⭐⭐⭐
怎么使用cookie保存用户信息⭐⭐⭐
怎么删除cookie⭐⭐⭐
面试官:Get和Post的区别⭐⭐⭐⭐⭐
https://www.zhihu.com/question/28586791
面试官:讲讲http缓存⭐⭐⭐⭐⭐
https://www.jianshu.com/p/9c95db596df5
cache-control
里的max-age
,判断数据有没有过期,如果没有直接使用该缓存 ,有些用户可能会在没有过期的时候就点了刷新按钮,这个时候浏览器就回去请求服务端,要想避免这样做,可以在cache-control
里面加一个immutable
.If-None-Match
,也就是响应头中的etag
属性,每个文件对应一个etag
;另一个参数是If-Modified-Since
,也就是响应头中的Last-Modified
属性,带着这两个参数去检验缓存是否真的过期,如果没有过期,则服务器会给浏览器返回一个304状态码,表示缓存没有过期,可以使用旧缓存。etag
的作用 last-modified
属性的时间就会改变,导致服务器会重新发送资源,但是etag
的出现就完美的避免了这个问题,他是文件的唯一标识缓存位置:
面试官:tcp
和udp
有什么区别⭐⭐⭐⭐⭐
面试官:从浏览器输入url后都经历了什么⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐具重要!
defer
或者async
的标签。 DOMContentloaed
之前滑动窗口和拥塞窗口有什么区别⭐⭐⭐
解析TCP之滑动窗口(动画演示) 以动画的形式解释滑动窗口,
什么是CDN?⭐⭐⭐⭐
1.首先访问本地的 DNS ,如果没有命中,继续递归或者迭代查找,直到命中拿到对应的 IP 地址。
2.拿到对应的 IP 地址之后服务器端发送请求到目的地址。注意这里返回的不直接是 cdn 服务器的 IP 地址,而是全局负载均衡系统的 IP 地址
4.全局负载均衡系统会根据客户端的 IP地址和请求的 url 和相应的区域负载均衡系统通信
5.区域负载均衡系统拿着这两个东西获取距离客户端最近且有相应资源的cdn 缓存服务器的地址,返回给全局负载均衡系统
6.全局负载均衡系统返回确定的 cdn 缓存服务器的地址给客户端。
7.客户端请求缓存服务器上的文件
什么是xss?什么是csrf?⭐⭐⭐⭐⭐
OWASP top10 (10项最严重的Web应用程序安全风险列表)都有哪些?⭐⭐⭐
怎么样?计算机网络是不是没有想象中的那么难,如果你没看过瘾的话,推荐你这篇文章:【长文】前端需要了解的计算机网络知识 是不是得感激我一下【手动滑稽】
面试官:什么是回流 什么是重绘?⭐⭐⭐⭐⭐
事件冒泡和事件捕捉有什么区别⭐⭐⭐⭐⭐
什么是防抖?什么是节流?手写一个⭐⭐⭐⭐⭐
// ---------------------------------------------------------防抖函数
function debounce(func, delay) {
let timeout
return function () {
let arg = arguments
if (timeout) clearTimeout(timeout)
timeout = setTimeout(() => {
func(arg)
}, delay);
}
}
// ---------------------------------------------------------立即执行防抖函数
function debounce2(fn, delay) {
let timer
return function () {
let args = arguments
if (timer) clearTimeout(timer)
let callNow = !timer
timer = setTimeout(() => {
timer = null
}, delay);
if (callNow) {
fn(args) }
}
}
// ---------------------------------------------------------立即执行防抖函数+普通防抖
function debounce3(fn, delay, immediate) {
let timer
return function () {
let args = arguments
let _this = this
if (timer) clearTimeout(timer)
if (immediate) {
let callNow = !timer
timer = setTimeout(() => {
timer = null
}, delay);
if (callNow) {
fn.apply(_this, args) }
} else {
timeout = setTimeout(() => {
func.apply(_this, arguments)
}, delay);
}
}
}
// ---------------------------------------------------------节流 ,时间戳版
function throttle(fn, wait) {
let previous = 0
return function () {
let now = Date.now()
let _this = this
let args = arguments
if (now - previous > wait) {
fn.apply(_this, arguments)
previous = now
}
}
}
// ---------------------------------------------------------节流 ,定时器版
function throttle2(fn, wait) {
let timer
return function () {
let _this = this
let args = arguments
if (!timer) {
timer = setTimeout(() => {
timer = null
fn.apply(_this, arguments)
}, wait);
}
}
}
函数柯里化原理⭐⭐⭐⭐⭐
function add() {
var args = Array.prototype.slice.call(arguments)
var adder = function () {
args.push(...arguments)
return adder
}
adder.toString = function () {
return args.reduce((prev, curr) => {
return prev + curr
}, 0)
}
return adder
}
let a = add(1, 2, 3)
let b = add(1)(2)(3)
console.log(a)
console.log(b)
console.log(add(1, 2)(3));
console.log(Function.toString)
什么是requestAnimationFrame?⭐⭐⭐⭐
js常见的设计模式⭐⭐⭐⭐⭐
JS性能优化的方式⭐⭐⭐⭐⭐
数据劫持: vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()
来劫持各个属性的setter
,getter
,在数据变动时发布消息给订阅者,触发相应的监听回调
阐述一下你所理解的MVVM响应式原理⭐⭐⭐⭐⭐
vue是采用数据劫持配合发布者-订阅者的模式的方式,通过Object.defineProperty()
来劫持各个属性的getter和setter,在数据变动时,发布消息给依赖收集器(dep中的subs),去通知(notify)观察者,做出对应的回调函数,去更新视图
MVVM作为绑定的入口,整合Observer,Compile和Watcher三者,通过Observer来监听model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer,Compile之间的通信桥路,达到数据变化=>视图更新;视图交互变化=>数据model变更的双向绑定效果。
杂乱笔记
面试官:说说vue的生命周期⭐⭐⭐⭐⭐
beforeCreate
Created
beforeMount
mounted
vm
实例中已经添加完$el
了,已经替换掉那些DOM元素了(双括号中的变量),这个时候可以操作DOM了(但是是获取不了元素的高度等属性的,如果想要获取,需要使用nextTick()
)beforeUpdate
data
改变后,对应的组件重新渲染之前updated
data
改变后,对应的组件重新渲染完成beforeDestory
destoryed
面试官:vue中父子组件的生命周期⭐⭐⭐⭐⭐
beforeCreate
->父created
->父beforeMount
->子beforeCreate
->子created
->子beforeMount
->子mounted
->父mounted
beforeUpdate
->子beforeUpdate
->子updated
->父updated
beforeUpdate
->父updated
beforeDestroy
->子beforeDestroy
->子destroyed
->父destroyed
Vue中的nextTick
⭐⭐⭐⭐⭐
nextTick
:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。created()
操作DOM可以使用Vue.nextTick()
回调函数nextTick
面试官:computed和watch的区别⭐⭐⭐⭐⭐
computed
的值会有缓存watch
deep:true
immediate:true
面试官:Vue优化方式⭐⭐⭐⭐⭐
Object.freeze()
方式冻结data中的属性,从而阻止数据劫持
addEventListener
,所以当一个组件销毁的时候需要手动去removeEventListener
面试官:Vue-router的模式⭐⭐⭐⭐⭐
面试官:MVC与MVVM有什么区别⭐⭐⭐⭐⭐
哎呀呀,这个要参考的就多了。 mvc和mvvm的区别 基于Vue实现一个简易MVVM 不好意思!耽误你的十分钟,让MVVM原理还给你
diff算法⭐⭐⭐⭐⭐
虚拟DOM的优缺点⭐⭐⭐⭐⭐
Vue的Key的作用 ⭐⭐⭐⭐
Vue组件之间的通信方式⭐⭐⭐⭐⭐
Vue-router有哪几种钩子函数⭐⭐⭐⭐⭐
webpack常用的几个对象及解释⭐⭐⭐⭐
loader和plugin的区别是什么?⭐⭐⭐
flex布局⭐⭐⭐⭐⭐
这个我就不例举了,看看阮一峰老师的文章叭!Flex 布局教程
grid布局⭐⭐⭐⭐
同样是阮一峰老师的,CSS Grid 网格布局教程
常见的行内元素和块级元素都有哪些?⭐⭐⭐⭐⭐
请说明px,em,rem,vw,vh,rpx等单位的特性⭐⭐⭐⭐⭐
常见的替换元素和非替换元素?⭐⭐
first-of-type和first-child有什么区别⭐⭐⭐⭐
doctype
标签和meta
标签⭐⭐⭐⭐⭐
script标签中defer和async都表示了什么⭐⭐⭐⭐⭐
什么是BFC?⭐⭐⭐⭐⭐
如何清除浮动⭐⭐⭐⭐⭐
什么是DOM事件流?什么是事件委托⭐⭐⭐⭐⭐
link标签和import标签的区别⭐⭐⭐⭐
这里推荐一个排序算法的动画网站,应该是一个国外团队做的,Sorting Algorithms
冒泡算法排序⭐⭐⭐⭐⭐
// 冒泡排序
/* 1.比较相邻的两个元素,如果前一个比后一个大,则交换位置。 2.第一轮的时候最后一个元素应该是最大的一个。 3.按照步骤一的方法进行相邻两个元素的比较,这个时候由于最后一个元素已经是最大的了,所以最后一个元素不用比较。 */
function bubbleSort(arr) {
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr.length; j++) {
if (arr[j] > arr[j + 1]) {
var temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp
}
}
}
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
bubbleSort(Arr)
console.log(Arr, "after");
快速排序⭐⭐⭐⭐⭐
/* 快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。 然后递归调用,在两边都实行快速排序。 */
function quickSort(arr) {
if (arr.length <= 1) {
return arr
}
var middle = Math.floor(arr.length / 2)
var middleData = arr.splice(middle, 1)[0]
var left = []
var right = []
for (var i = 0; i < arr.length; i++) {
if (arr[i] < middleData) {
left.push(arr[i])
} else {
right.push(arr[i])
}
}
return quickSort(left).concat([middleData], quickSort(right))
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
var newArr = quickSort(Arr)
console.log(newArr, "after");
插入排序⭐⭐⭐⭐
function insertSort(arr) {
// 默认第一个排好序了
for (var i = 1; i < arr.length; i++) {
// 如果后面的小于前面的直接把后面的插到前边正确的位置
if (arr[i] < arr[i - 1]) {
var el = arr[i]
arr[i] = arr[i - 1]
var j = i - 1
while (j >= 0 && arr[j] > el) {
arr[j+1] = arr[j]
j--
}
arr[j+1] = el
}
}
}
var Arr = [3, 5, 74, 64, 64, 3, 1, 8, 3, 49, 16, 161, 9, 4]
console.log(Arr, "before");
insertSort(Arr)
console.log(Arr, "after");
是否回文⭐⭐⭐⭐⭐
function isHuiWen(str) {
return str == str.split("").reverse().join("")
}
console.log(isHuiWen("mnm"));
正则表达式,千分位分隔符⭐⭐⭐⭐
function thousand(num) {
return (num+"").replace(/\d(?=(\d{3})+$)/g, "$&,")
}
console.log(thousand(123456789));
斐波那契数列⭐⭐⭐⭐⭐
// num1前一项
// num2当前项
function fb(n, num1 = 1, num2 = 1) {
if(n == 0) return 0
if (n <= 2) {
return num2
} else {
return fb(n - 1, num2, num1 + num2)
}
}
数组去重的方式⭐⭐⭐⭐⭐
var arr = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
// 最low1
let newArr2 = []
for (let i = 0; i < arr.length; i++) {
if (!newArr2.includes(arr[i])) {
newArr2.push(arr[i])
}
}
console.log(newArr2);
// 最low2
let arr2 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
for (let i = 0; i < arr2.length; i++) {
var item = arr2[i]
for (let j = i + 1; j < arr2.length; j++) {
var compare = arr2[j];
if (compare === item) {
arr2.splice(j, 1)
j--
}
}
}
console.log(arr2);
// 基于对象去重
let arr3 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
let obj = {
}
for (let i = 0; i < arr3.length; i++) {
let item = arr3[i]
if (obj[item]) {
arr3[i] = arr3[arr3.length - 1]
arr3.length--
i--
continue;
}
obj[item] = item
}
console.log(arr3);
console.log(obj);
// 利用Set
let newArr1 = new Set(arr)
console.log([...newArr1]);
let arr4 = [1, 2, 1, 1, 1, 2, 3, 3, 3, 2]
//利用reduce
newArr4 = arr4.reduce((prev, curr) => prev.includes(curr)? prev : [...prev,curr],[])
console.log(newArr4);
console.log(document);
git的常用命令⭐⭐⭐⭐⭐
震惊!你竟然看完了,看来你距离大神就差一点点了!
ok,今天的文章就到这里了。
—————2021.9.17——————————————————–
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/126134.html原文链接:https://javaforall.c