防抖

const debounce = (fn,delay) => {
    let timeout = null
        return function(...args) {
          // 有定时器的存在 将该定时器清除
          if(timeout !== null) {
            clearTimeout(timeout)
          }
          timeout = setTimeout(()=>{
            fn.apply(this,args)
          },delay)
        }
    }

节流

const throtte = (fn,delay) => {
    let falg = true;
    return function() {
      if(!flag) return 
      flag = false
      setTimeout( ()=> {
        fn.apply(this,arguments)
        flag = true
      },delay)
    }
  }

isEqual深度比较

function isObject(obj) {
    return (typeof obj === 'object' && null !== obj);
}

function isEqual(obj1, obj2) {
    // 1.判断是不是引用类型,不是引用
    if (!isObject(obj1) || !isObject(obj2)) {
        return obj1 === obj2;
    }
    // 2.比较是否为同一个内存地址
    if (obj1 === obj2) return true;
    // 3.比较 key 的数量
    const obj1KeysLength = Object.keys(obj1).length;
    const obj2KeysLength = Object.keys(obj2).length;
    if (obj1KeysLength !== obj2KeysLength) return false;
    // 4.比较 value 的值
    for (let key in obj1) {
        const result = isEqual(obj1[key], obj2[key]);
        if(!result) return false;
    }
    return true;
}

手写instanceof

描述:instanceof运算符用来检测 constructor.prototype是否存在于参数object的原型链上

语法: object instanceof constructor

参数:object(某个实例对象)constructor(某个构造函数) 由于constructor主要存在于prototype原型对象上,故需要先找到object的原型对象 ,通过proto属性来找

因为instanceof只能检测引用类型,而基本数据类型检测出来的都是false 并且找出来的类型基本都是构造函数创建的类型,且该函数还应该有prototype原型对象,否则proto就不能指向该类

instanceof左侧的话必须是对象类型 右侧的话必须是函数且有prototype属性

function myInstanceof(L = null,R) {
  // 先判断界限
  // 如果左侧不是对象的话 直接返回false
  if (Object(L) !== L) return false
  // 右侧需要是函数且有prototype原型对象
  if(typeof R !== 'function' || !R.prototype) throw new TypeError('Right-hand side of 'instanceof' is not an object')

  // 满足条件了就可以继续判断 获取到proto来得到原型对象
  let link = L.__proto__
  while(link !== null) {
    if(link = R.prototype) return true
    // 没找到就沿着原型链查找 直到找到或者为空
    link = link.__proto__
  }
  // 运行到这里说明最后还是没有找到
  return false
}

map和foreach的区别

相同点:都循环遍历数组中的每一项,匿名函数都支持三个参数,第一个是数组中的当前项item,当前项索引index,原始数组input,匿名函数中的this都是指向window,只能遍历数组。

区别:

map 有返回值(新数组),不改变原数组

foreach 没有返回值,匿名函数中的操作是对原始数组中的每一项进行修改

foreach与for(i)的区别:for i可以停止,break或者continue,但是foreach不能停止,如果要停止,需要使用throw new Error才可以

统计数组中字符的出现次数并将其排序

let arr = ['a','b','c','d','d','a','d','a','a','b'];
let map = new Map()
arr.forEach((item)=> {
  if(map.has(item)) {
    map.set(item,map.get(item)+1)
  } else {
    map.set(item,1);
  }
})
let arrObj = Array.from(map);
console.log(arrObj);

arrObj.sort(function(a,b){return a[1]-b[1]})

js中的数组方法手撕

数据转换

num -> string :num.toString()

类型判断

typeof 可以判断基本数据类型undefined string number boolean function

instance of 可以判断类 引用数据类型 object array

判断是否是对象

function isObject(obj) {
    return typeof obj === 'object' && obj !== null
}

是否是整型:Number.isInteger(1.00) // true

获取字符串中的某一位str.charAr(index)

截取子字符串str.slice(beginIndex,endIndex)

for of 与for in

遍历数组使用of

遍历对象使用in

js中new关键字的执行过程

微信图片_20210528132147.png

具体分为四步

1、创建一个新对象

2、将新建对象的__proto__属性指向构造函数的prototype对象

3、改变构造函数的this绑定到新对象obj,执行构造函数

4、返回新对象

详述vue2和vue3之间的区别

① vue2使用的是webpack形式去构建项目
webpack是一开始是入口文件,然后分析路由,然后模块,最后进行打包,然后告诉你,服务器准备好了可以开始干了
②vue3使用vite构建项目
先告诉你服务器准备完成,然后等你发送HTTP请求,然后是入口文件,Dynamic import(动态导入)code split point(代码分割)

最大的好处和区别就是为了让项目中一些代码文件多了以后去保存更新数据时更快能够看到实际效果,也就是所谓的(热更新)

vue3中app组件中可以没有根标签

