前端面试题笔记

居中实现方法

  1. 行内元素水平居中

    1
    2
    3
    .container{
    text-align: center;
    }
  2. 已知块级元素的宽水平居中 通过设置左右margin的值为auto

    1
    2
    3
    4
    .container{
    width: 600px;
    margin: 0 auto;
    }
  3. 通过 table-cell 居中

    1
    2
    3
    4
    5
    6
    7
    .box{
    display: table;
    }
    .container{
    display: table-cell;
    vertical-align: middle;
    }
  4. 通过 上下相同padding来达到垂直居中

    1
    2
    3
    .container{
    padding: 20px 0;
    }
  5. 使用flex容器

    1
    2
    3
    4
    5
    .container{
    display: flex;
    align-items: center;
    justify-content: center;
    }
  6. 使用 position

    1
    2
    3
    4
    5
    6
    7
    8
    .box{
    position: relative;
    }
    .container{
    position:relative;
    top: 50%;
    transform: translateY(-50%);
    }

align-items 与 align-self 的区别

align-items是用于外部容器中,对作用于内部所有元素;而align-self是对内部元素的自我作用。


跨域问题

之前用到的跨域问题 感觉前端并没有什么工作 只需后端更改以下配置就可以了 这次可以系统的学习一下

什么是跨域

跨域源自互联网的同源策略,参考该地址的说法,浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。

解决方法

  • CORS(Cross Origin Resource Share)
  • JSONP(JSON with Padding)
  • nginx反向代理

CORS

参考自 https://segmentfault.com/a/1190000017867312?utm_source=tag-newest

CORS 是一种跨域资源共享方案,通过增加一系列请求头和响应头,规范安全地进行跨域数据传输。

如何使用

  • 客户端只需按规范设置请求头。
  • 服务端按规范识别并返回对应响应头,或者安装相应插件,修改相应框架配置文件等。具体视服务端所用的语言和框架而定。

在实际的项目中,往往只需后端在Access-Control-Allow-Origin中设置可以请求的请求头即可。

JSONP 跨域

该方法利用了script,img,iframe等标签可以跨域访问的特性

前端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var script = document.createElement('script');
script.type = 'text/javascript';

// 传参并指定回调执行函数为getData
script.src = 'http://localhost:8080/users?username=xbc&callback=handleData';
document.body.appendChild(script);
// 回调执行函数
function handleData(res) {
data = JSON.stringify(res)
console.log(data);
}
</script>
</body>
</html>

该方法比较像是利用漏洞,而不是正规的方法。

nginx反向代理

通过nginx的proxy_pass把请求代理到其他主机上

1
2
3
4
5
6
7
8
9
10
server{
# 监听9099端口
listen 9099;
# 域名是localhost
server_name localhost;
#凡是localhost:9099/api这个样子的,都转发到真正的服务端地址http://localhost:9871
location ^~ /api {
proxy_pass http://localhost:9871;
}
}

ProxyTable等前端代理

1
2
3
4
5
6
7
8
9
proxyTable: {
'/api': {
target: 'http://www.abc.com', //目标接口域名
changeOrigin: true, //是否跨域
pathRewrite: {
'^/api': '/api' //重写接口
}
}
}

js基本数据类型

  • 基本类型:Stirng、Number、Boolean、Symbol、Undefined、Null
  • 引用类型:Object

防抖、节流

防抖与节流


webpack

ajax原理

ajax技术最核心的依赖是浏览器的XMLHttpRequest对象,使得浏览器可以发出http请求和接收http响应。

http状态码

this指向

  • 函数内部的this指向调用者

  • 匿名函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var name = 'window'
    var person = {
    name :'Alan',
    sayOne:function () {
    console.log(this.name)
    },
    sayTwo:function () {
    return function () {
    console.log(this.name)
    }
    }
    }
    person.sayOne()//Alan
    person.sayTwo()() // window
  1. sayOne的调用者是person对象,所以this指向person。
  2. sayTwo的调用者虽然也是person对象,但是区别在于这次调用并没有打出this而是在全局返回了一个匿名函数

匿名函数是一个没有指针的全局变量,那么它的this指向的就是全局 就是window对象。

  • 箭头函数
1
2
3
4
5
6
7
8
9
10
11
12

