字节跳动广告系统 面经

一面

自我介绍

手撕防抖(如果滚动条判断一个div是否存在会用什么来做?节流)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>实现防抖</title>
<style>
.container {
width: 200px;
height: 200px;
background-color: aqua;
font-size: 30px;
line-height: 200px;
text-align: center;
}
</style>
</head>
<body>
<div class="container"></div>
<script>
var btn = document.getElementsByClassName('container')[0]
// let cnt = 0
// let timeId = null
// btn.onmouseover = () => {
// clearTimeout(timeId)
// timeId = setTimeout(() => {
// btn.innerHTML = ++cnt
// }, 2000)
// }
let cnt = 0
let doSomething = () => {
btn.innerHTML = ++cnt
}
let debounce = (fn,time,triggerNow) => {
let timeId = null
let debounced = (...args) => {
if(timeId){
clearTimeout(timeId)
}
if(triggerNow){
let exec = !timeId
timeId =setTimeout(()=>{
timeId = null
},time)
if(exec){
fn.apply(this,args)
}
}else{
timeId = setTimeout(()=>{
fn.apply(this,args)
},time)
}
}
debounced.remove = () => {
clearTimeout(timeId)
timeId = null
}
return debounced
}
btn.onmouseover = debounce(doSomething,2000,false)
</script>
</body>
</html>

CSS实现三角形

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>画三角形</title>
<style>
.triggle{
width: 0px;
height: 0px;
border-top: 40px solid transparent;
border-left: 40px solid transparent;
border-bottom: 40px solid red;
border-right: 40px solid transparent;
margin: 40px;
}
</style>
</head>
<body>
<div class="triggle"></div>
</body>
</html>

了解伪元素和伪类吗?


参考:CSS伪类和伪元素的区别 - Web前端工程师面试题讲解

盒模型

div: 
width=100px;
border: 10px;
padding: 15px;
margin: 10px;
height=100px;

content-box => 100px

border-box => 150px

Vue双向绑定实现 Object.defineProperty() 它有哪些不足点?

  • 只能监听某个属性,不能对全对象进行监听
  • 需要 for in遍历找对象中的属性
  • 不能监听数组,需要单独的对数组进行特异性操作
  • 会污染原对象
data : {
name; 'abc';
age: 23,
},


this.name = 'brown';
this.gender = '男';

如何让 gender 改变也会让视图变化(面试官意思是如何用Vue动态新增对象属性,触发dom渲染)

背景:项目中因为一些需求需要在JSON中新增一个属性,也能console出来,但是就是不能在页面渲染,即不能触发视图更新

其实在vue 中新增属性应该用 $set 这个方法的

1. 添加单个属性

$set()方法,既可以新增属性,又可以触发视图更新。

this.$set(this.data,”key”,value)

2.添加多个属性

使用 Object.assign()用原对象与要混合进去的对象的属性一起创建一个新的对象。

this.obj = Object.assign({}, this.obj, {
age: 18,
name: 'Chocolate',
})

询问输出结果

