前言
在阿里和腾讯工作了6年,当了3年的前端面试官,把期间我和我的同事常问的面试题和答案汇总在我 Github 的 Weekly-FE-Interview 中。希望对大家有所帮助。
如果你在bat面试的时候遇到了什么不懂的问题,欢迎给我提issue,我会把题目汇总并将面试要点和答案写好放在周刊里,大家一起共同进步和成长,助力大家进入自己理想的企业。
项目地址是:github.com/airuikun/We…
常见面试题精选
以下是十道大厂一面的时候常见的面试题,如果全部理解并且弄透,在一面或者电话面的时候基本上能中1~2题。小伙伴可以先不急着看答案,先自己尝试着思考一下和自己实现一下,然后再看答案。
第 1 题:http的状态码中,499是什么?如何出现499,如何排查跟解决
499对应的是 “client has closed connection”,客户端请求等待链接已经关闭,这很有可能是因为服务器端处理的时间过长,客户端等得“不耐烦”了。还有一种原因是两次提交post过快就会出现499。 解决方法:
- 前端将timeout最大等待时间设置大一些
- nginx上配置proxy_ignore_client_abort on;
提问解答与更多解析:github.com/airuikun/We…
第 2 题:如何遍历一个dom树
function traversal(node) {
//对node的处理
if (node && node.nodeType === 1) {
console.log(node.tagName);
}
var i = 0,
childNodes = node.childNodes,
item;
for (; i < childNodes.length; i++) {
item = childNodes[i];
if (item.nodeType === 1) {
//递归先序遍历子节点
traversal(item);
}
}
}
提问解答与更多解析:github.com/airuikun/We…
第 3 题:new操作符都做了什么
四大步骤:
1、创建一个空对象,并且 this 变量引用该对象,// lat target = {};
2、继承了函数的原型。// target.proto = func.prototype;
3、属性和方法被加入到 this 引用的对象中。并执行了该函数func// func.call(target);
4、新创建的对象由 this 所引用,并且最后隐式的返回 this 。// 如果func.call(target)返回的res是个对象或者function 就返回它
function new(func) {
lat target = {};
target.__proto__ = func.prototype;
let res = func.call(target);
if (typeof(res) == "object" || typeof(res) == "function") {
return res;
}
return target;
}
提问解答与更多解析:github.com/airuikun/We…
第 4 题:手写代码,简单实现call
Function.prototype.call2 = function(context) {
var context = context || window; //因为传进来的context有可能是null
context.fn = this;
var args = [];
for (var i = 1; i < arguments.length; i++) {
args.push("arguments[" + i + "]"); //不这么做的话 字符串的引号会被自动去掉 变成了变量 导致报错
}
args = args.join(",");
var result = eval("context.fn(" + args + ")"); //相当于执行了context.fn(arguments[1], arguments[2]);
delete context.fn;
return result; //因为有可能this函数会有返回值return
}
提问解答与更多解析:github.com/airuikun/We…
第 5 题:手写代码,简单实现apply
Function.prototype.apply2 = function(context, arr) {
var context = context || window; //因为传进来的context有可能是null
context.fn = this;
var args = [];
var params = arr || [];
for (var i = 0; i < params.length; i++) {
args.push("params[" + i + "]"); //不这么做的话 字符串的引号会被自动去掉 变成了变量 导致报错
}
args = args.join(",");
var result = eval("context.fn(" + args + ")"); //相当于执行了context.fn(arguments[1], arguments[2]);
delete context.fn;
return result; //因为有可能this函数会有返回值return
}
提问解答与更多解析:github.com/airuikun/We…
第 6 题:手写代码,简单实现bind
Function.prototype.bind2 = function(context) {
var _this = this;
var argsParent = Array.prototype.slice.call(arguments, 1);
return function() {
var args = argsParent.concat(Array.prototype.slice.call(arguments)); //转化成数组
_this.apply(context, args);
};
}
提问解答与更多解析:github.com/airuikun/We…
第 7 题:讲解一下HTTPS的工作原理
HTTPS在传输数据之前需要客户端(浏览器)与服务端(网站)之间进行一次握手,在握手过程中将确立双方加密传输数据的密码信息。TLS/SSL协议不仅仅是一套加密传输的协议,更是一件经过艺术家精心设计的艺术品,TLS/SSL中使用了非对称加密,对称加密以及HASH算法。握手过程的简单描述如下:
-
浏览器将自己支持的一套加密规则发送给网站。
-
网站从中选出一组加密算法与HASH算法,并将自己的身份信息以证书的形式发回给浏览器。证书里面包含了网站地址,加密公钥,以及证书的颁发机构等信息。
-
获得网站证书之后浏览器要做以下工作:
-
a) 验证证书的合法性(颁发证书的机构是否合法,证书中包含的网站地址是否与正在访问的地址一致等),如果证书受信任,则浏览器栏里面会显示一个小锁头,否则会给出证书不受信的提示。
-
如果证书受信任,或者是用户接受了不受信的证书,浏览器会生成一串随机数的密码,并用证书中提供的公钥加密。
-
使用约定好的HASH计算握手消息,并使用生成的随机数对消息进行加密,最后将之前生成的所有信息发送给网站。
-
-
网站接收浏览器发来的数据之后要做以下的操作:
-
a) 使用自己的私钥将信息解密取出密码,使用密码解密浏览器发来的握手消息,并验证HASH是否与浏览器发来的一致。
-
b) 使用密码加密一段握手消息,发送给浏览器。
-
-
浏览器解密并计算握手消息的HASH,如果与服务端发来的HASH一致,此时握手过程结束,之后所有的通信数据将由之前浏览器生成的随机密码并利用对称加密算法进行加密。
提问解答与更多解析:github.com/airuikun/We…
第 8 题:讲解一下https对称加密和非对称加密。
对称加密: 发送方和接收方需要持有同一把密钥,发送消息和接收消息均使用该密钥。相对于非对称加密,对称加密具有更高的加解密速度,但双方都需要事先知道密钥,密钥在传输过程中可能会被窃取,因此安全性没有非对称加密高。
非对称加密: 接收方在发送消息前需要事先生成公钥和私钥,然后将公钥发送给发送方。发送放收到公钥后,将待发送数据用公钥加密,发送给接收方。接收到收到数据后,用私钥解密。 在这个过程中,公钥负责加密,私钥负责解密,数据在传输过程中即使被截获,攻击者由于没有私钥,因此也无法破解。 非对称加密算法的加解密速度低于对称加密算法,但是安全性更高。
提问解答与更多解析:github.com/airuikun/We…
第 9 题: 简单实现项目代码按需加载,例如import { Button } from 'antd',打包的时候只打包button
原理很简单,就是将
import { Select, Pagination, Button } from 'xxx-ui';
通过babel转化成
import Button from `xxx-ui/src/components/ui-base/Button/Button`;
import Pagination from `xxx-ui/src/components/ui-base/Pagination/Pagination`;
import Select from `xxx-ui/src/components/ui-base/Select/Select`;
自定义拓展一个babel插件,代码如下:
visitor: {
ImportDeclaration (path, { opts }) {
const specifiers = path.node.specifiers;
const source = path.node.source;
// 判断传入的配置参数是否是数组形式
if (Array.isArray(opts)) {
opts.forEach(opt => {
assert(opt.libraryName, 'libraryName should be provided');
});
if (!opts.find(opt => opt.libraryName === source.value)) return;
} else {
assert(opts.libraryName, 'libraryName should be provided');
if (opts.libraryName !== source.value) return;
}
const opt = Array.isArray(opts) ? opts.find(opt => opt.libraryName === source.value) : opts;
opt.camel2UnderlineComponentName = typeof opt.camel2UnderlineComponentName === 'undefined'
? false
: opt.camel2UnderlineComponentName;
opt.camel2DashComponentName = typeof opt.camel2DashComponentName === 'undefined'
? false
: opt.camel2DashComponentName;
if (!types.isImportDefaultSpecifier(specifiers[0]) && !types.isImportNamespaceSpecifier(specifiers[0])) {
// 遍历specifiers生成转换后的ImportDeclaration节点数组
const declarations = specifiers.map((specifier) => {
// 转换组件名称
const transformedSourceName = opt.camel2UnderlineComponentName
? camel2Underline(specifier.imported.name)
: opt.camel2DashComponentName
? camel2Dash(specifier.imported.name)
: specifier.imported.name;
// 利用自定义的customSourceFunc生成绝对路径,然后创建新的ImportDeclaration节点
return types.ImportDeclaration([types.ImportDefaultSpecifier(specifier.local)],
types.StringLiteral(opt.customSourceFunc(transformedSourceName)));
});
// 将当前节点替换成新建的ImportDeclaration节点组
path.replaceWithMultiple(declarations);
}
}
}
提问解答与更多解析:github.com/airuikun/We…
第 10 题:简单手写实现promise
// 简易版本的promise
// 第一步: 列出三大块 this.then resolve/reject fn(resolve,reject)
// 第二步: this.then负责注册所有的函数 resolve/reject负责执行所有的函数
// 第三步: 在resolve/reject里面要加上setTimeout 防止还没进行then注册 就直接执行resolve了
// 第四步: resolve/reject里面要返回this 这样就可以链式调用了
// 第五步: 三个状态的管理 pending fulfilled rejected
// *****promise的链式调用 在then里面return一个promise 这样才能then里面加上异步函数
// 加上了catch
function PromiseM(fn) {
var value = null;
var callbacks = [];
//加入状态 为了解决在Promise异步操作成功之后调用的then注册的回调不会执行的问题
var state = 'pending';
var _this = this;
//注册所有的回调函数
this.then = function (fulfilled, rejected) {
//如果想链式promise 那就要在这边return一个new Promise
return new PromiseM(function (resolv, rejec) {
//异常处理
try {
if (state == 'pending') {
callbacks.push(fulfilled);
//实现链式调用
return;
}
if (state == 'fulfilled') {
var data = fulfilled(value);
//为了能让两个promise连接起来
resolv(data);
return;
}
if (state == 'rejected') {
var data = rejected(value);
//为了能让两个promise连接起来
resolv(data);
return;
}
} catch (e) {
_this.catch(e);
}
});
}
//执行所有的回调函数
function resolve(valueNew) {
value = valueNew;
state = 'fulfilled';
execute();
}
//执行所有的回调函数
function reject(valueNew) {
value = valueNew;
state = 'rejected';
execute();
}
function execute() {
//加入延时机制 防止promise里面有同步函数 导致resolve先执行 then还没注册上函数
setTimeout(function () {
callbacks.forEach(function (cb) {
value = cb(value);
});
}, 0);
}
this.catch = function (e) {
console.log(JSON.stringify(e));
}
//经典 实现异步回调
fn(resolve, reject);
}
提问解答与更多解析:github.com/airuikun/We…
结语
本人还写了一些前端进阶知识的文章,如果觉得不错可以点个star。
blog项目地址是:github.com/airuikun/bl…
我是小蝌蚪,腾讯高级前端工程师,跟着我一起每周攻克几个前端技术难点。希望在小伙伴前端进阶的路上有所帮助,助力大家进入自己理想的企业。