2022.7.17 初级前端面试记录 (长更)

246 阅读18分钟

面试系列会长期更新,下一次面试是20号。

面试日期:2022年7月17日

面试公司:上海某小厂

面试岗位:初级前端开发

整场下来,一个项目相关的问题都没有问😮,简短自我介绍结束后,无情的抛问题机器(面试官)开始发问

问题总览

1、CSS常用的水平垂直居中方法

2、CSS选择器优先级排序,CSS选择器中的 + 、> 分别代表什么

3、谈一下移动端适配方案,以及如何计算出适合当前页面的元素大小

4、你一般用什么来查询节点

5、原生JS绑定事件一般都用什么方法

6、如何为同一个元素添加多个相同的点击事件?

7、有使用过addEventListener吗?addEventListener的第三个参数是

8、什么是事件代理

9、说一下事件的捕获和冒泡

10、说一下event.target

11、JS的数据类型?基础类型和引用类型的区别

12、如果要深拷贝一个引用类型的数据,应该怎么做

13、数组常用的API有哪些

14、遍历一般都用哪些方法

15、说一下reduce方法

16、map和forEach的区别

17、说一下对于原型链的理解

18、是否有使用过ES5的方式实现类

19、如何统计字符串中出现最多的字母与个数

20、++a 和 a++ 的区别

21、谈一下Promise

22、谈一谈graph图状的数据结构数据,和树状结构有什么区别

23、给一个树状的数据,找到其中某个符合条件的叶子节点,如何查找,是否用代码实现过

24、开发中使用的是类组件还是函数式组件,React类组件中有哪些优化方案

25、无限式瀑布流滚动到最后还会有很多数据,之前加载的数据,你会卸载掉吗

26、是否遇到过页面上DOM元素过多造成的页面卡顿的情况,如何解决

27、是否有用过可视化的组件

28、还有其他要问我的没

1、CSS常用的水平垂直居中方法

一、CSS元素水平垂直居中

1、【flex布局】 
display: flex;  
justify-content: center;  
align-items: center;

2、【grid布局】
display: grid;  
place-items: center;    //place-items: <align-items> <justify-items>;  
//align-items属性控制垂直位置,justify-items属性控制水平位置。  
//这两个属性的值一致时,就可以合并写成一个值。  
//所以,place-items: center;等同于place-items: center center;。

3、【table-cell布局】      
父元素display: table-cell  
子元素display: inline-block    
table-cell 只能处理具备行内特性的元素布局  
text-align: center;  
vertical-align: middle;

4、【绝对定位】
父元素相对定位(position: relative),子元素绝对定位(position: absolute)

1)负margin值 由于margin不能设为百分比,所以必须已知宽高
left: 50%; top: 50%; margin-left: -元素宽度的一半px; margin-top: -元素高度的一半px; (负的)

2transform:translate ****可用于不知道自身宽高情况使用
top: 50%; left: 50%;
transform: translate(-50%, -50%) //往x轴, y轴平移自身长宽的 50%
实现水平居中或垂直居中可使用translateX、translateY

3)利用 calc 计算偏移量
top: calc(50% - 自身高的一半px);
left: calc(50% - 自身宽的一半px);
//百分比是基于元素的左上角,均设为50%,再减去自身宽高的一半即可

4margin: auto,利用块状元素的流体特性
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
块状水平元素在默认情况下(非浮动、绝对定位等),
给水平/垂直方向的对立定位(left、right、top、bottom)各设定一个值,
将水平/垂直方向的 margin 均设为 auto,auto就会自动平分块状父元素的剩余空间。
left、right、top、bottom 的值没有实际的大小描述意义。
只要确认其存在性就行,不是0也可以激发块状元素的流体特性。

二、文本水平垂直居中

text-align: center;
height与line-height与设为相同的值

2、CSS选择器优先级排序,CSS选择器中的 + 、> 分别代表什么

!important > 内联 > id选择器 > 类、属性、伪类选择器 > 标签元素、伪元素

