[学习笔记]快速搞定前端技术一面 匹配大厂面试要求

简介

HTML、CSS

HTML语义化

image-20210228104000746

  • 让人更容易读懂(增加代码可读性)
  • 让搜索引擎更容易读懂(SEO)

块状元素、内联元素

  • 块状元素:独占一行。display:block/table div h1 h2 table ui ol p 等
  • 内联元素:不独占,向后排列。display:inline/inline-block; span img input button

盒模型宽度

image-20210228120531175

  • offsetWidth = (内容宽度 + 内边距 + 边框),无外边距。
  • 答案:122px

box-sizing:border-box

1
2
3
4
5
6
7
8
9
#div1 {
width: 100px;
padding: 10px;
border: 1px solid;
margin: 10px;
box-sizing: border-box;
}
// 加了border-box之后,offsetWidth为width100px

margin相关

margin 纵向重叠

image-20210228121356711

  • 相邻元素的margin-top和margin-bottom会发生重叠(取最大值)

  • 空白内容的 <p></p> 也会重叠

  • 答案:15px

image-20210228122242124

margin 负值

  • margin-top 和 margin-left 负值,元素向上、向左移动
  • margin-right负值,右侧元素左移,自身不受影响;margin-bottom负值,下方元素上移,自身不受影响

line-height 继承问题

image-20210301085508334

  • 写具体数值,如30px,继承该值

    image-20210301121911375

  • 写比例,如2/1.5,继承该比例

    image-20210301122100088

  • 写百分比,如200%,继承计算出来的结果

    image-20210301122212572

css - 响应式

rem

rem是一个长度单位;

  • px,绝对长度单温;

  • em,相对长度单位,相对于父元素;

  • rem,相对长度单位,相对于根元素,让用于响应式布局。

响应式布局常用方案

  • media-query,根据不同屏幕宽度设置根元素 font-size
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
@media only screen and(max-width:374px) {
/* iphone5 或更小尺寸,一iphone5的宽度 320px 比例设置 font-size */
html {
font-size: 86px;
}
}
@media only screen and(min-width :375px) and(max-width: 413px) {
/* iphone6/7/8 ,依此为基准 */
html {
font-size: 100px;
}
}
@media only screen and(min-width:414px) {
/* iphone6p 或更大尺寸,以iphone6p的宽度 414px 比例设置 font-size */
html {
font-size: 110px;
}
}
body {
font-size: 0.16rem;
}
#div1 {
width: 1rem;
background-color: #ccc;
}

JS 基础

变量类型和计算

值类型、引用类型

1
2
3
4
5
6
7
8
9
10
11
// 值类型
let a = 100
let b = a
a = 200
console.log(b) // 100

// 引用类型
let c = { age: 18 }
let d = c
d.age = 20
console.log(c) // {age:20}

值类型存储于

引用类型存储于

应用类型存储的是内存地址,d = c,表示d和c指向同一块内存

1
2
3
4
5
6
7
8
// 常见引用类型
const obj = { x:100 }
const arr = ['a','b','c']

const n = null // 特殊引用类型,指针指向为空指针

// 特殊引用类型,但不用于存储数据,所以没有拷贝,复制函数
function fun() {}

typeof

  • 识别所有值类型
  • 识别函数
  • 判断是否是引用类型(不可再细分、不能区分数组、对象、null
1
2
// 区分数组 和 对象
[] instanceof Array --> true

image-20210302104739491

深拷贝

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
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用,解决多级对象\数组
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}

变量计算-类型转换

字符串拼接
1
2
3
4
100 + 10 // 110 number
100 + '10' // '10010' string
true + '1' // 'true1' 字符串
true + 1 // 2 number
== 运算符
1
2
3
4
5
6
100 == '100' // true
0 == '' // true
0 == false // true
false == '' // true
null == undefined // true
null == false // false
1
2
3
4
5
6
// 日常使用中 除了 == null 之外,其他都一律使用 ===

const obj = { x: 100 }
if (obj.a == null) {
// 相当于 if (obj.a === null || obj.a === undefined)
}
truly变量、falsely变量
  • truly 变量:!!a === true 的变量
  • false 变量:!!a === false 的变量
1
2
3
// 举例、1 两次去反、1 是 truly 变量
!1 = false
!!1 = !false => ture
1
2
3
4
5
6
7
// 以下是 falsely 变量。除此之外都是 truly 变量
!!0 === false
!!NaN === false
!!'' === false
!!null === false
!!undefined === false
!!false === false
  • if 语句判断就是 truly 变量;如 if('')1;else 0; // 0

原型和原型链

类型判断–instanceof

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Object 是所有类的父类,基类
class People {
// ……
}

class Student extends People {
// ……
}

const xialuo = new Student()

console.log(xialuo instanceof Student) // true
console.log(xialuo instanceof People) // true
console.log(xialuo instanceof Object) // true

console.log([] instanceof Array) // true
console.log([] instanceof Object) // true
console.log([] instanceof Object) // true

原型–隐士原型、显示原型

1
2
3
4
5
6
// class 实际上是函数 ,语法糖
typeof People // 'function'

console.log( xialuo.__proto__) // 隐式原型
console.log( Student.prototype ) // 显示原型
xialuo.__proto__ === Student.prototype // ture

image-20210303102314479

  • 每个 class 都有显示原型 prototype
  • 每个实例都有隐式原型 __proto__
  • 实例的 __proto__ 执行对应 class 的 prototype

基于原型和执行规则

  • 获取属性 xialuo.name 或执行方法 xialuo.sayhi() 时
    • 先在自身属性和方法寻找
    • 找不到,去 __proto__ 总查找

