前言
大家好我是搬砖小胡,初次发布文章总结,说的不太对的地方还请网友们多多包涵。发布文章主要也是巩固自己的知识更加熟练,全凭自己的理解和网上查资料总结出来的,如有不对的地方还望多多指点。下面是我总结的一下常见面试题,为了督促自己还可以会继续更新😀
*注:末尾有下一篇链接😼
js部分
1.谈一谈你对原型链的理解
在js语言中,每个实例对象都有一个__proto__
属性,改属性指向他的原型对象,且这个实例对象的构造函数都有一个原型属性prototype
,与实例对象的__proto__属性指向同一个对象,当这个对象在查找一个属性的值时,自身没有就会根据__proto__向他的原型
上寻找,如果不存在,则会到生成这个实例对象的构造函数的原型对象上寻找,如果还是不存在,就继续道Object的原型对象上找,在往上找就为null了,这个链式寻找的过程,就被称为原型链
。
2. 原型、构造函数、实例对象三者的关系
首先从构造函数说起,构造函数通过prototype
指向他的原型对象,原型对象通过他的constructor
属性指回这个构造函数,表明原型对象是由哪个构造函数生成的。原型对象通过new关键字生成的实例对象,这个实例对象可以通过__proto__
属性指向生成这个实例对象的构造函数的原型对象,实现一个三角关系。
3.实现继承的方式
继承的方式有很多种,网上的答案都有很多,我自己总结且大致说的明白的有这五种
1.原型链继承 借助原型可以基于已有的对象创建对象,同时还不必因此创建自定义类型。 在 object()函数内部,先创建一个临时的构造函数,然后将传入的对象作为这个构造 函数的原型,最后返回了这个临时类型的一个新实例。关键代码:Star.proyotype = new Person(), Star.proyotype.constructor = Star
缺点:只能继承父类的方法
2.借用构造函数继承
在子类构造函数的内部调用超类型构造函数。可以通过使用 apply()
和 call()
方 法在新创建的对象上执行构造函数。关键代码:Person.call(this,age,name)
缺点:无法复用,只能继承父类的属性
3.组合继承
也叫伪经典继承。指的是将原型链和借用构造函数的技术组合到一 起,从而发挥二者之长。使用原型链实现对原型属性属性和方法的继承,通过借用构造函数来实现实例 属性的继承。 既通过在原型上定义方法实现了函数复用,又能保证每一个实例都有它自己的属性。但是会有一个小bug,里面的age,name,有两份,有一份的值为undefined,因为里面的apply()
和call()
方法会自动多调用一次。
4.寄生组合继承
通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。 本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。是公认继承比较全面的一种方法,要写全的话还是非常多的,我只会一个简单的🤣,关键代码:Star.prototype = Object.create(Person.prototype)
5.ES6的Class类继承方式
可利用class关键字配合extends关键字来实现继承。ES6中引入了class关键字来声明类,而class(类)可通过extends
来继承父类中属性和方法,super
指向父类的原型对象,可以调用父类的属性和方法,且子类constructor方法中必须有super关键字,且必须出现在this之前。
4.js数据类型
数据类型从大的方向来说分为两种
- 基本数据类型:字符串(String),数字(Number),布尔(Boolean),空(Null),未定义(Undefined),Symbol(nbs)
- 复杂数据类型:对象(Object),数组(Array),函数(Function)
Symbol表示独一无二的值,避免属性名的冲突
5.检测数据类型
typeof
检测 存在的问题:null 或者数组打印出来也是 objectinstanceof
(只能检测复杂数据类型)
返回值是 true 或者 false 相关的构造函数只要在原型链上,就是 true,否则就是 false 可以用于检测是不是数组Object.prototype.toString.call
(要检测的数据值)
为什么要借 Object.prototype.toString,因为自己的 toString 被自己原型重写了,得不到类似[object Object]
6.如何检测一个数据是数组
var arr = [2, 3, 4] console.log(arr instanceof Array) console.log(Array.isArray(arr)) console.log(Object.prototype.toString.call(arr))
7.深拷贝和浅拷贝
- 浅拷贝:只是拷贝一层,更深层次对象级别的只拷贝了地址
- 深拷贝:层层拷贝,每一级别的数据都会拷贝
- 浅拷贝方法:
1. 使用 lodash 浅拷贝clone
方法,让他们俩指向不同地址
2. 使用Object.assign
方法
3. 使用es6语法的...
拓展运算符 - 深拷贝方法:1. 使用
JSON.parse(JSON.stringify(obj))
,缺点:当对象有方法和undefined属性的时候会丢失2. 使用递归
- 如果存在
循环引用
就会出现堆栈溢出
- 解决思路:把处理好的对象存起来,在处理新的对象的时候,会现在这个存的地方找一找有没有处理好,如果有就直接返回就行了
let obj = { name: "zs", age: 20, father: [2, 3, 4], }; function deepClone(target) { //这一行如果不用三元判断 如果是数组会有bug会被拷贝成伪数组对象 let tempObj = Array.isArray(target) ? [] : {}; for (let key in target) { if (typeof target[key] === "object") { tempObj[key] = deepClone(target[key]); } else { tempObj[key] = target[key]; } } return tempObj; } let obj1 = deepClone(obj); console.log(obj1);
8. slice和splice的区别
- 两者都是
数组删除
的方法
1.splice改变原数组,slice不改变原数组。
2.slice会返回一个新的数组,可以用于截取数组
3.splice除了可以删除之外,还可以替换,添加数组
4.splice可传入3个参数,slice接受2个参数
9. substr和substring的区别
- 两者的作用都是
截取字符串
的 - substr是从起始索引号开始提取指定长度的字符串
- substring是提取字符串中两个指定索引号之间的字符
10.let const var区别
let和const都是用来声明变量
的,在ES5中我们可以使用var来进行变量声明 -使用let和const作用
- 防止for循环中变量提升的经典场景
- 不污染全局变量
var关键字声明变量
1.var关键字声明变量存在,变量提升
的问题;
2.var声明的变量不存在块级作用域
,如果是全局变量在任何地方都可以调用;
3.var声明变量如果名称重复了,后面声明的会将前面声明的覆盖掉;
let关键子声明变量
1.不存在变量提升
,let声明变量不存在变量提升的问题:如果在let声明变量前调用该变量就会报错(提示初始化前无法访问该变量);
2.块级作用域
,let声明变量存在块级作用域(全局、函数、eval严格模式),只在当前的代码块中生效,如果在当前代码块以外调用就会报错(当前的变量没有定义);
3.不影响作用域链的操作
4.不允许变量重复声明
,let声明的变量是不允许重复声明的,如果同一个名称被重复声明了就会报错(当前的标识已经被声明了);
const声明变量
1.const声明的变量也具有:不存在变量提升
、块级作用域
、不允许重复声明
的特点;
2.const声明的变量都是常量
(不允许改变的量),一旦声明就不允许被修改,如果修改就会报错--常数变量赋值
3.一般第三方的框架中会大量使用const声明变量,这样可以避免用户修改框架中的变量;
4.const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址
,因此等同于常量。
11.new的过程
- 创建一个新的空对象。(即实例对象)
- 让this指向这个新对象
- 执行构造函数里面的代码,给这个新对象添加属性和方法
- 返回这个新对象obj。(定义的构造函数中不写返回值。)
12.防抖节流
防抖
- 防抖是指在事件触发n秒后再执行,如果在n秒内再次被触发,则重新计算时间。(就是在触发某个事件后,在下一次触发之前,中间的间隔时间如果超过设置的时间才会发送请求,一直触发就不会发送请求 应用场景:
a、scroll事件滚动触发,
b、搜索框输入查询
c、表单验证
d、按钮提交事件
e、浏览器窗口缩放,resize事件
function debounce(func, delay) { let timer = null // 计时器 return function (...args) { clearTimeout(timer) // 清除上一次计时器 timer = setTimeout(() => { // 重新定时 func.apply(this, args) }, delay) } }
节流
- 节流是指如果持续触发某个事件,则每隔n秒执行一次。
function throtte(func, time) { let timer = null // 计时器 return function (...args) { if (timer) return // 无视,直接返回 timer = setTimeout(() => { func.apply(this, args) }, time) } }
13.promise的3种状态
这点简单介绍概念,用法后面在详细介绍
1 . 初始态pending
- pending。它的意思是 "待定的,将发生的",相当于是一个初始状态。创建[Promise]对象时,且没有调用resolve或者是reject方法,相当于是初始状态。这个初始状态会随着你调用resolve,或者是reject函数而切换到另一种状态。
2 . 成功态resolved--
也叫fulfilled
- resolved。表示解决了,就是说这个承诺实现了。 要实现从pending到resolved的转变,需要在 创建Promise对象时,在函数体中调用了resolve方法(即第一个参数)。
3 . 失败态rejected
- rejected。拒绝,失败。表示这个承诺没有做到,失败了。要实现从pending到rejected的转换,只需要在创建Promise对象时,调用reject函数。
14.冒泡排序
// 上口诀 双层for循环 外层长度-1 内层长度-1-i let arr = [4, 3, 1, 7, 8, 10] for (let i = 0; i < arr.length - 1; i++) { for (let j = 0; j < arr.length - 1 - i; j++) { if (arr[j] > arr[j + 1]) { let temp = arr[j] arr[j] = arr[j + 1] arr[j + 1] = temp } } } console.log(arr)
Vue部分
1.MVVM
MVVM
是三个单词的缩写,model
(数据,一般来自ajax或本地存储)+view
(视图template)+viewmodel(vue实例)
- model数据变了,视图会跟着改变,如果用的是v-model,数据也会跟着改变,viewmodel在中间起一个桥梁作用
- model 和 view 就像现实中房东和租客一样,他们是不认识的,通过中介 viewmodel
好处
:
- 数据驱动
- 因为数据变了。视图也会跟着变,所以在 vue 中不用操作dom来改变视图
- 解耦(
降低了耦合性
)
- 由于 model 和 view 是没有关系的,是通过 viewmodel 结合在一起的,所以维护起来很方便,因为 model 逻辑代买改了,view 不用改
2.vue生命周期
- vue中的生命周期是指组件从创建到销毁的过程,主要分为4个周期8个钩子函数
1.分别是创建阶段的beforeCreate
,created
,一般在beforeCreate写loading加载效果,使用户体验更好,一般在created中发送ajax请求获取数据
2.然后是挂载阶段的beforeMount
,mounted
,一般会在mounted中操作DOM元素
3.更新阶段的是beforeUpdate
,updated
,当数据更新时需要做统一的业务处理时,拿到最新的dom,可以使用updated 这个钩子函数
4.最后是销毁阶段的beforeDestroy
,destroyed
,可以在beforeDestroy做一些清理的工作,比如说定时器 和解绑一些addEventListener监听的事件
- 补充:(还有
keep-alive
的两个钩子函数,使用场景是当组件切换时会进行销毁,因此组件中的初始化的4个钩子函数会多次执行,比较浪费资源,此时可以使用keep-alive纪行组件的缓存,可以让组件切换时不被销毁,keep-alive有两个独有的钩子函数,分别是activated
和deactivated
,是组件激活和失活时会执行的两个钩子函数) - 问:在
created
钩子里面可以操作dom吗?(有坑)
- 其实是可以的,用
$nextTick
可以实现的
3.单向数据流
单向数据流是指父组件向子组件传递数据,子组件通过props
接收,当父组件中的值改变了,子组件中对应的数据也会改变,因为props是只读
的,所以无法直接在子组件中对父组件传递过来的值进行修改,但是如果这个数据是一个引用数据类型,是可以直接在子组件中修改数据中的某个属性的,只要不改变这个数据的内存地址
就可以
4.双向数据绑定
- 数据 -> 视图
- 视图 -> 数据
vue中普通指令都可以实现数据变了,视图会跟着变,但是有一个特殊的指令叫v-model
,它一般用于表单控件,它可以实现双向数据绑定,所谓的双向数据就是数据变了,视图就会跟着改变,反过来也是
5.v-model原理
v-model
一般配合input
框使用,实现双向数据绑定的效果,它是v-bind
和v-on
的语法糖,原理是通过v-bind将数据绑定给input框,再通过v-on:input
,在input中的值改变时,通过$event可以获取到事件源对象 再通过target.value
获取到input中更新后的值 将这个值再赋值给绑定的数据即可
6.事件传参
在vue的组件使用自定义事件时,$event代表子组件抛出的数据,当这个自定义事件触发一个方法时, 可以不传$event而且可以在方法中进行接收,但是如果写的话就一定要写成$event的形式,这是一个固定写法, 或者这个方法既要传参又要使用事件对象,这个时候$event也是必须要写的 - @click='fn' 在回调函数直接通过参数可以拿到事件对象 - @click='fn($event)' 这个时候@event是固定写法
7.父子组件的声明周期执行顺序
1.初始化阶段时,先执行父组件的beforeCreate
、created
、beforeMount
三个钩子函数,然后执行子组件的beforeCreate
、created
、beforeMount
、mounted
四个钩子函数,最后执行父组件的mounted钩子函数
2.更新阶段,先执行父组件的beforeUpdate
,然后执行子组件的beforeUpdate
,updated
,最后执行父组件的updated
3.销毁阶段,先执行父组件的beforeDestroy
,然后执行子组件的eforeDestroy
,destroyed
,最后执行父组件的destroyed
8.v-if和v-show的区别
v-if
和v-show
都可以控制标签,实现组件的显示与隐藏,不同点是v-show是通过display
的block和none
属性来控制的,当元素隐藏时,页面结构依然存在
- v-if是通过将元素创建和销毁来控制显示与隐藏的,当v-if的条件为否时,会直接销毁该元素,当满足时会重新创建出来,有可能会影响页面的回流或重绘
- 如果该元素需要频繁切换时可以使用v-show,不需要频繁切换时可以使用v-if,
提高性能
9.v-for和v-if为什么要避免一起使用
- 因为v-for的优先级比v-if要高,两者同时作用于一个标签或组件时,v-for会优先执行,执行后再进行v-if的判断,但是不满足v-if的条件的时候是可以不执行v-for的,这时候就会造成资源浪费,性能比较差
- 解决办法是可以通过计算属性将满足v-if判断条件的数据筛选出来,再使用v-if直接渲染筛选后的数据,或者是当v-if不依赖v-for时,可以通过
template
将v-if写在循环的外部,这样当不满足v-if的判断条件时,就不会再执行v-for了,也可以将数据放在计算属性
里面计算过滤出来的数据在交给v-for循环,代替v-if的作用,即可解决。
10.自定义指令:directive
应用场景
v-imgerror
公司项目中有的用户头像可能加载报错,可以给他一张默认图片, onerror this.img=默认图片v-focus
打开带有搜索的页面的时候,实现自动把光标定位到 input 中v-lazy
图片到可视区可以懒加载- 自定义指令的钩子函数
1.bind
属性绑定的时候执行 只会有一次
inserted
当前指令所在的元素插入到页面中的时候执行一次update
当前指令所在的组件中的 data 数据有更新就会执行,可以执行多次 (vue3改成mounted
)componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
// 指令的钩子有三个 bind inserted update // bind inserted 只会执行一次 // update 会反复执行 Vue.directive('focus', { inserted(el) { el.focus() }, }) Vue.directive('red', { bind(el) { el.style.color = 'red' }, }) Vue.directive('check', { update(el) { const reg = /^[a-zA-Z0-9]+$/ if (reg.test(el.value)) { el.style.color = 'green' } else { el.style.color = 'red' } }, }) 复制代码
浏览器的缓存机制
这块部分理解不是很透彻,大家浅看一下就可以了🤣
- 概念:浏览器会将请求后的资源进行存贮为离线资源,当下次需要该资源时,浏览器会根据缓存机制决定直接使用缓存资源还是再次向服务器发送请求
作用: - 减少了不必要数据的传输、降低服务器的压力
- 加快了客户端访问速度
- 增强用户体验
- 强缓存:过期之前一直用本地离线资源 不会和服务器交互
- http1.0 expire 具体的时间2023年1月1日
- http1.1 cache-control 时间期限1年 (优先级高)
- 协商缓存 本质是看本地东西和服务器有没有变旧(服务器上有没有更新的资源) 强缓存不会和服务器交互 协商缓存会交互一次来判断东西有没有变旧
- http1.0 last-modified/if-modified-since
- http1.1 etag/if-none-match(优先级高)
- 当前页面中有一个img,它的src是logo.png
1. 先看本地有没有缓存资源,如果没有,就需要向服务器发请求 拿回来这个资源同时拿回来expire
,cache-control
,last-modified
,etag
(响应报文中)
2. 过了一段时间(不确定的),又有一个别的页面上面有一个img,src也是logo.png,这个时候就去看一下本地有没有缓存资源,发现有,再看一下它expire
,catch-control
(如果有,优先级是看cache-control
),如果没有过期,就用就行了(这块属于强缓存) 但是发现如果过期了,就开始进入协商缓存的阶段,就向服务器发送一个请求把if-modified-since
(值就是last-modifyed
)/if-none-match(etag)
通过请求头发过去, 服务器开始对比看看服务器上的资源有没有比本地更新一点,如果服务器资源还是旧的,返回一个状态码叫304,浏览器一看状态是304就继续用本地离线资源,如果服务器资源有更新的资源,状态码就是200,服务器就需要传给浏览器一个新的logo.png,流程重新再走一遍
设计模式
本人技术栈是主要是前端vue的,所以对这方面的知识还是有所欠缺的,尽量说的明白一点,其实我也不是很懂,大致明白,如果想要全面理解透还是需要很多技术储备的,很明显我不是的哈哈🤣
1.观察者模式
观察者模式即一个对象被多个对象所依赖,当被依赖的对象发生更新时,会自动通知所有依赖的对象
- 观察者模式定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新
比喻:
宝宝 -> 父母爷爷奶奶 一对多的依赖关系
宝宝哭 -> 父母爷爷奶奶赶紧过来服务 当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新 - 模式特点: 有
二个主体
一个是被观察者Dep
一个是观察者watcher
,在vue中v-band
就是采用这种模式理念,缺点是耦合性太高
2.发布订阅模式
发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。
- 在现在的发布订阅模式中,称为发布者的消息发送者不会将消息直接发送给订阅者,这意味着发布者和订阅者不知道彼此的存在。在发布者和订阅者之间存在第三个组件,称为调度中心或事件通道(event bus),它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者
- 模式特点:有
三个主体
发布者 调度中心 订阅者,在vue中eventBus
体现出来了这种模式理念,可以实现解耦
3.两者模式区别
- 主体数量不一样,观察者模式有二个主体 分别是被观察者
Dep
和观察者watcher
,发布订阅模式有三个主体 分别是发布者 调度中心(事件通道 ) 订阅者 - 发布订阅模式相比观察者模式多了个事件通道,事件通道作为调度中心,管理事件的订阅和发布工作,彻底隔绝了订阅者和发布者的依赖关系,订阅者和发布者是解耦的(不知道彼此存在)
总结给大家推荐一个实用面试题库
1、前端面试题库 (面试必备) 推荐:★★★★★
地址:前端面试题库
2、前端技术导航大全 推荐:★★★★★
地址:前端技术导航大全
3、开发者颜色值转换工具 推荐:★★★★★
地址 :开发者颜色值转换工具