前端面试中,回答不出的那些问题

更新时间:2023-05-09 21:18:00 阅读: 评论:0

前端⾯试中,回答不出的那些问题
前两天电话⾯试了⼀个公司的前端,差不多问题都能回答出⼀点,但是⼀旦向下深挖,就不会了,还是⾃⾝基础打得不够啊,怕以后⾯试还是会遇到这些个问题,所以就觉得把⾯试官问我的,我回答不上来的,且现在还记得问题记录⼀下,⽅便以后巩固复习,顺便分享给⾯试的⼩伙伴
1. 深度拷贝和浅度拷贝了解过吗,是否可以说⼀下
当时回答:了解过,浅度拷贝的话,拷贝的如果是基本数据类型的话,他就会原样拷贝,如果拷贝的时引⽤数据类型,⽐如说对象,数组,函数这类的,他只能拷贝他的应⽤地址,所以拷贝完成,修改引⽤数据类型⾥⾯的数据时,原来的也会发⽣修改(当时有点紧张所以说的不是很清楚,再解释⼀下,a对象⾥⾯⼜有基本数据类型和引⽤数据类型,拷贝a得到b,修改b⾥⾯的引⽤数据类型的数据时,a也会发⽣变化),深度拷贝的话,他会拷贝的更加彻底,当他拷贝的时引⽤数据类型的数据,他会递归向下继续拷贝,直到拷贝的数据都是基本数据类型。
⾯试官继续问:你刚才提到了递归,那你能说⼀下,深度拷贝的递归出⼝是什么?
问道这个问题时,⼼⾥暗喜,我可是敲过深度拷贝的代码的,但是还是因为紧张,想不起来了,哎,果然⾯试总归会紧张,想了⼀会后终于想起来了,判断这个数据是不是引⽤数据类型,如果不是,结束递
归,✌✌✌,⾯试官继续问:当深度拷贝中出现两个对象相互引⽤,该如何处理?你懂的,我傻了,相互引⽤我咋从来没有想过这个问题呢,想了⼀会,告诉⾯试官,我们是不是可以判断这两个数据的地址是不是相等的是不是就好了,⾯试官:可是我不知道,哪两个对象相互引⽤,你怎么去判断地址是否相等呢?,完了完了,我已经彻底傻了...
⾯试完查的资料:可以使⽤⼀个WeakMap结构存储已经被拷贝的对象,每⼀次进⾏拷贝的时候就先向WeakMap查询该对象是否已经被拷贝,如果已经被拷贝则取出该对象并返回。
//判断是数组还是对象
function getType(target){
// console.log(String.call(target).slice(8,-1))
return String.call(target).slice(8,-1)
}
function cloneUtil(target,hasObj = new WeakMap()){
let result;
// 判断数据类型
if(getType(target) === 'Array'){
result = []
} el if(getType(target) === 'Object'){
result = {}
} el {
return result
}
for (let i in target) {
let item = target[i]
//当遍历的数据中仍然有数组/对象时,重新按照之前的步骤进⾏拷贝,直到所有拷贝数据的类型都是基本数据类型
if(getType(item) === 'Object' || getType(item) === 'Array'){
//判断hasObj中是否已经拷贝过该对象,如果已经被拷贝则取出该对象并返回。
if(hasObj.has(item)) (item)
hasObj.t(item,{})
let cloneitem = cloneUtil(item,hasObj)
result[i] = cloneitem
} el{
result[i] = item
}
}
return result
}
let obj = {
name:'Bob',
age:22,
x:{
option1:'男',
option2:'⼥'
}
}
let cloneObj = cloneUtil(obj)
obj.obj = obj
console.log(obj)
console.log(cloneObj)
2. vue项⽬中你⽤了ui框架,⽐⽅说element-ui,你要修改ui框架提供的组件样式的时候,你会怎么去操作
当时回答:vue的style标签中,会添加⼀个scoped属性,防⽌样式的污染,但是你要修改element-ui组件的话,再带有scoped属性的style标签中去修改,是不会达到想要的结果,vue组件是可以有多个style标签的,所以当你要修改element-ui组件的样式时,可以添加⼀个没有scoped的style,就可以修改组件样式。
⾯试官继续追问:那我就是想要在有scoped属性的style标签⾥⾯去修改,那要怎么处理?
⾯试官这个问的时候,我顿时emmm傻了,仔细想来了想,记得如果项⽬⽤来stylus,less这样的预处理器,可以⽤样式穿透来实现,我就这么回答了,但是本⼈没有实践过,所以还是有点慌慌的,⾯试完之后就去查了资料。资料显⽰,我的回答是对的,举个例⼦把,看⼀下到底怎么穿透。
<style lang="stylus" scoped>
.a >>> .b {
/* ... */
}
</style>
//或者
<style lang="scss" scoped>
.homePage /deep/ .el-main {
padding: 0;
}
}
3. call,apply了解过吗,能⾃⼰实现⼀个call,apply吗,还有其他什么⽴即绑定的函数吗
当时回答:了解过,call,apply都是更改this指向的,然后其他的我都没回答出来,后来⾯试完之后想想其他⽴即绑定还有bind啊,哎,傻了傻了
补完知识:
let person ={
name: 'bob',
age: 18
}
function student (){
console.log(this.name,this.age,x,like)
}
student.call(person,'nan','eat')
//call,apply的原理相当于,把上⾯的两个合并成⽴这个下⾯⼀个
let person = {
name: 'bob',
age: 18,
fn:function student (){
console.log(this.name,this.age,x,like)
}
}
person.fn('nan','like')
Call = function (context = window){//如果传⼊context没有赋值,就赋默认值window
if(String.call(context) !== '[object Object]'){
console.log('不是对象')
return
}
let fn = Symbol('fn')//产⽣⼀个唯⼀不重复的值,以免冲突
context[fn] = this//这⾥的this,是调⽤了myCall的函数,⽐⽅说a.mayCall(),那a就是这⾥的this
let args = [...arguments].slice(1)//这⾥是传值
let result = context[fn](...args)//运⾏这个函数
delete context[fn] //删除context上的⽅法
return result
}
Apply = function (context = window){
if(String.call(context) !== '[object Object]'){
console.log('不是对象')
return
}
let fn = Symbol('fn')
context[fn] = this
let args = arguments[1]
let result = context[fn](...args)
delete context[fn]
return result
}
let person = {
name: 'bob',
age: 18
}
function student(x,like){
console.log(this.name,this.age,x,like)
}
4.因为之前问了事件循环机制是怎么样⼦操作的,当时回答的时候,说了js单线程的,然后就有了这个问题,js如果不是单线程会怎么样?后⾯再回答es6⾥⾯除了let,const新增的,es6⾥⾯还有什么你记得的内容吗,当时中间有回答了async 和 promi,所以,js单线程,⼜出了⼀道问题,promi在js单线程是怎么运⾏过程(突然描述不清楚了,⼤概就是这个意思)?
当时回答:js如果不是单线程的话,对dom会有影响,但是具体什么影响我记不清楚了,然后⾯试官就把答案告诉我了:如果js被设计了多线程,多个线程都要操作⼀个dom元
素,如果有⼀个线程要修改⼀个dom元素,另⼀个线程要删除这个dom元素,这时候就会产⽣问题。
下⾯的那个promi 在js上运⾏过程我⼜没说出来,哎,⾯试完之后躺在床上好好的想了下,promi⾥⾯不就是装异步函数的么,啥ajax请求不是都是放⾥⾯的么,那不就是js运
⾏到这边的时候,会交给对应的管理模块管理,满⾜条件之后,进⼊回调队列,然后,js主线程上的同步代码执⾏完成之后,通过事件轮询机制,看回调队列⾥是否⼜东西,有的
话就把他钩到主线程上去运⾏,嗯,当时⼈傻已经实锤了,换种问法,问事件循环机制就不知道怎么说了,所以啊,⼩伙伴们,记得⾯试的时候要冷静,冷静,冷静,虽然我⾯试
的时候也这么跟我⾃⼰说的
这两天在家好好的看了⼀下关于promi的内容,针对于这个问题,做出相应的补充
我们知道js运⾏代码时,都是从上到下执⾏,当遇到异步任务的操作就会交给相应的处理异步任务的模块,然后,异步任务符合结果才会进⼊回调队列,但是回调队列⼜分为宏队
列和微队列(这是我之前不知道的),像ajax,定时器等满⾜要求会进⼊宏队列,但是像promi满⾜要求后是会进⼊到微队列当中,当要执⾏宏队列中的异步任务时,他会先询
问微队列中的异步任务是否清空,如果清空,他就会执⾏宏队列中的异步任务,举个例⼦把,⽐如说宏队列中有1,2,3三个异步任务,微队列中此时只有4,5两个异步任务,当
执⾏1时,会先把4,5两个执⾏掉,再执⾏1,执⾏1的过程中,⼜有6,7进⼊微队列,那么等1执⾏完想执⾏2时,他⼜会去问微队列中是否还有要执⾏的异步任务,此时6,7再
微队列中,所以他会先把6,7执⾏掉,然后在执⾏2,2执⾏完之后,他⼜会继续去问微队列是否有任务,此时微队列中没有新的任务,所以他回去执⾏宏队列中的3
5. 隐式类型转换,2 == {a:2} 会出现什么结果,判断过程是怎么样的
当时回答:emmmmmmmmm了半天,还是没回答出来
补完知识的答案:左边是数字右边是对象进⾏⽐较,会将不是数字的⼀⽅转化为数字然后进⾏⽐较,但是像对象,数组这样引⽤类型的数据的转换⽐较复杂,他会先将对象通过valueOf获取原始值,然后在对原始值进⾏toString转化成字符串,然后对字符串转化为Number,然后在与数组进⾏⽐较,所以{a:2}通过valueof之后就会变成[object Object],在
经过toString的处理就变成"[object Object]",在Number("[object Object]") 就会变成NaN,就是不是数字的数字,最后就变成 2==NaN 的⽐较了,显然是fal
6. 图⽚懒加载原理
这个之前没有去了解过,所以当时没有回答出来,⾯试完后补了知识之后的答案:将img标签中的src指向⼀个loading图⽚地址,然后定义⼀个data-src属性(他有个data-属性 - 后⾯可以随便取名字,我这取的是data-src)指向真实的图⽚地址,根据页⾯的可视⾼度和滑轮滚动的⾼度与图⽚的⾼度进⾏⽐较,如果图⽚快要进⼊可视区域,就将src中的地址换成data-src中的地址,从⽽达到图⽚懒加载的效果
//窗⼝可视⼤⼩兼容性⽅法
function getViewportSize(){
if(window.innerHeight){
return {
height:window.innerHeight,
width:window.innerWidth
}
}el{
patMode === 'BackCompat'){
return {
height: document.documentElement.clientHeight,
width: document.documentElement.clientLeft,
}
}el{
return {
height: document.body.clientHeight,
width: document.body.clientLeft
}
}
}
}
//滚动⼤⼩兼容性⽅法
function getScrollOfft(){
if(window.pageXOfft){
return {
top: window.pageYOfft,
left: window.pageXOfft
}
}el{
return {
top: document.body.scrollTop + document.documentElement.scrollTop,
left: document.body.scrollLeft + document.documentElement.scrollLeft
}
}
}
//获取当前所有的img
let imgs = ElementsByTagName('img')
let len = imgs.length
/
/获取窗⼝⾼度
let windowHeight = getViewportSize().height
//滚动事件
function lazyload(){
//获取滚动⾼度
let scrollHeight = getScrollOfft().top
for(let i = 0; i<len; i++){
//判断照⽚是不是快要显⽰
if(imgs[i].offtTop < windowHeight + scrollHeight){
//判断是不是默认图⽚
if(imgs[i].getAttribute('src') == "./img/1.jpg"){
/
/将真实图⽚地址替代默认图⽚地址
imgs[i].tAttribute('src',imgs[1].getAttribute('data-src'))
}
}
}
}
//页⾯刚打开的时候,没有滚动,所以不会执⾏lazyload⽅法,所以要提前执⾏⼀次
lazyload()
//绑定滚动事件
7. ⽤const定义常量是否可以修改
当时回答:应该不可以吧(虚虚的),⾯试完补了知识之后的答案:const定义的是基本类型修改会报错,但是定义的是引⽤数据类型就不会报错,⽐如说对象,数组之类的,原因是因为,基本数据类型他都是存在栈中,const不能修改栈上的内容,但是引⽤数据类型,栈上只是保存了指向对象或者数组的指针,其他的属性啊或者是数组⾥⾯的数据都是放在堆上的,所以const可以修改堆上的内容,也就是const是可以修改对象的属性、数组的内容,记得const定义的常量是不可以重复定义的哦
8. 可以描述⼀下浏览器渲染⽹页吗
当时回答:浏览器渲染html,会先将html渲染成dom树,css被渲染成css树,dom树和css树合并到⼀起之后就会变成⼀个render树,之后就会计算dom的⼤⼩和位置称之为回流,之后会将计算后的dom位置⼤⼩样式渲染到屏幕上,这称之为重绘。
⾯试官追问:怎么样会导致回流与重绘?emmm之前看过,所以就只有简单的回答出来改变样式,dom节点会导致,但是时间有点久远详细的就说不出来了,查完资料之后的回答:回流⼀定会造成重绘,但是重绘不⼀定回流。页⾯的⾸次渲染,浏览器窗⼝⼤⼩的改变,dom元素的变化,就是操作会导致⽂档流发⽣改变的,⽐如说新增加dom元素,删除dom元素,或者某个dom元素的⼤⼩发⽣改变,⽂本节点发⽣改变,字体⼤⼩发⽣改变等等都会导致回流。当你修改的样式不会对⽂档流发⽣改变,就不会发⽣回流,⽐如说,颜⾊改变,visibility,圆⾓,阴影什么的都是不会造成回流只会重绘。
9.NaN知道是什么吗,是否可以说⼀下Number.isNaN和isNaN的区别
当时回答:NaN是判断是不是个数字,NaN === NaN返回fal
资料补充:NaN是判断是不是数字,但是因为NaN不等于NaN,所以需要有⼀个判断这个东西是不是NaN,所以出现了⼀个isNaN()来判断,isNaN(NaN)返回为true,但是isNaN()会将字符串,或者数组,对象都会都认为是不是数字,所以返回true,Number.isNaN()是⽤来判断是不是真的为NaN的,其他的字符串,数组,数字都会返回为fal只有
Number.isNaN(NaN)才会返回true
10.ba64与md5的区别以及应⽤场景
当时回答:不知道具体区别,应⽤场景:ba64可以⽤于将图⽚转化为ba64字符串进⾏上传,md5主要⽤于登录,对密码按照⼀定的规则进⾏加密
资料补充:ba64主要是通过encode进⾏加密,所以他是可以⽤decode解密,⼀般⽤于当图⽚过⼤时,可以采⽤ba64加密,从⽽压缩图⽚的⼤⼩,进⾏上传,md5是⼀种被⼴泛使⽤的密码散列函数,可以产⽣出⼀个128位(16字节)的散列值(hash value),⽤于确保信息传输完整⼀致,⼀旦加密后不能解开,所以⼀般⽤于密码,或者向服务器进⾏通讯时,双⽅可以按照相同的规则,对⼀个
东西进⾏加密,对⽐两者是否⼀致,从⽽防⽌内容篡改
11.什么是闭包以及他的应⽤场景
js中内部函数通过作⽤域链可以获取外部函数的变量,例如:
function f1() {
var a  = 1
console.log('f1:',a)
}
f1()
console.log(a) //报错,因为a是f1的私有变量,外部不能获取
但是外部函数想访问内部函数的变量,是会报错的,所以闭包相当于是外部函数和内部函数的⼀个桥梁
function f1() {
var a = 1
return function (){
console.log(a) //f2通过作⽤域链可以访问f1中的变量
}
}
var b = f1()
b() //外部访问到了变量a
形成闭包的条件:1.函数嵌套 2.内部函数使⽤外部函数的局部变量 3.执⾏外部函数
主要作⽤:延长外部局部变量的⽣命周期,使得从外部能访问到函数内部的变量
应⽤场景:
返回值:外部需要访问函数内部的变量
函数参数
function f2(num){
console.log(num)
}
function f1(){
var a = 1
function f3(){
return a
}
var b = f3()
f2(b)
}
f1()
函数赋值
var num;
function f1(){
var a = 1
function f2(){
return a
}
num = f2()
}
f1()
console.log(num)
⽴即执⾏函数(IIFE)
循环赋值
function f(){
var arr = []
for(var i = 0; i < 5; i++){
(function(i){
arr[i] = function(){
return i
}
})(i);
}
return arr
}
var a = f()
console.log(a[1]())
对私有变量进⾏修改
var getName, tName;
function fn(){
var name = 'aaa'
tName = function(newName){
name = newName
}
getName = function (){
return name
}
}
fn()
tName('bbb')
console.log(getName())
计数器
//计数器
var add = function (){
var num = 0
return function (){
return num++
}
}()
console.log(add())
console.log(add())
12.webpack的原理
13. scoped的原理
Vue中的scoped属性的效果主要通过PostCSS转译实现,转译前的Vue代码:
<div class="box">
hello world
</div>
<style scoped>
.box{
height: 100px;
width: 100px;
background-color: rgb(35, 85, 85);
}
</style>
转译后的代码:
<div data-v-431182d6 class="box">
hello world
</div>
.box[data-v-431182d6] {
height: 100px;
width: 100px;
background-color: rgb(35, 85, 85);
}
说明:从上⾯的代码,我们不难看出postcss给所有的都没添加了⼀个随机且不重复的data-属性,css选择器添加了⼀个属性选择器来选择这个dom,从⽽达到每个dom节点的样式私有化不会相互污染
14.数组扁平化(除了⽤flat⽅法)
⽅法1
let arr = [1,[2,3,[4,5,[6],7,8],9],0]
let newArr = []
//可以将多层数组嵌套数组的数组扁平化
function flatOne(array){
array.map(item => {
//判断遍历的每⼀项是否是数组,是的话递归,不是的话就把数组push进⼊newArr
Array.isArray(item) ? flatOne(item) : newArr.push(item)
})
return newArr
}
console.log(flatOne(arr))
⽅法2
function flatTwo(array){
return [].concat(
...array.map(item => {
return (Array.isArray(item) ? flatTwo(item) : item)
})
)
}
console.log(flatTwo(arr))
⽅法三
function flatThree(array){
let newArr = []
//将数组转化成字符串
//于数组对象,toString ⽅法连接数组并返回⼀个字符串,其中包含⽤逗号分隔的每个数组元素
//将字符串形式的数字转化为数字类型的数字
newArr.push(parInt(element))
});
return newArr
}
console.log(flatThree(arr))

本文发布于:2023-05-09 21:18:00,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/875989.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:拷贝   函数   数组
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图