ref使用.value进行修改操作

ref与reactive区别

ref定义的是基本数据类型
ref通过Object.defineProperty()的get和set实现数据劫持
ref操作数据.value,读取时不需要。value
reactive定义对象或数组数据类型
reactive通过Proxy实现数据劫持
reactive操作和读取数据不需要.value

vue2的响应式原理用Object.defineProperty的get和set进行数据劫持,从而实现响应式

  • vue2中只有get和set方法去进行属性的读取和修改操作,当我们进行新增,删除时,页面不会实时更新
  • 直接通过下标改数组,页面也不会实时更新

vue3中响应式原理使用Proxy进行代理,使用window内置对象Reflect反射,学了Es6的语法的就知道我们在使用Proxy进行代理,好比甲方公司给出需要什么技术的前端攻城狮,让乙方去干招聘、面试等环节

Proxy可以拦截对象中任意的属性变化,当然包括读写,添加,删除等
Reflect对源对象属性进行操作

生命周期

vue3中区别:beforeCreate与created并没有组合式API中,setup就相当于这两个生命周期函数

css特性

盒模型

通过box-sizing来指定传统模型content-box或者css3模型border-box

传统盒模型 宽度和高度的计算仅考虑内容区域的尺寸。

css3盒模型 宽度和高度(width和height)的计算包含了内容区域、内边距和边框的尺寸。

http特性

http1.0缺点:服务器发送完响应,就会关闭连接,如果后面需要请求新的数据,则需要再次建立连接。

解决:客户端的请求头中使用keep-alive,服务器在发送完响应数据之后,就不会断开TCP连接了,从而达到复用同一个TCP连接的目的。

http1.1 header里面携带的内容过大,在一定程度上增加了传输的成本,并且每次请求header基本不怎么变化

http2.0:默认不再使用ASCII编码传输,改为了二进制数据,来提升传输效率。头部信息压缩 服务器主动推送数据给客户端 头部和数据部分都是二进制,且统称为帧

OSI七层模型

一文看懂网络七层协议/OSI七层模型 (zhihu.com)

项目中遇到的难点和解决方式

  • Situation:事情是在什么情况下发生,基于一个怎样的背景;
  • Task:你是如何明确你的任务的;
  • Action:针对这样的情况分析,你采用了什么行动方式,具体做了哪些工作内容;
  • Result:结果怎样,带来了什么价值,在整个过程中你学到了什么,有什么新的体会。

登录鉴权

动态路由 权限管理

webpack配置或者vite配置

script中的defer属性:立即下载 延迟执行

菜单权限用动态添加路由addRoutes解决。

有一个公共路由,登录后获取权限,得到需要动态添加的路由表,把路由添加到router里。实现方式是提前定义好完整的路由表,然后跟后台传输的权限做对比,过滤出一个路由权限表,再用addRoutes动态添加到路由里。然后根据过滤出的路由权限表渲染侧边栏。

连接http注意url的写法以及自己api地址是否正确

难点:

对自己的功能还有模块有一个整体的完整的了解,

笔试题目

url解析

需要了解常见的url组成

// 直接使用js中的url方法 功能很强大,但是IE的支持不太好
const url = 'https://juejin.cn/post/6910810031264890893'
const parseUrl = new URL(url)
// hash host hostname href origin password prot protocol search等属性都可以拿到 
// 但是还需要对search进行解析 search = "?p1=v1&p2=v2"
const search = parseUrl.search.slice(1)
const pairs = search ? search.split("&") : []
const query = {}
for(let i = 0;i<pairs.length;i++) &#123;
  const [key,value] = pairs[i].split('=')
  query[key] = value;
&#125;
console.log(pairs);

整数反转

/**
 * @param &#123;number&#125; x
 * @return &#123;number&#125;
 */
var reverse = function(x) &#123;
    // 解法一 反转字符串 
    // 将数字转换成字符串然后进行反转reverse()然后连接join('') 期间需要判断是否是负数
     let str = x.toString();
     let arr = str.split('');
    // 判断是否有-号
     if(arr[0] !== '-') &#123;
         // 没有-号,则直接翻转连接即可
         let num = Number(arr.reverse().join(''));
         if(num >= Math.pow(-2,31) && num<=Math.pow(2,31)-1) &#123;
             return num;
         &#125; else 
             return 0;
     &#125;
     // 有-号 删除-号翻转连接
     delete arr[0]
     let num = Number(arr.reverse().join(''));
     if(num >= Math.pow(-2,31) && num <= Math.pow(2,31)-1) 
         return -1*num;
     else 
         return 0;

    // 上述简便写法
     let res = 0;
     // x为整数
     if(x>0)&#123;
         res = String(x).split('').reverse().join('')
     &#125;else if(x<0)&#123;
         x = -x;
         res = -String(x).split('').reverse().join('')
     &#125; else&#123;
         res = 0;
     &#125;
     if(res >= (-2)**31 && res <= 2**31-1)&#123;
         return res;
     &#125; else return 0;

    // 解法二
    // 采用商与余数的形式
    let res = 0;
    while(x) &#123;
        // 这里是直接将得到的余数进行拼接
        res = res * 10 + (x % 10);
        if(res < (-2)**31 || res > 2**31-1) &#123;
            return 0;
        &#125;
        // ~~ 按位取反再取反 对于整数的话取反还是自身
        // 对于小数,等于舍去小数位 相当于正数向下取整 
        // 负数向上取整
        x = ~~(x / 10);
    &#125;
    return res;
    
