2020字節(jié)跳動前端面試題一面解析(附答案)
最近有文章漏出了一位實習生面試字節(jié)跳動今日頭條的前端面試題,總共四輪面試,現(xiàn)在就跟大家一起來探討一下這些面試題,為疫情后的工作做些準備。
1.算法:實現(xiàn)36進制轉(zhuǎn)換
首先新建一個Stack類,用于定義基本操作方法
class Stack {
constructor() {
this.count = 0;
this.items = {};
}
push(element) {
this.items[this.count] = element;
this.count++;
}
pop() {
if (this.isEmpty()) {
return undefined;
}
this.count--;
const result = this.items[this.count];
delete this.items[this.count];
return result;
}
peek() {
if (this.isEmpty()) {
return undefined;
}
return this.items[this.count - 1];
}
isEmpty() {
return this.count === 0;
}
size() {
return this.count;
}
clear() {
this.items = {};
this.count = 0;
}
toString() {
if (this.isEmpty()) {
return '';
}
let objString = `${this.items[0]}`;
for (let i = 1; i < this.count; i++) {
objString = `${objString},${this.items[i]}`;
}
return objString;
}
}
然后定義一個轉(zhuǎn)換方法,用于進制轉(zhuǎn)換
function baseConverter(decNumber, base) {
// 創(chuàng)建 Stack 類
const remStack = new Stack();
// 定義一個進制位數(shù),這里設置了 36 位進制,可自定義位數(shù)
const digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
let number = decNumber;
let rem;
let baseString = '';
if (!(base >= 2 && base <= 36)) {
return '';
}
while (number > 0) {
rem = Math.floor(number % base);
remStack.push(rem);
number = Math.floor(number / base);
}
while (!remStack.isEmpty()) {
// 對棧中的數(shù)字做轉(zhuǎn)化
baseString += digits[remStack.pop()];
}
return baseString;
}
最后進行測試
console.log(baseConverter(1314, 2)); //10100100010
console.log(baseConverter(1314, 8)); //2442
console.log(baseConverter(1314, 16)); //522
console.log(baseConverter(1314, 20)); //35E
console.log(baseConverter(1314, 30)); //1DO
console.log(baseConverter(1314, 35)); //12J
2.簡述https原理,以及與http的區(qū)別
http請求都是明文傳輸?shù)模@里的明文就是指沒有經(jīng)過加密的信息,如果請求被黑客攔截就會非常危險。因此Netscape公司制定了https協(xié)議,https可以將傳輸?shù)臄?shù)據(jù)進行加密,這樣即使被黑客攔截也無法破譯,保證了網(wǎng)絡通信的安全。
https協(xié)議=http協(xié)議+SSL/TLS協(xié)議,需要用SSL/TLS對數(shù)據(jù)進行加密和解密,SSL(Secure Sockets Layer)即安全套接層協(xié)議;TLS(Transport Layer Security)即安全傳輸層協(xié)議,它建立在SSL協(xié)議規(guī)范之上,是SSL的后續(xù)版本。TSL和SSL各自所支持的加密算法不同,但在理解https的過程中,可以把它們看作是同一種協(xié)議。
HTTPS開發(fā)的主要目的,是提供對網(wǎng)站服務器的身份認證,保護交換數(shù)據(jù)的隱私與完整性。它其實就是HTTP+加密+身份認證+完整性保護。
為了兼顧安全與效率,https同時使用了對稱加密和非對稱加密。要傳輸?shù)臄?shù)據(jù)使用了對稱加密,對稱加密的過程需要客戶端一個秘鑰,為了確保能把該秘鑰安全地傳輸?shù)椒掌鞫?,將該秘鑰進行了非對稱加密傳輸??偨Y(jié)就是:數(shù)據(jù)進行了對稱加密,對稱加密使用的秘鑰進行了非對稱加密。
客戶端與服務器建立連接后,各自生成私鑰和公鑰。服務器返給客戶端一個公鑰,客戶端拿著公鑰把要傳輸?shù)膬?nèi)容進行加密,連同自己的公鑰一起返給服務器,服務器用自己的私鑰解密密文,然后把響應的數(shù)據(jù)用客戶端公鑰加密,再返給客戶端,客戶端用自己的私鑰解密密文,完成數(shù)據(jù)的展現(xiàn)。
3.操作系統(tǒng)中進程和線程怎么通信
進程和線程的區(qū)別
| 進程 | 線程 |
|---|---|
| 進程是資源分配的最小單位 | 線程是程序執(zhí)行的最小單位,CPU調(diào)度的最小單位 |
| 進程有自己獨立的地址空間 | 線程共享進程的地址空間 |
| 進程之間的資源是獨立的 | 線程共享本進程的資源 |
進程和線程通信
| 進程通信 | 線程通信 |
|---|---|
| 管道(包括管道和命名管道) 內(nèi)存中類似于文件的模型,多進程可讀寫 | 共享內(nèi)存 |
| 消息隊列 內(nèi)核中的隊列 | 管道 |
| 共享內(nèi)存 | |
| 信號量 | |
| 套接字 不同主機上的進程通信方式 |
4.node中cluster是怎樣開啟多進程的,并且一個端口可以被多個進程監(jiān)聽嗎?
nodejs是單線程的模式,不能充分利用服務器的多核資源。使用node的cluster模塊可以監(jiān)控應用進程,退出后重新啟動node應用進程,并可以啟動多個node應用進程,做到負載均衡,充分利用資源。
const cluster = require('cluster');
const cpus = require('os').cpus();
const accessLogger = require("../logger").accessLogger();
accessLogger.info('master ' + process.pid + ' is starting.');
cluster.setupMaster({
/* 應用進程啟動文件 */
exec: 'bin/www'
});
/* 啟動應用進程個數(shù)和服務器CPU核數(shù)一樣 */
for (let i = 0; i < cpus.length; i++) {
cluster.fork();
}
cluster.on('online', function (worker) {
/* 進程啟動成功 */
accessLogger.info('worker ' + worker.process.pid + ' is online.');
});
cluster.on('exit', function (worker, code, signal) {
/* 應用進程退出時,記錄日志并重啟 */
accessLogger.info('worker ' + worker.process.pid + ' died.');
cluster.fork();
});
5.實現(xiàn)原生ajax
通過XmlHttpRequest對象向服務器發(fā)異步請求,從服務器獲得數(shù)據(jù),然后用 javascript 來操作DOM更新頁面的技術
var xhr = new XMLHttpRequest();
xhr.open("post","http:www.domain.com");
xhr.setRequestHeader('content-type','application/x-www-form-urlencoded');
xhr.send();
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
return xhr.responseText;
}
}
主要考察的是服務器響應的5個狀態(tài)
0: 請求未初始化(代理被創(chuàng)建,但尚未調(diào)用 open() 方法)
1: 服務器連接已建立(open方法已經(jīng)被調(diào)用)
2: 請求已接收(send方法已經(jīng)被調(diào)用,并且頭部和狀態(tài)已經(jīng)可獲得)
3: 請求處理中(下載中, responseText 屬性已經(jīng)包含部分數(shù)據(jù))
4: 請求已完成,且響應已就緒(下載操作已完成)
6.vue-router源碼
這里僅展示關鍵方法,細節(jié)處不討論。
先看目錄結(jié)構(gòu)
├─vue-router │ ├─components # 存放vue-router的兩個核心組件 │ │ ├─link.js │ │ └─view.js │ ├─history # 存放瀏覽器跳轉(zhuǎn)相關邏輯 │ │ ├─base.js │ │ └─hash.js │ ├─create-matcher.js # 創(chuàng)建匹配器 │ ├─create-route-map.js # 創(chuàng)建路由映射表 │ ├─index.js # 引用時的入口文件 │ ├─install.js # install方法
編寫install方法
export let _Vue;
export default function install(Vue) {
_Vue = Vue;
Vue.mixin({ // 給所有組件的生命周期都增加beforeCreate方法
beforeCreate() {
if (this.$options.router) { // 如果有router屬性說明是根實例
this._routerRoot = this; // 將根實例掛載在_routerRoot屬性上
this._router = this.$options.router; // 將當前router實例掛載在_router上
this._router.init(this); // 初始化路由,這里的this指向的是根實例
} else { // 父組件渲染后會渲染子組件
this._routerRoot = this.$parent && this.$parent._routerRoot;
// 保證所有子組件都擁有_routerRoot 屬性,指向根實例
// 保證所有組件都可以通過 this._routerRoot._router 拿到用戶傳遞進來的路由實例對象
}
}
})
}
編寫createMatcher方法
import createRouteMap from './create-route-map'
export default function createMatcher(routes) {
// 收集所有的路由路徑, 收集路徑的對應渲染關系
// pathList = ['/','/about','/about/a','/about/b']
// pathMap = {'/':'/的記錄','/about':'/about記錄'...}
let {pathList,pathMap} = createRouteMap(routes);
// 這個方法就是動態(tài)加載路由的方法
function addRoutes(routes){
// 將新增的路由追加到pathList和pathMap中
createRouteMap(routes,pathList,pathMap);
}
function match(){} // 稍后根據(jù)路徑找到對應的記錄
return {
addRoutes,
match
}
}
創(chuàng)建映射關系,還需要createRouteMap方法
export default function createRouteMap(routes,oldPathList,oldPathMap){
// 當?shù)谝淮渭虞d的時候沒有 pathList 和 pathMap
let pathList = oldPathList || [];
let pathMap = oldPathMap || Object.create(null);
routes.forEach(route=>{
// 添加到路由記錄,用戶配置可能是無限層級,稍后要遞歸調(diào)用此方法
addRouteRecord(route,pathList,pathMap);
});
return { // 導出映射關系
pathList,
pathMap
}
}
// 將當前路由存儲到pathList和pathMap中
function addRouteRecord(route,pathList,pathMap,parent){
// 如果是子路由記錄 需要增加前綴
let path = parent?`${parent.path}/${route.path}`:route.path;
let record = { // 提取需要的信息
path,
component:route.component,
parent
}
if(!pathMap[path]){
pathList.push(path);
pathMap[path] = record;
}
if(route.children){ // 遞歸添加子路由
route.children.forEach(r=>{
// 這里需要標記父親是誰
addRouteRecord(r,pathList,pathMap,route);
})
}
}
vue路由有三種模式:hash / h5api /abstract,這里以hash為例
以hash路由為主,創(chuàng)建hash路由實例
import History from './base'
// hash路由
export default class HashHistory extends History{
constructor(router){
super(router);
}
}
// 路由的基類
export default class History {
constructor(router){
this.router = router;
}
}
如果是hash路由,打開網(wǎng)站如果沒有hash默認應該添加#/
import History from './base';
function ensureSlash(){
if(window.location.hash){
return
}
window.location.hash = '/'
}
export default class HashHistory extends History{
constructor(router){
super(router);
ensureSlash(); // 確保有hash
}
}
再把焦點轉(zhuǎn)向初始化邏輯
init(app){
const history = this.history;
// 初始化時,應該先拿到當前路徑,進行匹配邏輯
// 讓路由系統(tǒng)過度到某個路徑
const setupHashListener = ()=> {
history.setupListener(); // 監(jiān)聽路徑變化
}
history.transitionTo( // 父類提供方法負責跳轉(zhuǎn)
history.getCurrentLocation(), // 子類獲取對應的路徑
// 跳轉(zhuǎn)成功后注冊路徑監(jiān)聽,為視圖更新做準備
setupHashListener
)
}
這里我們要分別實現(xiàn) transitionTo(基類方法)、 getCurrentLocation 、setupListener
//getCurrentLocation
function getHash(){
return window.location.hash.slice(1);
}
export default class HashHistory extends History{
// ...
getCurrentLocation(){
return getHash();
}
}
//setupListener
export default class HashHistory extends History{
// ...
setupListener(){
window.addEventListener('hashchange', ()=> {
// 根據(jù)當前hash值 過度到對應路徑
this.transitionTo(getHash());
})
}
}
//核心方法TransitionTo
export function createRoute(record, location) { // {path:'/',matched:[record,record]}
let res = [];
if (record) { // 如果有記錄
while(record){
res.unshift(record); // 就將當前記錄的父親放到前面
record = record.parent
}
}
return {
...location,
matched: res
}
}
export default class History {
constructor(router) {
this.router = router;
// 根據(jù)記錄和路徑返回對象,稍后會用于router-view的匹配
this.current = createRoute(null, {
path: '/'
})
}
// 核心邏輯
transitionTo(location, onComplete) {
// 去匹配路徑
let route = this.router.match(location);
// 相同路徑不必過渡
if(
location === route.path &&
route.matched.length === this.current.matched.length){
return
}
this.updateRoute(route); // 更新路由即可
onComplete && onComplete();
}
updateRoute(route){ // 跟新current屬性
this.current =route;
}
}
不難發(fā)現(xiàn)路徑變化時都會更改current屬性,我們可以把current屬性變成響應式的,每次current變化刷新視圖即可
export let _Vue;
export default function install(Vue) {
_Vue = Vue;
Vue.mixin({ // 給所有組件的生命周期都增加beforeCreate方法
beforeCreate() {
if (this.$options.router) { // 如果有router屬性說明是根實例
// ...
Vue.util.defineReactive(this,'_route',this._router.history.current);
}
// ...
}
});
// 僅僅是為了更加方便
Object.defineProperty(Vue.prototype,'$route',{ // 每個實例都可以獲取到$route屬性
get(){
return this._routerRoot._route;
}
});
Object.defineProperty(Vue.prototype,'$router',{ // 每個實例都可以獲取router實例
get(){
return this._routerRoot._router;
}
})
}
其中不難看出 Vue.util.defineReactive 這個方法是vue中響應式數(shù)據(jù)變化的核心。
export default class History {
constructor(router) {
// ...
this.cb = null;
}
listen(cb){
this.cb = cb; // 注冊函數(shù)
}
updateRoute(route){
this.current =route;
this.cb && this.cb(route); // 更新current后 更新_route屬性
}
}
實現(xiàn)router-view
export default {
functional:true,
render(h,{parent,data}){
let route = parent.$route;
let depth = 0;
data.routerView = true;
while(parent){ // 根據(jù)matched 渲染對應的router-view
if (parent.$vnode && parent.$vnode.data.routerView){
depth++;
}
parent = parent.$parent;
}
let record = route.matched[depth];
if(!record){
return h();
}
return h(record.component, data);
}
}
實現(xiàn)router-link
export default {
props:{
to:{
type:String,
required:true
},
tag:{
type:String
}
},
render(h){
let tag = this.tag || 'a';
let handler = ()=>{
this.$router.push(this.to);
}
return <tag onClick={handler}>{this.$slots.default}</tag>
}
}
實現(xiàn)beforeEach
this.beforeHooks = [];
beforeEach(fn){ // 將fn注冊到隊列中
this.beforeHooks.push(fn);
}
function runQueue(queue, iterator,cb) { // 迭代queue
function step(index){
if(index >= queue.length){
cb();
}else{
let hook = queue[index];
iterator(hook,()=>{ // 將本次迭代到的hook 傳遞給iterator函數(shù)中,將下次的權限也一并傳入
step(index+1)
})
}
}
step(0)
}
export default class History {
transitionTo(location, onComplete) {
// 跳轉(zhuǎn)到這個路徑
let route = this.router.match(location);
if (location === this.current.path && route.matched.length === this.current.matched.length) {
return
}
let queue = [].concat(this.router.beforeHooks);
const iterator = (hook, next) => {
hook(route,this.current,()=>{ // 分別對應用戶 from,to,next參數(shù)
next();
});
}
runQueue(queue, iterator, () => { // 依次執(zhí)行隊列 ,執(zhí)行完畢后更新路由
this.updateRoute(route);
onComplete && onComplete();
});
}
updateRoute(route) {
this.current = route;
this.cb && this.cb(route);
}
listen(cb) {
this.cb = cb;
}
}
7.vue原理(手寫代碼,實現(xiàn)數(shù)據(jù)劫持)
1.核心點:Object.defineProperty
2.默認Vue在初始化數(shù)據(jù)時,會給data中的屬性使用Object.defineProperty重新定義所有屬性,當頁面取到對應屬性時。會進行依賴收集(收集當前組件的watcher) 如果屬性發(fā)生變化會通知相關依賴進行更新操作
3.本文主要描述的是vue2.0版本的實現(xiàn)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() // ** 收集依賴 ** /
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
val = newVal
childOb = !shallow && observe(newVal)
dep.notify() /**通知相關依賴進行更新**/
}
})
數(shù)組方法的劫持涉及到原型相關的知識,首先數(shù)組實例大部分方法都是來源于 Array.prototype對象。
但是這里不能直接篡改 Array.prototype 對象,這樣會影響所有的數(shù)組實例,為了避免這種情況,需要采用原型繼承得到一個新的原型對象:
const methods = [
'push',
'pop',
'shift',
'unshift',
'sort',
'reverse',
'splice'
]
const arrayProto = Array.prototype
const injackingPrototype = Object.create(arrayProto);
methods.forEach(method => {
const originArrayMethod = arrayProto[method]
injackingPrototype[method] = function (...args) {
const result = originArrayMethod.apply(this, args)
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) {
// 對于新增的元素,繼續(xù)劫持
// ob.observeArray(inserted)
}
// 通知變化
return result
}
})
通過對能改變原數(shù)組的方法進行攔截,達到對數(shù)組對象的劫持,遍歷直到普通值。
8.算法:樹的遍歷有幾種方式,實現(xiàn)下層次遍歷
前序遍歷
//根節(jié)點、左子樹、右子樹
function DLR(tree){
console.log(tree.value);
if(tree.left){
DLR(tree.left);
}
if(tree.right){
DLR(tree.right);
}
}
中序遍歷
//左子樹、根節(jié)點、右子樹
function LDR(tree){
if(tree.left){
LDR(tree.left);
}
console.log(tree.value);
if(tree.right){
LDR(tree.right);
}
}
后序遍歷
//左子樹、右子樹、根節(jié)點
function LRD(tree){
if(tree.left){
LRD(tree.left);
}
if(tree.right){
LRD(tree.right);
}
console.log(tree.value);
}
三種遍歷操作大致相同,而差異就在于執(zhí)行額外操作的時機,例如console.log
9.算法:判斷對稱二叉樹
首先判斷根節(jié)點是否相同
左子樹的右節(jié)點和右子樹的左節(jié)點是否相同
右子樹的左節(jié)點和左子樹的右節(jié)點是否相同
//一個對稱二叉樹
const symmetricalBinaryTree = {
val: 8,
left: {
val: 6,
left: { val: 2, left: null, right: null },
right: { val: 4, left: null, right: null }
},
right: {
val: 6,
left: { val: 4, left: null, right: null },
right: { val: 2, left: null, right: null }
}
}
//一個非對稱二叉樹
const AsymmetricBinaryTree = {
val: 8,
left: {
val: 6,
left: { val: 2, left: null, right: null },
right: { val: 4, left: null, right: null }
},
right: {
val: 9,
left: { val: 4, left: null, right: null },
right: { val: 2, left: null, right: null }
}
}