var obj= {
name:'obj',
li:function() {
// 这里的上下文是obj.li,所以this指代的就是obj.li的this,所以指向obj
console.log(this); // obj
},
fe: () => {
// 这里的上下文是obj,所以this指代的就是obj的this,所以指向window
console.log(this); // window
}
}

箭头函数内部的this是词法作用域,由上下文确定。因此箭头函数的this是固定的,不再善变。

position 属性以及区别

display

箭头函数与普通函数的区别

  1. 箭头函数体内的this对象,就是定义时所在的对象,而不是运行时所在的对象。
  2. 不可以使用 arguments 对象,该对象在函数体内不存在。
  3. 不可以使用yield命令,因此箭头函数不能作为Genertor函数。
  4. 不可以使用new命令,因为:
    • 没有自己的this,因此不能调用call,apply
    • 没有prototype属性,而new函数需要把构造函数的prototype赋值给新的对象的proto

var let const 作用域

  • var 可以进行变量提升;let、const 不能进行变量提升,只能先定义后使用

  • let、const可以用来创建块作用域变量,const的值是固定的(常量)

  • 同一作用域下let和const不能声明同名变量,而var可以。

  • const:

    1. 一旦声明必须赋值,不能用null占位
    2. 声明后不能再修改。
    3. 如果声明的是复合类型数据,可以修改其属性。

js变量提升

  • js并不是一行一行向下执行的
  • js执行分成两个步骤:
    • 编译(词法解释/预解释)
    • 执行
1
2
3
4
5
6
7
8
9

a = 'ghostwu';
var a;
console.log( a );
//上面这段代码经过编译之后,变成下面这样

var a; //被提升到当前作用域的最前面
a = 'ghostwu'; //留在原地,等待执行
console.log( a );

函数声明会被提升

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
show()
function show(){
console.log(a);
var a = 'test';
}

// 上面的代码经过编译之后会变成这样

function show(){
var a;
console.log(a);
a = 'test';
}

show() // undefined

而函数表达式不会被提升

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

show() // 报错,show is not a function

var show = function(){
console.log(a);
var a = 'test';
}

// 编译后

var show

show()

show = function(){
var a
console.log(a)
a = 'test'
}

当出现同名的函数声明、变量声明的时候,函数声明的优先级会被提升,变量声明会被忽略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
show() // 你好
function show(){
console.log('你好')
}
var show

show = function(){
console.log('hello')
}

// 编译后

function show(){
console.log('你好')
}

show()

show = function(){
console.log('hello')
}

如果有同名的函数声明,后面的会覆盖前面的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
show() // how are you
var show

function show(){
console.log('你好')
}

show = function(){
console.log('hello')
}

function show(){
console.log('how are you')
}

// 编译后

function show(){
console.log('how are you')
}

show()

show = function(){
console.log('hello')
}

https://www.cnblogs.com/ghostwu/p/7287189.html


闭包

什么是闭包

闭包是指有权访问另一个函数作用域中的变量的函数。—— 《JavaScript高级程序设计》

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function createComparisonFunction(propertyName){
return function(object1, object2){
var value1 = object1[propertyName]
var value2 = object2[propertyName]

if(value1 > value2){
return -1;
} else if(value1 < value2){
return 1;
} else {
return 0;
}
}
}

在这个例子中,var value1 = object1[propertName]var value2 = object2[propertyName]访问了外部函数中的变量propertyName,即使这个函数被返回了,而且是在其他地方被调用了,但它依然可以访问变量propertyName之所以还能访问这个变量,是因为内部函数的作用域链包含了createComparisonFunction()的作用域

  • 被引用的变量拷贝到闭包中的时机发生在:被引用的变量离开自己所属的作用域时(循环中的 var / let)

VUE 双向绑定

数据劫持 + 订阅者-发布者模式

ES5 转 ES6

  1. 将代码字符串解析成抽象语法树,即所谓的 AST。
  2. 对 AST 进行处理,在这个阶段可以对 ES6 代码进行相应转换,即转成 ES5 代码。
  3. 根据处理后的 AST 再生成代码字符串。

js中的事件循环(eventLoop)

eventLoop 最主要分三个部分:主线程、宏队列(marcotask)、微队列(microtask)