原型链

1
2
// class Student 是 class People 的子类
People.prototype === Student.prototype.__proto__

image-20210303103401585

1
2
3
xialuo.hasOwnProperty('name') // true
xialuo.hasOwnProperty('sayHi') // false
xialuo.hasOwnProperty('hasOwnProperty') // false

instanceof 原理

1
2
xialuo instanceof People  // true
// 按照上图 xialuo 的原型链,去找 People 的显示原型 prototype 能找到 true

提示

  • class 是 ES6 语法规范,由 ECMA 委员会发布
  • ECMA 只规定语法规则,即只规定代码的书写规范,不规定如何使用
  • 以上实现方式 为 V8 引擎的实现方式,也是主流实现方式

作用域和闭包

作用域、自由变量

1
2
3
4
5
6
7
8
9
10
11
// 创建 10 个 `<a>` 标签,点击的时候弹出来对应的序号
let a
for (let i = 0; i < 10; i++) {
a = document.createElement('a')
a.innerHTML = i + '<br>'
a.addEventListener('click', function (e) {
e.preventDefault()
alert(i)
})
document.body.appendChild(a)
}

image-20210303115729445

  • 全局作用域
  • 函数作用域
  • 块级作用域(ES6新增)
自由变量
  • 一个变量在当前作用域没有定义,但被使用了
  • 向上级作用域,一层一层一次寻找,指导找到为止
  • 如果找到全局作用域都没找到,则报错 xx is not defined

闭包

  • 作用域应用的特殊情况,有两种表现
    • 函数作为参数被传递
    • 函数作用返回值被返回
1
2
3
4
5
6
7
8
9
10
11
// 函数作为返回值
function create() {
let a = 100
return function () {
console.log(a)
}
}
let a = 200
let fn = create()
fn() // 100
// create()()
1
2
3
4
5
6
7
8
9
10
// 函数作为参数
function print(fnn) {
let b = 200
fnn()
}
let b = 100
function fnn() {
console.log(b)
}
print(fnn) // 100

闭包:自由变量从函数定义的地方开始找,依次向上级作用域查找。

闭包的实际应用
  • 隐藏数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function createCache() {
const data = {} // 闭包中的数据,被隐藏,不被外界访问
return {
set: function (key, val) {
data[key] = val
},
get: function (key) {
return data[key]
},
}
}

const c = createCache()
c.set('a', 100)
// 在外部无法直接访问data中的数据
// console.log(data.a) // data is not defined
console.log(c.get('a'))

this 赋值问题

  • 常见情况

    • 作为普通函数
    • 使用 call apply bind
    • 作为对象方法被调用
    • 在 class 方法中调用
    • 箭头函数
  • this 在各个场景中取值:函数执行的时候确定的,不是函数定义的时候确认的

1
2
3
4
5
6
7
function fn1() {
console.log(this)
}
fn1() // window
fn1.call({ x:100 }) //{ x: 100 }
const fn2 = fn1.bind({ x: 200 })
fn2() // {x: 200}

image-20210303123535183

call apply bind 简介-区别

改变函数执行时的上下文,即改变函数运行时的 this 指向

  • call 和 apply

    1
    2
    obj.call(thisObj, arg1, arg2, ...);
    obj.apply(thisObj, [arg1, arg2, ...]);

两者作用一致,都是把obj(即this)绑定到thisObj,这时候thisObj具备了obj的属性和方法。或者说thisObj『继承』了obj的属性和方法。绑定后会立即执行函数。

唯一区别是apply接受的是数组参数call接受的是连续参数

bind的使用

1
obj.bind(thisObj, arg1, arg2, ...);

把obj绑定到thisObj,这时候thisObj具备了obj的属性和方法。与call和apply不同的是,bind绑定后不会立即执行。需要调用

JS 异步

异步基础

单线程和异步

  • JS 是单线程语言,只能同时做一件事
  • 浏览器和 nodejs 已支持 JS 启动进程,如 Web Worker
  • JS 和 DOM 渲染共用同一个线程,因为 JS 可修改 DOM 结构
  • 异步不会阻塞代码运行,同步会阻塞代码执行

回调地狱-callback hell

image-20210304133154583

Promise 基础

image-20210304133507684

image-20210304133521043

异步进阶

JS 如何执行

  • 从前到后,一行一行执行
  • 如果某一行执行错误,则停止下面代码的执行
  • 先把同步代码执行完,在执行异步

event loop(事件循环/事件轮循)

JS 是单线程运行的,异步要基于回调来实现;event loop 就是异步回调的实现原理

image-20210304181153409

  • event loop 过程
    • 同步代码,一行一行放在 Call StacK 执行
    • 遇到异步,会先“记录”下,等待时机(定时、网络请求)
    • 时机到了,就移动到 Callback Queue
    • 如 Call Stack 为空(即同步代码执行完)Event Loop 开始工作
    • 轮询查找 Callback Queue,如有则移动到 Call Stack 执行
    • 然后继续轮询查找(永动机一样)
DOM 事件和 event loop
  • 异步(setTimeout,ajax等)使用回调,基于 event loop

  • DOM 事件(dom事件不是异步)也使用回调,基于 event loop

Promise 进阶

  • Promise 的三种状态
    • pending
    • resolved
    • rejected
  • resolved 触发 then 回调,rejected 触发 catch 回调
  • then 和 catch 改变状态(没有报错,都返回resolved)
    • then 正常返回 resolved,里面有报错则返回 rejected
    • catch 正常返回 resolved,里面有报错则返回 rejected

image-20210304204607634

image-20210305143458087

async/await

  • 产生背景