function Foo() {
getName = function () { alert (1); };
return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//请写出以下输出结果:
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName();
new Foo().getName();
new new Foo().getName();

参考:一道常被人轻视的前端JS面试题

第一问

先看此题的上半部分做了什么,首先定义了一个叫Foo的函数,之后为Foo创建了一个叫getName静态属性存储了一个匿名函数,之后为Foo的原型对象新创建了一个叫getName的匿名函数。之后又通过函数变量表达式创建了一个getName的函数,最后再声明一个叫getName函数。

第一问的Foo.getName 自然是访问Foo函数上存储的静态属性,自然是2,没什么可说的。

第二问

第二问,直接调用 getName 函数。既然是直接调用那么就是访问当前上文作用域内的叫getName的函数,所以跟1 2 3都没什么关系。此题有无数面试者回答为5。此处有两个坑,一是变量声明提升,二是函数表达式。

变量声明提升

所有声明变量或声明函数都会被提升到当前函数的顶部。
例如下代码:

console.log('x' in window);//true
var x;
x = 0;

代码执行时js引擎会将声明语句提升至代码最上方,变为:

var x;
console.log('x' in window);//true
x = 0;

函数表达式

var getNamefunction getName 都是声明语句,区别在于var getName函数表达式,而function getName函数声明。关于JS中的各种函数创建方式可以看 大部分人都会做错的经典JS闭包面试题 这篇文章有详细说明。

函数表达式最大的问题,在于js会将此代码拆分为两行代码分别执行。
例如下代码:

console.log(x);//输出:function x(){}
var x=1;
function x(){}

实际执行的代码为,先将 var x=1拆分为var x;x = 1;两行,再将 var x;function x(){}两行提升至最上方变成:

var x;
function x(){}
console.log(x);
x=1;

所以最终函数声明的x覆盖了变量声明的x,log输出为x函数。
同理,原题中代码最终执行时的是:

function Foo() {
getName = function () { alert (1); };
return this;
}
var getName;//只提升变量声明
function getName() { alert (5);}//提升函数声明,覆盖var的声明

Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
getName = function () { alert (4);};//最终的赋值再次覆盖function getName声明

getName();//最终输出4

第三问

第三问的Foo().getName(); 先执行了Foo函数,然后调用Foo函数的返回值对象的getName属性函数。

Foo函数的第一句 getName = function () { alert (1); }; 是一句函数赋值语句,注意它没有var声明,所以先向当前Foo函数作用域内寻找getName变量,没有。再向当前函数作用域上层,即外层作用域内寻找是否含有getName变量,找到了,也就是第二问中的alert(4)函数,将此变量的值赋值为 function(){alert(1)}

此处实际上是将外层作用域内的getName函数修改了。

注意:此处若依然没有找到会一直向上查找到window对象,若window对象中也没有getName属性,就在window对象中创建一个getName变量。

简单的讲,this的指向是由所在函数的调用方式决定的。而此处的直接调用方式,this指向window对象

遂Foo函数返回的是window对象,相当于执行 window.getName() ,而window中的getName已经被修改为alert(1),所以最终会输出1

此处考察了两个知识点,一个是变量作用域问题,一个是this指向问题

第四问

直接调用getName函数,相当于 window.getName() ,因为这个变量已经被Foo函数执行时修改了,遂结果与第三问相同,为1

第五问

new Foo.getName(); ,此处考察的是js的运算符优先级问题。

js运算符优先级:

通过查上表可以得知点(.)的优先级高于new操作,遂相当于是:

new (Foo.getName)();

所以实际上将getName函数作为了构造函数来执行,遂弹出2

第六问

第六问 new Foo().getName(),首先看运算符优先级括号高于new,并且带参数的new操作符是优先级最高的,实际执行为

(new Foo()).getName()

遂先执行Foo函数,而Foo此时作为构造函数却有返回值,所以这里需要说明下js中的构造函数返回值问题

原题中,返回的是this,而this在构造函数中本来就代表当前实例化对象,遂最终Foo函数返回实例化对象。之后调用实例化对象的getName函数,因为在Foo构造函数中没有为实例化对象添加任何属性,遂到当前对象的原型对象(prototype)中寻找getName,找到了。

遂最终输出3。

第七问

最终实际执行为:

new ((new Foo()).getName)();

先初始化Foo的实例化对象,然后将其原型上的getName函数作为构造函数再次new。

遂最终结果为3

答案

function Foo() {
getName = function () { alert (1); };
return this;
}
Foo.getName = function () { alert (2);};
Foo.prototype.getName = function () { alert (3);};
var getName = function () { alert (4);};
function getName() { alert (5);}

//答案:
Foo.getName();//2
getName();//4
Foo().getName();//1
getName();//1
new Foo.getName();//2
new Foo().getName();//3
new new Foo().getName();//3

promise相关,下面代码输出结果

Promise.reject(2).catch(e => e).then(d => {
console.log(d);
});
// 输出2

一般总是建议,Promise 对象后面要跟catch方法,这样可以处理 Promise 内部发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。

参考:你真的完全掌握了promise么?

Promise 必知必会(十道题)

关于 Promise 的 9 个面试题

http是一个无状态协议

什么是无状态呢?就是说这一次请求和上一次请求是没有任何关系的,互不认识的,没有关联的。这种无状态的的好处是快速。坏处是假如我们想要把www.zhihu.com/login.htmlwww.zhihu.com/index.html关联起来,必须使用某些手段和工具

cookie和session

由于http的无状态性,为了使某个域名下的所有网页能够共享某些数据,session和cookie出现了。客户端访问服务器的流程如下:

  • 首先,客户端会发送一个http请求到服务器端。
  • 服务器端接受客户端请求后,建立一个session,并发送一个http响应到客户端,这个响应头,其中就包含Set-Cookie头部。该头部包含了sessionId。Set-Cookie格式如下:
    Set-Cookie: value[; expires=date][; domain=domain][; path=path][; secure]
  • 在客户端发起的第二次请求,假如服务器给了set-Cookie,浏览器会自动在请求头中添加cookie
  • 服务器接收请求,分解cookie,验证信息,核对成功后返回response给客户端


注意

  • cookie只是实现session的其中一种方案。虽然是最常用的,但并不是唯一的方法。禁用cookie后还有其他方法存储,比如放在url中
  • 现在大多都是Session + Cookie,但是只用session不用cookie,或是只用cookie,不用session在理论上都可以保持会话状态。可是实际中因为多种原因,一般不会单独使用
  • 用session只需要在客户端保存一个id,实际上大量数据都是保存在服务端。如果全部用cookie,数据量大的时候客户端是没有那么多空间的。
  • 如果只用cookie不用session,那么账户信息全部保存在客户端,一旦被劫持,全部信息都会泄露。并且客户端数据量变大,网络传输的数据量也会变大

拓展:token

token 也称作令牌,由uid+time+sign[+固定参数]
token 的认证方式类似于临时的证书签名, 并且是一种服务端无状态的认证方式, 非常适合于 REST API(表现层状态转换) 的场景. 所谓无状态就是服务端并不会保存身份认证相关的数据。

组成

  • uid: 用户唯一身份标识
  • time: 当前时间的时间戳
  • sign: 签名, 使用 hash/encrypt 压缩成定长的十六进制字符串,以防止第三方恶意拼接
    固定参数(可选): 将一些常用的固定参数加入到 token 中是为了避免重复查库

存放

token在客户端一般存放于localStorage,cookie,或sessionStorage中。在服务器一般存于数据库

token认证流程

token 的认证流程与cookie很相似

  • 用户登录,成功后服务器返回Token给客户端。
  • 客户端收到数据后保存在客户端
  • 客户端再次访问服务器,将token放入headers
  • 服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码

token可以抵抗csrf,cookie+session不行

假如用户正在登陆银行网页,同时登陆了攻击者的网页,并且银行网页未对csrf攻击进行防护。攻击者就可以在网页放一个表单,该表单提交src为http://www.bank.com/api/transfer,body为count=1000&to=Tom。倘若是session+cookie,用户打开网页的时候就已经转给Tom1000元了.因为form 发起的 POST 请求并不受到浏览器同源策略的限制,因此可以任意地使用其他域的 Cookie 向其他域发送 POST 请求,形成 CSRF 攻击。在post请求的瞬间,cookie会被浏览器自动添加到请求头中。但token不同,token是开发者为了防范csrf而特别设计的令牌,浏览器不会自动添加到headers里,攻击者也无法访问用户的token,所以提交的表单无法通过服务器过滤,也就无法形成攻击。

分布式情况下的session和token

负载均衡多服务器的情况,不好确认当前用户是否登录,因为多服务器不共享session。该解决方案是 session 数据持久化写入数据库或别的持久层。各种服务收到请求后,都向持久层请求数据。这种方案的优点是架构清晰,缺点是工程量比较大。另外,持久层万一挂了,就会单点失败。

而token是无状态的,token字符串里就保存了所有的用户信息

客户端登陆传递信息给服务端,服务端收到后把用户信息加密(token)传给客户端,客户端将token存放于localStroage等容器中。客户端每次访问都传递token,服务端解密token,就知道这个用户是谁了。通过cpu加解密,服务端就不需要存储session占用存储空间,就很好的解决负载均衡多服务器的问题了。这个方法叫做JWT(Json Web Token)

Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

参考:阮一峰 JSON Web Token 入门教程

总结

  • session存储于服务器,可以理解为一个状态列表,拥有一个唯一识别符号sessionId,通常存放于cookie中。服务器收到cookie后解析出sessionId,再去session列表中查找,才能找到相应session所依赖cookie
  • cookie类似一个令牌,装有sessionId,存储在客户端,浏览器通常会自动添加。
  • token也类似一个令牌,无状态,用户信息都被加密到token中,服务器收到token后解密就可知道是哪个用户。需要开发者手动添加。
  • jwt只是一个跨域认证的方案

如何保护cookie

Secure和HttpOnly

Secure属性意味着把cookie通信限制在加密传输中,指示浏览器只能通过安全/加密连接使用cookie。然而如果一个web服务器在非安全连接中给cookie设置了一个secure属性,这个cookie在发送给用户时仍然可以通过中间人攻击拦截到。因此,为了安全必须通过安全连接设置cookie的Secure属性。

HttpOnly属性指示浏览器除了HTTP/HTTPS请求之外不要显示cookie。这意味着这种cookie不能在客户端通过脚本获取,因此也不会轻易的被跨站脚本窃取。

浏览器设置

大部分浏览器都支持cookie,并且允许用户禁止掉他们。下面是一些常用的选项:

  • 完全允许或者禁止cookie,以便浏览器总是接受或者总是阻止cookie
  • 通过cookie管理器查看或者删除cookie
  • 彻底清除所有的隐私数据,包括cookie

HTTPS中 TLS握手过程简述

参考:三元博客 (传统RSA版本)HTTPS为什么让数据传输更安全?

参考:TLS1.2 握手的过程是怎样的?

Koa 中间件 passport

仿美团项目登录怎么实现的

浏览器缓存 强缓存 和 协商缓存 状态码

(见后文)

结果

一面通过

二面

面经

开篇没有自我介绍,面试官直接说一面问的比较简单,我来考察一下。

1、考你一点操作系统知识,你知道进程和线程吗?它们有什么区别联系?

2、进程间通信有了解过吗?linux文件系统说一说?

3、说说网络吧,你知道子网掩码这个概念吗?这个出错了会怎么办?是访问不了内网还是外网还是怎么?

4、刚刚说了ABC类地址,你知道这个是怎么区分的吗?

5、你怎么学习前端的?

6、那你有了解过 BOM 和 DOM吗?

7、有了解过 map 吗?那你知道 Map和 WeakMap的区别吗?

8、你刚刚讲到了垃圾回收,那你知道v8垃圾回收机制吗?说说

9、你了解class吗?你能模拟实现它的私有属性吗?让他具有 private 功能

10、ES5中的继承实现方式

11、提供类似框架,实现时间过滤器

class DateHelper {
/**
* 将 UNIX timestamp 时间标签转换成 formatter 格式
* @param {Number} 时间标签 e.g. 1463368789
* @param {String} 格式 e.g. 'yyyy-mm-dd hh:MM'
* @returns {String} e.g. '2016-05-16 18:17'
*/
timestampConverter (timestamp: number, formatter: string): string {
// write code here
}
}

12、算法题

给定一个整数数组nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例: 输入: [-2,1,-3,4,-1,2,1,-5,4], 输出: 6

这道题卡了一小会,但最后还是以O(n)时间做出来了

13、设计题

微信扫描二维码登录网页是什么原理,前后两个事件是如何联系的?

场景是一台手机(已经登录了微信),PC端服务器,微信服务器,网页二维码。你怎么处理这四者的关系,不牵扯到网络,中间人攻击层面来讲。

这题想了挺久,不断尝试去套面试官的话,不过后面面试官说我还是猜到了一点点。

14、你还有什么要问我的吗?

请教了一下最后那个设计题的简单思路,我是最后结束了才明白场景居然是那样,不过面试官那边确实有点吵,一些点也没抓住。

参考知乎:微信扫描二维码登录网页是什么原理,前后两个事件是如何联系的?