js的任务队列分为同步队列和异步队列,同步任务都是在主线程中执行的,而异步任务可能会在宏队列或者微队列中执行。

  • 主线程:就是访问到的script标签里面包含的内容,或者是直接访问某一个js文件的时候,里面的可以在当前作用域直接执行的所有内容(执行的方法,new出来的对象等)

  • 宏队列:script(整体代码)、setTimeout、setInterval、UI 渲染、 I/O、postMessage、 MessageChannel、setImmediate(Node.js 环境)

  • 微队列:Promise、 MutaionObserver、process.nextTick(Node.js环境)

  • requestIdleCallback:requesIdleCallback会在当前浏览器空闲时期去依次执行,即当任务队列中的任务都执行完之后才执行。

await 是一个让出线程的标志。await后面的表达式会先执行一遍,将await后面的代码加入到microtask中,然后就会跳出整个async函数来执行后面的代码。

事件循环执行顺序

在事件循环中,每进行一次循环操作称为tick

  1. 在此次 tick 中选择最先进入队列的任务( oldest task ),如果有则执行(一次)
  2. 检查是否存在 Microtasks ,如果存在则不停地执行,直至清空Microtask Queue
  3. 更新 render
  4. 主线程重复执行上述步骤


回流和重绘

html加载时发生了什么

在界面加载时,浏览器会把html代码解析成一个dom tree,接着浏览器把所有样式(用户定义的css和用户代理)解析成样式结构体。

样式结构体和dom tree组合后构建render tree,即dom tree和css等样式结合在一起后,渲染出了render tree。

什么回流

当render tree中的一部分(或全部)因为元素的规模尺寸、布局、隐藏等改变需要重新构建,这就称为回流。

如:

  • 添加或删除可见的DOM元素
  • 元素的位置发生变化-
  • 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
  • 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代。
  • 页面一开始渲染的时候(这肯定避免不了)
  • 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)

每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候一定会发生回流,因为要构建render tree。回流的时候,浏览器会使render tree中受影响的部分失效,并重新构建这部分render tree,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,这部分称作重绘。

什么是重绘

当render tree中的一些元素需要更新,而这些属性只影响元素的外观、风格,而不影响布局,则称为重绘。

优化

最小化重绘和重排

合并多次对DOM和样式的修改,然后一次行处理掉。

1
2
3
4
5
6
7
8
9
10
11
12
const el = document.getElementById('test');
el.style.padding = '5px';
el.style.borderLeft = '1px';
el.style.borderRight = '2px';

// 使用cssText
const el = document.getElementById('test');
el.style.cssText += 'border-left: 1px; border-right: 2px; padding: 5px;';

// 修改CSS的class
const el = document.getElementById('test');
el.className += ' active';

批量修改DOM

当我们需要对DOM做一系列修改的时候,可以通过以下步骤减少回流重绘次数。

  1. 使元素脱离文档流
  2. 对其进行多次修改
  3. 将元素带回文档中

我的理解是先把节点脱离render tree 这样不管怎样瞎整 都不会影响到原来的render tree 也就不会回流重绘 最后再把修改完后的节点带回文档就可以了

避免触发同步布局

由于访问元素的一些属性的时候,会导致浏览器强制清空队列,进行强制同步布局。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function initP() {
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = box.offsetWidth + 'px';
}
}

// 由于在每个循环中 都会获取一次box.offsetWidth,都会触发一次浏览器清空队列 于是把获取box.offsetWidth放在循环外

const width = box.offsetWidth;
function initP() {
for (let i = 0; i < paragraphs.length; i++) {
paragraphs[i].style.width = width + 'px';
}
}

对于复杂的动画效果,使用绝对定位让其脱离文档流

原理和批量修改DOM差不多

css3硬件加速(GPU加速)


构造函数、原型和实例的关系

每个构造函数都有一个原型对象,每个原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

JS 继承

JS对象和对象继承

JS new

  1. 创建一个类的实例创建一个空对象obj,并将该空对象的proto赋值为构造函数的prototype
  2. 初始化实例构造函数被调用并传入参数,并将this指向obj
  3. 返回实例返回obj
1
2
3
4
5
6
7
8
9
function _new(F){
var obj = {__proto__:F.prototype};
return function(){
F.apply(obj,arguments); // 调用构造函数,并将this指向obj
return obj;
}
}

let a = _new(Person)('hello world')

JS中深拷贝和浅拷贝

深拷贝和浅拷贝都只针对引用类型

