今天上午接到了人生当中的第一个面试。面试的很欢快,不过由于第一次面试,我非常的紧张,也许是自己才准备了2天面试题,不充分的原因吧。从最初的对答入流,到最后尴尬的收场,重点是知道了自己的不足的地方,相信在接下来的学习中,会给我很大的启发。
今天就来复盘以下我的面试。
一开始接到电话,由于是手机电话,切换到了学校电话上面,这样足够有充足的时间面试。
最优先做了一个简单的自我介绍,说的很简单,没有丰富的介绍自己和特点。
然后就开始了技术问题的问答。
第一个问题就问:
CSS的权重
css的权重,我回答了以前的方案
- 内联样式1000
- ID选择器 100
- 类选择器、伪类、属性 10
- 元素选择器、伪元素 1
- !important的级别最高
然后面试官就追加了一个问题:
div{
width:100px !impotrant;
max-width:50px;
}
他的盒子的最终宽度是多少?最终的宽度是 50px
原因是:宽高会被max-width/min-width覆盖,所以!important会失效。
然后面试官就继续问下一个问题:
简单说一下盒模型
我直接回答到:有IE盒模型: border-box,和标准盒模型: content-box
IE盒模型的宽度计算:border+padding+content
标准盒模型的宽度计算:content
它们都是由:属性名:box-sizing声明的。
接着面试问道
说一下垂直剧中的几种方法
我一共说了5中方法:
用table做,flex布局,transform: translate(-50%,-50%);、margin和绝对定位还有margin-top -50%。
得到了面试官的肯定,在这里我列举6中方法:
方法一
使用table标签垂直居中
<table class="parent">
<tr>
<td class="child">
一串文字一串文字
</td>
</tr>
</table>
.parent{
border: 1px solid red;
height: 600px;
}
.child{
border: 1px solid green;
}
方法二
100% 高度的 after before 加上 inline-block 和 vertical-align: middle;
<div class="parent">
<div class="child">
一串文字
</div>
</div>
.parent{
border: 3px solid red;
height: 600px;
text-align: center;
}
.child{
border: 3px solid black;
display: inline-block;
width: 300px;
vertical-align: middle;
}
.parent:before{
content:'';
outline: 3px solid red;
display: inline-block;
height: 100%;
vertical-align: middle;
}
.parent:after{
content:'';
outline: 3px solid red;
display: inline-block;
height: 100%;
vertical-align: middle;
}
方法三
transform: translate(-50%,-50%);
<div class="parent">
<div class="child">
一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字
</div>
</div>
.parent{
height: 600px;
border: 1px solid red;
position: relative;
}
.child{
border: 1px solid green;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
方法四
margin-top -50%
<div class="parent">
<div class="child">
一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字
</div>
</div>
.parent{
height: 600px;
border: 1px solid red;
position: relative;
}
.child{
border: 1px solid green;
width: 300px;
position: absolute;
top: 50%;
left: 50%;
margin-left: -150px;
height: 100px;
margin-top: -50px;
}
方法五
flex布局
<div class="parent">
<div class="child">
一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字
</div>
</div>
.parent{
height: 600px;
border: 3px solid red;
display: flex;
justify-content: center;
align-items: center;
}
.child{
border: 3px solid green;
width: 300px;
}
方法六
绝对定位和margin:auto
<div class="parent">
<div class="child">
一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字一串文字
</div>
</div>
.parent{
height: 600px;
border: 1px solid red;
position: relative;
}
.child{
border: 1px solid green;
position: absolute;
width: 300px;
height: 200px;
margin: auto;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
接着面试官问:
清除浮动的几种方法说一下
我列举了三种方法:
第一个是加:clearfix
,当时背了以下代码。
.clearfix:after{
content: '';
display: block; /*或者 table*/
clear: both;
}
.clearfix{
zoom: 1; /* IE 兼容*/
}
第二个是给父元素加上 overflow: auto/hidden;
同时我说出了使用这个需要注意的地方:当子元素高于父元素时,使用auto会出现滚动条,hidden会隐藏父盒子高度之外的内容。
第三个给浮动元素盒子后面加一个空的div
然后加一个 *clear*: both;
。
我感觉面试官觉得OK,然后就接着问了
请说一说JS的类型
我回答道:有
string number boolen symbol bigInt null undefined object
面试官也没有追问,接着就问
原型链是什么
这个还好我在2的复习时间里面复习到了
我就举了个栗子
window上存了一个 Object,但是它存储的是一个地址,这个地址在#109 #109这个内存里面存储了Object的一些方法,里面最重要的有一个 prototype。它存储了一个原型的地址是#209,#209就是原型。
现在我们声明了一个对象 const a = {}
它也有个地址是#200 ,#200里面有个隐藏属性是 __proto__
它指向它的构造对象的原型,刚好就是Object的原型。
这就是一整个原型链。
图大改就是下面这样,自主手绘
我觉得了解原型链首先的了解这这三个词的理解
__proto__
prototype
constructor
__proto__
、constructor
属性是对象所独有的;prototype
属性是函数独有的;- 函数一个对象的一种,所以函数也有
__proto__
、constructor
属性。
prototype
相当于武功秘籍,徒子徒孙都能用。
__proto__
相当于通武功秘籍的道路,一层一层的找到武功秘籍
constructor
相当于自己的爸爸
这里有一个很多人总结出来的一个方法:来判断一个实例的原型
实例的 __proto__
属性(原型)等于其构造函数的 prototype
属性。
例如我们几个例子
function Person(name) {
this.name = name;
}
const a = new Person("ade");
console.log(a.__proto__ === Person.prototype);
console.log(Person.__proto__ === Function.prototype);
console.log(Person.prototype.__proto__ === Object.prototype);
面试官没有多问,接着我们就讨论了
说一下你对深拷贝与浅拷贝的理解
浅拷贝就是对于基础类型就拷贝值,对于引用类型就拷贝对象的地址。如果修改新的对象的值,会修改之前的对象,其实就同一个对象。
例如下面代码:
解决数组和循环引用
const target = {
field1: 1,
field2: undefined,
field3: "ConardLi",
field4: {
child: "child",
child2: {
child2: "child2",
},
},
};
function clone(target) {
let cloneTarget = target;
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
}
const a = clone(target);
console.log(a);
a.field4.child = "243";
console.log(target);
深拷贝就是在堆内存中重新开辟一个空间,将原有的对象完整的复制到新的内存地址当中。
const target = {
field1: 1,
field2: undefined,
field3: "ConardLi",
field4: {
child: "child",
child2: {
child2: "child2",
},
},
};
function clone(target, map = new WeakMap()) {
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;
}
}
const x = clone(target);
console.log(x);
为何用WeakMap
let obj = { name : 'ConardLi'}
const target = new Map();
target.set(obj,'code秘密花园');
obj = null;
使用map在对象间是存在强的引用关系,尽管能手动的释放:obj=null。但是存在强的引用关系,这部分的内存是无法释放的。
加入有很大的数据量拷贝,性能就会很差。
let obj = { name : 'ConardLi'}
const target = new WeakMap();
target.set(obj,'code秘密花园');
obj = null;
使用weakmaptarget
和obj
存在的就是弱引用关系,当下一次垃圾回收机制执行时,这块内存就会被释放掉。
)
面试官认为我是实习就没有深入的问weakmap的运行机制。
接着问道下面这个问题:
地址栏输入url到出现页面之间发生了什么?
自己没有准备,就非常模糊的回答,面试官让我说的详细一点,结果就面临就非常尴尬的场面,我不会。
接下来的更文有了。自己写一篇这个。
JS的作用域有那些
有全局作用域和函数作用域
var a = 1;
function b(){
}
全局作用域:全局作用域为程序的最外层作用域,一直存在。
函数作用域:函数作用域只有函数被定义时才会创建,包含在父级函数作用域 / 全局作用域内。
作用域类型
词法作用域(静态作用域)和动态作用域
词法作用域,就意味着函数被定义的时候,它的作用域就已经确定了,和拿到哪里执行没有关系,因此词法作用域也被称为 “静态作用域”。
块级作用域
这个不算是JS的原生作用域,是es6的新语法。
let和const声明会出现块级作用域
console.log(a); // 会报错,不能变量提升,同时形成暂时性死区
let a = 1
const b = 2
接着问我了不了解算法?
我说我知道几个,我列举一个
快速排序的思路
给一个数组
let a = [2,3,22,1,0,555,69]
先找出数组里面的中间数,进行比较,将小于中间数的放在左边,大于的放在右边,然后一直这样分裂出来进行对比,最后组合起来。
let quickSort = arr => {
if (arr.length <= 1) { return arr; }
let pivotIndex = Math.floor(arr.length / 2);
let pivot = arr.splice(pivotIndex, 1)[0];
let left = [];
let right = [];
for (let i = 0; i < arr.length; i++){
if (arr[i] < pivot) { left.push(arr[i])
} else { right.push(arr[i]) }
}
return quickSort(left).concat(
[pivot], quickSort(right) )
}
跨域问题,什么是JSONP?
说到跨域就得先知道什么是同源策略
同源策略是指:域名,协议,端口相同,例如下面就是一个同源的例子
同协议:如都是http或者https
同域名:如都是http://jirengu.com/a 和http://jirengu.com/b
同端口:如都是80端口
如果不是同源的话:浏览器就不会限制功能
- cookie不能读取
- dom无法获得
- ajax请求不能发送
我们知道了同源,那我们就来了解什么是跨域
跨域是指跨域名的访问
JSONP
实现原理:
- 利用
script
标签,规避跨域,<script src="url">
,但是只能发出GET请求 - 在客户端声明一个函数,
function jsonCallback() {}
。 - 在服务端根据客户端传来的信息,查找数据库,然后返回一份字符串。
- 客户端,利用
<script>
标签解析为可运行的JavaScript
代码,调用jsonCallback()
函数。
图借鉴:我们来动手写一个JSONP的插件吧!文章。
接着面试官就开始了react部分的题目,首先面试官问:
react的Dom Diff的理解
很尴尬,我只回答了虚拟Dom,没有答上React针对传统的diff算法进行了React风格的优化。
面试官说下来可以去了解一下。
下来我就去看了别人的博客:React 源码剖析系列 - 不可思议的 react diff
- 传统 diff 算法通过循环递归对节点进行依次对比,效率低下,算法复杂度达到 O(n^3),其中 n 是树中节点的总数。
- 利用Virtual DOM树进行 分层比较、层级控制。如果出现跨层次的移动操作,会直接删除之前的,在需要的地方重新创建。
- 对于同一类组件,一方面:按照原策略继续比较 virtual DOM tree。另一方面:可以人为的用shouldComponentUpdate()判断Virtual DOM是否发生了变化,若没有变化就不需要在进行diff,这样可以节省大量时间,若变化了,就对相关节点进行update。
- 对于非同一类的组件,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。(注意是直接删除,然后创建)
- React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。没有key 的直接使用diff差异化对比,删除后创建新的。如果有key,节点位置不同,就进行位置移动。
没有key
有key
useCallbak 和 useMemo
对于这个问题,我也直接懵逼了,很尬,就回答道,了解,但是没有用过。
面试官说那好,我们跳过这个问题
useCallback
和 useMemo
都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值,useMemo
返回缓存的 变量,useCallback
返回缓存的 函数
HOC原理
这题我也不会就非常的尴尬,面试官只有跳过这个题,然后问其他的
接下来打算认真看一看HOC
useState的批量更新机制
React里面隐式的帮我们设置了类似与canMerge
。来判断是否可以合并更新,它出现在componentDidMount
前后。
同时可以手动执行合并更新:unstable_batchedUpdates
自己也是看来别人的文章一知半解,就很尴尬,技术不到位。
React父子组件通信
父子组件通信:可以采取采用props
传递数据,使用onChange
,回调事件。父组件可以可以采用 ref
,获得子组件的值。
跨级组件通信:方法一:层层组件传递props ,方法二:使用context
没有嵌套关系的组件通信:使用自定义事件的方式
AMD和CMD
1、AMD推崇依赖前置,CMD推崇就近依赖
2、执行时机不同:AMD是加载完立即执行,CMD是延迟执行(二者的最大区别)
总结
整个面试下来,对于基础的问题,我回答的还是蛮好,但是没有进一步的学习和了解。这是迈入前端的第一步,保持着热情和激情。在写这篇文章,也在边写边看相关的文章,来弥补空缺知识,只有不断的学习,才会有进步,激流勇进。