优先级选择器
No. 1ID 选择器#
No. 2类选择器.className
No. 2标签属性选择器例:a[title] {}过已经存在的属性名或属性值匹配元素
No. 2伪类选择器:first-child:nth-child(n):nth-of-type(n)
No. 3标签元素选择器例:span{}
No. 3伪元素选择器::伪元素是一个附加至选择器末的关键词
No. 4通配选择器*
No. 4后代选择器例:li li {}
No. 4子代选择器子选择器
No. 4相邻兄弟选择器+可选择紧接在另一元素后的元素
No. 4通用兄弟选择器~查找某一个指定元素的后面的所有兄弟结点。

拓展 ➡ 深入理解CSS选择器优先级

3、谈一下移动端适配方案,以及如何计算出适合当前页面的元素大小

多方案一览 ➡ 关于移动端适配,你必须要知道的

vw、vh方案 ➡ 移动端适配解决方案(二)

4、你一般用什么来查询节点

.getElementById("idName") 将找到的标签包装成一个对象返回, 找不到就返回 null
.getElementsByClassName("className")** .getElementsByName("nameAttributeValue") .getElementsByTagName("tagName");** 返回一个存储了标签对象的数组, 找不到就返回一个空数组
.querySelector("") 只会返回根据指定选择器找到的第一个元素。类名用 ., id 名用 #, 各种选择器都可以用。
.querySelectorAll("") 会返回指定选择器找到的所有元素。各种选择器都可以用。

5、原生JS绑定事件一般都用什么方法

1、在标签中直接绑定原生函数
2、在js事件中获取dom事件绑定 dom事件绑定document.getElementById('btn').onclick=handleClick();
3、事件监听addEventListener和attachEvent attachEvent是后绑定先执行,addEventListener是先绑定先执行attachEvent是IE有的方法,它不遵循W3C标准。Element.attachEvent(Etype,EventName)

6、如何为同一个元素添加多个相同的点击事件?

addEventListener() 方法允许向相同元素添加多个事件,同时不覆盖已有事件。
addEventListener() 方法为指定元素指定事件处理程序。
addEventListener() 方法为元素附加事件处理程序而不会覆盖已有的事件处理程序。 可以向一个元素添加多个事件处理程序,也可以添加多个相同类型的事件处理程序,例如两个 "click" 事件。 可以向任何 DOM 对象添加事件处理程序而非仅仅 HTML 元素,例如 window 对象或其他支持事件的对象,比如 xmlHttpRequest 对象。
使用 removeEventListener() 方法可以删除事件监听器。

element.addEventListener("click", myFunction);
element.addEventListener("click", mySecondFunction);

//添加当用户调整窗口大小时触发的事件监听器
window.addEventListener("resize", function(){
    document.getElementById("demo").innerHTML = sometext;
});

//删除已通过 addEventListener() 方法附加的事件处理程序
element.removeEventListener("click", myFunction);
复制代码

7、addEventListener的第三个参数是什么

element.addEventListener(event, function, useCapture);

/*
第一个参数是事件的类型(比如 "click""mousedown")。
第二个参数是当事件发生时我们需要调用的函数。 
第三个参数是布尔值,指定使用事件捕获(true)或事件冒泡(false)。此参数是可选的。 
    默认为false,代表冒泡时绑定。注意:请勿对事件使用 "on" 前缀;请使用 "click" 代替 "onclick"。
*/
复制代码

8、什么是事件代理(事件委托)

事件委托,就是把一个元素响应事件(click、keydown......)的函数委托到另一个元素。一般会把一个或者一组元素的事件委托到它的父层或者更外层元素上,真正绑定事件的是外层元素,当事件响应到需要绑定的元素上时,会通过事件冒泡机制从而触发它的外层元素的绑定事件,然后在外层元素上去执行函数。

9、说一下事件的捕获和冒泡

事件捕获和事件冒泡.png

10、说一下event.target

event. target是触发事件的元素 (事件实际发生的元素), event.currentTarget是绑定事件的元素

事件可以由触发的元素冒泡到绑定事件的元素

event.target 属性可以用来实现事件委托 (event delegation)。