&#125;;

数组去重

一般去重 数组中没有复杂的对象结构

// 普通数组去重

  let arr = [12,3,5,12,3,5,12,67]
  const newArr = []
  for(let item of arr) &#123;
    if(newArr.indexOf(item) === -1)
      newArr.push(item)
  &#125;
  console.log(newArr);
// 如果数组中包含对象等类型
let arr2 = [12,123,[1,2,3],[1,'2',3],'meili','meili']
// 采用stringify进行去重 map
let map = new Map()
arr2.forEach(item => &#123;
map.set(JSON.stringify(item),item);
&#125;)
console.log([...map.values()]);

map类型

indexOf方法

// indexof手撕
// 思路1:正则匹配找到字符串中的字符
// 参数解释: 从str字符串中寻找 target表示要找的字符 start表示从下标为start开始查找
function myIndexOf(str,target,start=0) &#123;
    let regex = new RegExp(`$&#123;target&#125;`,'ig')
    // 通过lastIndex设置开始匹配的位置 只有flag为g或者y时才会有效果
    regex.lastIndex = start
    // exec主要配合捕获组使用,返回的是Array实例,包含index(字符串匹配模式的起始位置)和input(要查找的字符串)两个属性
    let result = regex.exec(str)
    return result ? result.index : -1
&#125;

let str = 'china has a very beautiful city'
console.log(myIndexOf(str,'city'));

// 思路2: 遍历匹配 找到数组中的某个值
function myIndexOf2 (arr,target,start = 0) &#123;
    if(!target) return -1;
    for(let i = start;i<arr.length;i++) &#123;
      if(arr[i] === target) return i;
    &#125;
    return -1
&#125;
let arr = ['ant','bison','camel','duck']
console.log(myIndexOf2(arr,'biso'));

// 最后整合一下就是将数组和字符串类型进行区分 调用不同的indexOf即可
function finalIndexOf(items,item,fromIndex = 0) &#123;
    // 判断是否是数组
    let isArray = Array.isArray(items);
    // 判断是否是字符串 ??
    let isString = Object.prototype.toString.call(items) == '[object String]';
    if(!isArray) &#123;
      return myIndexOf(items,item,fromIndex);
    &#125; else &#123;
      return myIndexOf2(items,item,fromIndex);
    &#125;
&#125;

纯CSS实现三角形

// 第1种方法 border
<body>
    <div class=triangle></div>
</body>
<style>
    .triangle &#123;
      width: 0;
      height: 0;
      border-bottom: 100px solid skyblue;
      border-top: 100px solid transparent;
      border-right: 100px solid transparent;
      border-left: 100px solid transparent;
    &#125;
</style>

实现一个简单的js深拷贝

// 使用stringfy
function deepClone () &#123;
    
&#125;

选择题

CSS可以继承的样式

字体系列属性:

font font-weight font-family…

文本系列属性:

text-indent text-align line-height color…

元素可见性:visibility

表格布局属性:caption-side。。。

不可继承的属性:

display vertical-align text-decoration border

盒模型属性:宽度、高度、内外边距等

背景属性:背景图片、颜色、位置等

定位属性:浮动、position等

八种排序算法的性能与手撕

得到数组最大值

var arr = [a,b,c,d],数组arr中每一项都是一个整数,下面得到其中最大整数语句

Math.max(arr[0],arr[1],arr[2],arr[3]) 里面参数是number类型 不能传入数组

Math.max.call(Math,arr[0],arr[1],arr[2],arr[3]) call里面可以传入多个参数

Math.max.apply(Math,arr) apply里面传入的是数组

Promise实例方法与静态方法

实例方法:

Promise.prototype.then()

Promise.prototype.catch()

Promise.prototype.finally()

静态方法:

Promise.all()

Promise.race()

Promise.allSettled()

any()

resolve()

reject()

循环队列

元素个数 头尾指针 front rear

(rear-front + m)% m

操作系统复习

margin塌陷

现象:父元素和子元素都在垂直方向上设置了偏离距离,但是子元素的设置没有起作用,两者的顶部紧挨着,相当于父元素的顶部塌陷下来了,所以叫做margin塌陷。

总结:父子嵌套的元素垂直方向的margin取最大值

解决:触发BFC

方法:

  • float属性为left/right (浮动 父或子)

  • overflow属性为hidden scroll auto等 (父 将溢出部分隐藏显示)

  • position为absolute fixed(父)

  • display为inline-block table-cell等(父或子)

  • 利用伪元素给父元素前面添加一个空元素

margin合并

兄弟之间垂直方向上的margin进行了合并,虽然两者都设置了margin的大小,但是显示的是最大值

解决:触发bfc

浏览器重排与重绘

浏览器的工作流程:输入url或者字符串,如果是url那么就会涉及到DNS解析(本地缓存映射或者去DNS服务器上寻找),如果是字符串,代表了搜索查询,返回的可能是html文件或者zip等压缩文件,综上两种情况,如果是html那就会开启渲染进程,渲染进程就是将img html js css等文件转化为用户可以交互的web网页,首先进行html解析,会构建DOM树,然后加载html文件中的一些子资源,例如css文件或者js文件,在解析过程中,如果遇到script标签,它会阻塞html页面的解析,因为js可以调用document.write方法来修改文件,所以如果不用该API的script标签可以添加上async或者defer,使得浏览器可以异步执行js代码。之后解析css文件计算每个DOM节点的样式,根据CSS选择器获取每个节点是继承样式默认样式还是自定义样式,最后在开发工具中可以查看每个节点的样式信息。布局(重排、回流),计算元素节点之间的位置,包括它们的大小、坐标信息等,与DOM树结构相似,但是只包含页面中可见元素的信息,display:none元素不显示,生成布局树。绘制(重绘):确定绘制的顺序,决定先画什么或者后画什么,因为渲染是流水线进程,前一道工序的输出是下一道工序的输入,所以如果布局树变化,相应的绘制记录也要重新生成。对应着重排一定会重绘,但是重绘不一定会发生重排。

重排:重新排列布局,打碎重组,对DOM的修改引发了DOM几何尺寸的变化(宽、高隐藏元素等),浏览器需要重新计算元素的几何属性。

js,css,html加载顺序:js,css,html加载顺序(DOM执行)_html script执行顺序-CSDN博客

setTimeout与promise的执行顺序

先执行promise微任务然后执行setTimeout宏任务

ES5和ES6中类定义的区别

前端数据安全

https

使用https,它是一种加密通信协议,可以确保数据在传输过程中不被窃取

输入验证

前后端都需要进行输入验证

防止XSS攻击

跨站脚本(XSS)攻击是一种黑客通过植入恶意脚本来窃取用户数据的方式,要防止XSS攻击,确保应用程序不会直接将用户提供的数据插入到HTML中

跨站点请求伪造(CSRF)防护

安全的存储和传输

函数扩展(rest参数&箭头函数

link和@import的区别引发的CSS渲染杂谈

1、link属于html标签,@import在css中使用表示导入外部样式表

2、页面被加载时,link会同时被加载,而@import引用的css会等到页面被加载完成之后再加载

3、import只在IE5以上才能够被识别,而link是html标签,无兼容问题

4、link方式的样式权重高于@import的权重

5、link支持使用javascript改变样式,后者不可以

为什么利用多个域名来存储网站资源更有效

CDN缓存更加方便

BOM和DOM的区别

1、BOM是浏览器对象模型

提供了独立于内容而与浏览器窗口进行交互的对象。描述了与浏览器进行交互的方法和接口,
可以对浏览器窗口进行访问和操作,譬如可以弹出新的窗口,改变状态栏中的文本,
对Cookie的支持,IE还扩展了BOM,加入了ActiveXObject类,可以通过js脚本实例化
ActiveX对象等等)

2、DOM是文档对象模型

DOM是针对XML的基于树的API。描述了处理网页内容的方法和接口,是HTML和XML的API,
DOM把整个页面规划成由节点层级构成的文档。DOM本身是与语言无关的API,它并不与Java,
JavaScript或其他语言绑定。

外边距重叠

也叫做margin合并

相邻外边距都是正数时,折叠结果是它们之间较大的值

都是负数时,折叠结果是两者绝对值的较大值

一正一负,折叠结果是两者相加的和

js中改变原数组的方法

reverse、sort、push、unshift、pop、shift、splice(理解为替换)

不改变原数组的方法

concat、slice、substring、substr、reduce、map、filter、

js面向对象以及class类的继承

ps:初始化写在constructor中,然后函数前面不用带function 直接写名字就可

eg

class Person &#123;
    constructor(name,age) &#123;
        this.name = name;
        this.age = age;
    &#125;
    getName() &#123;
        return this.name
    &#125;
&#125;
class Student extends Person &#123;
    constructor(name,age,grade) &#123;
        super(name,age)
        this.grade = grade
    &#125;
    ...
&#125;