异步回调 callback hell —> Promise then catch 链式调用,但也是基于回调函数 —> async\await是同步语法,彻底消灭回调函数

1
2
3
4
// 感叹号,用来分割上一行(上一行没分号结尾的话)
!(()=>{
console.log('')
})()
  • async/await 只是一个语法糖
async/await 与promise 关系
  • await 相当于 Promise then
  • promise catch 可以使用 try catch 代替
  • async 封装 promise 返回 promise

执行循序例题

1
2
3
4
5
6
7
8
9
10
11
async function async1() {
console.log('async start')
await async2() // await 后面,都可以看做是 callback 里的内容,即异步
console.log('async1 end')
}
async function async2() {
console.log('async2')
}
console.log('script start')
async1()
console.log('script end')

image-20210305173107739

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
async function async1() {
console.log('async start')
await async2()
console.log('async1 end')
await async3()
console.log('async1 end 2')
}
async function async2() {
console.log('async2')
}

async function async3() {
console.log('async3')
}
console.log('script start')
async1()
console.log('script end')

image-20210305174406323

for … of

  • for … in 以及 forEach for 是常规的同步遍历
  • for … of 常用于异步的遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function muti(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * num)
}, 1000)
})
}

const nums = [1, 2, 3]
// 同步遍历
nums.forEach(async (i) => {
const res = await muti(i)
// 一瞬间同时打印1,4,9
console.log(res)
})

// 异步遍历
!(async () => {
for (let i of nums) {
const res = await muti(i)
// 隔一秒打印一个
console.log(res)
}
})()

宏任务 macroTask 和微任务 microTask

1
2
3
4
5
6
7
8
9
10
console.log('start')  // 1
setTimeout(() => {
console.log('宏任务') // 4
})

Promise.resolve().then(() => {
console.log('微任务') // 3
})

console.log('end') // 2
  • 宏任务:setTimeout seInterval Ajax DOM 事件;DOM 渲染后触发;微任务是 ES6 语法规定的

  • 微任务:Promise async/await;DOM 渲染前触发;宏任务是由浏览器规定的

  • 微任务执行时机比宏任务要早

event-loop 和 dom 渲染的关系

image-20210305193518694

  • 每次 Call Stack 清空(即每次轮询结束),即同步任务执行完
  • 都是 DOM 重新渲染的机会,DOM 结构如有改变则重新渲染
  • 然后再去触发下一次 Event Loop
1
2
3
4
5
6
7
8
9
10
11
12
<div id="container"></div>

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container').append($p1).append($p2).append($p3)
console.log('length', $('#container').children().length)
alert('本次 call stach 结束,DOM 结构已更新,但尚未触发渲染')
// (alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果)
</script>
image-20210305202246153

题目

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
27
28
async function async1() {
console.log('async1 start') // 2
await async2()
console.log('async1 end') // await 后面是回调,相当于then里面,微任务 6
}

async function async2() {
console.log('async2') // 3
}
console.log('script start') // 1

setTimeout(function () { // 宏任务
console.log('setTimeout') // 8
}, 0)

async1() //

// Promise 声明中的代码会立即执行
new Promise(function (resolve) {
console.log('promise1') // 4
resolve()
}).then(function () {
console.log('promise2') // 7
})

console.log('scrtpt end') // 5 所有同步代码执行完毕、call stack清空,开始 event loop

// 同步代码 -> 微任务 -> 尝试触发DOM渲染 -> 触发 Event Loop 执行宏任务

image-20210305203855500

JS Web API

  • JS 基础知识,规定语法 ECMA 262 标准
  • JS Web API,网页操作的 API W3C 标准

DOM

  • DOM:Document Object Model

  • Vue 和 React 框架应用广泛,封装了 DOM 操作

DOM 节点操作

1
2
3
4
const div1 = document.getElementById('div1') // 元素
const divLIst = document.getElementsByTagName('div') // 集合
const containerList = document.getElementsByClassName('.container') // 集合
const PList = document.querySelectorAll('p') // 集合

property:修改 dom 结构的变量,不表现在 html 中

1
2
3
4
5
6
7
const pList = document.querySelectorAll('p')
const p1 = pList[0]
// property 形式
p1.style.width = '100px'
console.log(p1.style.width)
p1.className = 'red'
console.log(p1.className)

attribute:修改 dom 结构的属性,表现在 html 中

1
2
3
4
5
6
const pList = document.querySelectorAll('p')
const p = pList[0]
p.getAttrbute('data-name')
p.setAttribute('data-name','imooc')
p.getAttribute('style')
p.setAttribute('style','font-size:30px')
  • property:修改对象属性,不会体现到 html 结构中
  • attribute:修改 html 属性,会修改 html 结构
  • 两者都有可能引起 DOM 重新渲染;优先选择 property

DOM 结构操作

  • 新增/插入节点
1
2
3
4
5
6
7
8
const div1 = document.getElementById('div1')
// 添加新节点
const p1 = document.createElement('p')
p1.innerHTML = 'this is p1'
div1.appendChild(p1) // 添加新创建的元素
// 移动已有节点。移动
const p2 = document.getElementById('p2')
div1.appendChild(p2)
  • 获取子元素列表、获取父元素
1
2
3
4
5
6
// 获取子元素列表
const div1 = document.getElementById('div1')
const child = div1.childNodes
// 获取父元素
const div1 = document.getElementById('div1')
const parent = div1.parentNode
  • 删除节点
1
2
3
const div1 = document.getElementById('div1')
const child = div1.childNodes
div1.removeChild(child[0])

DOM 性能

  • DOM 操作非常 “昂贵”,避免频繁的 DOM 操作

  • DOM 查询作缓存