var ul = document.createElement('ul');
document.body.appendChild(ul);

var li1 = document.createElement('li');
var li2 = document.createElement('li');
ul.appendChild(li1);
ul.appendChild(li2);

function hide(e){
  e.target.style.visibility = 'hidden';
}

// 添加监听事件到列表,当每个 <li> 被点击的时候都会触发。
ul.addEventListener('click', hide, false);
复制代码

target 即 li, currentTarget 即 ul。由于事件冒泡机制,点击了 li 后会冒泡到 ul ,此时就会触发绑定在 ul 上的点击事件,再利用 target 找到事件实际发生的元素,就可以达到预期的效果。使用事件代理的好处不仅在于将多个事件处理函数减为一个,而且对于不同的元素可以有不同的处理方法。

event.preventDefault( )
阻止浏览器赋予元素的默认行为 不阻止事件的冒泡

浏览器的默认行为
没有给浏览器添加事件,而在一定条件下浏览器自己触发的事件行为,如:
*点击一个链接。(会自动触发该导航(navigation)跳到该 URL)
*点击表单的提交按钮。(会触发提交到服务器的行为)
*在文本上按下鼠标按钮并移动。(会选择文本)
有时我们不希望发生浏览器默认的行为,而是自己定义的其他行为,那么就需要先阻止浏览器的行为。

11、JS的数据类型?基础类型和引用类型的区别

一、基本类型(原始类型、值类型) 占据空间小、大小固定
string 、boolean、null 、undefined、number、BigInt (ES6新增) 、Symbol (ES10新增)
BigInt:表示任意大小的整数。比 Number 数据类型支持范围更大的整数值。适用于对大整数执行数学运算,避免整数溢出。同时,也可以安全使用更加准确的时间戳,大整数ID等。
Symbol:代表独一无二的值。通过Symbol创建的值一定是一个唯一的值,主要用于为对象添加独一无二的属性名,解决全局变量冲突的问题。
二、引用类型 (对象类型) 占据空间大、大小不固定
Object对象
如:普通对象-Object、数组对象-Array、正则对象-RegExp、日期对象-Date、数学函数-Math、函数对象-Function

存储位置赋值比较函数传参
原始类型值存储在栈 (stack) 中赋值比较值是否相等原始类型作为参数,函数内的操作不影响实参的值。
引用类型值存储在堆 (heap) 中,在栈里存地址,在堆里存内容赋引用比较引用是否指向统一对象引用类型作为参数,函数内的操作会影响实参的值。

12、如果要深拷贝一个引用类型的数据,应该怎么做

拷贝是为了部分或全部复用原对象的属性,在元对象的基础上创建一个新的对象。基本类型是按值访问的,不会影响到其他数据,基本类型的值没有深拷贝的概念。深拷贝和浅拷贝是针对复杂数据类型来说的,浅拷贝只拷贝一层,而深拷贝是层层拷贝。

浅拷贝如果属性是基本类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址 ,新旧对象共享同一块内存,修改对象属性会影响原对象。以下均为浅拷贝:\

  • 数组之间直接赋值arr1 = arr2
  • Object.assign
  • slice(), concat()
  • 使用拓展运算符实现的复制

深拷贝开辟一个新的栈,另外创造一个属性完全相同的对象,对应两个不同的地址,修改新对象不会改到原对象。由于不知道要拷贝的对象有多少层深度,可以用递归来解决问题,如果是原始类型,无需继续拷贝,直接返回。如果是引用类型,创建一个新的对象,遍历需要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。如果有更深层次的对象可以继续递归直到属性为原始类型,这样就完成了一个最简单的深拷贝。-\

//浅拷贝例子
var arr = [1, 2, 3];
var newArr = arr.slice(0);
console.log("浅拷贝后的数组: ", newArr); //1, 2, 3
arr.push(4);
console.log("原数组: ", arr); //1, 2, 3, 4
复制代码

详见:如何写出一个惊艳面试官的深拷贝?

//深拷贝例子1
const oldJson = { a: 1 };
const newJson = JSON.parse(JSON.stringify(oldJson));
oldJson.a = 2;
console.log(oldJson); // {a: 2}
console.log(newJson); // {a: 1}


