北京头条前端面经
# 11/15 头条前端
## 两数之和
O(n)的做法
### 哈希表
```js
const twoSum = function (nums, target) {
const arrMap = new Map()
for (let i = 0; i < nums.length; i++) {
const result = target - nums[i];
if (arrMap.has(result)) {
return [arrMap.get(result), i]
}
arrMap.set(nums[i], i)
}
};
```
### 双指针
```js
const twoSum = function (nums, target) {
nums.sort((a,b)=>a-b)
let low = 0;
high = nums.length - 1;
while (low < high) {
let sum = nums[low] + nums[high];
if (sum < target) {
low ++
} else if (sum > target) {
high --
} else {
return [low+1, high+1]
}
}
};
```
## 前端存储
### cookie
最早存储是用cookie,不超过4k。
存每次请求都要带的信息。每次请求都自动写在http请求头里面
为了辨识身份、session跟踪等。类似于通行证。
域名隔离。同源的网站cookie都能读写。
domain是域名,path是路径,两者加起来就构成了 URL。domain和path一起来限制 cookie 能被哪些 URL 访问、发送。
在设置的过期时间之前,一直在。
cookie有状态,服务器会记录session信息。
### token
cookie有跨站请求伪造CSRF的问题,所以出现了token
只要绕开同源策略,如form之类,就可以用其他域的cookie来向其他域发送请求,因为cookie是自动添加到请求头的
token是临时的证书签名。不会自动添加,其他域也无法访问token
token无状态,服务器不记录。带上token就是通过。登出客户端就销毁token
可以存在localstorage或cookie中
- 可以防止跨站请求伪造csrf
- 可以被多个域使用,需要多个服务授权的应用很适合。
### localStorage
cookie和token都存不下,于是出现了local/session storage。可以达到5M
除非删除,不然一直在浏览器当中。关闭了也在。
同一域名***享。即,相同协议、主机名、端口,才能读写。
### sessionStorage
可以达到5M。
临时存储。同源网页之间数据共享,但关闭tab或浏览器就没了。
session就是一次和同源网站的链接,刷新或进入同源另一页面,session依然存在。
### indexDB
为了存储更大量数据。
cookie和local/session storage都不够用了。而且只能存string。
同源,只能访问同域存储的数据。
#### 事务:
- 原子性、一致性:保证数据库操作,要么全部成功,要么全部失败。
如果修改多条数据,前面的成功了,有一条失败了,那么回滚返回错误,一条也不修改。
- 隔离性:并发执行,一个事物不会影响其他事务。
- 持久性:已提交的就永久保存。
## 跨域怎么解决
### 同源策略
- 协议相同
- 域名相同
- 端口相同
举例来说,`http://www.example.com/dir/page.html`这个网址,协议是`http://`,域名是`www.example.com`,端口是`80`(默认省略)
为了保证用户信息的安全,防止恶意的网站窃取数据。
同源即要设置相同的document.domain
### 非同源限制:
- Cookie、LocalStorage 和 IndexDB 无法读取。
- DOM 无法获得。
如iframe和window.open打开的窗口,与父窗口无法通信。
不然可以做假网站,直接获取用户名密码
- AJAX 请求,只能发送同源网址。
### 非同源,解决跨域窗口通信:
- 片段识别符(fragment identifier),URL的`#`号后面的部分
改变它页面不会刷新
- window.name
无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。
- 跨文档通信API(Cross-document messaging),`window.postMessage`方法
允许跨窗口通信,不论这两个窗口是否同源。
还能读取其他窗口的localstorage
### 绕开ajax跨域限制:
- 服务器***,请求同源服务器,然后服务器再请求外部服务
万能方法
- jsonp
图像ping
- websocket
- cors
#### 原理
一个域名的 JS ,在未经允许的情况下,不得读取另一个域名的内容。但浏览器并不阻止你向另一个域名发送请求。
对方觉得不安全可以丢一边不管,但不能让你未经允许获取信息。
跨域问题只是浏览器V8引擎强加给js的规则而已,世界本无跨域限制。是浏览器强制不允许js访问别的域,但是浏览器却没有限制它自己。
比如说img标签可以加载任何域的图片,script可以加载任何域的js。再比如说你不能从前端js去调淘宝的接口获取ip对应的城市地址信息,但是你可以手敲在浏览器地址栏,直接打开。
#### jsonp
网页动态插入一个`<script>`元素,向跨域服务器请求JSON数据。服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。
这种做法不受同源政策限制,目前最流行。
只支持get
```js
function foo(data) {
// 客户端要定义一个foo函数,来接受返回值
console.log('Your public IP address is: ' + data.ip);
};
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://example.com/ip?callback=foo');
// callback指定回调参数名字,是必须的。
}
// 后端识别并返回:
foo({
"ip": "8.8.8.8"
});
// 浏览器收到后立即执行foo()
```
`<script>`请求的脚本,直接作为代码运行。只要浏览器定义`foo()`,就会立即调用。
json数据是js对象,而不是字符串,因此不用json.parse
##### img跨域
img不受同源策略影响,可以跨域引用资源。通过src属性跨域。
- 可以用来实现,跟踪用户点击页面或动态广告曝光次数
- 只支持get,只能单向通信,浏览器无法访问服务器响应文本。
#### websocket
WebSocket是一种通信协议,使用`ws://`(非加密)和`wss://`(加密)作为协议前缀。
该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。
协议里面有一个origin,服务器可以根据这个判断,如果域名在白名单内,就能通信。
### cors
CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写,是跨源AJAX请求的根本解决方法。相比JSONP只能发`GET`请求,CORS允许任何类型的请求。
代码和ajax完全一样。浏览器如果发现ajax跨源,就会自动添加附加头部信息。主要是服务器的支持。
#### 简单请求/非简单请求
条件:
- 请求方法:head/get/post之一
- 头部信息不超出以下字段
- accept
- accept-language
- content-language
- last-event-id
- content-type: application/x-www-form-urlencoded, multipart/form-data, text/plain
#### 简单请求
简单请求直接发出cors请求,在头中增加origin字段,说明本次请求来自哪个源(协议 + 域名 + 端口),服务器判断是否回应。
如果浏览器发现,回应的头信息没有包含`Access-Control-Allow-Origin`字段,就知道出错了,从而抛出一个错误,被`XMLHttpRequest`的`onerror`回调函数捕获。
注意,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
CORS请求默认不发送Cookie和HTTP认证信息。
如果要把Cookie发到服务器,一方面要服务器同意,指定`Access-Control-Allow-Credentials`字段;另一方面,开发者必须在AJAX请求中打开`withCredentials`属性。
#### 非简单请求
种对服务器有特殊要求的请求,比如请求方法是`PUT`或`DELETE`,或者`Content-Type`字段的类型是`application/json`。
在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。先问问自己是否在白名单中。
## 三列布局,左右定宽,中间自适应屏幕宽度
### float
左右定宽,右边元素要在中间元素之前
```html
<div>
<div class="left"></div>
<div class="right"></div>
<div class="middle"></div>
</div>
```
```css
.left , .right{
width:60px;
height:30px;
}
.left{
float:left;
}
.middle{
margin-right:70px;
margin-left:70px;
height:30px
}
.right{
float:right;
}
```
### flex
父元素display:flex,左右定宽,中间flex:1 自适应
```html
<div class="container">
<div class="left"></div>
<div class="middle"></div>
<div class="right"></div>
</div>
```
```css
.container{
display: flex;
}
.left , .right{
width: 60px;
height:30px;
}
.middle{
flex:1;
margin-left:10px;
margin-right:10px;
}
```
### grid
```html
<div class="container">
<div class="left"></div>
<div class="middle"></div>
<div class="right"></div>
</div>
```
```css
.container{
display:grid;
width:100%;
grid-template-rows:30px;
grid-template-columns:60px auto 60px
}
.middle{
margin-left:10px;
margin-right:10px;
}
```
## 原型链
求输出结果:
```js
Object.prototype.a='Object'
Function.prototype.a = 'Function'
function Person(){}
var child = new Person()
console.log(Person.a)
console.log(child.a)
console.log(child.__proto__)
console.log(child.__proto__.__proto__)
console.log(child.__proto__.__proto__.constructor)
console.log(child.__proto__.__proto__.constructor.constructor)
console.log(child.__proto__.__proto__.constructor.constructor.constructor)
```
### 初始化
初始状态是这样的:

`Function()`new了`Object()`出来。然后他们各自的`.prototype`,也是各自new出来的。
这里面的指向有几个规则,也有几个特例:
#### 规则
1. 构造函数/对象,都是对象。
只要是对象,下面就有两个属性:`constructor`和`__proto__`
- `.constructor`:谁new的我 我就指谁。
这是用来记录谁是构造函数的
- `.__proto__`:谁new的我 我指他`.prototype`
顺着`constructor`找到爹,然后在下面找到他的`.prototype`,指向他。
这是用来记录,该去哪继承方法/属性的。
自己下面找不到方法/函数,就去`.__proto__`的指针地址里面找。
2. 构造函数下面多一个属性,叫`.prototype`
这只有构造函数才有,所以`Function.prototype`相当于`Function().prototype`
构造函数在出生的时候,同时会自动new一个对象出来,`.prototype`会指向这个对象
这个对象,对于这个构造函数没啥用,主要是给new出来的孩子们用的。即其他对象找不到了方法/属性,就通过`.__proto__`找到这里,在这里面继续找。类似于存放给人继承的东西的地方
#### 特例
1. `Function()`非常特殊,几乎整个都是特例
- `Function()`可以看作是自己new了自己,因此`.constructor`指的是自己,`.__proto__`指的也是自己的`.prototype`
- `Function()`new出的`Function.prototype`,是个函数
而其他构造函数,new出来的都是对象,图中我以颜***分。
- `Function.prototype.__proto__`应该是`Function.prototype`才是呀,但是这样就变成死循环了,自己下面找不到的方法/属性,还是找不到
所以就指向了`Object.prototype`,去继承了`Object.prototype`的方法
2. `Object.prototype.__proto__`,指向的是`null`
理应是指向`Object.prototype`的,但那样的话又死循环了,于是就让它指向null,他都没有的方法就全世界都没有了
这下`Object.prototype`就变成了万物方法/属性之源了
3. 由`Function`new出来的构造函数,它的`.prototype`的`.__proto__`,指向的是`Objet.prototype`,这个下面再说
感觉就是,遇到死循环不知道该继承谁,就去继承`object.prototype`
然后`Object.prototype.__proto__`就去继承`null`
```js
Object.prototype.a='Object'
Function.prototype.a = 'Function'
```
这是在两个`.prototype`下面多加了两个属性,像这样:

