复盘记录
正如大圣老师的面试学习法,让我收获颇丰。确实是给了自己耻辱感,让知道了自己水平大概在什么段位,确实是该学习了!
12.29面试题集合 - - (骄兵必败)
耻辱的我熬夜写下了这篇文章。😒😒😒😒😒😒
这次面试的是我最不满意的一个,很多知识我其实知道,但是就是答不上来,看了答案恍然大悟,惊呼:天!这个我会!为什么...... 还有是第一个问题我就没有回答好,之后又犯了一些问题,自己的表达能力真的太差了,让面试官听不懂,如果我是面试官的话,我也不会让这个状态的我过的。
可能前两次面试题回答的比较好,让我已成骄兵,正所谓骄兵必败!总结下来还是因为自己确实没有花时间去准备,导致自己看待面试的态度出现了问题。狠狠的打了自己的脸。
害~该学习了!
工作中遇到的难点,如何解决?
我:项目中遇到的难点其实也有蛮多的,只不过大部分的问题都是可以通过查看文档来进行解决的,之前有遇到过小程序处理并发请求的问题,后面是通过调研,使用一个库,实现将请求挂起,逐个进行请求。
第一个问题真的太重要了,虽然现在写的就那么几十个字,但是我的表达....一言难尽,过程中咿咿呀呀的含糊不清的讲了好久。以致于后面的问题让我感觉可能不会过,状态太差。所以第一个问题一定要回答好!。
说出ES6新增的数组的方法?
我:按说这种题目应该是送分题的,可是没有错,我居然翻车了,我说的是map、reduce、concat、filter。
面试官:没有一个是ES6的
我.......
正确答案:
实例方法:find、findIndex、includes、once、some
原型方法:Array.from()、Array.of()、Array.entries(),Array.keys()、Array.values()
总结:看到答案的我吐血了,这些方法真的每一个我都会用也都能说出来分别的不同点,只是让我说一个个说出来我卡壳了....对不起看了红宝书和犀牛书。
从性能考虑,数组插入元素从头插入性能高还是从尾插入性能高
听到这题的时候我是一脸懵逼的,真的这是啥题,我真的不知道啊
我:数组插入元素应该是从头插入性能更低吧(回答的非常的不自信,含糊其辞,就算蒙对了面试官肯定也不会有什么好感的)
正确答案:
在数组的起始位置插入和删除元素的性能是更低的,因为在内存中,数组是一块连续的区域。
- 插入数据时,待插入位置的的元素和它后面的所有元素都需要向后搬移
- 删除数据时,待删除位置后面的所有元素都需要向前搬移
所以这题考察的还是基本常识,要知道数组在计算机内存中存储的方式。
对象深拷贝方法?
我:通过类似于{...obj}解构,只答出了这一个,如果是数组我可能还能憋出一个Array.from()
正确答案:
ES6结构、Object.assign({} ,obj1)Object原型方法、JSON.parse(JSON.stringify(obj))JSON方法
闭包是什么,你认为的闭包最大的作用是什么?
我:因为JS采用的是词法作用域,也就是说只有一个函数或者变量的作用域取决于你在书写代码之后,而不是函数在哪里执行,当函数执行的作用域和函数书写时的作用域不在同一个地方的时候闭包就产生了。
我以为我说到这里就可以了,面试官继续问你觉得闭包最大的作用是什么?
我:...沉默了蛮久的,我说在我的想法中JS闭包最大作用是成就异步特性,因为所有的回调函数本质上都是闭包。
正确答案:
闭包的作用:
- 变量长期驻扎在内存当中(一般函数执行完毕,变量和参数会被销毁)
- 避免全局变量的污染
Vue响应式原理
我是看过源码的,并且也是写过整个迷你响应式代码的,我的这个问题回答的跟个屎一样。
我:Vue3是通过Proxy这个代理机制实现响应式,每当对象触发[[GET]] 、[[PUT]]操作的时候都可以对数据进行拦截进行数据的更改
面试官:能说说具体[[GET]] 、[[PUT]]Vue3是怎么做的吗
我:Vue3主要是通过代码,来实现拦截,当[[GET]]的时候会触发一个依赖收集的过程,这个收集的时候就会触发一个个的副作用更新函数,当通过[[PUT]]修改的时候就会找到一个依赖收集关系,去触发的依赖的一个个副作用函数。
我的回答问题的能力真的很差,感觉有点像是挤牙膏一样,按道理这个应该是我一口气说下来的,谁都知道是通过Proxy,但是应该要自己把所有的东西都说出来,而不是需要别人提醒你返回推敲。
除了Vue的这种观察监听的设计模式,你还知道什么设计模式
之前刷掘金有专门刷到一篇JS涉及模式文章,里面讲了很多,但是还是老毛病看了就忘。
我:涉及模式我知道有蛮多的,但是除了Vue的这个我目前能说出来的可能只有一个 单例模式、
面试官:那你说下单例模式是如何实现的,你在什么时候有用到过
我:在写自己毕设的时候那时候用node搭建后台服务器API时候,增删改查接口都涉及到连接数据库,那么连接数据库这个操作就需要使用单例模式,因为连接数据库只需要一次。实现单例模式可以简单的使用一个类,然后写一个类的静态方法,通过静态方法和静态属性实现单例
面试官:除了用类还有没有用其他更简单的方法
我:除了用类我可能还会用函数吧,因为类本质上是函数的语法糖。
正确答案:
js设计模式有
- 单例模式
- 策略模式
- 代理模式
- 中介者模式
- 装饰者模式
除了单例和策略代理有听过,别的都没有听过,好像有专本一本书写js设计模式的,有空一定要看下
实现单例模式的方法:
查了一下居然有6种:
-
instanceof
function User() { if (!(this instanceof User)) { return } if (!User._instance) { this.name = '无名' User._instance = this } return User._instance } const u1 = new User() const u2 = new User() console.log(u1===u2);// true -
在函数上直接添加方法属性调用生成实例
function User(){ this.name = '无名' } User.getInstance = function(){ if(!User._instance){ User._instance = new User() } return User._instance } const u1 = User.getInstance() const u2 = User.getInstance() console.log(u1===u2); -
使用闭包,改进方式2
function User() { this.name = '无名' } User.getInstance = (function () { var instance return function () { if (!instance) { instance = new User() } return instance } })() const u1 = User.getInstance() const u2 = User.getInstance() console.log(u1 === u2); -
使用包装对象结合闭包的形式实现
const User = (function () { function _user() { this.name = 'xm' } return function () { if (!_user.instance) { _user.instance = new _user() } return _user.instance } })() const u1 = new User() const u2 = new User() console.log(u1 === u2); // true -
在构造函数中利用new.target判断是否使用new关键字
class User{ constructor(){ if(new.target !== User){ return } if(!User._instance){ this.name = 'xm' User._instance = this } return User._instance } } const u1 = new User() const u2 = new User() console.log(u1 === u2); -
使用static静态方法
class User { constructor() { this.name = 'xm' } static getInstance() { if (!User._instance) { User._instance = new User() } return User._instance } } const u1 = User.getInstance() const u2 = User.getInstance() console.log(u1 === u2);
数组和链表的区别
我听到这个问题想的是,天哪这个问题我应该要怎么回答,好难,不会答。
我:链表的数据结构是存储值和下一个值的指针,而数组是维护一个索引下标,硬要说区别的话就是遍历的区别,链表没有办法从中间取值,要找到一个值就必须先获取这个值的前一个值。
正确答案:
- 在内存中,数组是一块连续的区域
- 在数组起始位置处,插入数据和删除数据效率低。
- 查找速度快,时间复杂度为O(1)
- 链表在内存中,元素的空间可以在任意地方,空间是分散的,不需要连续
- 查找数据时效率低,时间复杂度为O(N):因为链表的空间是分散的,所以不具有随机访问性,如要需要访问某个位置的数据,需要从第一个数据开始找起,依次往后遍历,直到找到待查询的位置,故可能在查找某个元素时,时间复杂度达到O(N)
- 任意位置插入元素和删除元素效率较高,时间复杂度为O(1),因为只需要改变指针的指向即可
- 链表的空间是从堆中分配的,数组的空间是从栈分配的
插入排序、选择排序、冒泡排序的时间复杂度
我:这三个排序我都会写,但是具体的孰优孰劣我一时没有办法说出来,只知道它们都是二层循环,冒泡排序的时间复杂度应该是最高的吧,每次都需要亮亮比对
我这个答的就非常的不好,首先是回答问题不自信,然后我确实也没有去刻意背这个,不过算法我是真的都写过这三种排序
冒泡排序
冒泡算法是最基础的一个排序算法
两层循环,每次两两进行比对。
// 冒泡排序
function bubbleSort(arr) {
let temp;
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[i]) {
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
选择排序
第一轮 从数组第0个元素开始遍历 在遍历的过程中如果找到最小的值 就将这个最小的值 和数组的第0个值进行替换
第二轮 从数组第1个元素开始遍历 在遍历的过程中如果找到最小的值 就将这个最小的值 和数组的第1个值进行替换
....
最终数组循环遍历结束之后 数组也就拍好了
需要使用两层循环 时间复杂度为 ON2
选择排序适用于数组量小的情况下 好处是操作的一直都是同一个数组 时间换空间的方式
function selectSort(arr) {
let minIndex;
for (let i = 0; i < arr.length; i++) {
minIndex = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
插入排序
这个排序的场景是十分的像我们打扑克的时候的场景,
抽一张牌时(current) 会一个个的和前面的牌进行比较
直到找打比这个牌小的地方(preIndex) 插入进去 后面的全部往后挪动一位
这个排序也比较好理解 时间复杂度也是 ON2
function insertSort(arr) {
let length = arr.length;
let preIndex, current;
for (let i = 1; i < length; i++) {
preIndex = i - 1; // 记录前一个数的索引
current = arr[i]; // 记录比较的这个值
while (preIndex >= 0 && arr[preIndex] > current) {
// 在这个循环下 如果前一个数比当前这个数大 则后一个将前一个数覆盖
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
// 当循环结束 说明找到了需要替换的位置 将这个位置的值替换成最初存的那个current即可
arr[preIndex + 1] = current;
}
return arr;
}
总结:
-
三者的时间复杂度都是O(n2)
-
冒牌排序的效率最低
-
当数组量小的时候,应该优先使用选择排序
二叉树左右子树呼唤
我:不好意思这个我没有写过这个,不过这个问题应该也是和二叉树的遍历差不多可以用递归的方式进行解决的。
我觉得这个问题我不会应该坦诚的说我不会,但是我又间接的告诉面试官我会二叉树的遍历,我不知道这样的回答问题的模式是否是不好的,有点不懂装懂的感觉。
不用递归,使用循环的方式完成二叉树的先序遍历
可能因为我说了递归解决二叉树的遍历,所以面试官特意问我通过循环遍历......,万马奔腾。
我:面试官不好意思,循环的方式我没有写过,我直接解决二叉树遍历问题都是通过递归的方式来进行解决的。
递归二叉树先序遍历
var preorderTraversal = function (root) {
let res = [];
const inOrder = (root) => {
if (!root) {
return;
}
res.push(root.val); // 先存
inOrder(root.left); // 进入左节点
inOrder(root.right); // 进入右节点
};
inOrder(root);
return res;
};
循环二叉树先序遍历
这个我确实不会,有空再刷刷题吧研究一下。
如何提升自己
每次我最喜欢的就是这个环节,问到这个问题我就赚了!
- 面试官先是指出了我的简历的问题,只说使用XXX技术实现小程序,并没有说项目中遇到问题,如何解决。
- 回答的问题有点含糊,不能让面试官知道你想表达的意思
- 还是得多敲代码,技术的积累主要还是靠代码量
12.29总结
这一场面试是让我感觉备受耻辱的一场的面试,肯定是凉凉了,如果我是面试官我也不会让我过的,就算过了就这个面试情况也失去和HR讨价还价的资本了。除了算法的问题其他的问题问的真的不难,我居然还是翻车了,亏自己看书看了很多遍还是不能应付下来。也是跟自己自负有点关系了,前两场比较简单我觉得自己已经很强了,果然做人尤其是做程序员不能太狂,骄兵必败。
- 磨练自己的嘴皮子,软技能真的太重要了,表达能力真的是差到不行!
- 认真对待面试,面试前应该复习!
- 算法题还是得继续坚持下去,这回问算法不算一点都没有答出来,但是总体还是差!