//深拷贝例子2,对object和array两种数据类型进行深拷贝
//如果是原始类型,无需继续拷贝,直接返回
//如果是引用类型,遍历要克隆的对象,将需要克隆对象的属性执行深拷贝后依次添加到新对象上。
function clone(target) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        for (const key in target) {
            cloneTarget[key] = clone(target[key]);
        }
        return cloneTarget;
    } else {
        return target;
    }
};


//深拷贝例子3
//用map解决循环引用问题
function clone(target, map = new Map()) {
    if (typeof target === 'object') {
        let cloneTarget = Array.isArray(target) ? [] : {};
        if (map.get(target)) {
            return map.get(target);
        }
        map.set(target, cloneTarget);
        for (const key in target) {
            cloneTarget[key] = clone(target[key], map);
        }
        return cloneTarget;
    } else {
        return target;
    }
};
复制代码

【拓展】 Map类似于对象,也是键值对的集合,但对象只接受字符串作为键名,而 map“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。详见:es6.ruanyifeng.com/#docs/set-m…

13、数组常用的API有哪些

更多方法,点击查看 ==> 数组对象方法列表

14、遍历一般都用哪些方法

详见:JS数组遍历方式 小小总结一下

15、说一下reduce方法

reduce方法提供了一种遍历手段,对数组所有成员进行"累积"处理。 reduce是一种数组运算,通常用于将数组的所有成员"累积"为一个值。 reduce 接受两个参数,回调函数和初始值,初始值表示从哪个值开始累计,可省略。reduce 返回一个新数组,不改变原数组。

var arr1 = [1, 2, 3];
var sum = (a, b) => a + b;
console.log(arr1.reduce(sum)); //无初始值,则默认从数组的第一个成员开始累计,即1+2+3,结果为6
console.log(arr1);  //不改变原数组,[1, 2, 3]

var arr2 = [1, 2, 3];
var sum = (a, b) => a + b;
console.log(arr2.reduce(sum, 10)); //初始值为10,从10开始累计,即10+1+2+3,结果为16

map是reduce的特例。所有的map方法都可以基于reduce实现。

var arr = [1, 2, 3, 4];
var handler = function (newArr, x) {
  newArr.push(x + 1);
  return newArr;
};
arr.reduce(handler, [])
// [2, 3, 4, 5]


//上面代码中,累积变量的初始值是一个空数组,
//结果reduce就返回了一个新数组,等同于执行map方法,对原数组进行一次"变形"。
复制代码

本质上,reduce是三种运算的合成:遍历、变形、累积。

transduce

在reduce里面,变形和累积是耦合的,不太容易拆分。 如果reduce允许变形运算和累积运算分开,那么代码的复用性就会大大增加,这就是transduce方法的由来。由 transform 和 reduce 两个单词合成,是reduce方法的一种不那么耦合的写法。

// 变形运算
var plusOne = x => x + 1;
// 累积运算
var append = function (newArr, x) {
  newArr.push(x);
  return newArr;
}; 
R.transduce(R.map(plusOne), append, [], arr);
// [2, 3, 4, 5]
复制代码

详见:阮一峰:Reduce 和 Transduce 的含义

16、map和forEach的区别

map 不改变原数组,返回值为一个全新的数组。适用于需要改变数据值的情况。可以使用break中断循环,可以使用return返回到外层函数。
forEach 不会直接改变原始的数组的值,返回值为undefined。适用于不打算改变数据,只是想用数据做一些事情,比如存入数据库或打印出来。不能使用break中断循环,不能使用return返回到外层函数,forEach()方法无法中断执行,总是会将所有成员遍历完。如果希望符合某种条件时,就中断遍历,要使用for循环。

image.png

17、说一下对于原型链的理解

对象都有__proto__属性,这个属性指向它的原型对象,原型对象也是对象,也有__proto__属性,指向原型对象的原型对象,这条一层一层形成的由对象及其原型组成的链就叫做原型链,是一个由对象组成的有限对象链,用于实现继承和共享属性。

