长文噩梦预警!
如果你
咳咳,如果你是大神的话当我什么都没说哈,小弟祝您发大财。
接下来的题我会根据重点程度使用来标记,越多标明越重点,满星是5颗星
ok,你准备好了吗?咱们开始吧!
本文章是根据2022年的面试题走向对《身为三本的我就是凭借这些前端面试题拿到百度京东offer的,前端面试题2021及答案》的做了一些增添删除。
面试官:JS的数据类型都有哪些
答:
数据类型分为基本数据类型和引用数据类型;
基本数据类型有:
引用数据类型统称为Object类型,细分的话有:
基本数据类型的数据直接存储在栈中;而引用数据类型的数据存储在堆中,在栈中保存数据的引用地址,这个引用地址指向的是对应的数据,以便快速查找到堆内存中的对象。
顺便提一句,栈内存是自动分配内存的。而堆内存是动态分配内存的,不会自动释放。所以每次使用完对象的时候都要把它设置为null,从而减少无用内存的消耗
为什么0.1+0.2>0.3
答:
因为在JS底层中,每个变量是以二进制表示,固定长度为64位,其中第1位是符号位,再往后11位是指数为,最后52表示的是尾数位,而0.1和0.2转为二进制的时候是无限循环小数,所以JS就会进行截取,截取以后0.1和0.2就不是他们本身了,要比原来大那么一丢丢,所以0.1+0.2就>0.3了
如何解决这个问题,使0.1+0.2等于0.3?
答:
先给他们放大倍数,随后在除以相应倍数
const a = 0.1;
const b = 0.2;
console.log(a + b === 0.3) // false
console.log((a * 1000 + b * 1000) / 1000 === 0.3) // true
数据类型的判断方式
答:
1.typeof
typeof null
的值为Object
,无法分辨是null
还是Object
2.instanceof
3.constructor
4.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]"
为什么要用Object.prototype.toString.call()
,为什么不用 Array.prototype.toString.call()
答:
因为只有Object.prototype.toString.call()
返回的是统一格式,而且 Array.prototype.toString.call()
的部分类型无法检验。
function fn() {
console.log("ming");
}
var date = new Date();
var arr = [1, 2, 3];
var reg = /[hbc]at/gi;
console.log(Array.prototype.toString.call(undefined)); // 报错
console.log(Array.prototype.toString.call(null)); // 报错
console.log(Array.prototype.toString.call(123)); // "[object Number]"
console.log(Array.prototype.toString.call("abc")); // "[object String]"
console.log(Array.prototype.toString.call(true)); // "[object Boolean]"
console.log(Array.prototype.toString.call(fn)); // "[object Function]"
console.log(Array.prototype.toString.call(date)); // "[object Date]"
console.log(Array.prototype.toString.call(arr)); // "1,2,3"
console.log(Array.prototype.toString.call(reg));// "[object RegExp]"
instanceof原理
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布尔类型
面试官:==
和===
有什么区别
答:
===
是严格意义上的相等,会比较两边的数据类型和值大小
==
是非严格意义上的相等,
两边类型相同,比较大小
两边类型不同,根据下方表格,再进一步进行比较。
面试官:NaN === NaN
返回什么?
返回 false
,NaN
永远不等于NaN
,判断是否为NaN
用一个函数 isNaN
来判断;
isNaN
传入的如果是其他数据类型,那么现将它使用Number()
转为数字类型在进行判断
面试官:手写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
}
面试官:什么是作用域,什么是作用域链?
答:
面试官:什么是执行栈,什么是执行上下文?
答:
执行上下文分为:
执行栈:
很多人都吃不透js闭包,这里推荐一篇文章:彻底理解js中的闭包
面试官:什么是闭包?闭包的作用?闭包的应用?
答:
函数执行,形成私有的执行上下文,使内部私有变量不受外界干扰,起到保护和保存的作用
作用:
应用:
缺点
面试官:什么是原型?什么是原型链?如何理解
答:
原型: 原型分为隐式原型和显式原型,每个对象都有一个隐式原型,它指向自己的构造函数的显式原型。每个构造方法都有一个显式原型。
__proto__
是隐式原型;prototype
是显式原型
所有实例的__proto__
都指向他们构造函数的prototype
所有的prototype
都是对象,自然它的__proto__
指向的是Object()
的prototype
所有的构造函数的隐式原型指向的都是Function()
的显示原型
Object的隐式原型是null
原型链: 多个__proto__
组成的集合成为原型链(概念类似于作用域链)
instanceof
就是判断某对象是否位于某构造方法的原型链上。面试官:说一说 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节点,导致浏览器不知道该听谁的
面试官:说说 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))
}
})
}
Promise.race = function (promises) {
return Promise((resolve, reject) => {
for (let i = 0; i < promises.length; i++) {
promises[i].then(res => {
resolve(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 有什么区别
答:
面试官:为什么要使用模块化?都有哪几种方式可以实现模块化,各有什么特点?
面试官:exports
和module.exports
有什么区别?
exports.xxx='xxx'
module.export = {}
exports
是module.exports
的引用,两个指向的是用一个地址,而require能看到的只有module.exports
面试官:JS模块包装格式有哪些?
commonjs
AMD
CMD
面试官:ES6和commonjs的区别
Commonjs、AMD、CMD、UMD、ESM 都有什么区别
Commonjs
是同步执行的,不适合前端,后端 nodejs 可以使用 commonjs。
使用方式
module.exports = xxx
require('xxx')
AMD/CMD/UMD 适用前端 异步执行
AMD
define(["a","b","c","d","e"],function(a,b,c,d,e){
// 相当于在前面声明并初始化了要用到的所有模块
a.dosomething()
if(false) {
// 即使没有用到模块 b,也会提前执行
b.dosomething()
}
})
CMD
define(function(require, exports, module){
var a = require("./a") //需要的时候声明
a.dosomething()
if(false) {
var b = require("./b")
b.dosomething()
}
})
AMD 和 CMD 的差别是
ESM
ESM 和 commonjs 的区别主要在于
问:require 和 import的区别?
调用时机
使用时,
解构赋值
面试官:箭头函数和普通函数的区别?箭头函数可以当做构造函数 new 吗?
arguments
对象,不能使用arguments
,如果要获取参数的话可以使用rest
运算符yield
属性,不能作为生成器Generator使用_proto_
指向函数的prototype手写 ajax
var Ajax = {
get: function (url, callback) {
let xhr = XMLHttpRequest();
xhr.open("get", url, false)
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status == 304) {
console.log(xhr.responseText);
callback(xhr.responseText)
}
}
}
xhr.send()
},
post: function (url, data, callback) {
let xhr = new XMLHttpRequest()
// 第三个参数为是否异步执行
xhr.open('post', url, true)
// 添加http头,设置编码类型
xhr.setRequestHeader("Content-type","x-www-form-urlencoded")
xhr.onreadystatechange = function () {
if(xhr.readyState == 4) {
if(xhr.status == 200 || xhr.status == 304) {
console.log(xhr.responseText);
callback(xhr.responseText)
}
}
}
xhr.setRequestHeader('Content-type', "application/x-www-urlencoded")
xhr.send(data)
}
}
什么是防抖?什么是节流?手写一个
n秒内只运行一次,若在n秒内重复触发,只有一次生效
// ---------------------------------------------------------防抖函数
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)
// --------普通函数转为柯里化函数------
function createCurry(fn, args = []) {
return function () {
let _args = args.concat(...arguments)
if (_args.length < fn.length) {
return createCurry.call(this, fn, _args)
}
return fn.apply(this, _args)
}
}
function add(a, b, c) {
return a + b + c;
}
var _add = createCurry(add);
console.log(_add(1, 2, 3));
console.log(_add(1)(2, 3));
console.log(_add(1)(2)(3));
什么是requestAnimationFrame?
requestAnimationFrame请求数据帧可以用做动画执行
可以自己决定什么时机调用该回调函数
能保证每次频幕刷新的时候只被执行一次
页面被隐藏或者最小化的时候暂停执行,返回窗口继续执行,有效节省CPU
var s = 0
function f() {
s++
console.log(s);
if (s < 999) {
window.requestAnimationFrame(f)
}
}
window.requestAnimationFrame(f)
js常见的设计模式
单例模式、工厂模式、构造函数模式、发布订阅者模式、迭代器模式、代理模式
单例模式
不管创建多少个对象都只有一个实例
var Single = (function () {
var instance = null
function Single(name) {
this.name = name
}
return function (name) {
if (!instance) {
instance = new Single(name)
}
return instance
}
})()
var oA = new Single('hi')
var oB = new Single('hello')
console.log(oA);
console.log(oB);
console.log(oB === oA);
工厂模式
代替new创建一个对象,且这个对象想工厂制作一样,批量制作属性相同的实例对象(指向不同)
function Animal(o) {
var instance = new Object()
instance.name = o.name
instance.age = o.age
instance.getAnimal = function () {
return "name:" + instance.name + " age:" + instance.age
}
return instance
}
var cat = Animal({name:"cat", age:3})
console.log(cat);
构造函数模式
发布订阅者模式
class Watcher {
// name模拟使用属性的地方
constructor(name, cb) {
this.name = name
this.cb = cb
}
update() {//更新
console.log(this.name + "更新了");
this.cb() //做出更新回调
}
}
class Dep {//依赖收集器
constructor() {
this.subs = []
}
addSubs(watcher) {
this.subs.push(watcher)
}
notify() {//通知每一个观察者做出更新
this.subs.forEach(w => {
w.update()
});
}
}
// 假如现在用到age的有三个地方
var w1 = new Watcher("我{{age}}了", () => { console.log("更新age"); })
var w2 = new Watcher("v-model:age", () => { console.log("更新age"); })
var w3 = new Watcher("I am {{age}} years old", () => { console.log("更新age"); })
var dep = new Dep()
dep.addSubs(w1)
dep.addSubs(w2)
dep.addSubs(w3)
// 在Object.defineProperty 中的 set中运行
dep.notify()
代理模式
迭代器模式
点击300ms 延迟问题
设置一个 meta 标签
fastClick 插件
自己实现
实现大概思路:封装一个函数,接受两个参数,一个是目标对象,一个是点击后的回调函数; 在 ontouchstart 开始计时,在 ontouchend 中计时结束,如果超出150ms 就
//封装函数,处理延迟300ms问题
function tap(obj, callback) {
var isMove = false;
var startTime = 0;
obj.addEventListener('touchstart', function () {
startTime += Date.now();
})
obj.addEventListener('touchmove', function () {
isMove = true;
})
obj.addEventListener('touchend', function () {
if (!isMove && (Date.now() - startTime) < 150) {
callback && callback();
}
isMove = false;
startTime = 0;
})
}
如何实现上传视频?
input type = 'file’去接收
用 window.URL.createObjectURL(file)把 file 文件转换为 URL(现场的/前端转为 URL)
或者用 FormData 去接收, 把 file 文件append 进去,然后传给后端,使用返回的 URL
setTimeOut第三个参数是什么?
可以作为参数传给函数,一般用于 for 循环赋值
什么是暂时性死区?
暂时性死区是指,当进入一个作用域,我去使用一个变量名,而这个变量名已经存在了,但是是不可获取的,就会报错,造成暂时性死区问题;比如一个作用域下面使用了 let 定义了 x
,但是在定义之前就使用了 x
,就会报错;暂时性死区意味着 typeof 也不是绝对安全的操作
x = '123'; // 报错
let x = 1
---------------------
typeof y; // 报错
let y = 123
js 遍历数组的方法
reduce、map、filter、every、some、foreach.
数组可以改变原数组的方法
Push、pop、shift、unshift、splice、sort、reverse
不改变的
join 变成字符
Slice,截取
concat 合并数组
foreach 和 map 有什么区别
如何捕获浏览器关闭事件?
window.onbeforeunload = function (e) {
e = e || window.event;
// 兼容IE8和Firefox 4之前的版本
if (e) {
e.returnValue = '关闭提示';
}
// Chrome, Safari, Firefox 4+, Opera 12+ , IE 9+
return '关闭提示';
};
localstorage 怎么存储图片
创建一个canvas对象,把图片保存在 canvas 中,然后 canvas 对象 toDataUrl,在把 dataurl 数据存储在 localstorage 中。
或者使用 blob 二进制流存储,canvas 对象toBlob
如何实现大文件上传?
使用 input 接受大文件,使用file.slice进行分割分块上传(制定好一个块的大小,然后进行分割),等所有块上传完毕之后,promise.all(),运行成功回调
如何实现 localstorage 定时清除
web worker是干什么的?
js是单线程的,而web worker可以多创建一个子线程,多出来的这个子线程执行代码时不会阻塞主线程。它有几个限制,
同源限制,子线程资源必须和主线程资源是同源
dom限制,子线程不能操作dom
文件限制,不能打开本机(file://)文件,只能来源于网络
通信限制,只能使用postmessage来传输信息
脚本限制,不能使用alert、confirm方法
jquery 如何实现链式调用
let fun = {
fun1: function() {
console.log("fun1");
return this;
},
fun2: function() {
console.log("fun2");
return this;
},
fun3: function() {
console.log("fun3");
return this;
}
}
fun.fun1().fun2().fun3();
node 事件循环机制和浏览器事件循环机制有什么区别
浏览器和 Node 环境下,microtask 任务队列的执行时机不同
https://zhuanlan.zhihu.com/p/54882306
讲一讲Reflect
顾名思义,reflect反射的意思。可以反射对象
Reflect可以提供一些方法去拦截js的操作,Reflect不是一个函数对象,所以它不可构造,Reflect内部的方法和属性都是静态的。
比如创建一个没有原型的对象,也就是说他自己不能调用任何基于Object原型链上的方法
var myObject = Object.create(null)
// 如果想列举它的key值,只需使用Reflect的静态方法,拦截该对象,然后做出处理
Reflect.ownKeys(myObject)
Object.keys和Object.getOwnPropertyNames有什么区别?
Object.keys只列出非原型上可枚举的key值,而Object.getOwnPropertyNames列出非原型上的所有key值(Symbol除外)
如何配置rem
//rem适配
(function () {
const styleEle = document.createElement('style');
const docWidth = document.documentElement.clientWidth;
const rootFontSize = docWidth / 16;
styleEle.innerHTML = 'html{font-size:' + rootFontSize + 'px!important}';
document.head.appendChild(styleEle);
})()
clientHeight、offsetHeight、scrollHeight有什么区别
clientHeight
offsetHeight
scrollHeight
scrollTop
触底加载
bom和dom的区别
bom就是window,包含windows(窗口)、navigator(浏览器)、screen(浏览器屏幕)、history(访问历史)、location(地址)等,浏览器相关的东西。bom是包含dom的。
dom是document, html相关的都在里面
倒计时用setimeout来实现还是setInterval
promise相对于async…await的优缺点
promise
async传染力比较强
fetch优缺点
fetch脱离了XHR,基于promise实现
对某些错误不会reject,比如状态码400、500
fetch不支持超时timeout处理
fetch默认不携带cookie,需要手动配置
fetch没有办法监测请求进度,而xhr可以
秒传、分片传输、断点传输
e.target和e.currentTarget的区别
e.target是点击的那个对象,e.currentTarget是绑定该事件的对象
JS性能优化的方式
嚯家伙,真没想到你有耐心能看到这里,马云都让你三分呀,冲刺吧!自律达人!
JS章节的结束了,下面迎接下一个boss——计算机网络
面试官:跨域的方式都有哪些?他们的特点是什么
JSONP
JSONP
通过同源策略涉及不到的"漏洞",也就是像img
中的src
,link
标签的href
,script
的src
都没有被同源策略限制到
JSONP
只能get请求
源码:
function addScriptTag(src) {
var script = document.createElement("script")
script.setAttribute('type','text/javascript')
script.src = src
document.appendChild(script)
}
// 回调函数
function endFn(res) {
console.log(res.message);
}
// 前后端商量好,后端如果传数据的话,返回`endFn({message:'hello'})`
document.domain
使用 document.domain 只能跨子域,需要主域相同才能使用(例如http:// www.example.com/a.html
和http:// example.com/b.html
)
通过 document.domain 设置主域,就可以访问并操作子域的 window 对象了,从而达到跨域的目的
使用方法
>
表示输入, <
表示输出 ,以下是在www.id.qq.com
网站下执行的操作
> var w = window.open("https://www.qq.com")
< undefined
> w.document
? VM3061:1 Uncaught DOMException: Blocked a frame with origin "https://id.qq.com" from accessing a cross-origin frame.
at :1:3
> document.domain
< "id.qq.com"
> document.domain = 'qq.com'
< "qq.com"
> w.document
< #document
location.hash
+iframe
因为hash传值只能单向传输,所有可以通过一个中间网页,a若想与b进行通信,可以通过一个与a同源的c作为中间网页,a传给b,b传给c,c再传回a
具体做法:在a中放一个回调函数,方便c回调。放一个iframe
标签,随后传值
在b中监听哈希值改变,一旦改变,把a要接收的值传给c
在c中监听哈希值改变,一旦改变,调用a中的回调函数
window.name
+iframe
postMessage
message
监听,监听到了以同样的方式返回数据,CORS
Accept
、AcceptLanguage
、ContentType
、ContentLanguage
、Last-Event-Id
XMLHttpRequest
只能拿到六个字段,要想拿到其他的需要在这里指定xhr.withCredentials = true
,后端设置Access-Control-Allow-Credentialsnginx代理跨域
websocket
面试官:讲一讲三次握手四次挥手,为什么是三次握手四而不是两次握手?
客户端和服务端之间通过三次握手建立连接,四次挥手释放连接
三次握手,客户端先向服务端发起一个SYN包,进入SYN_SENT状态,服务端收到SYN后,给客户端返回一个ACK+SYN包,表示已收到SYN,并进入SYN_RECEIVE状态,最后客户端再向服务端发送一个ACK包表示确认,双方进入establish状态。
四次挥手,首先客户端向服务端发送一个FIN包,进入FIN_WAIT1状态,服务端收到后,向客户端发送ACK确认包,进入CLOSE_WAIT状态,然后客户端收到ACK包后进入FIN_WAIT2状态,然后服务端再把自己剩余没传完的数据发送给客户端,发送完毕后在发送一个FIN+ACK包,进入LAST_ACK(最后确认)状态,客户端收到FIN+ACK包后,再向服务端发送ACK包,在等待两个周期后在关闭连接
面试官:HTTP的结构
HTTP头都有哪些字段
面试官:说说你知道的状态码
网络OSI七层模型都有哪些?TCP是哪一层的
面试官:http1.0和http1.1,还有http2有什么区别?
面试官:https和http有什么区别,https的实现原理?
面试官:localStorage、SessionStorage、cookie、session 之间有什么区别
indexDB与localStorage的区别
error
、abort
和complete
三个事件,不会出现失败后只改写了一部分的情况)问:服务端渲染和客户端渲染的区别,各自的优缺点?
推荐文章:服务器端渲染和客户端渲染
服务端渲染(SSR Server Site Rendering)
有利于 SEO,首屏加载快,但是重复请求次数多,开发效率低,服务器压力大
渲染的时候返回的是完整的 html 格式
应用场景:可能被搜索到的
客户端渲染(CSR Client Site Rendering)
不利于 SEO,首屏加载慢,前后端分离开发,交互速度快、体验好
渲染的时候返回的是 json 数据格式,由浏览器完成渲染
应用场景:app 内部"嵌套"的 h5页面
什么是JWT(Json Web Token)?
答:
在没有 JWT 之前,验证客户端的方式就是通过 token,具体方式如下
后来出现了 JWT,这种方法可以把 token 保存在客户端
JWT 相当于把数据转换成 JSON 对象,这个特殊的 JSON 对象分为三部分:头部、负载、签名,他们之间分别用.
区分开
面试官:Get和Post的区别
推荐文章: GET 和 POST 到底有什么区别?
面试官:讲讲http缓存
彻底弄懂强缓存与协商缓存
缓存分为强缓存和协商缓存
强缓存
cache-control
里的max-age
,判断数据有没有过期,如果没有直接使用该缓存 ,有些用户可能会在没有过期的时候就点了刷新按钮,这个时候浏览器就回去请求服务端,要想避免这样做,可以在cache-control
里面加一个immutable
.这样用户再怎么刷新,只要 max-age
没有过期就不会去请求资源。协商缓存
If-None-Match
,也就是响应头中的etag
属性,每个文件对应一个etag
;另一个参数是If-Modified-Since
,也就是响应头中的Last-Modified
属性,带着这两个参数去检验缓存是否真的过期,如果没有过期,则服务器会给浏览器返回一个304状态码,表示缓存没有过期,可以使用旧缓存。etag
的作用
last-modified
属性的时间就会改变,导致服务器会重新发送资源,但是etag
的出现就完美的避免了这个问题,他是文件的唯一标识last-modified
和etag
各有各自的优点和缺点:
etag
和 last-modified
属性,etag
要优先于 last-modified
,两个属性各有优缺点,比如 last-modified
很多时候不能感知文件是否改变,但 etag
能;last-modified
仅仅记录了时间点,性能肯定要高于etag
,etag
记录的是哈希值缓存位置:
面试官:tcp
和udp
有什么区别
连接方面
可靠性
工作效率
是否支持多对多
首部大小
tcp 是如何保证可靠传输的
校验和
序列号和确认应答
超时重传、滑动窗口、拥塞控制
为什么 TCP 要进行流量控制?
为了解决发送方和接收方的速率不一致问题,如果发送方的速率过快的话,接收方处理不过来,只能放在缓存区,缓存区满了,就只能丢包了。所以需要进行流量控制
TCP 为什么会重传?
TCP 传输是一应一答的,如果中间丢包了的话,那么就会处于僵持状态,所以在发送发会设置一个定时器,一段时间(这个时间应该略大于一个发送来回的时间)如果没有收到对方ACK
确认的话,就会重新发送数据,这就是超时重传
如果要发送12345
中间丢包的话 只收到了1、3、4、5·,服务器检测出来,会连续发送三个Ack=2,触发快速重传,在定时器之前就完成重传
TCP的四种拥塞控制算法:
1.慢开始
2.拥塞控制
3.快重传
4.快恢复
tcp 的四元组是什么
四元组:
五元组:
七元组:
面试官:从浏览器输入url后都经历了什么
defer
或者async
的标签。
DOMContentloaed
之前面试官:说一下回流和重绘
window.getComputedStyle
的时候creeateDocumentFragment
讲讲CDN缓存
答:
如果接入了CDN的话,DNS解析权也是会改变的,会把DNS解析权交给CNAME指向的CDN专用DNS服务器
其次就是缓存问题,正常情况下,浏览器在发起请求之前,会先查看本地缓存,如果过期的话再去向服务端发起请求;
如果使用了CDN,浏览器会先查看本地缓存,如果未命中,会向CDN边缘节点发起请求,如果没有过期,直接返回数据,此时完成http请求,如果过期,则CDN还需要向源站发起回源请求,来拉取最新数据。
CDN的缓存策略是因服务商的不同而不同的,但是大多会遵循http标准协议,通过Cache-Control里面的max-age来控制缓存时间
CDN优点
CDN缺点
面试官:说一说defer 和 async
浅谈script标签中的async和defer
首先在正常情况下,script 标签是会阻塞 DOM 的解析的,所以我们要尽量不要把script 放到 head 里,要尽量放到 body 的最下方。这样不至于会有白屏的效果;
然后是 defer和 async;他们两个是异步加载 js 代码的。
defer
async
meta 标签可以做什么?
为浏览器提供 html 的元信息(信息的信息)
规定 html 字符编码
设置移动端的视区窗口
移动端点击300ms 延时,可以对放大禁用
设置 http 头
图片403
dns 预解析
什么是 dns 预解析
正常输入 url 地址之后,会进行 dns域名解析,这个过程大概会花费20~120ms,所以这个时候出现了预解析,可以给当前页使用到的其他域名进行预处理,提前进行预解析,这样当再次访问这些域名的时候就不用 dns 解析了;
使用方式:
// chrome 火狐新版等浏览器自动为 on,要关闭可以设置为 off缺点:有时不会访问的网页页进行了预解析,有时为了节约性能,会选择关闭 dns 预解析。
DNS 用的什么网络协议
dns 在区域传输(将一个区域文件传输到多个 DNS服务器的过程叫做区域传输)的时候用的 tcp,在域名解析的时候用的是 udp
浏览器请求并发有限制,如何处理?
短轮询和长轮询
讲一讲登录实现
用户第一次登录的时候,后端生成该用户对应的token(唯一的并且有时效性的)并返回给前端,前端收到token之后存储在localStorage里面,并且记录用户的登录状态,下次在发送用户相关的请求的时候需要携带上token(后端需要设置Access-control-allow-headers : token避免跨域问题),后端给每个用户相关的接口都加上token校验。每次用户切换界面的时候都进行路由守卫的拦截验证,如果登录状态为true,则可以访问,如果为false,则不允许访问
什么是token
什么是xss?什么是csrf?
xss脚本注入
csrf跨域请求伪造
点击劫持
点击劫持是指利用 iframe+css 的 opacity 把危险网址设置为透明覆盖到安全的网址上面,使用户误以为在安全网址下操作。
防范:
怎么样?计算机网络是不是没有想象中的那么难?
奶奶滴!跟我玩阴的是吧?这么难!
如果你没看过瘾的话,推荐你这篇文章:【长文】前端需要了解的计算机网络知识
是不是得感激我一下【手动滑稽】
flex布局
这个我就不例举了,看看阮一峰老师的文章叭!Flex 布局教程
grid布局
同样是阮一峰老师的,CSS Grid 网格布局教程
常见的行内元素和块级元素都有哪些?
请说明px,em,rem,vw,vh,rpx等单位的特性
常见的替换元素和非替换元素?
first-of-type和first-child有什么区别
doctype
标签和meta
标签
script标签中defer和async都表示了什么
众所周知script会阻塞页面的加载,如果我们要是引用外部js,假如这个外部js请求很久的话就难免出现空白页问题,好在官方为我们提供了defer和async
defer
不会阻止页面解析,并行下载对应的js文件
下载完之后不会执行
等所有其他脚本加载完之后,在DOMContentLoaded
事件之前执行对应d.js
、e.js
async
不会阻止DOM解析,并行下载对应的js文件
下载完之后立即执行
补充,DOMContentLoaded
事件
load
事件之前。什么是BFC?
问:IFC
如何清除浮动
额外标签clear:both
Document
big
small
额外标签法
利用BFC
overflow:hidden
.fahter{
width: 400px;
border: 1px solid deeppink;
overflow: hidden;
}
使用after(推荐)
big
small
css 绘制三角形
// 给width 和 height 设为0,其他边框设为 transparent 透明
.x {
width: 0;
height: 0;
background-color: red;
border-left: 30px transparent solid;
border-top: 50px black solid;
border-right: 30px transparent solid;
}
什么是DOM事件流?什么是事件委托
事件冒泡和事件捕捉有什么区别
addEventListener
中的第三属性设置为false(默认)addEventListener
中的第三属性设置为truelink标签和import标签的区别
css优先级问题
important 无条件优先
内联样式1000
id选择器 100
class、伪类、属性 10
标签 伪元素 1
css 变量
使用--
开头定义变量,用var()
去使用
怪异盒模型和标准盒模型有什么区别
标准盒的总宽度为 width+padding+border+margin
而怪异盒模型的总宽度是 width+margin(width 已经包含了 padding 和 border 了)
当 box-sizing设置为 content-box为标准盒模型,当 box-sizing 设置为 border-box 为怪异盒模型
如何使div可聚焦
使div可聚焦,为元素加上tabIndex属性,按键盘上的tab键时在他们之间切换
W3School
Google
Microsoft
123123
html5相比于之前的有什么优化?
其实说实话,作者个人对 html 和 html5之间的差别并没有太大概念,因为我正式开始学习前端的时候 html5就已经出了,这就让我感觉html 本身就是这个样子的,司空见惯了。css和css3也一样。不过之前还是有看过一些文章,了解过他们之间的差别的。比如:
css3有什么优化
数据劫持: 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 如何监听数组的?
首先第一点是要看数组里面是不是还存在对象,如果存在对象的话在进行深层遍历是否还依然存在对象,再把对象进行 defineProperty
监听。然后数组,数组的改变实质上只是几个方法,什么 pop
,unshift
,push
…Vue 重写了这几个方法,只要在调用这些方法的时候做出回调更新就可以了
为什么 Vue 要采用异步更新
因为首先 Vue 本身是组件级更新的,更改数据如果非常多,更新非常频繁,如果不采用异步更新的话每次都需要重新渲染。
每次有数据需要更新的时候,Vue 会把它放在一个队列中,等最后的时候会调用 nexttick
方法。nexttick
就会清空这个队列。
用户也可以手动调用 nexttick(callback)
方法,会同样把callback 回调函数放入队列中,保证视图更新完之后被调用(因为会把 callback 放进队列的最后),并且是依次链式调用。
Vue中的nextTick
nextTick
nextTick
:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。created()
操作DOM可以使用Vue.nextTick()
回调函数nextTick
Vue中的nextTick
是微任务还是宏任务
nextTick
的内部实现如果支持 promise
那就使用 promise
,没有就用MutationObserver
(微任务),在没有就用 setImmediate
(宏任务),还没有就用 setTimeOut
;所以nextTick
有可能是宏任务,也有可能是微任务
讲一讲Vue的发布订阅者模式
data中每一个数据都绑定一个Dep,这个Dep中都存有所有用到该数据的观察者
当数据改变时,发布消息给dep(依赖收集器),去通知每一个观察者。做出对应的回调函数
const dep = new Dep()
// 劫持并监听所有属性
Object.defineProperty(obj, key, {
enumerable: true,
configurable: false,
get() {
// 订阅数据变化时,在Dep中添加观察者
Dep.target && dep.addSub(Dep.target)
return value
},
set: (newVal) => {
if (newVal !== value) {
this.observe(newVal)
value = newVal
}
// 告诉Dep通知变化
dep.notify()
},
})
面试官:说说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
面试官:computed 、watch、method的区别
computed
computed
的值会有缓存watch
deep:true
immediate:true
method
{{fn{xx}}}
,渲染的时候如果没有发生变化,这个也是会被执行的。而 computed
是有缓存的,如果没有变化就不用再去执行了面试官:Vue优化方式
v-if 和v-show
使用Object.freeze()
方式冻结data中的属性,从而阻止数据劫持
组件销毁的时候会断开所有与实例联系,但是除了addEventListener
,所以当一个组件销毁的时候需要手动去removeEventListener
图片懒加载
路由懒加载
为减少重新渲染和创建dom节点的时间,采用虚拟dom
面试官:Vue-router的模式
面试官:MVC与MVVM有什么区别
哎呀呀,这个要参考的就多了。
mvc和mvvm的区别
基于Vue实现一个简易MVVM
不好意思!耽误你的十分钟,让MVVM原理还给你
MVC
mvvm
diff算法
diff算法是指对新旧虚拟节点进行对比,并返回一个patch对象,用来存储两个节点不同的地方,最后利用patch记录的消息局部更新DOM
diff 算法
diff算法是虚拟节点的比较
先进行key值的比较
先进行同级比较,
然后再比较是不是一方有儿子,一方没儿子
在比较两方都有儿子的情况
递归比较子节点
虚拟DOM的优缺点
Vue的Key的作用
为什么 v-for 会要有key?
因为在 vue 中会有一个 diff 算法,假如子节点 AB 调换了位置,它会比较 key 值,会直接调换,而不是一个销毁重新生成的过程
Vue组件之间的通信方式
子组件设置props + 父组件设置v-bind:
/:
子组件的$emit + 父组件设置v-on
/@
任意组件通信,新建一个空的全局Vue对象,利用 e m i t 发 送 , emit发送, emit发送,on接收
传说中的$bus
任意组件
Vue.prototype.Event=new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {});
Vuex
...mapState(['方法名','方法名'])
...mapGetters(['方法名','方法名'])
父组件通过v-bind:
/:
传值,子组件通过this.$attrs
获取
this.$attrs
获取到的是一个对象(所有父组件传过来的集合)祖先组件使用provide提供数据,子孙组件通过inject注入数据
p a r e n t / parent/ parent/children
refs—$ref
Vue-router有哪几种钩子函数
为什么组件中 data 是个函数
在 Vue 底层中,在每次创建组件的时候,都会 new 一个VueComponent实例对象,他们的 data会挂载到VueComponent 的原型上共享,如果是一个对象的话,所有人都可以修改,但是如果是一个函数返回值的话就可以创建一个私有作用域来避免这个问题
vue 事件绑定原理
事件绑定分别分为组件事件绑定( 例如@click.native) 和非组件事件绑定,然后再对两种情况分别去处理
,一种是 addEventListener 另一个是$on $emit
@click.sync
native
stop
prevent
self
分别是做什么的
@click.sync
语法
相当于 bar = val">
@click.native
父组件的原生事件需要加上 native,否则不生效
@click.stop
是阻止冒泡
@click.prevent
是阻止默认行为
@click.self
点击自己的时候才能触发
v-model 的原理,
:value
@input
的语法糖v-html 回会导致什么问题
可能会造成 xss 攻击
v-html 会替换掉子标签
谈谈你对vue-router的 keep-alive 的理解
keep-alive 有一个最大缓存限制,使用的是 LRU(最久未使用法)(使用了就放到最上边,先删最下边)
vue3 的 proxy有什么优缺点
优点:
Object.defineProperty
那样遍历 Vue 中的 data、computed、props 的全部属性。只是经过了一个类似于拦截的操作。缺点:
Object.defineProperty
vue的$set是做什么的
this.$set(this.obj,'e',0)
vue 常见的性能优化
spa 使用 keep-alive
key 的使用
v-if 和v-show
v-if 不要和 v-for 一起使用
使用Object.freeze()
方式冻结data中的属性,从而阻止数据劫持
组件销毁的时候会断开所有与实例联系,但是除了addEventListener
,所以当一个组件销毁的时候需要手动去removeEventListener
图片懒加载
路由懒加载
防抖节流
长列表固定个数
为减少重新渲染和创建dom节点的时间,采用虚拟dom
v-if 和 v-show 有什么区别
vue3 相比于vue2有什么升级
采用 ts 编写
composition API
响应式原理使用的 proxy
什么是render函数
render是一个函数,该参数有一个createElement形参,这形参也作为一个方法(可以动态创建标签),可传入三个参数:标签名、属性、内容
documentFragment 和一次性渲染有什么不同
csrf 攻击原理
跨域请求伪造
用户登录了 A 页在不退出登录的情况下,访问了 危险网站B 页,这个时候 B 页带着 A 页的 cookie 向 A 的服务端发起请求,会让服务端认为这个是可信任用户,从而达到攻击的目的
实现方式:
防御方式
IPv4和 Ipv6的区别
IPV6的 IP 地址有32位增加到128位,寻址能力更强了
报头简化,虽然IPV6的IP地址是 IPv4的4倍,但是报头只有它的2倍
IPV6增加了身份验证、数据保密
更安全了
IPV6加强了对移动设备的支持
浏览器不同标签是线程还是进程?
chrome 不同标签之间是进程,其他浏览器是线程
浏览器的多进程都有什么?
Browser 主进程,只有一个
GPU 加速进程
渲染进程
第三方插件进程
为什么浏览器是多进程的?
因为避免一个 tab页出错而导致整个浏览器崩溃
浏览器每一帧都做了什么?
* 如果帧结束时,主线程还有时间,`requestIdleCallback`会被触发
进程间的通信方式
图片加载会阻塞dom渲染吗?
图片不会阻塞dom解析和渲染,但是如果网页中有很多图片的话,会消耗大量的资源(引擎吞吐量、请求数等等),并发请求数量是有限的,如果多个图片同时请求可能会造成请求拥堵,导致其他资源无法被及时请求到,所以图片最好做成懒加载。
进程和线程的区别
线程是进程中执行运算的最小单位,是进程中的一个实体。
进程是拥有资源的基本单位,而线程是调度和分配资源的基本单位
不同进程间可以并发至行;同一进程不同线程可以并发执行
进程是拥有资源的基本单位,而线程不拥有资源,但是可以访问它所在进程的资源
Git 中 rebase 和 merge 的区别
都是合并分支,
rebase 不会产生额外的 commit,
而 merge 会把这两个分支的遗漏 commit 记录重新创建一个commit保存起来。比较臃肿,所以尽量不要用 merge。
git fetch 和 git pull 的区别
git fetch 是把远程代码拉下来但是不会合并
git pull 会自动合并
git的常用命令
git常用命令与常见面试题总结
webpack常用字段及解释
mode
entry
output
devServer
module/rules 中可以配置loader //执行顺序,从下往上加载
处理 CSS
module:{
rules:[
{
//webpack 默认不能处理 css 模块的
test:/.css$/, //以 css 为结尾的文件
loader:[ //执行顺序,从下往上
'style-loader', // 将 css 插入到 style标签里
'css-loader', // 处理 css 之间的依赖关系
'postcss-loader', // 处理 css 的兼容性 它会依赖一个 autoprefixer 顺便在 package.json 中需要配置 browserslist(用来配置目标环境的)
'sass-loader' // 处理预处理器
]
},
]
}
//以上的是把 css 编译到 js 中的了,但是如果想抽离需要使用到一个插件
plugins:[
new MiniCssExtractPlugin({
filename: 'css/main[contentHash:8].css'
})
]
optimization: {
minimizer:[
// 压缩 JS
new TerserPlugin()
// 压缩 css
new OptimizeCssAssetsWebpackPlugin()
]
}
module:{
rules:[
{
test:/.css$/,
loader:[
MiniCssExtractPlugin.loader, // 将 css 插入到 link 中引入
'css-loader',
'postcss-loader',
'sass-loader'
]
},
]
}
处理图片
// 开发环境
module:{
rules:[
{
//webpack 默认不能处理 图片 模块的
test:/.(png|jpg)$/, //以 png或jpg 为结尾的文件
loader:[ //执行顺序,从下往上
'file-loader',
]
},
]
}
// 生产环境
module:{
rules:[
{
//webpack 默认不能处理 图片 模块的
test:/.(png|jpg)$/, //以 png或jpg 为结尾的文件
use:{
loader:'url-loader',
options:{
limit:5 * 1024 //小于这个范围的时候通过 base64 解析,可以减少 http 请求
}
}
},
]
}
optimization
optimization: { // 用来分包和压缩的
// 代码压缩
minimizer:[
// 压缩 JS
new TerserPlugin()
// 压缩 css
new OptimizeCssAssetsWebpackPlugin()
]
// 代码分割
splitChunks: {
// 分为两种代码分割
// 同步代码 例如 import { lodash } from 'lodash'
// 异步代码 例如 import('lodash')
// all 对同步代码、异步代码都做分割
// async 只对异步代码做分割
// initial 只对同步代码做分割
chunks:'all',
cacheGroups:{
....
}
}
}
plugins插件
//以上的是把 css 编译到 js 中的了,但是如果想抽离需要使用到一个插件
plugins:[
new MiniCssExtractPlugin({
filename: ‘css/main[contentHash:8].css’
})
]
plugins 和 loader 有什么区别?
loader 是解析规则,因为 webpack 默认只能解析js,所以需要在 loader 里面配置一些规则
plugin 是插件,是用来扩展 webpack 的功能的,比如压缩代码,提取公共代码
输出文件名的 hash、chunkhash 和 contenthash 有什么区别?
什么是 polyfill?
polyfill 英文翻译过来是腻子。实际上就是针对各个浏览器的 js 差异做出抹平处理的。就比如说html5的localStorage、sessionStorage,不同浏览器不同版本有些支持,有些不支持,这个时候就可以用到 polyfill 把这些差异抹平。
什么是 tree-shaking?
在做项目的时候,难免会封装一些方法,比如我封装了加的方法、减的方法、乘的方法、除的方法,结果项目中值引用了加的方法,如果正常打包的会,其他方法也会被打包到 bundle 中,这个时候我们就可以使用 tree-shaking ,这样就不会把多余的内容打包了。
webpack externals是做什么的
不想把某个东西打包出来,就可以用 externals,而是用的是 CDN
babel 实现原理
首先 babel 是会把 es6语法转成 es5语法的,先把 js 代码转化成 AST 语法树,然后根据语法树在生成对应的 es5的代码
jsbridge
主要是给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用 Native 的功能
写树的结构
webpack 从0搭建 (图片处理等等)
Es6转 es5 babel-loader
图片 url-loader
webpack常用的loaders
vue-loader:加载vue文件
babel-loader:加载js文件,并用babel进行转换,可配置.babelrc文件
file-loader:加载图片或其他媒体文件,解析url
url-loader:与file-loader基本相同,但对于小于指定limit大小的图片,会转换为base64这样做可以
减少网络请求,但会增加打包后的文件大小
svg-sprite-loader:加载svg并组装成雪碧图
自定义 loader
使用一个loaderUtils 可以获取loader 配置的 options( 一个getOptions的 API),然后再进行一系列操作,最后 return。然后把这个方法 model.exports 就可以了
Vite为什么这么快
Vite和webpack有什么区别
常见的查找算法有哪些
顺序搜索
二分搜索
二叉树搜索
哈希搜索
分块查找
Set Map WeakSet WeakMap 有什么区别
lru实现
最少使用置换算法
function LRU(length) {
this.length = length;
this.arr = [];
}
LRU.prototype.get = function (key) {
let index = this.arr.findIndex(item => item.key = key);
if (index == -1) return -1
const result = this.arr.splice(index, 1)
this.arr.push(result)
return result.val
}
LRU.prototype.set = function(key, val) {
let index = this.arr.findIndex(item => item.key = key);
if(index !== -1){
this.arr.splice(index, 1)
}
this.arr.push({
key,
val
})
if(this.arr.length > this.length) {
this.arr.shift();
}
}
二叉树广度优先
function guangduFirst(root) {
let arr = [];
arr.push(root);
let i = 0;
while (i < arr.length) {
console.log(arr[i].val);
if (arr[i].left) {
arr.push(arr[i].left);
}
if (arr[i].right) {
arr.push(arr[i].right);
}
i++;
}
}
讲一讲kmp算法
KMP算法易懂版
是否回文
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);
大数相加
// 使用 bigInt(结尾加个 n)
function add(a, b) {
var c = BigInt(a) + BigInt(b)
// 如果不使用 toString 的话,末尾会有一个 n
return c.toString()
}
// 方法二
function add2(a, b) {
let maxL = Math.max(a.length, b.length)
a = a.padStart(maxL, 0)
b = b.padStart(maxL, 0)
let result = []
let add = 0
for(let i = a.length - 1; i >= 0; i--) {
let sum = (+a[i]) + (+b[i])
let cur = sum % 10 + add
add = sum >= 10 ? 1 : 0
result.unshift(cur)
}
if(add === 1) {
result.unshift('1')
}
return result.join('')
}
讲一讲B树
B树又叫B-树,一颗m阶的B树满足以下特征
ceil(m / 2) - 1
个关键字,也就是最少含有ceil(m / 2)
颗子树(ceil
为向上取整)
n, P0, K1, P1, K2, P2, K3, P3, .... , Kn, Pn
B树的插入,
一颗m阶B+树满足以下特征
树中每个节点最多有m个关键字,且最多有m颗子树(子树数量和关键字数目相同),指针是从关键字触发的,而B-树的指针是在关键词两边的
除根节点外,每个节点最少含有m/2个关键字,最少含有m/2个子树
所有的叶节点是包含全部的关键字和指向记录的指针的,叶节点关键字有序排列,叶节点之间也是有序排列,指针相连
B+树又可以从根节点开始查找,也可以从叶节点开始查找
这里推荐一个排序算法的动画网站,应该是一个国外团队做的,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 selectSort(arr) {
for (let i = 0; i < arr.length; i++) {
let minIndex = i
for (let j = i + 1; j < arr.length; j++) {
if (arr[minIndex] > arr[j]) {
minIndex = j
}
}
let temp = arr[i]
arr[i] = arr[minIndex]
arr[minIndex] = temp
}
return arr
}
console.log(selectSort(array));
console.log(array);
快速排序
/*
快速排序是对冒泡排序的一种改进,第一趟排序时将数据分成两部分,一部分比另一部分的所有数据都要小。
然后递归调用,在两边都实行快速排序。
*/
// 左右各一列,左边放小的,右面放大的;不停的划分的过程
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 shellSort(arr) {
for (let gap = Math.floor(arr.length / 2); gap > 0; gap = Math.floor(gap / 2)) {
for (let i = gap; i < arr.length; i++) {
let cur = arr[i]
let j = i
while (j - gap >= 0 && cur < arr[j - gap]) {
arr[j] = arr[j - gap]
j -= gap
}
arr[j] = cur
}
}
return arr
}
归并排序
// 归并排序 先分为最小单位,再排序
function mergeSort(arr) {
if (arr.length < 2) return arr
let middle = Math.floor(arr.length / 2);
let left = arr.slice(0, middle);
let right = arr.slice(middle)
return merge(mergeSort(left), mergeSort(right))
}
function merge(left, right) {
let result = []
while (left.length > 0 && right.length > 0) {
if (left[0] >= right[0]) {
result.push(right.shift())
} else {
result.push(left.shift())
}
}
while (left.length) {
result.push(left.shift())
}
while (right.length) {
result.push(right.shift())
}
return result
}
震惊!你竟然看完了
你现在的技术水平和尤雨溪加起来堪比尤雨溪!加油!你是最棒的!
ok,今天的文章就到这里了。
还是熟悉的配方,文章结尾再打个广告叭,要喷的话请温柔一些。
如果有什么迷茫或者不懂的地方,或者想跟大家一起讨论面试题。
欢迎加微信进群一起讨论前端面试题。
这样你也不再迷茫,俺也能写出更好的文章,两全其美,岂不美哉?
加好友记得备注 csdn哦!