利用遞歸實現(xiàn)對稱二叉樹判斷
function isSymmetrical(root) {
return isSymmetricalTree(root, root);
}
function isSymmetricalTree(node1, node2) {
//判斷兩個節(jié)點都是否為空
if (!node1 && !node2) {
return true;
}
//判斷兩個節(jié)點是否存在一個為空
if (!node1 || !node2) {
return false;
}
//判斷兩個節(jié)點是否相同
if (node1.val != node2.val) {
return false;
}
return isSymmetricalTree(node1.left, node2.right) && isSymmetricalTree(node1.right, node2.left);
}
console.log(isSymmetrical(symmetricalBinaryTree)); //true
console.log(isSymmetrical(AsymmetricBinaryTree)); //false
主要是利用遞歸來判斷同一層級的節(jié)點左右是否同時相等,達到對稱判斷的目的。
到此這篇關于2020字節(jié)跳動前端面試題一面解析(附答案)的文章就介紹到這了,更多相關字節(jié)跳動前端面試題內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持腳本之家!
相關文章
- 這篇文章主要介紹了2020前端暑期實習大廠面經(jīng),主要包含了7個公司面經(jīng),華為,歡聚,京東,酷狗,美的,騰訊,網(wǎng)易,感興趣的可以了解一下2020-06-11
- 在面試前必看的一些基礎面試題目,本文是小編給大家精心收藏整理的非常不錯,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下2020-04-22

每個前端工程師都應該去了解的前端面試題小結(jié)(推薦)
面試對于我們每個程序員來說都是非常重要的環(huán)節(jié),掌握一些面試題技巧是非常有必要的,今天小編給大家分享幾個js有關的面試題,需要的朋友參考下吧2020-04-15- 一場疫情過后,又要經(jīng)歷一次次面試,今天小編給大家分享2020前端面試題之HTML篇,非常不錯,對大家有所幫助,需要的朋友參考下吧2020-03-25
- 這篇文章主要介紹了2019大廠前端面試題小結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2020-03-05
- 隨著疫情的不斷好轉(zhuǎn),各地都開始逐步的復工,當然對我們來說,也馬上迎來所謂的金三銀四跳槽季。今天小編給大家分享前端常見面試題,需要的朋友跟隨小編一起看看吧2020-02-27
- 這篇文章主要介紹了Web前端面試筆試題總結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2020-02-18
- 這篇文章主要介紹了80道前端面試經(jīng)典選擇題匯總,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習2020-01-08
- 這篇文章主要介紹了面試官常問的web前端問題大全,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2020-01-03
- 這篇文章主要介紹了前端十幾道含答案的大廠面試題總結(jié),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2020-01-02