```js
function Person(){}
```
这是在通过`Function()`,构造了一个`Person()`函数。相当于:
`var Person = new Function()`

然后套路还是一样的,`.constructor`指向new自己的人,`.__proto__`指向爹的`.prototype`
但是`Person.prototype.__proto__`,理应指向,`Person.prototype`,但那样的话又变成死循环了
所以就按照特例3,把它指向了`Object.prototype`
```js
var child = new Person()
```
构造函数`Person()`新建了一个`child`

这个就,没有特例那种幺蛾子
全局:

```js
console.log(Person.a)
// Function
// Person()下面找不到,沿着.__proto__找它爹Function.prototype,里面找到了a=Function,就是结果了。
console.log(child.a)
// Object
// child下面没有a,沿着.__proto__找爹,Person.prototype下面也没有a,再沿着Person.prototype.__proto__找爹,找到了Object.prototype下面有个a是Object,输出
console.log(child.__proto__)
// {constructor: Person()}
// 也就是Person.prototype
console.log(child.__proto__.__proto__)
// {constructor: Object()}
// 也就是Object.prototype
console.log(child.__proto__.__proto__.constructor)
// Object()
console.log(child.__proto__.__proto__.constructor.constructor)
// Function()
console.log(child.__proto__.__proto__.constructor.constructor.constructor)
// Function()
```
## 闭包
实现一个foo函数,返回自身调用次数:
```js
a=foo()
b=foo()
c=foo()
// 此时 a===1, b===2, c===3
foo.reset()
d=foo()
// d===1
```
```js
var helper = function() {
let count = 0
return function() {
count++
console.log("count", count)
}
}
var foo = helper()
```
#字节跳动##校招##前端工程师##面经#
查看7道真题和解析