引文
本文会介绍一个二本学历普通公司小小开发如何三年迈进字节跳动大门,主要讲的是方法、思维模式、认知升级、学习、技巧,对每个人都适用,对非技术岗位的打工人也通用。
介绍
开门见山,笔者在今年3月份成功拿到了字节前端offer,然而我的经历很平凡,来自一个普普通通的二本,并且在中小公司混迹了三年,还换了3家公司,有1家是被pua每天朝8晚11上6天,有1家还是被辞退的,因为一周要做50个页面就和PM撕逼就差打起来,可以说这种经历想进大厂真的是天方夜谭。你要是刚才很赞同这种想法,我觉得可以慢慢转换下思维模式了,我强烈推荐看看这段谈话,人生的剧本90%都不是我们自己选择的,包括出身、境遇、导师等都没法自己选择,我们要做的就是努力扮演好自己的角色;如果二本学历刚毕业就想着进大厂,往往会因为目标太高步子太大而扯到,我们要搞清楚大厂需要什么样的人,自己距离这样的人还有哪些差距,然后制定目标和学习计划一点点努力,找一些成功的大厂经历文章,去模仿去脚踏实地做,比如参考本篇文章笔者进大厂的真实历程。
面试四要素
简历
一份好的简历能让你跳出学历的桎梏,大厂HR一天要筛选几百份简历,精炼简洁一定是首要,结构排序也是很重要,重要的内容往上放,有以下几个方面可以改进:
-
结构
- 介绍:尽量暴露关键信息,比如会的技术栈、带过团队、个人学习方法、项目亮点难点
- 工作经历:清晰明确的写出工作干过的事情,承担的角色、个人贡献挑些有挑战的牛逼的写上
- 项目经历:主要是介绍清楚做了什么,担任了什么角色,写出项目的难点和怎么解决的,项目亮点,怎么实现的,原理是什么
- 个人技能:从了解、熟悉、掌握/精通三个维度写,了解的建议不写,被问到反而减分,掌握的一定是会原理,有自己的认知
- 加分项介绍:社区文章、gitlab、博客,不一定全写,挑有内容有深度的写一两个就行
- 获奖作品:不是国家级的建议不写
-
语言组织
用专业名词保持一致,每个结构下的内容结构都要清晰,比如项目经历下面都可以再分成项目角色、项目概述、项目亮点/难点、个人贡献四个维度,语言要简单通顺有逻辑
-
排版样式
黑白简历和彩色简历,有照片和没照片,这些细节往往会决定面试官对你的第一印象,至少说明彩色有照片排版好的同学是比其他人更加认真、细心、重视面试
基础功底
武功学的再好,基础不扎实,真正上场打的时候就没法灵活应变(参考马老师被one punch)。街霸5不知道大家有没有玩过,一个角色的连招技能表往往有好几页,但是真正能运用好关键的技能,能打出伤害最高的组合的人,一定是对这些技能都烂熟于心,并且有了自己的思考,练就出官方都意想不到的精彩Combo。比如面试官要问你一个知识点,你如果有自己的思考,就会从知识点的基础概念、运用场景、实现原理、优点缺点、相关知识等方面来回答,这就是有自己的思考,这种人必定是面试官很欣赏的人。试想歌手如果一味模仿别人,但没有自己特有的唱法,没有作词作曲的才能,没有一套自己的体系,那就没办法在挑剔的人群中火那么多年,顶多只能火一两年(除非有脑残粉)。
举个经典面试题例子,说一下继承
- 基础普通级:会说出继承的概念,原型继承、经典继承、组合继承、寄生组合式继承、es5继承的其中一种是如何实现,和背题没啥区别
- 有自己认知级:先说原型继承如何实现,并说出这种继承的问题是引用类型的属性被所有实例共享和在创建 Child 的实例时不能向Parent传参,然后说下借用构造函数(经典继承),他解决了原型继承的两个问题,但是又有新的问题是方法都在构造函数中定义,每次创建实例都会创建一遍方法,那能解决这个问题的自然是组合继承,那组合式继承就没有缺点了吗?当然是还能优化!组合继承最大的缺点是会调用两次父构造函数,如何解决呢,把寄生组合式继承搬出来说明白吧,这样有体系的知识认知是非常重要的,不仅能加强自己对知识点的掌握,还能面试场上一骑绝尘,一听就知道你很NB!
项目
你做的项目NB,那不重要,项目因为你而NB,那才是真的NB
跟基础一样重要的就是你的项目,对于做过的项目,至少要描述出来以下几个点
- 1.项目简介
- 一段清晰的描述:让面试官了解是干嘛的,做了什么业务,用户群体是谁,你对产品有多了解
- 使用的技术栈:知道你熟练什么,好用人所长
- 2.负责角色
- 至少写一个项目中的角色:如主开发,参与开发,主要负责人,架构师
- 3.项目难点
- 知难而能改进:做一个项目下来一定会遇到难点,能够好好复盘项目的人,一定知道难点在哪,如何解决的,有没有更优的解方案,遇到的阻塞点,如何协调之类的
- 4.项目亮点
- 加分加分加分:把用到的最牛的技术,改进别人的源码,和同类型的项目的优势等全写上,不一定是自己做的,但一定要熟悉怎么做的
闪光点
开源贡献、社区文章、上线的项目、酷炫的个人网站等等都搬出来,准备好几个最NB的来讲,最好把要讲的写下来好好组织下语言多训练下,战士上战场,还得真枪实弹演习下。还有还有!!什么学生会长、组这社团活动、志愿者这些我见一个砍一个,面试官是你的男朋友吗,这么关心你的大学生活?->_-
我是如何准备的
基础知识点
脑图梳理
用脑图梳理杂乱的知识点,不仅有助于记忆,还快速定位要找的知识和跳转链接文章,用蜘蛛爬行的方式把每个知识点延展开来,这和谷歌搜索引擎算法如出一辙,你甚至能在脑子里存储这张知识点图谱,因为这潜移默化地用到了联想和关键词记忆法,通过设置优先级和完成度,来加强关键点的印象,而再试想你小时候印象最深的事情,是不是都有一个“动人”的场景,而这个场景就是你联想到的一段段印象深刻的记忆画面的拼接,同样,脑图里的知识点也能以画面的形式呈现在脑海,你甚至可以把脑子当成一个画廊宫殿,给每个宫殿一个房间来放一个知识点以及和他相关的知识图谱。
建立自己的知识点文库
把一些知识点用问答的形式整理出来,多组织语言多改良结构,改到最好的版本然后梳理和记忆,这样不仅能帮你梳理知识结构,也能模拟面试场景,自己做自己的面试官,试着自己问自己这些问题,看能否给出满意答案,我列一下我整理的知识库(注意不会的没有答案,有擅长和不擅长的很正常,重要的是把擅长的东西做到极致,如果面试问到不擅长的,可以直接说不会,不要不懂装懂,坦诚不装)
JS
1.解释一下原型链(重点)
答:实例对象有__proto__,指向了其构造函数的prototype原型,proto 将对象和原型连接起来组成了原型链
加分项:instanceof原理、原型继承、寄生组合继承
2.instanceof原理(重点)
答:循环将左侧对象的__proto__和右侧构造函数的prototype原型对比,全等就返回true,否则返回false
加分项:原型链
3.apply和call的作用及区别(重点)
答:改变函数运行时上下文并执行,参数不同,call是序列,apply是数组或类数组
加分项:bind、this、new
4.实现 add(1)(2)(3)
答:
const curry = (fn, ...args) =>
args.length >= fn.length ?
fn(...args) :
(..._args) => curry(fn, ...args, ..._args);
复制代码
加分项:
5.es5实现继承
答:
// 完美继承
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function() {
return this.name;
}
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// Child.prototype = new Parent();
// Child.prototype.constructor = Child;
function create(o) {
function F() {}
F.prototype = o;
return new F();
}
function prototype(child, parent) {
var prototype = create(parent.prototype);
prototype.constructor = child;
child.prototype = prototype;
}
prototype(Child, Parent);
var children = new Child('lilin', 25);
复制代码
加分项:原型继承、经典继承、组合继承、寄生组合式继承优缺点
6.实现一个promise
答:
const PENDDING = 'PENDDING';
const FULLFILLED = 'FULLFILLED';
const REJECTED = 'REJECTED';
function MyPromise(fn) {
const that = this;
that.state = PENDDING;
that.value = null;
that.resolvedCallbacks = [];
that.rejectedCallbacks = [];
function resolve(value) {
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
setTimeout(() => {
that.state = FULLFILLED;
that.value = value;
that.resolvedCallbacks.forEach(cb => cb(that.value));
}, 0);
}
function reject(value) {
setTimeout(() => {
that.state = REJECTED;
that.value = value;
that.rejectedCallbacks.forEach(cb => cb(that.value));
}, 0);
}
try {
fn(resolve, reject);
} catch (e) {
reject(e);
}
}
MyPromise.prototype.then = function(onFullfilled, onRejected) {
const that = this;
onFullfilled = typeof onFullfilled === 'function' ? onFullfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : v => {throw v};
if (that.state === PENDDING) {
return (promise2 = new MyPromise((resolve, reject) => {
this.resolvedCallbacks.push(() => {
try {
const x = onFullfilled(that.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
this.rejectedCallbacks.push(() => {
try {
const x = onRejected(that.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
}));
} else if (that.state === FULLFILLED) {
return (promise2 = new MyPromise((resolve, reject) => {
this.resolvedCallbacks.push(() => {
try {
const x = onFullfilled(that.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
}));
} else if (that.state === REJECTED) {
return (promise2 = new MyPromise((resolve, reject) => {
this.rejectedCallbacks.push(() => {
try {
const x = onRejected(that.value);
resolutionProcedure(promise2, x, resolve, reject);
} catch (r) {
reject(r);
}
});
}));
}
}
function resolutionProcedure(promose2, x, resolve, reject) {
if (x === promose2) {
return reject(new TypeError('Error'));
}
let called = false;
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
try {
const then = x.then;
if (typeof then === 'function') {
then.call(
x,
y => {
if (called) return;
called = true;
resolutionProcedure(promose2, y, resolve, reject);
},
e => {
if (called) return;
called = true;
reject(e);
}
)
} else {
resolve(x);
}
} catch(r) {
if (called) return;
called = true;
reject(r);
}
} else {
resolve(x);
}
}
复制代码
加分项:别报错
7.闭包的作用和原理
答:
-
作用:
- 1.读取函数内部变量
- 2.保持这些变量到内存,不随上下文销毁而释放
-
原理:
函数执行时会初始化执行上下文,此时上下文中会创建变量对象、作用域链、this,创建的变量对象会保存在作用域链中,函数执行时访问的自由变量会从这个作用域链中从顶到底部查找,故而不会受创建该函数的上下文销毁的影响
加分项:函数执行的整个过程、执行上下文、作用域和作用域链、变量对象
8.0.1+0.2为什么不等于0.3
答:IEEE754标准来表示整数和浮点数值,有4种表示方式:单精确度(32位)、双精确度(64位)、延伸单精确度、延伸双精确度。ECMAScript中用的是双精度度64位方式,其中0.1二进制表示为0011,用科学计数法表示为2^-4 * 1.10011(循环),由于精度位数有限,所以数字就被裁剪了,最终转换成十进制的数值就是0.100000000000000002,同理0.2转换成0.200000000000000002,所以0.1 + 0.2 === 0.100000000000000002 + 0.200000000000000002 === 0.300000000000000004
加分项:
8.bind的实现
答:
Function.prototype.bind2 = function(context) {
var self = this;
var args = Array.prototype.slice.call(arguments, 1);
function fBound() {
var fBoundArgs = Array.prototype.slice.call(arguments);
self.apply(this instanceof fBound ? this : context, args.concat(fBoundArgs));
}
// fBound.prototype = this.prototype; // 前者修改影响后者
function F() {}
F.prototype = this.prototype;
fBound.prototype = new F();
return fBound;
}
复制代码
加分项:
9.实现一个发布订阅模式
答:
class Subjects {
constructor() {
this.subs = [];
this.state = 0;
}
addSubs(sub) {
var isExsit = this.subs.includes(sub);
if (isExsit) {
return console.log('sub existed');
}
this.subs.push(sub);
}
removeSubs(sub) {
var index = this.subs.indexOf(sub);
if (index === -1) {
return console.log('sub not exist');
}
this.subs.splice(index, 1);
}
notify() {
console.log('sub update');
this.subs.forEach(sub => sub.update(this.state));
}
doSomeLogic() {
console.log('doSomeLogic');
this.state = Math.floor(Math.random() * 10);
this.notify();
}
}
class Observer {
constructor(name) {
this.name = name;
}
update(state) {
console.log(this.name + ' Recived state' + state);
}
}
var observerA = new Observer('A');
var observerB = new Observer('B');
var subject = new Subjects();
subject.addSubs(observerA);
subject.addSubs(observerB);
subject.doSomeLogic();
subject.doSomeLogic();
subject.removeSubs(observerB);
subject.doSomeLogic();
复制代码
加分项:
10.谈谈变量提升
答:
- 是什么:虽然变量还没有声明,但我们仍然能访问该变量声明的值,这种情况叫做变量提升。
- 为什么:是为了解决函数互相调用
- 有几种提升:变量提升和函数提升,函数提升优先于变量提升
- var、let、const区别:var声明变量会导致变量提升,let和const不会,他们声明时会创建暂时性死区(块级作用域),声明的变量不会挂到window,无法重复声明,const声明的变量无法重新赋值。
加分项:
11.new操作符具体做了什么
答:
- 1.创建对象:创建obj,proto = fn.prototype
- 2.执行函数:执行函数并保存返回值
- 3.返回函数返回值或创建的对象:执行的函数的返回值是对象则返回这个对象,否则返回创建的对象obj
加分项:
12.什么是立即执行函数
答:
是指函数表达式被立即调用,通过双括号包裹起来就能将内部的函数识别为函数表达式来调用。
双括号直接放函数声明末尾并不会直接调用,而是将括号内的代码单独运行,除非他是用var声明的函数表达式。
优点:
- 1.私有变量保存内部状态;
- 2.模块化基石:模块模式通过将参数视为模块依赖传入,通过window.module来暴露模块对象,
加分项:模块化
13.谈下事件循环机制
答:
- 是什么:JS执行代码有两种情况,执行栈中执行同步任务,任务队列放置异步任务的结果。主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。
- 1.JS引擎执行同步代码(全局代码,函数,eval),压入执行栈中执行
- 2.同步代码执行完,执行栈为空
- 3.从微队列头部取出微任务压入执行栈执行,重复这个过程,执行微任务遇到微任务则把微任务压入微队列末尾
- 4.微任务执行完毕,微队列为空,执行栈为空
- 5.从宏队列头部取出宏任务压入执行栈中执行
- 6.宏队列为空,执行栈为空
- 7.重复以上步骤
加分项:nextTick
14.说下事件模型
答:
浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。
如何绑定监听函数:
- 1.HTML on-
- 2.元素节点的事件属性:onclick
- 3.EventTarget.addEventListener
事件传播:一个事件发生后,会在子元素和父元素之间传播
- 1.捕获阶段:window向目标节点传播
- 2.目标阶段:target
- 3.冒泡阶段:目标节点向window传播
事件代理:子元素事件会冒泡到父元素,所以可以在父元素上设置监听函数统一处理多个子元素的事件,这种方法叫事件代理。
- 优点:节省内存;不用注销子元素事件
- 加分项:如何绑定监听函数,事件传播、事件代理
15.实现一个trim方法
答:
String.prototype.trim = function () {
var str = this;
str = str.replace(/^\s*/, '');
var ws = /\s/;
var i = str.length;
while(ws.test(str.charAt(--i))) {}
return str.slice(0, i + 1);
}
复制代码
加分项:
16.谈谈你对作用域的理解
答:指的是程序源码中定义变量的区域;规定了如何查找变量;Js中的作用域是静态作用域,也就是说函数定义时就生成了,分为全局作用域和函数作用域。函数执行时如果使用了自由变量,那么就会依次从函数上级作用域中查找。
加分项:作用域链、执行上下文、变量对象
17.实现节流函数
答:
function throttle(fn, wait) {
var timeout, context, args;
var previous = 0;
var later = function() {
previous = +new Date();
timeout = null;
fn.apply(context, args);
}
var throttled = function() {
var now = +new Date();
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
fn.apply(context, args);
} else if (!timeout) {
timeout = setTimeout(later, remaining);
}
}
throttled.cancel = function() {
clearTimeout(timeout);
timeout = null;
previous = 0;
}
}
复制代码
加分项:
18.实现防抖函数
答:
function debounce(fn, wait, immediate) {
var timeout, result;
var deboundced = function() {
var context = this;
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(function() {
timeout = null;
});
if (callNow) result = fn.apply(context, arguments);
} else {
timeout = setTimeout(function() {
fn.apply(context, arguments);
}, wait);
}
return result;
}
deboundced.cancle = function() {
clearTimeout(timeout);
timeout = null;
}
}
复制代码
加分项:
19.找出数组中和为sum的n个数
答:
Array.prototype.findDups = function(arr, count) {
arr.reduce((prev, num) => {
var index = prev.findIndex(o => o === num);
if (index >= 0) {
prev[index].count++;
} else {
prev.push({ count: index, num });
}
return prev;
}, []).filter(item => o.count >= count).map(o => o.num);
}
复制代码
加分项:
20.适配器和外观模式的区别
答:
- 适配器:让本不能一起工作的接口能混在一起工作
通过交接文档让实习生能接收正式工部分工作
- 外观:让一个父层类实现不同接口实现的逻辑
通过TL采用更多更好的办法处理实习生和正式工的工作
加分项:
21.数组去重
答:const filter = (a) => […new Set(a)];
加分项:多写几种
22.v8垃圾回收机制
CSS
1.position有哪些值,作用分别是什么
答:
- static:正常默认文档流,无法通过定位属性修改位置;
- relative: 正常文档流,可以通过定位属性修改位置;
- absolute:脱离文档流,独立一层默认相对于 <html>元素或其最近的定位祖先
- fixed:脱离文档流,独立一层相对于浏览器视口本身
- sticky:相对和固定混合,表现和相对一样,直到他滚动到一定某个阈值点
加分项:
2.CSS选择器有哪些(重点)
答:
- 简单选择器:元素选择器,类选择器,ID选择器,通用选择器
- 属性选择器:存在和值属性选择器(精确匹配属性),字串值属性选择器(类正则匹配属性)
- 伪类和伪元素选择器:指定元素的各种状态的某个部分
- 组合器:空格,>,+, ~来组合各种选择器,分别表示所有后代,后代第一个,兄弟第一个,所有兄弟
- 多用选择器:逗号隔开,公用一套css规则
加分项:
3.什么是BFC,BFC有什么作用,如何形成BFC
答:
- 是什么:Format Context是W3C CSS2.1中的一种规范,他是一块渲染区域,有一套渲染规则,决定其子元素如何定位,和其他元素如的关系和相互作用,BFC指块级格式化上下文,属于正常文档流。
- 作用:1.元素外边距重叠 2.包含浮动元素(清除浮动) 3.阻止元素被浮动元素覆盖
- 如何形成:1.body根元素 2.position: absolute/fixed 3.float:除了none 4.display:table-cells/flex/inline-block 5.overflow:除了visible
加分项:
4.flex布局有什么好处
答:
- 简便:只需要几个属性搭配就能实现复杂布局
- 完整:支持传统布局拥有的所有布局方案
- 响应式:flex子元素能根据父容器大小适配
- 性能好:同样的布局相比传统布局性能更好
加分项:
5.介绍下盒子模型
答:
在CSS中,所有的元素都被一个个盒子包围着,盒子包括外边距、边框、内边距、元素内容
盒子类型有两种
- 标准模型:box-sizing: content-box,标准模型的width=内容宽度 + 内边距 + border
- IE模型:box-sizing: border-box,IE模型的width=内容宽度
如何获取盒子模型宽高:style,currentStyle,ComputedStyle,getBoundingClientRect
盒子包含或并列排列时,外边距会重叠,取的最大的那个元素外边距,如何解决,设置父级或兄弟元素为BFC
BFC:块级格式化上下文,是一个块级渲染区域,有一套渲染规则规定了其元素如何定位,以及和其他元素的关系和相互作用
如何设置BFC
- position:absolute,fixed
- display:table-cell,flex,inline-block;
- overflow: 不是visible
- float:不是none
- body根元素
BFC还解决了包含浮动元素,防止浮动元素覆盖其他元素,总的来说就是让块中的内容和块外的内容互不关联
加分项:BFC是什么,解决了什么,怎么设置
6.有哪些方式可以使div居中
答:
- flex: display:flex;align-items:center;justify-content:center;
- grid:display:grid;align-items:center;justify-items:center;
- position: absolute;元素margin-left,margin-top自身宽高50%
- position: absolute;元素transform:translate自身宽高50%
- position:absolute;元素已知宽高,top,left,bottom,right:0,margin:auto;
- table-cell;display:table-cell;vertical-align: center;
加分项:
7.css优先级是怎么计算的
答:
!important 无限大 > 行内样式1000 > id选择器100 > class选择器10 > 标签选择器1 > 通用选择器 0
如果样式指向同一个元素,权重规则是根据组合器中所有选择器权重的和来计算的
- 1.权重规则生效,权重大的优先
- 2.权重规则生效,权重相同时,离目标元素最近的样式生效
样式指向不同元素,权重规则失效,离目标元素最近的样式生效
加分项:
8.双飞冀/圣杯布局
答:
- 是什么:都是实现三栏目布局的常用布局方式
- 实现:
- 双飞翼
1.父:width:100%;
2.左中右:float:left,中width:100%;正中margin 0 200px;左margin-left:100%,右margin-left:200px;- 圣杯
1.父:width:100%;
2.左中右:float:left;中width:100%,左:position:relative;margin-left:100%;left:-200px;右:position:relative;margin-left:200px;right:-200px; - 优点:1.兼容性好;2.优先加载解析中间重要的内容,优化用户体验。
- 区别:双飞翼中间多了一个div,圣杯左右多了定位属性,结构自然
加分项:
9.浮动元素会造成什么影响,如何清除浮动
答:
影响:
- 1.块级不认:块级元素认为浮动元素不存在,比如一个块级元素包含浮动元素,上下边就会贴到一起
- 2.占位:会占行内元素的位置,影响行内元素的布局
- 3.影响包含块:通过影响行内元素布局,会间接导致包含块的布局也发生改变
清除浮动
- 1.包含块自身设置浮动,缺点:影响后面节点
- 2.包含块设置成BFC
- position: absolute,fixed; 缺点:脱离文档流
- display: flex, gird,table-cell/table, inline-(block/flex/table/grid)
- overflow: 除了visible
- float: 除了none
- body根元素
- 3.伪元素上设置 display: block;clear: both,清除元素两边内容的浮动
加分项:
10.CSS样式隔离手段
答:
- 动态样式表:应用切换时,替换应用对应的style
- 工程化手段:BEM,CSS Modules, CSS in JS
- shadow dom:
- runtime css transformer: 动态给属性选择器添加随机值
加分项:
11.行内元素、块级元素有哪些,区别是什么
答:
- 行内:span、b、i、sub、sup、a、small、strong、img、input……
- 块级:div、p、table、ol、ul、h1-6、form、pre、dl……
区别
- 1.是否一行
- 行内元素只能一行显示
- 块级元素单独占满一行
- 2.是否能改变全部盒模型属性
- 行内只可以改变内外边距的left和right
- 块级可以改变所有盒模型属性
- 3.是否可改变宽高
- 行内:不可以
- 块级:可以
- 4.宽度是否受内容影响
- 行内:是
- 块级:宽度是浏览器宽度
- 5.能包含的元素类型
- 行内:只能包含行内元素、文本
- 块级:行内和块级元素都可以
加分项:
11.CSS3有哪些新特性
答:
- 过渡+动画(transition、animation、transform)
- 属性选择器、伪类/伪元素选择器
- 边框(box-shadow、border-image、border-radius)
- 背景(background-clip、background-origin、background-size)
- 反射(-webkit-box-reflect)
- 文字(word-break,word-wrap、text-overflow、-webkit-box、text-shadow)
- 颜色(rgba,hsla)
- 渐变
- Filter
- 布局(flex,grid)
- 多列布局(column-count,column-rule)
- 盒模型定义(box-sizing)
- 媒体查询
- 混合模式(background-blend-mode、mix-blend-mode)
Vue
1.vue的数据绑定机制是如何实现的
答:
通过Object.defineProperty在属性get中收集依赖和set中派发更新实现的数据响应式绑定
依赖收集
- 渲染DOM之前调用渲染函数,渲染函数中解析字符串模版时会访问data对象中的属性,此时触发属性get方法进行依赖收集,将对应key和value以及更新DOM函数的Wacher实例保存在Dep的订阅列表中。
派发更新
- 属性值发生变化时,将会通过Dep的notify方法调用所有该属性值的Wacher实例中的update方法触发对应DOM视图的更新
加分项:
2.vue next tick实现原理
答:分别判断是否能用Promise/MutationObserver/setImmediate/setTimeout,如果能用就调用对应的方法执行微任务或宏任务,参数是缓冲在同一事件循环中发生的所有数据变更的函数。微任务中遇到微任务会加到栈尾执行,因此会在下个事件循环前执行更新
- 机制:vue会把属性所有修改操作收集起来,在下个事件循环中更新
加分项:事件循环
3.vue的computed和watch的区别
答:
computed可以设置成带get/set属性的对象
监听对象不同:
- computed:依赖值改变才会改变
- watch:属性值改变会触发监听函数传入改变前和后的值
有无缓存
- computed:有,依赖值不变则使用缓存的结果
- watch:无缓存
适用场景
- computed:依赖其他值计算出另一个值并需要实时响应,记录缓存
- watch:根据观察的属性变化,来进行复杂业务逻辑
加分项:
4.说下vue的keep alive
答:
- 是什么:
是一个抽象组件,他自身不渲染DOM,不显示在父组件链中,用于保留组件状态或避免重新渲染。
执行过程:
-
挂载/卸载不同: 普通组件实例化会执行$mount来创建组件vnode后挂载真实dom,销毁时会递归调用子组件的destroy移除真实DOM,而keep-alive是调用render在首次渲染时候创建第一个子组件vnode,并将其DOM挂到vnode的elm属性,并且缓存vnode,将vnode.data.keepAlive标记为true,再次渲染时会取出缓存的vnode替换组件实例,卸载时根据内部状态keepAlive是否为true,来决定是递归调用destroy,还是递归将子组件状态标记为deactivated表示停用组件。
-
include: 要缓存的组件数组,render第一次通过include/exclude决定是否用缓存,第二次查看实例上根据vnode.key在cache中查找是否命中
-
exclude: 不缓存的组件数组
-
max: 缓存的数组最大长度
就主要的几个阶段进行分析
- createComponent:生成DOM,保存vnode.elm
- render
- 拿vnode:拿第一个子组件vnode
- 判断缓存:include/exclude判断是否缓存;ref.key判断和存取缓存
- max优化:$ref.keys保存最新缓存的key到最后端,超过max时删除最前端的key的子组件
- vnode.data.keepAlive:重新createComponent DOM时启用之前停用的组件,要销毁组件前做判断决定是销毁还是停用
加分项:缓存vnode、DOM,标记状态,LRU
React
注意:会哪个技术栈就学哪个,不要都学了反而不精通,笔者当时不会React所以没学
1.react setState是同步还是异步
2.react为什么需要合成事件,和原生事件的区别
3.为什么有时react两次setState,只执行一次
4.redux有哪些原则
5.react如何处理异常
6.react为什么需要fiber,react fiber有哪些优点,怎样做到的
7.redux中间件机制
8.react有哪些性能优化的点
TCP/IP
1.https加密过程是怎样的
答:通过TLS协议利用非对称加解密得到的密钥进行的对称加密通信:
- 1.A生成随机数1 和协议、加密方式->B
- 2.B生成随机数2,返回CA证书
- 3.A验证证书生效,生成随机数3并利用证书公钥加密->B 证书私钥解密得到随机数3
- 4.A B 利用3各随机数用第一步A传的加密方式生成一个密钥,通过该密钥来进行对称加密通信
加分项:防止中间人攻击
2.3次握手过程
答:
- 第一次握手:浏览器向服务端发送连接请求SYN,发送后浏览器状态变为SYN_SENT,一定时间内等待ACK;
- 第二次握手:服务端收到SYN确认状态变为SYN_RECIVED;向浏览器发送SYN+ACK,状态变为LAST-ACK;一定时间内等待ACK;
- 第三次握手:浏览器收到ACK后,向服务端发送对应ACK,状态变为ESTABLISHED,服务端确认ACK后,状态也变为ESTABLISHED,此时建立链接完成
加分项:为什么要3次,两次不行吗
3.http2.0做了哪些改进,http2.0有哪些不足,http3.0是什么
答:
http2.0改进
- 1.二进制传输:帧、流
- 2.头部压缩:HPack压缩字符串
- 3.多路复用:把报文转换成流,传输时通过帧来传输,每个帧标识属于某个流,一个连接中可以传输多个流,对应多 条请求,减少连接开销,保持高速传输
- 4.服务端Push:收到请求会响应其余的资源
- 5.缺点:一个包失败会导致全部包重新请求
http2.0存在问题有
- 1./TCP层存在分组队首阻塞问题
- 2.单个包丢失导致整个连接阻塞
- 3.移动互联网领域表现不佳(弱网环境)
http3.0是谷歌基于基于UDP协议的QUIC协议来实现的传输层协议,相比于http2.0有以下几个优点
- 更完善的多路复用:传输隔离,UDP支持多个流传输,多个流之间互不影响
- 0RTT 建链:0RTT指数据包一来一回的往返时间,0RTT建立传输层/加密层 连接/加密连接
- 纠错机制(前向纠错):通过校验包和其余包计算丢失的一个包内容,多个包丢失不适用
- 更好的拥塞控制:默认TCP的慢启动,拥塞避免,快速重传,快速恢复拥塞控制算法,优化了几个性能点
- 连接迁移:64位的随机数作为连接的ID,WIFI -> 4G ID相同连接保持
加分项:HTTP3 如何解决HTTP2缺点,0RTT不是真的0
4.tcp滑动窗口是什么
答:是TCP协议用于网络流控的技术
接收端告诉发送端还能接收多少数据,发送端根据这个数据来控制传输的数据大小不超过这个范围
优点:合理控制网络流量,保证传输数据有序和且可靠
5.tcp重试机制
答:
超时重传机制:包丢失超时则死等包重传
- 1.重传漏的一个包
- 2.重传所有包
- 缺点:第一种重传时间慢,第二种浪费带宽,两种timeout等待时间长
快速重传机制:包丢失则发送3个ACK给发送端后开始重传
- 1.优点:解决了超时重传等timeout问题
- 2.缺点:没解决多个包丢失重传问题
SACK 方法:TCP头里加一个SACK,和ACK一起返回,带上数据碎板
- 优点:解决了确认重传哪些包
- 缺点:接收方Reneging,接收方有权把已经报给发送端SACK里的数据给丢了(内存不够),发送方还是要依赖 ACK,并维护Time-Out
Duplicate SACK:主要使用了SACK来告诉发送方有哪些数据被重复接收了
- 优点:让发送端确认哪些数据因为ACK丢失还是网络延迟等因素被重传了
工程化
1.谈下webpack loader机制
2.uglify原理的是什么
答:
-
- 将code转换成AST
-
- 将AST进行优化,生成一个更小的AST(名称变字母,分号变逗号)
-
- 将新生成的AST再转化成code
3.是否有写过webpack插件
4.webpack工作流程是怎样的
5.tree shaking是什么,有什么作用,原理是什么
答:
- 是什么:通过工具“摇”我们的”JS”文件,将其中用不到的代码摇掉,是一个性能优化的范畴
- 作用:去除打包后没有用到的函数和变量等代码
- 原理:基于ES6 提供的静态代码依赖分析,得到没有依赖的代码进行删除
6.babel是什么,怎么做到的(重点)
性能优化
1.重排和重绘是什么,有什么区别
答:
- 重排:DOM的改变影响了元素的几何属性(布局+尺寸)改变,浏览器需要重新计算几何属性并安放在对应位置的过程;
- 重绘:元素外观改变,没有改变位置,把元素外观重新绘制出来的过程;重排会导致重绘,重绘不会导致重排;
什么时候触发重排:
- 1.页面初次渲染
- 2.改变元素几何属性,大小、字体大小、位置、内外边距
- 3.增删DOM
- 4.获取某些属性或调用计算方法:offsetHeight,getComputedStyle
- 5.激活伪类
- 6.设置style
如何减少重排:
- 1.样式集中处理:cssText
- 2.属性读写分离:
- 3.离线操作
- 4.脱离文档流
- 5.优化动画
2.如何减少白屏的时间
答:
- DNS解析:DNS缓存,DNS预加载,DNS可靠服务器,CDN,域名切分,HTTP2
- TCP建立连接传输:预连接,TCP网络链路优化(花钱)
- 服务端处理请求:Redis缓存、数据库存储优化、各种中间件、Gzip压缩
解析下载HTML,JS,CSS等资源:
- HTML:1.精简HTML代码和结构 2.资源顺序css头,js尾 3.js用defer/async,监控/埋点js async 4.压缩 HTML,Gzip
- JS:1.减少请求:code split,按需,合并js 2.减少包体积:Uglify/Gzip 压缩;Tree Shaking;合理 ponyfill;3.优化解析与执行:去掉不必要代码;减少long task;函数中少嵌套函数;减少DOM操作;4.缓存:文件名.hash+协商缓存,库文件单独打包
- CSS:1.减少请求:合并CSS,按需 2.减少包体积:Uglify,Gzip,ponyfill 3.加快解析与渲染CSSOM:扁平 CSS选择器结构,用先进布局flex,慎用@import,复用样式,继承样式,昂贵属性,不用通配符属性匹配,不用计算 属性,合理运用GPU加速动画, 4.缓存
- 图片:1.减少请求:雪碧图,懒加载,base64 2.优化图片大小:选择合适格式图片,适当降低图片质量压缩图 片,不同分辨率加载不同大小图片,删除图片冗余信息,SVG压缩
- 字体:内嵌字体,FOUT,FOFT
- 视频:vedio代替gif,合适格式,压缩
3.你知道的前端性能优化手段有哪些(重点)
答:
缓存
- 1.本地存储缓存
- 2.memery 缓存
- 3.http缓存:强缓存,协商缓存
- 4.cache API缓存
- 5.push cache缓存
http请求
- 1.DNS预解析
- 2.预先建立连接
- 3.CDN
- 4.HTTP2,域名切分
下载与解析页面
- 1.文档资源顺序:css放head,js放body尾
- 2.js defer/async
- 3.压缩HTML:gzip
页面静态资源
- 1.JS
- 2.减少不必要请求:code split,按需加载,合并js
- 3.减少包体积:Uglify,Gzip,Three Shaking,合理Ponyfill
- 4.加快Js解析执行:删除不必要代码,避免longTask,摇树优化减少函数嵌套减少Class,
- 5.缓存:文件名加上文件内容hash,并设置协商缓存;单独打包基础库
- 2.CSS
- 1.优化资源请求:按需加载;合并文件;慎用@import;
- 2.减少包体积大小:uglify,gzip;ponyfill
- 3.加快解析与渲染树构建:简化选择器;避免高昂属性;先进布局,复用样式,继承样式,不用通配符属性选择器,不用计算属性,动画开启GPU硬件加速
- 4.缓存:WebpackMiniCssExtractPlugin
- 3.图片
- 1.优化请求:雪碧图;懒加载;base64
- 2.减小图片大小:使用合适的图片格式;适当降低图片质量;选择合适的大小/分辨率;删除图片冗余信息;SVG压缩
- 3.缓存
运行时
- 长列表优化:Virtual Scroll
4.有听过前端性能优化指标RAIL吗
答:
RAIL是一个以用户为中心的性能模型,它把用户的体验拆分成几个关键点(例如,tap,scroll,load),并且帮你定义好了每一个的性能指标
- Response: 用户的输入到响应的时间不超过100ms,给用户的感受是瞬间就完成了
- Animation: 产生每一帧的时间不要超过10ms
- Idel: 最大化空闲时间,以增大50ms内响应用户输入的几率
- Load: 优化首次和二次加载速度
5.网站首页有大量的图片,加载很慢,如何去优化呢?
答:
减少请求数
- 1.雪碧图
- 2.懒加载:优先加载视口内图片,然后按需加载其余图片
- 3.base64
- 4.域名切分
- 5.HTTP 2.0
大小优化
- 1.压缩质量:有损压缩,减少图片像素色道值;无损压缩,算法计算相邻像素差异,不损失像素色道值;
- 2.不同分辨率用不用的图片:根据视口分辨率不同来加载不同分辨率图片
- 3.选择合适的图片格式:不同的图片格式压缩比率不同,选择需要的图片格式,比如jpeg和png不支持动画,jpeg 不透明体积小,webp体积小,透明,可动画,浏览器可用;
- 4.删除图片冗余信息;
- 5.SVG压缩;
缓存
浏览器
1.说下你对DOM树的理解
答:DOM是表述HTML的内部数据结构,将HTML和javascript连接起来,并且过滤了一些不安全的内容,DOM在浏览器中解析过程是通过HTML解析器的分词器将原始数据转换成Token,然后HTML 解析器将Token从头部开始依次压入栈顶,压入时会创建对应的DOM,解析出文本Token时,会直接在DOM中添加,解析到结束Token会弹出Token栈表示解析完成。
2.浏览器缓存策略是怎样的(重点)
答:
浏览器缓存机制是通过HTTP报文的缓存标识来进行的,进行的过程则需要通过浏览器的缓存策略来推动,分为强缓存和协商缓存,强缓存指的是向浏览器缓存请求的结果,根据其缓存规则决定是否使用该缓存的过程;协商缓存指的是强缓存失效后携带结果缓存标识发送给服务端,由服务端决定是否使用缓存的过程;具体获取缓存过程是:浏览器先通过强缓存向浏览器缓存区请求资源,此时会有3种情况,1是没有找到缓存结果和其标识,会重新发送http请求,服务端会返回缓存结果和标识并存入浏览器缓存。2是找到了缓存结果和其标识,但结果失效,会进入协商缓存,3是找到了缓存结果和其标识并有效,则直接使用缓存;强缓存标识有Cache-Control和Expires,前者优先级高,并能解决Expires因为时区不同导致的缓存无效问题,当出现第二种情况时,进入协商缓存,此时浏览器携带缓存标识发起http请求,服务端根据缓存标识If-Match-None/If-Modified-Since来和文件的Etag/Last-Modified对比,如果改变则返回200和资源还有缓存标识,没有改变则返回304,直接用浏览器缓存。强缓存和协商缓存的区别是:强缓存优先于协商缓存,并且是根据时间来进行判断缓存是否失效;协商缓存是通过文件是否修改来判断是否用缓存;使用场景是:频繁变动的资源用协商缓存,不常变的资源用强缓存;
加分项:缓存机制
3.渲染合成层是什么
答:
渲染层
- 是什么:DOM树每个节点对应着一个LayoutObject,当他们的LayoutObject处于相同坐标空间时,就会形成RenderLayers渲染层。它负责元素按顺序渲染,保证了透明和重叠元素的正确显示。
合成层
- 是什么:特殊的渲染层(transform3d,translateZ,vedio,canvas,iframe,position:fixed;will-change;),拥有单独的图形层。
- 优点:独立图形层进行GPU加速
- 缺点:某些情况导致层爆炸、无法层压缩
4.描述下浏览器从输入网址到页面展现的整个过程
答:
-
0.输入URL -> CPU并处理 -> 操作系统内核 -> GUI -> 浏览器 -> 浏览器内核
-
1.请求前
- URL 解析/DNS 查询
-
2.应用层发送HTTP请求
-
3 传输层TCP传输报文
- 3.1 浏览器查找oi缓存
-
- 网络层IP协议查询MAC地址
-
- 数据到达数据链路层
-
- 服务端处理请求后返回响应,遇到静态资源立即返回
-
7.HTML解析器解析渲染
字节流 – 字符串 -Token – NODE – DOM(CSSOM) -Render tree-渲染引擎计算每个节点精确位置和尺寸,放在对应位置,然后渲染颜色和图片,DOM中遇到js会等js执行完再接着渲染,js执行过程操作css,cssom还没渲染完也会阻塞js进而阻塞DOM。
渲染层合成层
5.history和hash两种路由方式的最大区别是什么?
答:hash会在浏览器地址后面增加#号,而history可以自定义地址
Node
1.node模块机制是怎样的
2.node require具体实现是什么
3.node事件循环与浏览器的哪些不一样
4.cluster原理是怎样的
5.pipe原理是怎样的
6.node的异常处理方式
算法
1.判断链表是否有环
2.求解平方根
3.实现一个斐波那契数列
4.合并二维有序数组成一维有序数组
5.层次遍历二叉树
6.找出数组中和为sum的n个数
7.判断括号字符串是否有效
代码手写
列了常见的手写代码,目的都不是看你会不会写,而是看你怎么写的NB,比如节流和防抖,一般人就直接写一个闭包完事,但是真正NB的,是写出下面这种神仙代码(能秒懂的也是仙人)
function throttle(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
previous = options.leading === false ? 0 : new Date().getTime();
timeout = null;
func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = new Date().getTime();
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
};
return throttled;
}
复制代码
- 手写符合PromiseA+规范的代码
- 手写观察者模式代码
- 手写vue核心流程源码:实现响应式
- 手写常考js代码:new、call、bind、apply、继承、柯里化、闭包、节流、防抖……
- 手写常考css代码:所有div居中的实现、双飞翼/圣杯布局的所有实现、清除浮动的所有实现……
项目梳理
也是利用脑图梳理项目,从简介、职责、功能、痛点、亮点五个维度梳理,面试前一定要把这些烂熟于心,不一定要整理很多项目,挑一个最大的最NB的出来,好好给整理好了,倒背如流了,相信面试官不管问到哪都能巧舌如簧,就跟知道自己老婆所有缺点一样()
简历
前面已经说了很多了,这里强烈推荐一波ssh_晨曦时梦见兮的《? 如何写「前端简历」,能敲开字节跳动的大门?》
找早早聊评估简历
还不知道前端早早聊?也许这就是你一直没有进大厂的原因吧,还不快去关注加微信!
面试技巧
- 开头介绍
- 结构要明确:比如我是介绍个人情况+工作经历+掌握的技术+最近做过的项目+个人学习方法\体系+最近在看的书
- 事先练习:玩游戏的时候通常是看别人操作,脑子会了脚也会了就是手不会,光说不练假把式,别人是天天玩,你连手都没动一下还相会?提前写下来,合理组织下语言通畅,尽量简短清晰,别说简历里已经有的,多暴露你的长处
- 时间战术:结构为什么这么长,不就是为了让你简介的时候多说点,留给面试官问你题的时间少一点增加容错率吗?
- 甩知识体系
- 问你一个知识点,你把知识点的概念、场景、原理、优缺点说完后还不够,再接着扯知识点相关的知识,全部拉出来欧啦一遍,这样的好处就是,又能说一大堆拖延时间减少后面的问题,还能间接反应你对其他关联知识点的掌握程度,也在面试官心里默默加分了。我当时一个性能优化硬是说了将近10来分钟,只要不被打断,就是你的领域展开<_<
总结
以上便是本人进大厂前半年做的准备,总结下来是以下几个要点:
- 养成好习惯:提前准备,夯实自己,稳扎稳打,掌握方法,有体系有自律,随时回顾
- 掌握面试核心四要素:简历、基础、项目、闪光点
- 拥有自己的一套知识体系,整理成自己最清晰认知的语言,不断修正理解,不断写代码加强认知
- 制定学习计划和目标:一个月内学完多少知识点,几个月后要投递简历到大厂,简历什么时候找人评估,内推资源找谁要(SuperLLL8523),对症下药,查阅对应大厂高频面试考点并针对性复习
- 掌握自己的一套面试技巧
- 好好梳理你的项目,这是你跟面试官battle的重要武器
最后打个广告
好消息!字节跳动又又又开始招人了!!