浅拷贝(shallow copy):只复制指向某个对象的指针,而不复制对象本身,新旧对象共享一块内存;
  深拷贝(deep copy):复制并创建一个一摸一样的对象,不共享内存,修改新对象,旧对象保持不变。


js的隐性转换和显性转换

https://github.com/foreverZ133/blogs/issues/13

JS的类型转换


TCP和UDP的区别。

  • TCP(Transmission Control Protocal):面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源)
  • UDP(User Datagram Protocal):面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快
  • TCP和UDP协议的一些应用例子:
    TCP一般用于文件传输(FTP HTTP 对数据准确性要求高,速度可以相对慢),发送或接收邮件(POP IMAP SMTP 对数据准确性要求高,非紧急应用),远程登录(TELNET SSH 对数据准确性有一定要求,有连接的概念)等等;UDP一般用于即时通信(QQ聊天 对数据准确性和丢包要求比较低,但速度必须快),在线视频(RTSP 速度一定要快,保证视频连续,但是偶尔花了一个图像帧,人们还是能接受的),网络语音电话(VoIP 语音数据包一般比较小,需要高速发送,偶尔断音或串音也没有问题)等等。

HTTP method

  1. GET 通常于请求服务器发送某个资源。
    • 参数可见:参数会被明文可见的显示在url中
    • 数据类型只允许ASCII(不能传递二进制文件)
    • 可以保存书签
    • 可以被缓存:当本次请求允许被缓存时,会将本地资源存在本地cache中,在未过期时直接读取本地cache
    • 参数会保留在浏览器历史记录
    • 请求长度会受限于所使用的浏览器与服务器:不同的浏览器对url的长度有限。
  2. POST 的主要目的是用来提交的,一般是用来添加资源。
    • 参数不可见:参数不会在url中明文显示
    • 不能收藏为书签
    • 不会被缓存
    • 不会保存在浏览器历史中
    • 不限制请求长度
    • 数据类型不限:可以提交文件
    • 请求方式:会先提交HEAD信息,得到100响应后,才会再次将DATA提交。
  3. OPTIONS 主要用来Preflight Request(预检请求)。
  4. HEAD 用于获取报头信息,例如检查cache是否被修改,是否过期。
  5. PUT 与 PATCH 主要用来更新资源。
  6. DELETE 主要用来删除资源。

事件流

git merge和git rebase的区别

js中 运算符 + 的规则

  • 若任何一侧是 string 则两边转换为 string 进行拼接。
  • 否则均转换为 number 进行运算。

实现flatten 函数

1
const flatten = array => array.reduce((acc, cur) => (Array.isArray(cur) ? [...acc, ...flatten(cur)] : [...acc, cur]), [])

函数柯里化

定义

函数柯里化又叫部分求值,维基百科定义如下:

在数学和计算机科学中,柯里化是一种将使用多个参数的函数转换成一系列使用一个参数的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

函数柯里化的作用

函数柯里化的主要作用和特点就是参数复用、提前返回和延迟执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var addEvent = (function(){
if(window.addEventListener){
return function(el, type, fn, capture){
el.addEventListener(type, function(e){
fn.call(el, e)
}, capture)
}
} else {
return function(el, type, fn){
el.attachEvent(type, function(e){
fn.call(el, e)
})
}
}
})()

js引擎在执行该段代码的时候就会进行兼容性判断,并且返回需要使用的事件监听封装函数,这里使用了函数柯里化的两个特点:提前返回和延迟执行。

bind函数的实现也是柯里化的一个典型应用,使用了柯里化的两个特点:参数复用和延迟执行。

1
2
3
4
5
6
7
8
Function.prototype.bind = function(){
var fn = this;
var args = Array.prototype.slice.call(arguments);
var context = args.shift();
return function(){
return fn.apply(context,args.concat(Array.prototype.slice.call(arguments)))
}
}

实现函数柯里化

函数柯里化就是封装[ 一系列的处理步骤 ],通过闭包将参数集中起来计算,最后把需要处理的参数传进去

有一道面试题:如何实现multi(2)(3)(4) = 24

通用版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function curry(fn){
// 获取参数
var args = Array.prototype.slice.call(arguments,1)
return function(){
var newArgs = args.concat(Array.prototype.slice.call(arguments))
return fn.apply(this,newArgs)
}
}

var multiFn = function(a, b, c){
return a * b * c;
}

var multi = curry(multiFn)

multi(2,3,4) // 24

