# 字节跳动面试题
如果需要了解了解算法以及没有很大把握的同学,建议把面试时间约在一周或者两周后,期间好好准备
另外面试时间可以调整,至少提前一天修改时间
面试可以现场可以视频,如果时间不方便可以约视频面试
面试共计四轮: 第一轮笔试或直接面试,重基础,细节 第二轮面试技术问题更深入一些 第三轮着重项目经验和技术广都和深度 第四轮 HR 面试,看稳定性,意向性,性价比,了解薪资 最后是 offer 沟通,定级、出薪资方案、入职时间等等 技术面试要知其然并知其所以然,每一轮面试或多或少都有算法,请做一些准备
前端题库:仅做参考,不同的面试官有不同的面试风格和提问方式、内容 笔试:
# 1、换行字符串格式化
# 2、屏幕占满和未占满的情况下,使 footer 固定在底部,尽量多种方法。
# 考虑兼容性
<style>
.container {
box-sizing: border-box;
min-height: 100vh;
padding-bottom: 100px;
}
header,
footer {
height: 100px;
}
footer {
margin-top: -100px;
}
</style>
<div class="container">
<header></header>
<main></main>
</div>
<footer></footer>
# flex 布局
<style>
.container {
display: flex;
height: 100%;
flex-direction: column;
}
header,
footer {
min-height: 100px;
}
main {
flex: 1;
}
</style>
<div class="container">
<header></header>
<main>
<div>...</div>
</main>
<footer></footer>
</div>
# 3、日期转化为 2 小时前,1 分钟前等
const minute = 1000 * 60;
const hour = minute * 60;
const day = hour * 24;
const week = day * 7;
const month = day * 30;
function getTimer(stringTime) {
const time1 = new Date().getTime(); //当前的时间戳
console.log(time1);
const time2 = Date.parse(new Date(stringTime)); //指定时间的时间戳
console.log(time2);
const time = time1 - time2;
let result = null;
if (time < 0) {
alert('设置的时间不能早于当前时间!');
} else if (time / month >= 1) {
result = '发布于' + parseInt(time / month) + '月前!';
} else if (time / week >= 1) {
result = '发布于' + parseInt(time / week) + '周前!';
} else if (time / day >= 1) {
result = '发布于' + parseInt(time / day) + '天前!';
} else if (time / hour >= 1) {
result = '发布于' + parseInt(time / hour) + '小时前!';
} else if (time / minute >= 1) {
result = '发布于' + parseInt(time / minute) + '分钟前!';
} else {
result = '刚刚发布!';
}
console.log(result);
}
getTimer('2020-05-23 10:11:12'); // 发布于1小时前!
# 4、多个 bind 连接后输出的值
bind 的多次绑定只有第一次是有效的, 在 Javascript 中,多次 bind() 是无效的。更深层次的原因, bind() 的实现,相当于使用函数在内部包了一个 call / apply ,第二次 bind() 相当于再包住第一次 bind() ,故第二次以后的 bind 是无法生效的
# 5、原码,补码,反码
# 1. 原码
原码就是符号位加上真值的绝对值,即用第一位表示符号,其余位表示值。比如:如果是 8 位二进制:
- [+1]原 = 0000 0001
- [-1]原 = 1000 0001
原码是人脑最容易理解和计算的表示方式。
# 2. 反码
反码的表示方法是: 正数的反码是其本身; 负数的反码是在其原码的基础上,符号位不变,其余各个位取反。
- [+1] = [0000 0001]原 = [0000 0001]反
- [-1] = [1000 0001]原 = [1111 1110]反
可见如果一个反码表示的是负数,人脑无法直观的看出来它的数值。通常要将其转换成原码再计算。
# 3. 补码
补码的表示方法是: 正数的补码就是其本身; 负数的补码是在其原码的基础上,符号位不变,其余各位取反,最后+1。(也即在反码的基础上+1)
- [+1] = [0000 0001]原 = [0000 0001]反 = [0000 0001]补
- [-1] = [1000 0001]原 = [1111 1110]反 = [1111 1111]补
对于负数,补码表示方式也是人脑无法直观看出其数值的。通常也需要转换成原码再计算其数值。
# 6、事件委托
就是事件代理
# 7、输入一个日期 返回几秒前 几天前或者几月前;
const seconds = 1000;
const minute = seconds * 60;
const hour = minute * 60;
const day = hour * 24;
const week = day * 7;
const month = day * 30;
function getTimer(stringTime) {
const time1 = new Date().getTime(); //当前的时间戳
console.log(time1);
const time2 = Date.parse(new Date(stringTime)); //指定时间的时间戳
console.log(time2);
const time = time1 - time2;
let result = null;
if (time < 0) {
alert('设置的时间不能早于当前时间!');
} else if (time / month >= 1) {
result = parseInt(time / month) + '月前!';
} else if (time / week >= 1) {
result = parseInt(time / week) + '周前!';
} else if (time / day >= 1) {
result = parseInt(time / day) + '天前!';
} else if (time / hour >= 1) {
result = parseInt(time / hour) + '小时前!';
} else if (time / minute > 1) {
result = parseInt(time / minute) + '分钟前!';
} else if (time / seconds >= 1 && time / seconds <= 60) {
result = parseInt(time / seconds) + '秒前!';
} else {
result = '刚刚发布!';
}
console.log(result);
}
getTimer('2020-05-23 11:27:12'); // 24秒前!
# 8、153812.7 转化 153,812.7
字符串原生方法
function thousandsSplit(num = 0) {
if (num !== null) {
return num.toLocaleString();
} else {
return '-';
}
}
console.log(thousandsSplit(153812.7)); // 153,812.7
正则
function micrometerLevel(value) {
if (typeof value === 'undefined' || value === null || isNaN(value)) {
return value;
}
const stringValue = value.toString();
const [integer, decimal] = stringValue.split('.');
if (integer.length <= 3) {
return stringValue;
}
let total = '';
for (let i = integer.length - 1, j = 1; i > -1; i--, j++) {
const num = j % 3 === 0 ? `,${integer[i]}` : integer[i];
total = num + total;
}
total = total.replace(/^,/, '') + (decimal ? `.${decimal}` : '');
return total;
}
console.log(micrometerLevel(153812.7));
# 11、实现一个 Promise.all
// Promise.all 表示全部成功才成功 有任意一个失败 都会失败
Promise.all = function(promises) {
return new Promise((resolve, reject) => {
let arr = [],
currentIndex = 0;
function processData(index, value) {
arr[index] = value;
currentIndex++;
if (currentIndex === promises.length) {
resolve(arr);
}
}
for (let i in promises) {
promises[i].then(data => {
processData(i, data);
}, reject);
}
});
};
# 12.手写代码
给定一个数组,形如 [1, 1, 2 , 3, 3, 3, 3, 4, 6, 6],给定一个数 n,例如 3,找出给定的数 n 在数组内出现的次数,要求时间复杂度小于 O(n)
const countOccurrences = (arr, value) => arr.reduce((a, v) => (v === value ? a + 1 : a + 0), 0);
console.log(countOccurrences([1, 1, 2, 3, 3, 3, 3, 4, 6, 6], 3)); // 4
# 一轮
# 1.dom react 原理
# 2.css 布局
实际上我们在讨论布局的时候,会把网页上特定的区域进行分列操作。按照分列数目,可以大致分为 3 类,即单列布局、2 列布局、3 列布局。
# 3.js 原型链继承
每个函数都有 prototype 属性,除了 Function.prototype.bind(),该属性指向原型。
每个对象都有 __proto__
属性,指向了创建该对象的构造函数的原型。其实这个属性指向了 [[prototype]],但是 [[prototype]] 是内部属性,我们并不能访问到,所以使用 __proto__
来访问。
对象可以通过 __proto__
来寻找不属于该对象的属性,__proto__
将对象连接起来组成了原型链。
如果你想更进一步的了解原型,可以仔细阅读 深度解析原型中的各个难点。
总结
Object
是所有对象的爸爸,所有对象都可以通过__proto__
找到它Function
是所有函数的爸爸,所有函数都可以通过__proto__
找到它Function.prototype
和Object.prototype
是两个特殊的对象,他们由引擎来创建- 除了以上两个特殊对象,其他对象都是通过构造器
new
出来的 - 函数的
prototype
是一个对象,也就是原型 - 对象的
__proto__
指向原型,__proto__
将对象和原型连接起来组成了原型链
# 4.fetch 取消
JavaScript 的 promise 一直是该语言的一大胜利——它们引发了异步编程的革命,极大地改善了 Web 性能。原生 promise 的一个缺点是,到目前为止,还没有可以取消 fetch 的真正方法。 JavaScript 规范中添加了新的 AbortController,允许开发人员使用信号中止一个或多个 fetch 调用。
以下是取消 fetch 调用的工作流程:
- 创建一个 AbortController 实例
- 该实例具有 signal 属性
- 将 signal 传递给 fetch option 的 signal
- 调用 AbortController 的 abort 属性来取消所有使用该信号的 fetch。
实现代码
const controller = new AbortController();
const { signal } = controller;
fetch('http://localhost:8000', { signal })
.then(response => {
console.log(`Request 1 is complete!`);
})
.catch(e => {
if (e.name === 'AbortError') {
// We know it's been canceled!
}
});
// Abort request
controller.abort();
# 5.eventloop
# 6.instanceof
instanceof
可以正确的判断对象的类型,因为内部机制是通过判断对象的原型链中是不是能找到类型的 prototype。
我们也可以试着实现一下 instanceof
function instanceof(left, right) {
// 获得类型的原型
let prototype = right.prototype;
// 获得对象的原型
left = left.__proto__;
// 判断对象的类型是否等于类型的原型
while (true) {
if (left === null) return false;
if (prototype === left) return true;
left = left.__proto__;
}
}
# 7.promise 封装 setstate
很多时候业务需要,在 setState 后再进行操作,比如改变下拉框的 state 值需要刷新 table 列表。这时候不可避免的需要使用 setState 中自带的 callback 方式。callback 方式缺点是不好定位错误,很容易就进入了回调地狱。 所以想到了可以将 setState 进行 promise 化,用.then 的方式取代回调。
/**
* 将setState方法promise化,用promise的方式去取代回调。
* @param state
* @returns {Promise}
*/
const setStateP = function(state) {
return new Promise(resolve => this.setState(state, resolve));
};
# 8.redux 基本组成和设计单向数据流
# 9.https 协议的过程
# 10.https 获取加密密钥的过程
HTTPS 简介 同上
# 11.http 的方法有哪几种
# 12.类式继承的方案
# 13.prototype 继承的实现
# 14.数字千分位处理,正则和非正则都要实现
同上 - 153812.7 转化 153,812.7
# 15.借用构造继承,几种组合继承方式
# 16.看编程代码说出运行结果
Process.nextTick,setImmediate 和 promise.then 的优先级
- 解读 setTimeout, promise.then, process.nextTick, setImmediate 的执行顺序
- promise.then,process.nextTick, setTimeout 以及 setImmediate 的执行顺序
# 17.实现一个 bind 函数
bind
和call、apply
两个方法作用也是一致的,只是该方法会返回一个函数。并且我们可以通过 bind
实现柯里化。
同样的,也来模拟实现下 bind
Function.prototype.myBind = function(context) {
if (typeof this !== 'function') {
throw new TypeError('Error');
}
var _this = this;
var args = [...arguments].slice(1);
// 返回一个函数
return function F() {
// 因为返回了一个函数,我们可以 new F(),所以需要判断
if (this instanceof F) {
return new _this(...args, ...arguments);
}
return _this.apply(context, args.concat(...arguments));
};
};
# 18.千位加逗号
同上 - 153812.7 转化 153,812.7
# 19.三个继承方式的优缺点
看上面的继承
# 20.nodejs 的事件循环
# 21.bfc
# 22.css 实现正方形 div 水平垂直居中
实现居中的方案有很多,这里介绍下纯 CSS 使用 absolute 配合 margin 的方案。
# 1. div 宽高固定
.box {
width: 400px;
height: 200px;
position: absolute;
top: 50%;
left: 50%;
margin-top: -100px;
margin-left: -200px;
}
margin-top 为-(height / 2)
,margin-left 为-(width / 2)
。当然也可以不用 margin
,即 top 为 calc(100% - height) / 2
,left 为 calc(100% - width) / 2
,但是建议可以不用 calc()
就不要用。
就像这样
.box {
width: 400px;
height: 200px;
position: absolute;
top: calc((100% - 100px) / 2);
left: calc((100% - 400px) / 2);
}
也可以不用 margin
用 translate()
,如下:
.box {
width: 50%;
height: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translateX(-50%) translateY(-50%);
}
# 2. div 宽高不固定
五马分尸式
.box {
width: 50%;
height: 50%;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
注意,这适用于宽高需指定的情况,比如使用百分比或者用 js 动态修改,不然可能被当成 100%处理。
# 3. CSS3 不定宽高水平垂直居中
.box {
justify-content: center; /* 子元素水平居中 */
align-items: center; /* 子元素垂直居中 */
display: -webkit-flex;
}
在父级元素上添加,即可实现子元素水平垂直居中。
# 23.koa1 的原理,继承
# 24.最后是一个写代码, 处理有依赖的异步任务 加重试
# 25.自己实现 bind 函数
# 26.什么是闭包
闭包是指有权访问另外一个函数作用域中的变量的函数
关键在于下面两点:
- 是一个函数
- 能访问另外一个函数作用域中的变量
# 27.最长子序列
/**
* @param {number[]} nums
* @return {number}
*/
var lengthOfLIS = function(nums) {
let n = nums.length;
if (n == 0) {
return 0;
}
let dp = new Array(n).fill(1);
let max = 0;
for (let i = 0; i < n; i++) {
for (let j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
max = Math.max(max, dp[i]);
}
return max;
};
# 28.二叉树的前序遍历
遍历顺序是:左 -> 中 -> 右
方法一:简单粗暴的递归方法
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root) {
return root ? [root.val, ...preorderTraversal(root.left), ...preorderTraversal(root.right)] : [];
};
方法二:老老实实用迭代法
/**
*
* @param {TreeNode} root
* @return {number[]}
*/
var preorderTraversal = function(root) {
let res = [],
arr = [];
root && arr.push(root);
while (arr && arr.length) {
const current = arr.pop();
res.push(current.val);
// 栈结构是先进后出,因此先将右节点压栈、再将左节点压栈(右 -> 左),最后弹栈的顺序是左->右
current.right !== null && arr.push(current.right);
current.left !== null && arr.push(current.left);
}
return res;
};
# 29.二叉树的中序遍历
遍历顺序是:中 -> 左 -> 右
方法一:递归大法好
/**
* @param {TreeNode} root
* @return {number[]}
*/
const inorderTraversal = root => {
const res = [];
const pushValue = node => {
if (node != null) {
node.left && pushValue(node.left);
res.push(node.val);
node.right && pushValue(node.right);
}
};
pushValue(root);
return res;
};
方法二:老老实实用迭代法一
/**
* @param {TreeNode} root
* @return {number[]}
*/
const inorderTraversal = root => {
let res = [];
if (root == null) return res;
const stack = [];
let curr = root;
while (curr != null || stack.length > 0) {
if (curr) {
stack.push(curr);
curr = curr.left;
} else {
const node = stack.pop();
res.push(node.val);
curr = curr.right;
}
}
return res;
};
方法二:老老实实用迭代法二
/**
* @param {TreeNode} root
* @return {number[]}
*/
// 如果 left 节点存在, 就入栈, 然后跳 left
// 如果 left 和 right 都不存在, 则保存当前节点, 然后出栈, 并让 left 等于 null
// 如果 right 节点存在, 并且 left 为 null, 则保存当前节点, 然后跳 right
const inorderTraversal = root => {
// 用来保存节点
let res = [],
// 存放根节点
stack = [];
while (root || stack.length) {
if (root.left) {
// 如果 left 节点存在, 就入栈
stack.push(root);
// 跳到 left
root = root.left;
} else if (!root.left && !root.right) {
// 如果 left 和 right 都不存在, 则保存当前节点
res.push(root.val);
// 出栈
root = stack.pop();
// 让 left 等于 null
root && (root.left = null);
} else if (root.right) {
// 如果 right 节点存在, 并且 left 为 null, 则保存当前节点
res.push(root.val);
// 跳 right
root = root.right;
}
}
return res;
};
# 28.二叉树的后序遍历
遍历顺序是:左 -> 右 -> 中
方法一:递归大法好
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/
var postorderTraversal = function(root) {
let res = [];
const pushNode = node => {
if (node != null) {
// 利用栈的特点:先进后出
node.left != null && pushNode(node.left);
node.right != null && pushNode(node.right);
// 一个个压栈
res.push(node.val);
}
};
pushNode(root);
return res;
};
方法二:使用迭代
/**
* @param {TreeNode} root
* @return {number[]}
*/
var postorderTraversal = function(root) {
let res = [],
stack = [];
while (root || stack.length) {
// 每次都把值插入到最前面
res.unshift(root.val);
// 先入左节点,再入右节点(利用栈的特点:先进后出)
if (root.left) stack.push(root.left);
if (root.right) stack.push(root.right);
root = stack.pop();
}
return res;
};
# http 握手原理
卧槽!牛皮了,头一次见有大佬把 TCP 三次握手四次挥手解释的这么明白
# 29.react 新版本的特性
# 35.上中下三栏布局
flex
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<style>
.container {
display: flex;
height: 100vh;
flex-direction: column;
}
header,
footer {
min-height: 100px;
background-color: aqua;
}
main {
flex: 1;
}
</style>
<div class="container">
<header></header>
<main>
<div></div>
</main>
<footer></footer>
</div>
</body>
</html>
考虑兼容性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<style>
.container {
box-sizing: border-box;
min-height: 100vh;
padding-bottom: 100px;
}
header,
footer {
background-color: aqua;
height: 100px;
}
footer {
margin-top: -100px;
}
</style>
<div class="container">
<header></header>
<main></main>
</div>
<footer></footer>
</body>
</html>
# 37.实现一个子类实例可以继承父类的所有方法
# 38.实现 sum(1)(2)(3).valueOf(),实现这么一个 sum 函数,返回 6
function sum() {
// 类数组转换成真正的数组,也可以用 [...arguments] or Array.from(arguments)
// 接收第一次传入的参数 -> 1
const args = [].slice.call(arguments);
const fn = function() {
// 接收后面调用传入的参数, [1] -> [1,2] -> [1,2,3]多个参数不断收集到数组中
const args2 = [].slice.call(arguments);
return sum.apply(null, args.concat(args2));
};
fn.valueOf = function() {
return args.reduce((a, b) => a + b);
};
return fn;
}
console.log(sum(1)(2)(3).valueOf()); // 6
# 串行异步任务
taskSum(1000,()=>{console.log(1)}).task(1200,()=>{console.log(2)}).task(1300,()=>{console.log(3)}),这里等待 1s,打印 1,之后等待 1.2s,打印 2,之后打印 1.3s,打印 3
class Queue {
constructor() {
this.queue = [];
}
task(f, ms) {
this.queue.push({ f, ms });
return this;
}
async run() {
while (this.queue.length > 0) {
// 遵循队列的先进先出原则,取出最前面一项来执行
let { f, ms } = this.queue.shift();
// 等待promise成功,阻塞后面的同步代码执行
await sleep(f, ms);
}
}
}
// 睡眠ms后让promise状态转为成功态
const sleep = (f, ms) =>
new Promise((resolve, reject) => {
setTimeout(() => {
f();
resolve();
}, ms);
});
const start = () => {
new Queue()
.task(() => {
console.log(1);
}, 1000)
.task(() => {
console.log(2);
}, 2000)
.task(() => {
console.log(3);
}, 3000)
.run();
};
start();
# 31.Jsonp 跨域
# 32.js 原型继承 & 原型链
# 33.promise
# 34.二叉树搜寻算法(另外一篇文章中)
# 35.算法:前端做并发请求控制(另外一篇文章中)
# 38. express ctx 中间件代码实现
# 39.. vue 发布订阅和虚拟 dom 代码实现
# 40. 请实现如下的函数,可以批量请求数据,所有的 URL 地址在 urls 参数中,同时可以通过 max 参数控制请求的并发度,当所有请求结束之后,需要执行 callback 回调函数。发请求的函数可以直接使用 fetch 即可
function sendRequest(arr, max, callback) {
let fetchArr = [], // 存储并发max的promise数组
i = 0;
function toFetch() {
if (i === arr.length) {
// 所有的都处理完了, 返回一个resolve
return Promise.resolve();
}
let one = fetch(arr[i++]); // 取出第i个url, 放入fetch里面 , 每取一次i++
fetchArr.push(one); //将当前的promise存入并发数组中,先push进去,再等promise执行完了之后再删除。
one.then(() => {
fetchArr.splice(fetchArr.indexOf(one), 1);
}); // 当promise执行完毕后,从数组删除
let p = Promise.resolve();
if (fetchArr.length >= max) {
// 当并行数量达到最大后, 用race比较 第一个完成的, 然后再调用一下函数自身。
p = Promise.race(fetchArr);
}
return p.then(() => toFetch());
}
// arr循环完后, 现在fetchArr里面剩下最后max个promise对象, 使用all等待所有的都完成之后执行callback
toFetch()
.then(() => Promise.all(fetchArr))
.then(() => {
callback();
});
}
# 41.二叉树遍历
# 42.并发请求最大值是 10,怎么处理队列
同上
# 43.css 画出一个三角形
#div1 {
border-style: solid;
border-width: 100px 100px 100px 100px;
border-color: transparent transparent blue transparent;
width: 0px;
height: 0px;
}
# 44.node 网关
当您希望在 API 网关中执行简单的操作,比如将请求路由到特定服务,您可以使用像 nginx 这样的反向代理。但在某些时候,您可能需要实现一般代理不支持的逻辑。在这种情况下,您可以在 Node.js 中实现自己的 API 网关。
在 Node.js 中,您可以使用 http-proxy 软件包简单地代理对特定服务的请求,也可以使用更多丰富功能的 express-gateway 来创建 API 网关。
在我们的第一个 API 网关示例中,我们在将代码委托给 user 服务之前验证请求。
const express = require('express');
const httpProxy = require('express-http-proxy');
const app = express();
const userServiceProxy = httpProxy('https://user-service');
// 身份认证
app.use((req, res, next) => {
// TODO: 身份认证逻辑
next();
});
// 代理请求
app.get('/users/:userId', (req, res, next) => {
userServiceProxy(req, res, next);
});
另一种示例可能是在您的 API 网关中发出新的请求,并将响应返回给客户端:
const express = require('express');
const request = require('request-promise-native');
const app = express();
// 解决: GET /users/me
app.get('/users/me', async (req, res) => {
const userId = req.session.userId;
const uri = `https://user-service/users/${userId}`;
const user = await request(uri);
res.json(user);
});
Node.js API 网关总结
API 网关提供了一个共享层,以通过微服务架构来满足客户需求。它有助于保持您的服务小而专注。您可以将不同的通用逻辑放入您的 API 网关,但是您应该避免 API 网关的过度使用,因为很多逻辑可以从服务团队中获得控制。
# 45.csrf/xss 攻击原理(前端攻击有哪些)
# 46.react diff 原理
# 47.事件循环
# 48.react diff 算法,key 的作用,setData 的机制,事件合成
- react fiber 的实现原理
- 【React 深入】React 事件机制
- 【React 深入】setState 的执行机制
- 【React 深入】深入分析虚拟 DOM 的渲染原理和特性
- 【React 深入】从 Mixin 到 HOC 再到 Hook
# vue 相关面试题
# wepack-dev-server 热更新功能实现原理
开始一个 React 项目(二) 彻底弄懂 webpack-dev-server 的热更新
# 59.[1,2,3].map(parseInt) 执行结果
parseInt('1', 0);
// radix 为 0 时,使用默认的 10 进制。parseInt('2', 1);
// radix 值 1 不在 2-36 范围,因此无法解析,返回 NaNparseInt('3', 2);
// 基数为 2,2 进制数表示的数中,最大值小于 3,无法解析,返回 NaN
# 67.hybrid 实现 bridge 的方法
# 69.小程序框架的实现原理
# 78.vue-router 路由监听的原理
# 79.webpack 打包的原理,webpack 有没有针对打包过程做一些优化提升打包速度
# 85.小程序架构优化
# 86.写一个 eventBus
# 88.vuex mobx
# 91.CSS 栅格布局
很多 UI 框架都提供了栅格系统来帮助页面实现等分或等比布局,比如 Bootstrap 提供了 12 列栅格,elment ui 和 ant design 提供了 24 列栅格。
那么你思考过栅格系统设定这些列数背后的原因吗?
首先从 12 列说起,12 这个数字,从数学上来说它具有很多约数 1、2、3、4、6、12,也就是说可以轻松实现 1 等分、2 等分、3 等分、4 等分、6 等分、12 等分,比例方面可以实现 1:11、1:5、1:3、1:2、1:1、1:10:1、1:4:1 等。如果换成 10 或 8,则可实现的等分比例就会少很多,而更大的 16 似乎是个不错的选择,但对于常用的 3 等分就难以实现。
至于使用 24 列不使用 12 列,可能是考虑宽屏幕(PC 端屏幕宽度不断增加)下对 12 列难以满足等分比例需求,比如 8 等分。同时又能够保证兼容 12 列情况下的等分比例(方便项目迁移和替换)。
# 92. CSS 伪类和伪元素
css 伪类:
css 伪类用于向某些选择器添加特殊的效果。 锚伪类:
:link, :visited, :hover, :focus, :active, :lang
- css3 新增的伪类:
:first-child, :last-child, :only-child, :first-of-type, :last-of-type, :only-of-type, :nth-child(n), :nth-last-child(n), :nth-of-type(n), :nth-last-of-type(n), :root, :empty, :target, :enabled, :disabled, :checked, :not(selector),
- css3 新增的伪类:
css 伪元素:
css 伪元素用于向某些选择器设置特殊效果。
::first-letter, ::first-line, ::before, ::after
css3 新增的伪元素::selection
css 伪类和伪元素的区别
- 为了避免大家混淆伪类和伪元素,css3 中的标准规定伪类使用单冒号“:” ,伪元素使用双冒号“::”,但在此之前都使用的单冒号“:”,所以为了保证兼容伪元素两种使用方法都是可以的。
- 伪类可以叠加使用,而伪元素在一个选择器中只能出现一次,并且只能出现在末尾。
.box:first-child:hover { color: #000; } //使用伪类 .box:first-letter { color: #000; } //使用伪元素 .box:first-letter:hover { color: #000; } //错误写法
- 伪类与类优先级相同,伪元素与标签优先级相同。顺便说一下优先级怎么判断,一般是 !important > 行内样式> ID 选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性。还有一个简单的计算方法,内联样式表的权值为 1000,ID 选择器的权值为 100,Class 类选择器的权值为 10,HTML 标签选择器的权值为 1,权值实际并不是按十进制计算的,用数字表示只是说明思想,我们可以把选择器中规则对应做加法,比较权值大小,权值越大优先级越高,如果权值相同前面的样式会被后面的覆盖。
# 93.根据条件获取递归树中过的某一节点
const orgTreeData = {
nodeId: '19A5B',
nodeName: '预分析用户:1313.85万人',
nodeType: 'root',
expand: true,
dataCnt: 13138494,
children: [
{
nodeId: '19A5B_19A5C',
nodeName: '客户状态',
nodeType: 'tag',
children: [{}]
}
]
};
const findParentNodeById = (data, nodeId) => {
//设置结果
let result;
if (!data) return; //如果data传空,直接返回
for (let i = 0; i < data.children.length; i++) {
let item = data.children[i];
//找到id相等的则返回父节点
if (item.nodeId == nodeId) return (result = data);
else if (item.children && item.children.length > 0) {
//如果有子集,则把子集作为参数重新执行本方法
result = findParentNodeById(item, nodeId);
//关键,千万不要直接return本方法,不然即使没有返回值也会将返回return,导致最外层循环中断,直接返回undefined,要有返回值才return才对
if (result) return result;
}
}
//如果执行循环中都没有return,则在此return
return result;
};
# 94.JavaScript this 的指向;箭头函数的 this 指向
JavaScript 深入之史上最全--5 种 this 绑定全面解析
# 95.Promise / setTimeout 的执行顺序;实际考察知识点:对「事件队列 / 宏任务 / 微任务」的了解
Promise 是微任务,会优先于 setTimeout 执行
# 96.执行代码求输出,并说明为什么,严格模式下输出有变化吗,为什么
var a = function() {
this.b = 3;
};
var c = new a();
a.prototype.b = 9;
var b = 7;
a();
console.log(b);
console.log(c.b);
解析
- 执行
a();
, 没有显式指定调用方式,在非严格模式下,则指向windows
,因此函数表达式a
里面的this
指向windows
, 即this.b = 3 -> window.b = 3
; - 执行
c = new a();
,c
就是a
的实例,所以this.b = 3 -> c.b = 3
; - 执行
a.prototype.b = 9;
, 根据原型链规则,就是c.__proto__ -> a.prototype
;
# 97.给定一个升序整型数组[0,1,2,4,5,7,13,15,16],找出其中连续出现的数字区间,输出为["0->2","4->5","7","13","15->16"]
function summaryRanges(arr) {
//TODO
}
# 98.请实现以下的函数,可以批量请求数据,所有的 URL 地址在 urls 参数中,同时可以通过 max 参数控制请求的并发度,当所有请求结束之后,需要执行 callback 回调函数。发请求的函数可以直接使用 fetch 即可
function sendRequest(urls: sring[], max: number, callback: () => void) {
//TODO
}
# 99.弹性盒子中 flex: 0 1 auto 表示什么意思
三个参数分别对应的是 flex-grow, flex-shrink 和 flex-basis,默认值为 0 1 auto。
flex-grow
属性定义项目的放大比例,默认为 0,即如果存在剩余空间,也不放大。flex-shrink
属性定义了项目的缩小比例,默认为 1,即如果空间不足,该项目将缩小。flex-basis
属性定义了在分配多余空间之前,项目占据的主轴空间(main size)(设置了宽度跟宽度走,没设置宽度跟内容实际宽度走)。
# 100.Promise 相关
(1)promise
的链式调用怎么中断?
返回一个既不成功也不失败的 promise
, 相当于是返回pending
状态的 Promise
对象
return new Promise(resolve,reject) => {});
(2)promise
如何取消
Promise.race
竞速方法
let p1 = new Promise((resolve, reject) => {
resolve('ok1');
});
let p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('ok2');
}, 10);
});
Promise.race([p2, p1])
.then(result => {
console.log(result); //ok1
})
.catch(error => {
console.log(error);
});
- 当
Promise
链中抛出一个错误时,错误信息沿着链路向后传递,直至被捕获。
利用这一特性能跳过链中被捕获前的函数的调用,直至链路终点。
Promise.resolve()
.then(() => {
console.log('ok1');
throw 'throw error1';
})
.then(
() => {
console.log('ok2');
},
err => {
// 捕获错误
console.log('err->', err);
}
)
.then(() => {
// 该函数将被调用
console.log('ok3');
throw 'throw error3';
})
.then(() => {
// 错误捕获前的函数不会被调用
console.log('ok4');
})
.catch(err => {
console.log('err->', err);
});
(3)Promise.prototype.finally
的使用场景
- 隐藏
AJAX
请求的loading
效果 - 关掉打开的文件
- 不管操作完成的状态是成功还是失败都想要记录日志
等等这些情况下,就需要用 finally
# 101.打开 url 后的流程
# 102.怎么解决内存泄漏。还有内存回收的有哪几种方式
# 二面
# 1.实现一个字符串反转:输入:www.toutiao.com.cn 输出:cn.com.toutiao.www
要求:1.不使用字符串处理函数 2.空间复杂度尽可能小
/**
* @param {string} s
* @return {string}
*/
var reverseWords = function(s) {
var l = '';
var l1 = '';
for (var i = 0; i < s.length; i++) {
if (s[i] != ' ') {
l = s[i] + l;
} else {
l1 = l1 + l + ' ';
l = '';
}
}
return l1 + l;
// return s.split("").reverse().join("").split(" ").reverse().join(" ")
};
# 2.不借助变量,交换两个数
# 1. 算术交换
针对的是 Number
,或者类型可以转换成数字的变量类型
function swap(a, b) {
a = a + b;
b = a - b;
a = a - b;
}
通过算术运算过程中的技巧,可以巧妙地将两个值进行互换。但是,有个缺点就是变量数据溢出。因为 JavaScript
能存储数字的精度范围是 -2^53
到 2^53
。所以,加法运算,会存在溢出的问题。
# 2. 异或运算
^ 按位异或 若参加运算的两个二进制位值相同则为 0,否则为 1
此算法能够实现是由异或运算的特点决定的,通过异或运算能够使数据中的某些位翻转,其他位不变。这就意味着任意一个数与任意一个给定的值连续异或两次,值不变.
a = a ^ b;
b = a ^ b;
a = a ^ b;
# 3. ES6 的解构(真香~)
[a, b] = [b, a];
# 3.观察者模式 vs 发布-订阅模式,说说区别
# 观察者模式的概念
观察者模式模式,属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主体对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
# 发布订阅者模式的概念
发布-订阅模式,消息的发送方,叫做发布者(publishers
),消息不会直接发送给特定的接收者,叫做订阅者。意思就是发布者和订阅者不知道对方的存在。需要一个调度中心媒介,叫做信息中介,它将订阅者和发布者串联起来,它过滤和分配所有输入的消息。换句话说,发布-订阅模式用来处理不同系统组件的信息交流,即使这些组件不知道对方的存在。
我们把这些差异快速总结一下:
在观察者模式
中,观察者是知道Subject
的,Subject
一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理(调度中心)进行通信。
在发布订阅模式中,组件是松散耦合的,正好和观察者模式
相反。
观察者模式
大多数时候是同步的,比如当事件触发,Subject
就会去调用观察者的方法。而发布-订阅
模式大多数时候是异步的(使用消息队列)。
观察者模式
需要在单个应用程序地址空间中实现,而发布-订阅
更像交叉应用模式。
# 4. 与项目无绝对相关的问答题
- vue 事件机制是如何实现的
- vue 的组件通信方式有哪些
- react fiber 的实现原理
- vue 响应式数据原理(vue2/vue3/依赖收集/发布订阅/watcher 消息队列控制/Vue.set 实现)
- vue 转小程序怎么实现(ast/生命周期对齐/跨平台模块兼容/兼容细节点实现过程)(可以看看 mpvue 的实现原理)
- 性能指标,如何理解 TTI,如何统计,与 FID 有什么区别,如何实现统计,以及性能的东西
- 说说你所了解的安全问题及防护方法
- 说说你知道的设计模式,并举个对应的模式例子
# 5.http 网络协议
# 6.tcp 为什么是可靠的
# 7.solid 原则
# 8.柯里化
是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接收余下的参数,而且返回结果的新函数的技术。
简单点说,就是把接收多个参数的函数执行转换成一个个接收并执行的函数
示例代码如下:
const isType = (type, value) => Object.prototype.toString.call(value) === `[object ${type}]`;
const currying = (fn, arr = []) => {
const len = fn.length;
return (...args) => {
let concatArgs = [...arr, ...args];
if (concatArgs.length < len) {
// 如果参数还没有收集玩,就继续curry参数
return currying(fn, concatArgs); // 递归不停的产生函数
} else {
// 否则直接执行函数并返回结果
return fn(...concatArgs);
}
};
};
const isArray = currying(isType)('Array', []);
const isString = currying(isType)('String', '');
const isNumber = currying(isType)('Number', '1');
console.log(`isArray`, isArray); // isArray true
console.log(`isString`, isString); // isString true
console.log(`isNumber`, isNumber); // isNumber false
# 9.css 单行和多行截断
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>css处理超出文本截断问题的两种情况(多行或者单行)</title>
<style>
/* 单行的简单处理方式 */
.words {
width: 400px;
/*超过部分不显示*/
overflow: hidden;
/*超过部分用点点表示*/
text-overflow: ellipsis;
/*不换行*/
white-space: nowrap;
}
/* 规定行数的截断处理方式 */
.words {
width: 400px;
text-overflow: ellipsis;
/*有些示例里需要定义该属性,实际可省略*/
display: -webkit-box;
-webkit-line-clamp: 2;
/*规定超过两行的部分截断*/
-webkit-box-orient: vertical;
overflow: hidden;
word-break: break-all;
/*在任何地方换行*/
}
</style>
</head>
<body>
<div class="words">
For a long time it seemed to me that life was about to begin , real life. But, there was
always some obsacle in the way, something to be gotten through first, some unfinnished
business, time still to be served or a debt to be paid. Then life would begin.
</div>
</body>
</html>
# 10.手写一个 redux middleware
# 11.ssr(ssr csr 混合怎么处理)
# 12.TS 常见问题整理(60 多个,持续更新 ing)
# 三轮
# 1.什么是微前端
# 2.浅拷贝深拷贝实现
# 3. redux 和 mobx 的区别
redux、mobx、concent 特性大比拼, 看后生如何对局前辈
# 4.长列表渲染
# 5.redis 数据结构和实现
# 其他
- js 实现依赖注入
- 接口攻击的方式和防御措施
- js 多线程如何共享大的数据
- 二叉树 ,diff 算法,
- 页面渲染原理,
- 图像算法 事件循环
# 最后
文中若有不准确或错误的地方,欢迎指出,有兴趣可以的关注下Github,一起学习呀~~