image-20210306115222530

  • 将频繁操作改为一次性操作

image-20210306115307526

BOM

  • BOM:Browser Object Model
1
2
3
4
5
6
7
// navigator、浏览器标识
const ua = navigator.userAgent
const isChrome = ua.indexOf('Chrome')
console.log('isChrome')
// screen 屏幕信息
screen.width
screen.height

location、history

1
2
3
4
5
6
7
8
location.href // "https://coding.imooc.com/class/chapter/400.html#Anchor"
location.protocol // 'https'
location.pathname // '/class/chapter/400.html'
location.search // url参数
location.hash // '#Anchor'
// history
history.back() // 后退
history.forward() // 前进

事件

事件绑定

1
2
3
4
5
const btn = document.getElementById('btn1')
btn.addEventListener('click',event=>{
console.log('clicked')
event.preventDefault() // 阻止默认行为
})

事件冒泡

  • 基于 DOM 树形结构
  • 事件会顺着触发元素向上冒泡
  • 应用场景:事件代理
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
<body>
<div id="div1">
<p id="p1">激活</p>
<p id="p2">取消</p>
<p id="p3">取消</p>
<p id="p4">取消</p>
</div>
<div id="div1">
<p id="p5">取消</p>
<p id="p6">取消</p>
</div>
</body>
<script>
function bindEvent(elem, type, fn) {
elem.addEventListener(type, fn)
}
const p1 = document.getElementById('p1')
const body = document.body
bindEvent(p1, 'click', (e) => {
// e.stopPropagation() // 阻止冒泡;注释掉这一行,体会冒泡
alert('激活')
})
bindEvent(body, 'click', (e) => {
alert('取消')
})
</script>

事件代理

扩展阅读:掘金:js中的事件委托或事件代理详解

image-20210306191353215

Ajax

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 手写一个简单的 ajax
function ajax(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText))
} else {
reject(new Error(xhr))
}
}
}
xhr.send()
})
return p
}

XMLHttpRequest

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
27
// get 请求
const xhr = new XMLHttpRequest()
xhr.open('GET', '/data/test.json', true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
alert(xhr.responseText)
}
}
}
xhr.send(null)

// post 请求
const xhr = new XMLHttpRequest()
xhr.open('POST', '/login', true)
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
alert(xhr.responseText)
}
}
}
const postData = {
userName:'zhangsan',
password:'xxx'
}
xhr.send(JSON.stringify(postData))
xhr.readySate
  • 0—未初始化,还没有调用 send() 方法
  • 1—载入,已调用 send() 方法,正在发送请求
  • 2—载入完成,sedn() 方法执行完成,已经接收到全部相应内容
  • 3—交互,正在解析相应内容
  • 4—完成,相应内容解析完成,可以在客户端调用
xhr.status
  • http 状态码
  • 2xx:表示成功处理请求,如 200
  • 3xx:需要重定向,浏览器直接跳转,如 301 302 304
  • 4xx:客户端请求错误,如 404 403
  • 5xx:服务端错误

跨域

同源策略
  • ajax 请求时,浏览器要求当前网页和 server 必须同源(为了保证安全)
  • 同源:协议、域名、端口三者必须一致
  • 加载图片 css js 可无视同源策略;img src、link href、script src
jsonp

JSONP是一种发送JSON数据而无需担心跨域问题的方法。

JSONP不使用该 XMLHttpRequest 对象。

JSONP使用 <script> 标记代替。

由于跨域策略,从另一个域请求文件可能会导致问题。

从另一个域请求外部脚本不会出现此问题。

JSONP利用了这一优势,并使用脚本标签而不是 XMLHttpRequest 对象来请求文件。

1
2
3
4
5
6
7
8
9
// jQuery 实现 jsonp
$.ajax({
url:'http://localhost:8882/x-origin.json',
dataType:'jsonp',
jsonCallback:'callback',
success:function(data){
console.log(data)
}
})
CORS
  • 服务端设置 http header,服务端的设置
1
2
3
4
5
6
// 跨域的域名称,不建议直接写 "*"
response.setHeader('Access-Control-Allow-Origin', 'https://localhost:8011')
response.setHeader('Access-Control-Allow-Headers', 'X-Requested-With')
response.setHeader('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS' )
// 接受跨域的 coolie
response.setHeader("Access-Control-Allow-Credentials","true")

存储

  • 本身用于浏览器和 server 通讯
  • 被 ”借用“ 到本地存储来
  • 可用 document.cookie = ‘…’ 来修改
  • 最大 4k
  • 默认跟随 http 请求,发送到服务器

localStorage 和 sessionStorage

  • HTML5 专门为存储而设计,最大可存 5M(每个域)

  • localStorage 数据会永久储存,触发代码或手动删除

  • sessionStorage 数据只存在于当前会话,浏览器关闭则清空

  • 一般用 localStorage 会更多一些

Http

http 状态码和http methods,仅仅使一个约束规范。可以更改(不建议)

http 状态码

  • 状态码分类
    • 1xx:服务器收到请求
    • 2xx:请求成功,如 200
    • 3xx:重定向,如 302
    • 4xx:客户端错误,如 404
    • 5xx:服务端错误,如 500
  • 常用状态码
    • 200 成功
    • 301 永久重定向 配合 location,浏览器自动处理。如换域名,浏览器会存缓存。
    • 302 临时重定向 配合 location,浏览器自动处理。如搜索引擎的跳转链接、短网址的跳转。
    • 304 资源未被修改,两次请求的内容是相同的
    • 404 资源未找到
    • 404 没有权限
    • 500 服务器错误
    • 504 网关超时