虽然结果得到了正确的24,但不是题目要求那样的mulit(2)(3)(4)

改进版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function curry(fn, args){
var length = fn.length;
var args = args || []

return function(){
var newArgs = args.concat(Array.prototype.slice.call(arguments))

if(newArgs.length < length){
return curry.apply(this, fn, newArgs)
} else {
return fn.apply(this,newArgs)
}

}
}

function multiFn(a, b, c) {
return a * b * c;
}
var multi = curry(multiFn);
console.log(multi(2)(3)(4)) // 24
console.log(multi(2, 3)(4)) // 24
console.log(multi(2)(3, 4)) // 24

但是这种方法太过于局限,需要预先知道求值的参数个数。

优化版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function multi(){
var args = Array.prototype.slice.call(arguments)
var fn = function(){
var newArgs = args.concat(Array.prototype.slice.call(arguments))

return multi.apply(this, newArgs)
}

fn.toString = function(){
return args.reduce((a,b)=>(
a * b
))
}

return fn
}

在这个multi函数中定义了一个参数fn,并让fn赋值为返回 执行multi函数 的函数,导致了不管调用几次multi函数,都会返回fn函数(第一次调用multi函数,返回了fn,再次调用fn,先执行multi.apply(this, newArgs),该结果返回的还是fn,并且让参数复用,最后打印出来fn重写的toString函数)


实现call、apply

1
2
3
4
5
6
7
8
9
10
11
12
Function.prototype.myCall= function(context = window){
var context.fn = this //在context下增加一个属性,赋值为该函数。

var args = [...arguments].slice(1) // call 接受一系列参数

var result = context.fn(...args) // 执行该函数,由于该函数在执行时的上下文为context,因此该函数体内的this也就指向了context,完成了call的任务。

delete context.fn // 删除该属性

return result // 返回结果

}

apply 大体上与 call 相似, 不同的是 call 接受一系列参数,而 apply 接受的是一个参数数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Function.prototype.myApply = function(context = window){
var context.fn = this //在context下增加一个属性,赋值为该函数。

var result

// 判断 arguments[1] 是否为undefined
if(arguments[1]){
// 不为undefined
result = context.fn(arguments[1])
} else {
// 为undefined
result = context.fn()
}

delete context.fn

return result

}

CommonJs 与 ES6 Module 的区别

  • CommonJs 导出的基本变量是值,引用类型变量是引用地址;ES6 Module导出的是变量的绑定(export default是特殊的)。
  • CommonJs 是单个值导出;ES6 Module可以导出多个。
  • CommonJs 是动态语法可以写在判断语句中;ES6 Module是静态语法只能写在顶层。
  • CommonJsthis是当前模块;ES6 Modulethisundefined

Cookie、loaclStorage和sessionStorage

大小限定在4kb左右。主要用途在保存用户信息。

HTTP请求都会携带cookie,会带来性能问题

作用域:

  • 不可以在不同的浏览器
  • 可以是不同的tab页
  • 必须同源
  • 要同一个路径下或者子路径下

localStorage

除非被清除,否则永久有效。大小一般为5Mb

仅在客户端保存,不与服务端通信。

作用域:

  • 不可以在不同的浏览器
  • 可以是不同的tab页
  • 必须同源

sessionStorage

仅在当前会话下有效,关闭页面或者浏览器会被清除。大小一般为5Mb

仅在客户端保存,不与服务端通信。

作用域:

  • 不可以在不同的浏览器
  • 不可以是不同的tab页
  • 必须同源

CSRF和XSS

CSRF(Cross-Site Request Forgery)

定义:攻击者盗用你的身份,以你的名义发送恶意请求,对服务器来说该请求是完全合法的。

防御:

  • 验证HTTP Referer

  • 添加token并验证

XSS(Cross-Site Scripting)

定义:XSS攻击就是攻击者通过各种办法,在用户访问的网页中插入自己的脚本,让其在用户访问网页时在其浏览器中进行执行。攻击者通过插入的脚本的执行,来获得用户的信息,比如cookie,发送到攻击者自己的网站(跨站了)。所以称为跨站脚本攻击。

防御(总体思路::对输入(和URL参数)进行过滤,对输出进行编码。):

  • 对输入和URL参数进行过滤(白名单和黑名单)
  • 对输出进行编码
  • Cookie设置HttpOnly