18、是否有使用过ES5的方式实现类

| ES5 主要通过构造函数和原型来定义一个类ES6 中可以通过class来定义类
| ES5 通过构造函数+原型链来实现继承ES6 的 Class 通过extends关键字实现继承
| ES6类语法与ES5构造函数语法区别- 类声明不能提升,函数声明可以提升

  • 类声明的代码默认运行在严格模式下
  • 在类中,所有方法都是不可枚举的
  • 在类中,只能通过new关键词去调用类的构造函数
  • 类中通过static关键词定义的属性和方法只能通过类本身去访问,不能通过类实例去访问。
  • 在类中不可以修改类名

ES5

//ES5 声明一个动物类
function Animal(type, name) {
    this.type = type;
    this.name = name;
}
Animal.eat = function () {}
Animal.prototype.speak = function () {
    console.log(this.name + ' say');
}


//ES5 声明一个猫类,继承动物类
function Cat(name) {
    Animal.call(this, 'cat', name);
}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;


// 静态方法
Cat.eat = Animal.eat;


Cat.prototype.speak = function () {
  Animal.prototype.speak.call(this);
  console.log('meow');
};
复制代码

ES6

//ES6 声明一个动物类
class Animal {
  constructor(type, name) {
    this.type = type;
    this.name = name;
  }
  static eat() {}
  speak() {
    console.log(this.name + ' say:');
  }
}


//ES6 声明一个猫类,继承动物类
class Cat extends Animal {
    constructor(name) {
        super('cat', name);
    }
   
    speak() {
        super.speak();
        console.log('meow');
    }
}
复制代码

19、如何统计字符串中出现最多的字母与个数

详见 ➡ 前端 JavaScript 获取字符串中重复次数最多的字符

20、++a 和 a++ 的区别

a++先赋值再自加,++a是先自加再赋值。

var a = 1;
var x = a++;
console.log("x的值:", x); //1
console.log("a的值:", a); //2


var b = 1;
var x = ++b;
console.log("x的值:", x); //2
console.log("b的值:", b); //2
复制代码

21、谈一下Promise

详见:es6.ruanyifeng.com/#docs/promi…

22、谈一谈graph图状的数据结构,和树状结构有什么区别

前端必看js数据结构与算法(栈,队列,链表,集合,字典,树,图,堆)

23、给一个树状的数据,找到其中某个符合条件的叶子节点,如何查找,是否用代码实现过

24、开发中使用的是类组件还是函数式组件,React类组件中有哪些优化方案

React性能优化总结

25、无限式瀑布流滚动到最后还会有很多数据,之前加载的数据,你会卸载掉吗

考察点: 通过虚列表来实现长列表的加载渲染优化

解决思路: 将超出屏幕一定部分的列表内的组件用 wx:if 卸载掉,当到达渲染临界点时再开始渲染;保证每次少量的数据展示。出现在屏幕上下n屏的内容块进行展示,此范围外的内容块就用一个等高度的骨架代替(必须要用某种方法将内容区域撑起来,这样才会出现比较合适的滚动条),并且卸载这些组件。

五步操作:

  • 不把长列表数据一次性全部直接渲染在页面上
  • 截取长列表一部分数据用来填充可视区域
  • 长列表数据不可视部分使用骨架占位填充
  • 监听滚动事件根据滚动位置动态改变可视列表
  • 监听滚动事件根据滚动位置动态改变骨架填充

详见:解决小程序渲染复杂长列表,内存不足问题

详见:虚拟列表,我真的会了!!!

26、是否遇到过页面上DOM元素过多造成的页面卡顿的情况,如何解决

详见 ➡ DOM操作造成的页面卡顿问题及解决

27、是否有用过可视化的组件

28、还有其他要问我的没

详见 ➡ 面试的反杀-你有没有想要问我的

: )    : )    : )    : )    : )     : )     : )     : )    : )    : )    : )    : )

以上,欢迎大家在评论区留言指出错误,谢谢


作者:发文章不许删
链接:juejin.cn/post/712230… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。