http methods

  • 传统 methods
    • get 获取服务器的数据
    • post 向服务器提交数据
  • 现在的 methods
    • get 获取数据
    • post 新建数据
    • patch/put 更新数据
    • delete 删除数据

Restful API

  • 一种新的 API 设计方法(早已推广使用)
  • 传统 API 设计:把每个 url 当做一个功能
  • Restful API 设计:把每个 url 当做一个唯一的资源

如何设计成一个资源?

  • 尽量不用 url 参数
    • 传统 API 设计:/api/list?pageIndex=2
    • Restful API 设计:/api/list/2
  • 用 method 表示操作类型
    • 传统 API 设计,如
      • post 请求 /api/create-blog 创建博客
      • post 请求 /api/update-blog?id=100 更改博客内容
      • get 请求 /api/get-blog?id=100 获取博客内容
    • Restful API
      • post 请求 /api/blog 新建博客
      • patch 请求 /api/blog/100 更新博客
      • get 请求 /api/blog/100 获取博客内容

http headers

  • 常见的 Request Headers:请求头

    • image-20210307142126063
    • Accept:浏览器可接收的数据格式
    • Accept-Encoding 浏览器可接收的压缩算法,如 gzip
    • Accept-Languange 浏览器可接收的语言,如 zh-CH
    • Connection: keep-alive 一次 TCP 连接重复使用
    • User-Agent:简称UA,浏览器信息
    • Content-type 发送数据的格式,如 application/json
  • 常见 Response Headers:响应头

    • image-20210307142233196
    • Content-type 返回数据的格式,如 application/json
    • Content-length 返回数据的大小,多少字节
    • Content-Encoding 返回数据的压缩算法,如 gzip
  • 自定义 header:如,Authentication;常用语接口鉴权

  • 缓存相关的 headers

    • image-20210307195941177

http 缓存

image-20210307210410090

http 缓存-强制缓存

image-20210307203735748

image-20210307203900106

Cache-Control
  • Response Headers 中
  • 控制强制缓存的逻辑
  • 例如:cache-control: max-age=2592000 单位是秒
  • cache-control的值
    • max-age:缓存的最大过期时间
    • no-cache:不用强制缓存,交给服务端处理
    • no-store:不缓存,让服务端再次返回
    • private:用户缓存
    • public:允许中间路由、代理进行缓存
Expires
  • 同在 Response Headers 中
  • 同为控制缓存过期
  • 已被 Cache-Control 代替

http缓存-协商缓存(对比缓存)

image-20210307204931779

  • 服务端缓存策略,服务端判断这个文件是否需要缓存
  • 服务器判断客户端资源,是否和服务端资源一样
    • 一致则返回 304 ,负责返回 200 和最新的资源
资源标识
  • 在 Response Headers 中,有两种
    • last-Modified 资源的最后修改时间
    • Etag 资源的唯一标识(一个字符串,类似人类的指纹)
  • 两者会优先使用 Etag
    • Last-Modified 只能精确到秒级
    • 如果资源被重复生成,而内容不变,则 Etag 更精确
Last-Modified

image-20210307205350512

Etag
image-20210307205627943

刷新页面对缓存的影响

三种刷新方式
  • 正常操作:地址栏输入url、跳转链接、前进后退
  • 手动刷新:F5、点击刷新按钮、右击菜单刷新
  • 强制刷新:ctrl + F5
不同刷新操作、不同缓存策略
  • 正常操作:强制缓存有效,协商缓存有效
  • 手动刷新:强制缓存失效,协商缓存有效
  • 强制刷新:强制缓存失效,协商缓存失效

开发环境

git

  • 最常用的代码版本管理工具
  • 常用 git 命令
    • git add .
    • git checkout xxx
    • git commit -m “xxx”
    • git push origin master
    • git pull origin master

webpack 和 babel

为什么需要

  • ES6 模块化,浏览器暂不支持
  • ES6 语法,浏览器并不完全支持
  • 压缩代码,整合代码,让网页加载更快

webpack 配置

注意版本问题:最新版本的需要配置一些其他项

所以我的是指定版本安装:和视频演示的版本一致

npm install webpack@4.41.0 webpack-cli@3.3.9 webpack-dev-server@3.8.2 html-webpack-plugin@3.2.0 -D

  • 初始化:npm init -y –> 生成 package.json 文件

  • 安装 webpack 依赖:npm install webpack webpack-cli -D -D 选项为开发依赖

  • webpack.config.js 文件基本配置

    • ```javascript
      const path = require(‘path’)
      module.exports = {
      mode: ‘development’, // production development
      entry: path.join(__dirname, ‘src’, ‘index.js’),
      output: {
      filename: ‘bundle.js’,
      path: path.join(__dirname, ‘dist’),
      },
      }
      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
      27
      28
      29
      30
      31

      * Ïpachage.json scripts 配置:`"build": "webpack"` 打包命令

      * 扩展 html 打包; 安装插件

      * `npm i html-webpack-plugin -D`
      * `npm i webpack-dev-server -D `

      * webpack.config.js 配置

      * ```javascript
      const path = require('path')
      const HtmlWebpackPlugin = require('html-webpack-plugin')
      module.exports = {
      mode: 'development', // production development
      entry: path.join(__dirname, 'src', 'index.js'),
      output: {
      filename: 'bundle.js',
      path: path.join(__dirname, 'dist'),
      },
      plugins: [
      new HtmlWebpackPlugin({
      template: path.join(__dirname, 'src', 'index.html'),
      filename: 'index.html', // 打包后的 dist 下的文件名
      }),
      ],
      devServer: {
      port: 3000,
      contentBase: path.join(__dirname, 'dist'),
      },
      }
  • package.json 配置

    • ```javascript
      “scripts”: {
      “dev”: “webpack-dev-server”,
      “build”: “webpack [–config webpack.config.js]”,
      “test”: “echo "Error: no test specified" && exit 1”
      },
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      * `npm run dev` 执行本地运行

      ##### babel 配置、转义为 ES5

      * `npm i @babel/core @babel/preset-env babel-loader -D`

      * `.babelrc` 文件配置

      * ```javascript
      {
      "presets": ["@babel/preset-env"]
      }
  • webpack.config.js 配置

    • ```javascript
      module: {
      rules: [
      {
      test: /.js$/, // 符合此正则的走 babel-loader 插件
      loader: [‘babel-loader’],
      include: path.join(__dirname, ‘src’), // 此目录下的走 babel-loader 插件
      exclude: /node_modules/, // 排除此目录
      },
      ],
      },
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18

      ##### ES6 模块化

      * 第一种方式

      ```javascript
      // a.js 导出多个
      export function fn() {
      console.log('fn')
      }
      export const name = 'a'
      export const obj = {
      name: 'zhangsan',
      }
      // index.js 导入,需要解构
      import { fn, name, obj } from './a'
      fn() // 正常使用
      console.log(name,obj)
  • 第二种方式

1
2
3
4
5
6
7
8
// 导出一个
const obj = {
name:'xxx'
}
export default obj

// 导入,不需要解构
import obj from './c'
生产环境配置
  • webpack.prod.js

  • package.json scripts "build": "webpack --config webpack.prod.js",

  • ```javascript
    const path = require(‘path’)
    const HtmlWebpackPlugin = require(‘html-webpack-plugin’)

    module.exports = {
    mode: ‘production’, // production development
    entry: path.join(__dirname, ‘src’, ‘index.js’),
    output: {
    filename: ‘bundle.[contenthash].js’,// contenthash 根据代码内容计算出的哈希值
    path: path.join(__dirname, ‘dist’),
    },
    module: {
    rules: [
    {
    test: /.js$/, // 符合此正则的走 babel-loader 插件
    loader: [‘babel-loader’],
    include: path.join(__dirname, ‘src’), // 此目录下的走 babel-loader 插件
    exclude: /node_modules/, // 排除此目录
    },
    ],
    },
    plugins: [
    new HtmlWebpackPlugin({
    template: path.join(__dirname, ‘src’, ‘index.html’),
    filename: ‘index.html’, // 打包后的 dist 下的文件名
    }),
    ],
    }

    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
      

    完整 webpack demo [gitee仓库](https://gitee.com/yishen_yishen/webpack-demo)

    ## 运行环境

    ### 网页加载渲染过程

    #### 加载资源的过程

    * DNS 解析:域名 -> IP 地址
    * 浏览器根据 IP 地址向服务器发起 http 请求
    * 服务器处理 http 请求,并返回给浏览器

    #### 渲染过程

    * 根据 HTML 代码生成 DOM Tree;DOM:document object model
    * 根据 CSS 代码生成 CSSOM

    * 将 DOM Tree 和 CSSOM 整合形成 Render Tree(渲染树)
    * 根据 Render Tree 渲染页面
    * 遇到 `<script>` 则暂停渲染,优先加载并执行 JS 代码,完成在继续
    * 直至 Render Tree 渲染完成

    ##### window.onload 和 DOMContentLoaded区别

    ```javascript
    document.addEventListener('DOMContentLoaded', function () {
    // DOM 渲染完即可执行,此时图片、视频可能还没有加载完成
    console.log('1')
    })
    window.addEventListener('load', function () {
    // 页面的全部资源加载完,才会执行,包括图片、视频等
    console.log('2')
    })

性能优化

性能优化原则

  • 多使用内存、缓存或其他方法
  • 减少 CPU 计算量,减少网络加载耗时
  • (适用于所有编程的性能优化—空间换时间)

从何入手

  • 让加载更快

    • 减少资源代码:压缩代码
    • 减少访问次数:合并代码,SSR 服务端渲染,缓存
    • 使用更快的网络:CDN
  • 让渲染更快

    • CSS 放 head ,JS 放在 body 最下面
    • 今早开始执行 JS ,用 DOMContentLoaded 触发
    • 懒加载(图片懒加载,上滑加载更多)
    • 对 DOM 查询进行缓存
    • 频繁 DOM 操作,合并到一起插入 DOM 结构
    • 节流 throttle 防抖 debounce
  • SSR–Server-Side Rendering

    • 服务端渲染:将网页和数据一起加载,一起渲染
    • 非 SSR (前后端分离):先加载网页,再加载数据,再渲染数据

防抖 debounce

监听一个输入框,文字变化后触发 change 事件;直接用 keyup 事件,则会频繁触发 change 事件;

防抖:用户输入结束或暂停时,才会触发 change 事件。

扩展阅读:https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/5

1
2
3
4
5
6
7
8
9
10
11
12
// 纯 JS 中写法
let timer = null
function debounce() { // debounce 函数会被快速多次执行
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(()=>{
// 业务代码
// 清空定时器
timeer = null
},500)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 封装一下,使其实用性更强
function debounce(fn, dealy = 500) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, dealy)
}
}
// 使用
function demo() {
// 业务函数
console.log('业务函数')
}
input1.addEventListener('keyup', debounce(demo, 300))

节流 throttle

拖拽一个元素时,要随时按到该元素被拖拽的位置;直接用 drag 事件,则会频繁触发,很容易导致卡顿;节流:无论拖拽速度多快,都会每隔 100ms 触发一次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- 基础写法 -->
<div id="div1" draggable="true">可拖拽</div>

<script>
const div1 = document.getElementById('div1')
let timer = null

div1.addEventListener('drag', function (event) {
if (timer) {
return
}
timer = setTimeout(() => {
console.log(event.offsetX, event.offsetY)
timer = null
}, 100)
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 封装一下
function throttle(fn, frequency = 100) {
let timer = null
return function () {
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, frequency)
}
}
// 使用
const div1 = document.getElementById('div1')
div1.addEventListener(
'drag',
throttle((event) => {
console.log(event.offsetX, event.offsetY)
})
)

安全

扩展阅读:前端安全系列(一):如何防止XSS攻击?

XSS 跨站请求攻击

一个博客网站,我发表一篇博客,其中嵌入 <script> 脚本;脚本内容:获取 cookie,发送到我的服务器(服务器 配合跨域);发布这篇博客,有人查看它,我轻松收割访问者的 cookie

  • XSS 预防

image-20210311105909688

XSRF 跨站请求伪造

image-20210311110220784

image-20210311110242575

  • XSRF 预防

image-20210311110336823

面试题

下面的三级标题,代表的是此小节视频对应的标题。四级标题为对应的面试题。

何为变量提升

变量提升

1
2
3
4
5
6
7
// 变量提升 ES5
console.log(a) // undefined
var a = 200
// 等价于下面,js 执行中会把所有 var 提升的前面;
var a
console.log(a) // undefined
a = 200

typeof 判断类型

  • undefined、string、number、boolean、symbol
  • object(typeof null === ‘object’)
  • function

手写深度比较 isEqual

手写深度比较 lodash.isEqual

1
2
3
4
// 实现效果如下
const obj1 = {a:10,b:{x:100,y:200}}
const obj2 = {a:10,b:{x:100,y:200}}
isEqual(obj1,obj2) === true
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function isEqual(obj1, obj2) {
if (!isObject(obj1) || !isObject(obj2)) {
return obj1 === obj2
}
if (obj1 === obj2) {
return true
}
// 1、先取出 obj1 和 obj2 的 keys,比较个数
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
if (obj1Keys.length !== obj2Keys.length) {
return false
}
// 2、以 obj1 为基准,和obj2 依次递归比较
for (let key in obj1) {
// 比较当前 key 的 val --递归调用,遍历深层次
const res = isEqual(obj1[key], obj2[key])
if (!res) {
return false
}
}
// 3、全相等
return true
}

split() 和 join()

1
2
'1-2-3'.split('-') // [1,2,3]
[1,2,3].join('-') // '1-2-3'

数组的 pop push unshift shift

  • 功能是什么?
  • 返回值是什么?
  • 是否会对原数组造成影响?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const arr = [10,20,30,40]

// pop, 删除数组的最后一个元素,返回数组的最后一个元素值,原数组发生改变
const popRes = arr.pop()
console.log(popRes,arr) // 40,[10,20,30]

// shift,删除数组的第一个元素,返回被删除的元素值,原数组发生改变
const shiftRes = arr.shift()
console.log(shiftRes, arr) // 10 [ 20, 30, 40 ]

// push,向数组后面追加一个元素,返回新数组的length,原数组发生改变
const pushRes = arr.push()
console.log(pushRes, arr) // 4 [ 10, 20, 30, 40 ]

// unshift() ,在数组第一个元素前增加一个元素(传入的参数值),返回新数组的length,原数组发生改变
const unshiftRes = arr.unshift(20)
console.log(unshiftRes, arr) // 5 [ 20, 10, 20, 30, 40 ]
纯函数
  • 不改变源数组(没有副作用)
  • 返回一个数组
1
2
3
4
5
6
7
8
9
10
11
12
13
// arr1.concat(arr2) 拼接两个数组,不影响源数组,返回新数组
const arr1 = arr.concat([50, 60, 70]) // arr1:[10, 20, 30, 40, 50, 60, 70 ]

// 遍历数组
const arr2 = arr.map((num) => num * 10) // [ 100, 200, 300, 400 ]

// 过滤数组
const arr3 = arr.filter((num) => num > 25)

// slice 从已有的数组中返回选定的元素。可以用来做深拷贝
// https://www.w3school.com.cn/jsref/jsref_slice_array.asp
const arr4 = arr.slice()

你是否真的会用数组

数组 slice 和 splice 的区别

  • 功能区别(slice-切片,splice-剪接)
1
2
3
4
5
6
7
8
const arr = [10,20,30,40]

// slice(start?:number,end?:number),截取函数的一部分,返回新函数,不改变旧函数
const arr4 = arr.slice(1,3) // [ 10, 20, 30, 40 ]

// splice(start:number,deleteCount?:number),剪接函数,改变源函数
const arr5 = arr.splice(1, 2) // arr5:[ 20, 30 ] arr:[ 10, 40 ]
const spliceReg = arr.splice(1, 2, 'a', 'b', 'c') // spliceReg:[ 20, 30 ] arr:[ 10, 'a', 'b', 'c', 40 ]

[10,20,30].map(parseInt)

  • map 的参数和返回值
  • parseInt 的参数和返回值
    • parseInt(*string*, *radix*) 解析一个字符串并返回指定基数的十进制整数, radix 是2-36之间的整数,表示被解析字符串的基数
1
2
3
4
5
[10,20,30].map(parseInt) // [10, NaN, NaN]
// 拆解
[10,20,30].map((item,index)=>{
return parseInt(item,index)
})

ajax 请求 get 和 post 的区别

  • get 一般用于查询操作,post 一般用于提交操作
  • get 参数拼接在 url 上,post 放在请求体内(数据体积可以更大)
  • 安全性:post 易于防止 CSRF

再学闭包

函数 call 和 apply 的区别

1
2
3
// 传参不同,call 参数分开传,apply 传一个数组
fn.call(this,p1,p2,p3)
fn.apply(this,arguments)

事件代理(委托)是什么

image-20210313095115799

闭包是什么,有什么特性?有什么负面影响?

跳转 [闭包](# 闭包)

闭包影响:变量会常驻内存,得不到释放。闭包不要乱用

回顾 DOM 操作和优化

如何阻止事件冒泡和默认行为?

1
2
event.stopPropagation()
event.preventDefault()

如何减少 DOM 操作?

1
2
// DOM 查询结果做缓存
// 创建一个文档片段,document.createDocumentFragment()

image-20210313100717748

jsonp 本职是 ajax 吗?

解释 jsonp 的原理,为何它不是真正的 ajax

  • 浏览器的同源策略(服务端没有同源策略)和跨域
  • jsonp 原理

image-20210313101530622

jsonp 是使用 script 标签来进行通信。没有使用 xhr 请求。

document load 和 ready 的区别

image-20210313101634495

== 和 === 的不同

跳转 [链接](# == 运算符)

  • 日常使用中 除了 == null 之外,其他都一律使用 ===

常见的正则表达式

关于作用域和自由变量的场景题

1
2
3
4
5
6
7
8
9
10
11
// question 1
let i
for(i=1;i<=3;i++){
setTimeout(function(){
console.log(i)
},0)
}
// answer1
4
4
4
1
2
3
4
5
6
7
8
9
10
11
12
13
// question 2
let a = 100
function test(){
alert(a)
a = 10
alert(a)
}
test()
alert(a)
// answer 2
100
10
10

判断字符串以字母开头,后面字母数字下划线,长度 6-30

1
const reg = /^[a-zA-Z]\w{5,29}$/

正则表达式30分钟入门教程

如何获取最大值

手写字符串 trim 方法,保证浏览器兼容性

1
2
3
4
String.prototype.trim = function(){
return this.replace(/^\s+/,'').replace(/\s+$/,'')
}
// 原型、this、正则表达式

如何获取多个数字中的最大值

1
2
3
4
5
6
7
8
9
10
function max() {
const nums = Array.prototype.slice.call(arguments) // 变为数组
let max = 0
nums.forEach((n) => {
if (n > max) {
max = n
}
})
return max
}

如何用 JS 实现继承

  • class 继承
  • protorype 继承

解析 url 参数

如何捕获 JS 程序中的异常?

1
2
3
4
5
6
7
try{
// todo
} catch (ex) {
console.error(ex) // 手动捕获 catch
} finally {
// todo
}
1
2
3
4
window.onerror = function(message,source,lineNom,colNom,error){
// 第一,对跨域的 js,如 CDN 的,不会有详细的报错信息
// 第二,对于压缩的 js,还要配合 sourceMap 反查到未压缩代码的行、列
}

什么是 JSON

  • json 是一种数据格式,本职是一段字符串
  • json 格式和 js 对象结构一致,对 JS 语言更友好
  • window.JSON 是一个全局对象:JSON.stringify JSON.parse

获取当前页面 url 参数

image-20210318173641426

image-20210318173718181

数组去重有几种方式

将 url 参数解析为 JS 对象

1
2
3
4
5
6
7
8
9
10
11
12
// 传统方法
function queryToObj() {
const res = {}
const search = location.search.substr(1) // 去掉前面的 '?'
search.split('&').forEach((paramStr) => {
const arr = paramStr.split('=')
const key = arr[0]
const val = arr[1]
res[key] = val
})
return res
}
1
2
3
4
5
6
7
8
9
10
// 新方法
function queryToObj() {
const res = {}
const pList = new URLSearchParams(location.search)
pList.forEach((val, key) => {
res[key] = val
})
return res
}

手写数组 flatern,考虑多层级

flatern 可以理解为,拍平,扁平化(v.)

1
2
flat([1,2,[1,2,3],4,5])
// [1,2,1,3,4,5]
1
2
3
4
5
6
7
8
9
10
11
function flat(arr) {
// 验证 arr 中,还有没有深层数组 [1,2,[3,4]]
const isDeep = arr.some((item) => item instanceof Array)
if (!isDeep) {
return arr // 已经是 flatern [1,2,3,4]
}
const res = Array.prototype.concat.apply([], arr) // 这东西还是没看懂
return flat(res) // 递归
}

console.log(flat([1, 2, [3, 4, [5, 6, 7, [8, 9]]]]))

数组去重

1
2
3
4
5
6
7
8
9
10
11
// 传统方式
function unique(arr) {
const res = []
arr.forEach((item) => {
if (res.indexOf(item) < 0) {
res.push(item)
}
})
return res
}
console.log(unique([1, 2, 2, 2, 11, 3, 3]))
1
2
3
4
5
// 使用 Set(无序,不能重复)
function unique(arr){
const set = new Set(arr)
return [...set]
}

是否用过 requestAnimationFrame

手写深拷贝

[跳转链接](# 深拷贝)

  • Object.assign 不是深拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用,解决多级对象\数组
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}

RAF requestAnimationFrame

  • 要想动画流畅,更新频率要 60帧/s,即16.67ms 更新一次视图
  • setTimeout 要手动控制频率,而 RAF 浏览器会自动控制
  • 后台标签或隐藏 iframe 中,RAF 会暂停,而 setTimeout 依然执行

前端性能如何优化?一般从哪几个方面考虑?

  • 原则:多使用内存、缓存、减少计算、减少网路请求
  • 方向:加载页面,页面渲染,页面操作流畅度