高效学习三部曲:找准知识体系;刻意训练;及时反馈
从哪些方面梳理:
W3C
标准ECMA 262
标准- 开发环境
- 运行环境
一、HTML
1. 如何理解HTML语义化?
- 让人更容易读懂(增加代码可读性)
- 让搜索引擎更容易读懂(SEO优化)
2. 默认情况下,哪些HTML标签是块级元素、哪些是内联元素?
- 块级(
display: block/table
):div
、h1
、h2
、table
、ul
、ol
、p
- 内联(
display: inline/inline-block
):span
、img
、input
、button
二、CSS
2.1 布局
2.1.1 盒子模型的宽度如何计算
div1的
offsetWidth
是多大?(122px)
<style type="text/css">
#div1 {
width: 100px;
padding: 10px;
border: 1px solid #ccc;
margin: 10px;
}
</style>
<div id="div1">this is div1</div>
//document.getElementById('div1').offsetWidth
offsetWidth
= 内容宽度+内边距+边框
box-sizing: border-box; //100px
2.1.2 margin
纵向重叠的问题
AAA和BBB之间的距离是多少?(15px)
<style type="text/css">
p {
font-size: 16px;
line-height: 1;
margin-top: 10px;
margin-bottom: 15px;
}
</style>
<p>AAA</p>
<p></p>
<p></p>
<p></p>
<p>BBB</p>
- 相邻元素的
margin-top
和margin-bottom
会发生重叠 - 空白内容的
<p></p>
也会重叠 margin
重叠三个条件:同属于一个BFC;相邻;块级元素(行内元素如a标签便不会发生重叠,而是累加)
2.1.3 margin
负值的问题
<style type="text/css">
body {
margin: 20px;
}
.float-left {
float: left;
}
.clearfix:after {
content: '';
display: table;
clear: both;
}
.container {
border: 1px solid #ccc;
padding: 10px;
}
.container .item {
width: 100px;
height: 100px;
}
.container .border-blue {
border: 1px solid blue;
}
.container .border-red {
border: 1px solid red;
}
</style>
<p>用于测试 margin top bottom 的负数情况</p>
<div class="container">
<div class="item border-blue">
this is item 1
</div>
<div class="item border-red">
this is item 2
</div>
</div>
<p>用于测试 margin left right 的负数情况</p>
<div class="container clearfix">
<div class="item border-blue float-left">
this is item 3
</div>
<div class="item border-red float-left">
this is item 4
</div>
</div>
margin-top
和margin-left
负值,元素向上、向左移动margin-right
负值,右侧元素左移,自身不受影响margin-bottom
负值,下方元素上移,自身不受影响margin
值上下一正一负,间距是两个值的相加
2.1.4 BFC的理解和应用
Block format context
:块级格式化上下文- 一块独立渲染区域,内部元素的渲染不会影响边界以外的元素
形成BFC
的常见条件
float
不是none
:left
|right
position
:absolute
|fixed
overflow
不是visible
:hidden
|auto
|scroll
display
:table-cell
|table-caption
|inline-block
|inline-flex
|flex
例子:一个div里包含两个div,div左浮动导致外层div高度坍塌。在外层div添加
overflow: hidden; /* 触发元素 BFC */
即可,便不会出现坍塌的情况
BFC布局规则
- 内部的Box会在垂直方向,一个接一个地放置(即块级元素独占一行)。
- BFC的区域不会与float box重叠(利用这点可以实现自适应两栏布局)。
- 内部的Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠(
margin
重叠三个条件:同属于一个BFC;相邻;块级元素(行内元素如a标签便不会发生重叠,而是累加))。 - 计算BFC的高度时,浮动元素也参与计算。(清除浮动 haslayout)
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。
BFC有特性
- BFC会阻止垂直外边距折叠
- 相邻兄弟元素margin重叠问题
- 父子元素margin重叠问题
- BFC不会重叠浮动元素
实现两列自适应布局
- BFC可以包含浮动----清除浮动 容器内两个div元素浮动,脱离了文档流,父容器内容宽度为零(即发生高度塌陷),未能将子元素包裹住。解决这个问题,只需要把把父元素变成一个BFC就行了。
2.1.5 float布局的问题,以及clearfix
如何实现圣杯布局和双飞翼布局?
- 圣杯布局和双飞翼布局的目的
- 三栏布局,中间一栏最先加载和渲染(内容最重要)
- 两侧内容固定,中间内容随着宽度自适应
- 一般用于PC网页
- 总结
- 使用float布局
- 两侧使用margin负值,以便和中间内容横向重叠
- 防止中间内容被两侧覆盖,一个用
padding
一个用margin
圣杯布局
body {
min-width: 550px;
}
#header {
text-align: center;
background-color: #f1f1f1;
}
#container {
padding-left: 200px;
padding-right: 150px;
}
#container .column {
float: left;
}
#center {
background-color: #ccc;
width: 100%;
}
#left {
position: relative;
background-color: yellow;
width: 200px;
margin-left: -100%;
right: 200px;
}
#right {
background-color: red;
width: 150px;
margin-right: -150px;
}
#footer {
text-align: center;
background-color: #f1f1f1;
}
/* 手写 clearfix */
.clearfix:after {
content: '';
display: table;
clear: both;
}
<div id="header">this is header</div>
<div id="container" class="clearfix">
<div id="center" class="column">this is center</div>
<div id="left" class="column">this is left</div>
<div id="right" class="column">this is right</div>
</div>
<div id="footer">this is footer</div>
双飞翼布局
body {
min-width: 550px;
}
.col {
float: left;
}
#main {
width: 100%;
height: 200px;
background-color: #ccc;
}
#main-wrap {
margin: 0 190px 0 190px;
}
#left {
width: 190px;
height: 200px;
background-color: #0000FF;
margin-left: -100%;
}
#right {
width: 190px;
height: 200px;
background-color: #FF0000;
margin-left: -190px;
}
<div id="main" class="col">
<div id="main-wrap">
this is main
</div>
</div>
<div id="left" class="col">
this is left
</div>
<div id="right" class="col">
this is right
</div>
圣杯布局和双飞翼布局区别
两边边距:圣杯(padding
)、双飞翼(margin
)
右边栏:圣杯(margin-right: -150px
)、双飞翼(margin-left: -150px
)
2.1.6 手写clearfix
手写clearfix
/* 手写 clearfix */
.clearfix:after {
content: '';
display: table;
clear: both;
}
/* 兼容ie低版本 */
.clearfix{
*zoom: 1;
}
2.1.7 flex布局
flex布局画色子,实现三点
2.2 定位
2.2.1 absolute和relative
absolute和relative分别依据什么定位?
relative
根据自身定位absolute
根据最近一层元素的定位(定位元素:absolute
、relative
、fixed
、body
)
2.2.2 水平居中
居中对齐有哪些方式?
inline
元素:text-align: center
block
元素:margin: auto
absolute
:left:50% + margin-left
负值
2.2.3 垂直居中
inline
元素:inline-height
的值等于height
的值absolute
元素:top:50% + margin-top
负值absolute
元素:transform(-50%, -50%)
absolute
元素:top
、left
、bottom
、right = 0 +
amrgin: auto
水平垂直居中,不知道元素长宽并且没有兼容问题,应该采用什么方式?
width: 100px;
height: 50px;
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
margin: auto;
2.3 图文样式
2.3.1 line-height
的继承问题
p标签的行高将会是多少?(40px)
body {
font-size: 20px;
line-height: 200%;
}
p {
background-color: #ccc;
font-size: 16px;
}
<p>这是一行文字</p>
- 写具体数值,如30px,则继承该值
- 写比例,如2/1.5,则继承该比例
写百分比,如200%,则继承计算出来的值(重要!!!)
2.4 响应式
2.4.1 rem是什么
rem
是一个长度单位
px
,绝对长度单位,最常用em
,相对长度单位,相对于父元素,不常用rem
,相对长度单位,相对于根元素(html
),常用于响应式布局
2.4.2 如何实现响应式
@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 和 iphone x */
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;
}
2.4.3 vw/vh
rem
的弊端:阶梯性- 网页视口尺寸
window.screen.height
//屏幕高度window.innerHeight
//网页视口高度document.body.clientHeight
//body高度
vw
/vh
vh
网页视口高度的1/100vw
网页视口宽度的1/100vmax
取两者最大值;vmin
取两者最小值
2.5 CSS3(flex、动画)
三、JS
3.1 JS变量类型
3.1.1 原始类型和对象类型
常见的值有哪些
-
7 种原始类型(不可变):
Boolean、Undefined、Number、String、Null、Symbol(ES6)、BigInt(ES6)
-
1 种对象类型Object: • 包含了Function、Array、特殊对象(RegExp、Date)
typeof
能判断那些类型number、string、boolean、undefined、object、function、symbol、bigint
识别所有值类型、识别函数、判断是否是引用类型(不可再细分)
值类型和引用类型的区别 值类型:
- 占用空间固定,保存在
栈
中 - 保存与复制的是值本身
- 使用
typeof
检测数据的类型 - 基本类型数据是值类型
- 在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了
引用类型:
- 占用空间不固定,保存在堆中
- 保存与复制的是指向对象的一个指针
- 使用
instanceof
检测数据类型 - 只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它
3.1.2 深拷贝
手写深拷贝
/**
* 深拷贝
* @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
}
3.1.3 变量计算:强制类型转换
== 和 === 区别
- 字符串拼接
const a = 100 + 10 //110
const b = 100 + '10' //10010
const c = true + '10' //true10
const d = 100 + parseInt('10') //110
- ==
100 == '100' //true
0 == '' //true
0 == false //true
false == '' //true
null == undefined //true
//除了 == null之外,其他一律用 ===,例如:
const obj = {x: 100}
if (obj.a == null) {}
//相当于:if (obj.a === null || obj.a === undefined) {}
- if语句和逻辑运算
- truly变量:!!a === true的变量
- falsely变量:!!b === false的变量
//falsely变量,此外都是truly变量
!!0 === false
!!NaN === false
!!'' === false
!!null === false
!!undefined === false
!!false === false
- 逻辑判断
console.log(10 && 0) // 0
console.log('' && 'abc') // 'abc'
console.log(!window.abc) // true
3.2 JS原型和原型链
3.2.1 class
- constructor
- 属性
- 方法
// 类
class Student {
constructor(name, number) {
this.name = name
this.number = number
// this.gender = 'male'
}
sayHi() {
console.log(
`姓名 ${this.name} ,学号 ${this.number}`
)
// console.log(
// '姓名 ' + this.name + ' ,学号 ' + this.number
// )
}
// study() {
// }
}
// 通过类 new 对象/实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
const madongmei = new Student('马冬梅', 101)
console.log(madongmei.name)
console.log(madongmei.number)
madongmei.sayHi()
3.2.2 class继承
- extends:继承
- super:执行父类构造函数
- 扩展或重写方法
// 父类
class People {
constructor(name) {
this.name = name
}
eat() {
console.log(`${this.name} eat something`)
}
}
// 子类
class Student extends People {
constructor(name, number) {
super(name)
this.number = number
}
sayHi() {
console.log(`姓名 ${this.name} 学号 ${this.number}`)
}
}
// 子类
class Teacher extends People {
constructor(name, major) {
super(name)
this.major = major
}
teach() {
console.log(`${this.name} 教授 ${this.major}`)
}
}
// 实例
const xialuo = new Student('夏洛', 100)
console.log(xialuo.name)
console.log(xialuo.number)
xialuo.sayHi()
xialuo.eat()
// 实例
const wanglaoshi = new Teacher('王老师', '语文')
console.log(wanglaoshi.name)
console.log(wanglaoshi.major)
wanglaoshi.teach()
wanglaoshi.eat()
3.2.3 JS 类型判断:instanceof
[] instanceof Array // true
[] instanceof Object // true
{} instanceof Array // true
// class实际上是函数,是一种语法糖
typeof People // 'function'
typeof Student // 'function'
//隐式原型、显式原型
xialuo.__proto__ //隐式原型
xialuo.prototype //显式原型
xialuo.__proto__ === xialuo.prototype //true
- 每个
class
都有显式原型prototype
- 每个实例都有隐式原型
__proto__
- 实例的
__proto__
指向对应class
的prototype
如何判断一个变量是数组?
a instanceof Array
class的原型本质
- 原型和原型链的图示
- 属性和方法的执行规则
手写简易jQuery考虑插件和扩展性代码演示
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
this.selector = selector
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
// 扩展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
// “造轮子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展自己的方法
addClass(className) {
}
style(data) {
}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))
3.2.4 作用域和闭包
知识点
- 作用域
- 全局作用域
- 函数作用域
- 块级作用域(ES6新增,if/while/for {}里的作用域)
- 自由变量
- 一个变量在当前作用域没有定义,但被使用了
- 向上级作用域,一层一层依次寻找,直至找到为止
- 如果到全局作用域都没找到,则报错
xx is not defined
- 闭包
- 作用域应用的特殊情况,有两种表现:
- 函数作为
参数
被传递
// 函数作为返回值 function create() { const a = 100 return function () { console.log(a) } } const fn = create() const a = 200 fn() // 100
- 函数作为
返回值
被返回
// 函数作为参数被传递 function print(fn) { const a = 200 fn() } const a = 100 function fn() { console.log(a) } print(fn) // 100
所有的自由变量的查找,是在函数定义的地方,向上级作用域查找
不是在执行的地方!!!
- this
- 作为普通函数
- 使用 call、apply、bind
- 作为对象方法被调用
- 在class方法中调用
- 箭头函数
this取什么值是在函数执行的时候确定的,不是在定义的时候确定的
function fn1() {
console.log(this)
}
fn1() // window
fn1.call({x: 100}) // { x: 100 }
const fn2 = fn1.bind({ x: 200 })
fn2() // { x: 200}
const zhangsan = {
name: '张三',
sayHi() {
// this 即当前对象
console.log(this)
},
wait() {
setTimeout(function(){
// this === window
console.log(this)
})
},
waitAgain() {
setTimeout(() => {
// this 即当前对象
console.log(this)
})
}
}
题目
1、this的不同应用场景,如何取值?
- 作为普通函数
- 使用 call、apply、bind
- 作为对象方法被调用
- 在class方法中调用
- 箭头函数
2、手写bind函数
// 模拟 bind
Function.prototype.bind1 = function () {
// 将参数拆解为数组
const args = Array.prototype.slice.call(arguments)
// 获取 this(数组第一项)
const t = args.shift()
// fn1.bind(...) 中的 fn1
const self = this
// 返回一个函数
return function () {
return self.apply(t, args)
}
}
function fn1(a, b, c) {
console.log('this', this)
console.log(a, b, c)
return 'this is fn1'
}
const fn2 = fn1.bind1({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)
3、实际开发中闭包的应用场景,举例说明
- 隐藏数据,做一个简单的cache工具
// 闭包隐藏数据,只提供 API
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)
console.log( c.get('a') )
4、创建10个
<a>
标签,点击的时候弹出来对应的序号
let a //i在这里执行,则都是10
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)
}
3.3 同步和异步
3.3.1
- 单线程和异步
- JS是单线程语言,只能同时做一件事
- 浏览器和nodejs已经支持JS启动
进程
,如Web Worker
- JS和DOM渲染共用同一个线程,因为JS可修改DOM结构
- 遇到等待(网络请求,定时任务)不能卡住
- 需要异步
- 回调callback函数形式
// 异步 (callback 回调函数) console.log(100) setTimeout(() => { console.log(200) }, 1000) console.log(300) console.log(400) // 同步 console.log(100) alert(200) console.log(300)
- 基于JS是单线程语言,异步不会阻塞代码执行,同步会阻塞代码执行
- 应用场景
- 网络请求,如ajax图片加载
- 定时任务,如setTimeout
callback hell
(回调地狱)和Promise
Javascript深入系列(九):Promise
// setTimeout 笔试题
console.log(1)
setTimeout(function(){
console.log(2)
},1000)
console.log(3)
setTimeout(function(){
console.log(4)
},0)
console.log(5)
//1/3/5/4/2
同步和异步的区别是什么?
手写Promise加载一张图片
function loadImg(src) {
const p = new Promise(
(resolve, reject) => {
const img = document.createElement('img')
img.onload = () => {
resolve(img)
}
img.onerror = () => {
const err = new Error(`图片加载失败 ${src}`)
reject(err)
}
img.src = src
}
)
return p
}
// const url = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
// loadImg(url).then(img => {
// console.log(img.width)
// return img
// }).then(img => {
// console.log(img.height)
// }).catch(ex => console.error(ex))
const url1 = 'https://img.mukewang.com/5a9fc8070001a82402060220-140-140.jpg'
const url2 = 'https://img3.mukewang.com/5a9fc8070001a82402060220-100-100.jpg'
loadImg(url1).then(img1 => {
console.log(img1.width)
return img1 // 普通对象
}).then(img1 => {
console.log(img1.height)
return loadImg(url2) // promise 实例
}).then(img2 => {
console.log(img2.width)
return img2
}).then(img2 => {
console.log(img2.height)
}).catch(ex => console.error(ex))
前端使用异步的场景有哪些?
3.3.2 异步进阶
- 一、
event loop
- JS是单线程运行的
- 异步(setTimeout,ajax)要基于回调来实现
event loop
就是异步回调的实现原理DOM
事件也使用回调,基于event loop
JS
如何执行:
+ 从前到后,一行一行执行
+ 如果一行执行报错,则停止下面的代码的执行
+ 先把同步代码执行完,再执行异步
console.log('Hi')
setTimeout(function cb1(){
console.log('cb1')
}, 5000)
console.log('Bye')
// Hi/Bye/cb1
// DOM 事件,也用 event loop
<button id="btn1">提交</button>
<script>
console.log('Hi')
$('#btn1').click(function (e) {
console.log('button clicked')
})
console.log('Bye')
</script>
请描述
event loop
(事件循环/事件轮询)的机制,可画图
+ 同步代码,一行一行放在`Call Stack`执行
+ 遇到异步,会先记录下,等待时机(定时(web api)、网络请求等)
+ 时机到了,就移动到`Callback Queue`
+ 如`Call Stack`为空(即同步代码执行完)`Event Loop`开始工作
+ 轮询查找`Callback Queue`,如有则移动到`Call Stack`执行
+ 然后继续轮询查找(永动机一样)
- 自行回顾
Event Loop
的过程 - 和DOM渲染的关系
- 微任务和宏任务在
Event Loop
过程中的不同处理
什么是宏任务和微任务,两者有什么区别?
- 二、
promise
进阶
Promise
有哪三种状态?如何变化?
pending
(过程中)、resolved
(已解决,成功)、rejected
(已拒绝,失败)pending
->resolved
;pending
->rejected
(变化不可逆)
// 刚定义时,状态默认为 pending
const p1 = new Promise((resolve, reject) => {
})
// 一开始打印是pending;执行 resolve() 后,状态变成 resolved
const p2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve()
})
})
// 执行 reject() 后,状态变成 rejected
const p3 = new Promise((resolve, reject) => {
setTimeout(() => {
reject()
})
})
// 直接返回一个 resolved 状态
Promise.resolve(100)
// 直接返回一个 rejected 状态
Promise.reject('some error')
then、catch对状态的影响(重要)
状态的表现:
pending
状态,不会触发then
和catch
resolved
状态,会触发后续的then
回调函数rejected
状态,会触发后续的catch
回调函数
then catch 会继续返回 Promise ,此时可能会发生状态变化!!!
then
和catch
改变状态
then
正常返回resolved
,里面有报错则返回rejected
catch
正常返回resolved
,里面有报错则返回rejected
// then() 一般正常返回 resolved 状态的 promise
Promise.resolve().then(() => {
return 100
})
// then() 里抛出错误,会返回 rejected 状态的 promise
Promise.resolve().then(() => {
throw new Error('err')
})
// catch() 不抛出错误,会返回 resolved 状态的 promise
Promise.reject().catch(() => {
console.error('catch some error')
})
// catch() 抛出错误,会返回 rejected 状态的 promise
Promise.reject().catch(() => {
console.error('catch some error')
throw new Error('err')
})
场景题:
Promise then
和catch
的链接调用(常考)
// 第一题
Promise.resolve().then(() => {
console.log(1)
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
// 1 3
// 第二题
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).then(() => {
console.log(3)
})
// 1 2 3
// 第三题
Promise.resolve().then(() => {
console.log(1)
throw new Error('erro1')
}).catch(() => {
console.log(2)
}).catch(() => { // 注意这里是 catch
console.log(3)
})
// 1 2
Promise和setTimeout的顺序
console.log(100)
setTimeout(() => {
console.log(200)
})
Promise.resolve().then(() => {
console.log(300)
})
console.log(400)
// 100 400 300 200
场景题:各类异步执行顺序问题
- 三、
async
/await
场景题:async/await 语法
- 异步回调
callback hell
Promise then catch
链式调用,但也是基于回调函数async/await
是同步语法
,彻底消灭回调函数
async function fn() {
return 100
}
(async function () {
const a = fn() // ?? // promise
const b = await fn() // ?? // 100
})()
(async function () {
console.log('start')
const a = await 100
console.log('a', a)
const b = await Promise.resolve(200)
console.log('b', b)
const c = await Promise.reject(300)
console.log('c', c)
console.log('end')
})() // 执行完毕,打印出那些内容?
async/await
和Promise
的关系:
- 执行
async
函数,返回的是Promise
对象 await
相当于Promise
的then
try……catch
可捕获异常,代替了Promise
的catch
- async 函数返回结果都是 Promise 对象(如果函数内没返回 Promise ,则自动封装一下)
async function fn2() {
return new Promise(() => {})
}
console.log( fn2() )
async function fn1() {
return 100
}
console.log( fn1() ) // 相当于 Promise.resolve(100)
- await 后面跟 Promise 对象:会阻断后续代码,等待状态变为 resolved ,才获取结果并继续执行
- await 后续跟非 Promise 对象:会直接返回
(async function () {
const p1 = new Promise(() => {})
await p1
console.log('p1') // 不会执行
})()
(async function () {
const p2 = Promise.resolve(100)
const res = await p2
console.log(res) // 100
})()
(async function () {
const res = await 100
console.log(res) // 100
})()
(async function () {
const p3 = Promise.reject('some err')
const res = await p3
console.log(res) // 不会执行
})()
- try...catch 捕获 rejected 状态
(async function () {
const p4 = Promise.reject('some err')
try {
const res = await p4
console.log(res)
} catch (ex) {
console.error(ex)
}
})()
总结来看:
- async 封装 Promise
- await 处理 Promise 成功
- try...catch 处理 Promise 失败
场景题:外加async/await的顺序问题 网上很经典的面试题
async function async1 () {
console.log('async1 start')
await async2() // 这一句会同步执行,返回 Promise ,其中的 `console.log('async2')` 也会同步执行
console.log('async1 end') // 上面有 await ,下面就变成了“异步”,类似 cakkback 的功能(微任务)
}
async function async2 () {
console.log('async2')
}
console.log('script start')
setTimeout(function () { // 异步,宏任务
console.log('setTimeout')
}, 0)
async1()
new Promise (function (resolve) { // 返回 Promise 之后,即同步执行完成,then 是异步代码
console.log('promise1') // Promise 的函数体会立刻执行
resolve()
}).then (function () { // 异步,微任务
console.log('promise2')
})
console.log('script end')
// 同步代码执行完之后,屡一下现有的异步未执行的,按照顺序
// 1. async1 函数中 await 后面的内容 —— 微任务
// 2. setTimeout —— 宏任务
// 3. then —— 微任务
for...of
和普通的for循环有差别
// 定时算乘法
function multi(num) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(num * num)
}, 1000)
})
}
// // 使用 forEach ,是 1s 之后打印出所有结果,即 3 个值是一起被计算出来的
// function test1 () {
// const nums = [1, 2, 3];
// nums.forEach(async x => {
// const res = await multi(x);
// console.log(res);
// })
// }
// test1();
// 使用 for...of ,可以让计算挨个串行执行
async function test2 () {
const nums = [1, 2, 3];
for (let x of nums) {
// 在 for...of 循环体的内部,遇到 await 会挨个串行计算
const res = await multi(x)
console.log(res)
}
}
test2()
- 四、微任务、宏任务
什么是宏任务,什么是微任务?宏任务有哪些,微任务有哪些?
微任务
触发时机更早
- 宏任务:
setTimeout
,setInterval
,Ajax
,DOM
事件 - 微任务:
Promise
async
/await
微任务
执行时机比宏任务
要早
console.log(100)
setTimeout(() => {
console.log(200)
})
Promise.resolve().then(() => {
console.log(300)
})
console.log(400)
// 100 400 300 200
event loop和DOM渲染/微任务、宏任务和DOM渲染的关系,在event loop的过程
JS
是单线程的,而且和DOM
渲染共用一个线程JS
执行的时候,得留一些时机供DOM
渲染
- 每一次 call stack 结束,都会触发 DOM 渲染(不一定非得渲染,就是给一次 DOM 渲染的机会!!!)
- 然后再进行 event loop
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 stack 结束,DOM 结构已更新,但尚未触发渲染')
// (alert 会阻断 js 执行,也会阻断 DOM 渲染,便于查看效果)
// 到此,即本次 call stack 结束后(同步任务都执行完了),浏览器会自动触发渲染,不用代码干预
// 另外,按照 event loop 触发 DOM 渲染时机,setTimeout 时 alert ,就能看到 DOM 渲染后的结果了
setTimeout(function () {
alert('setTimeout 是在下一次 Call Stack ,就能看到 DOM 渲染出来的结果了')
})
宏任务和微任务的区别
- 宏任务:DOM 渲染后再触发
- 微任务:DOM 渲染前会触发
先后触发的原因:
微任务
是ES6
语法规定的宏任务
是由浏览器
规定的(web api)
// 修改 DOM
const $p1 = $('<p>一段文字</p>')
const $p2 = $('<p>一段文字</p>')
const $p3 = $('<p>一段文字</p>')
$('#container')
.append($p1)
.append($p2)
.append($p3)
// // 微任务:渲染之前执行(DOM 结构已更新)
// Promise.resolve().then(() => {
// const length = $('#container').children().length
// alert(`micro task ${length}`)
// })
// 宏任务:渲染之后执行(DOM 结构已更新)
setTimeout(() => {
const length = $('#container').children().length
alert(`macro task ${length}`)
})
再深入思考一下:为何两者会有以上区别,一个在渲染前,一个在渲染后?
- 微任务:ES 语法标准之内,JS 引擎来统一处理。即,不用浏览器有任何关于,即可一次性处理完,更快更及时。
- 宏任务:ES 语法没有,JS 引擎不处理,浏览器(或 nodejs)干预处理。
3.4 JS WEB API
JS
基础知识,规定语法(ECMA 262
标准)JS WEB API
,网页操作的API
(W3C
标准)- 前者是后者的基础,两者的结合才能真正实际应用
DOM是哪种数据结构(DOM本质)
树(DOM树)
DOM操作常用API(DOM节点操作)
attr和property的区别(DOM结构操作)
一次性插入多个DOM节点,考虑性能(DOM性能)
DOM节点操作
const div1 = document.getElementById('div1')
console.log('div1', div1)
const divList = document.getElementsByTagName('div') // 集合
console.log('divList.length', divList.length)
console.log('divList[1]', divList[1])
const containerList = document.getElementsByClassName('container') // 集合
console.log('containerList.length', containerList.length)
console.log('containerList[1]', containerList[1])
const pList = document.querySelectorAll('p')
console.log('pList', pList)
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 )
console.log(p1.nodeName)
console.log(p1.nodeType) // 1
// attribute
p1.setAttribute('data-name', 'imooc')
console.log( p1.getAttribute('data-name') )
p1.setAttribute('style', 'font-size: 50px;')
console.log( p1.getAttribute('style') )
property
:修改对象属性,不会体现到html结构中attribute
:修改html属性,会改变html结构- 两者都可能引起DOM重新渲染
DOM结构操作
const div1 = document.getElementById('div1')
const div2 = document.getElementById('div2')
// 新建节点
const newP = document.createElement('p')
newP.innerHTML = 'this is newP'
// 插入节点
div1.appendChild(newP)
// 移动节点
const p1 = document.getElementById('p1')
div2.appendChild(p1)
// 获取父元素
console.log( p1.parentNode )
// 获取子元素列表
const div1ChildNodes = div1.childNodes
console.log( div1.childNodes )
const div1ChildNodesP = Array.prototype.slice.call(div1.childNodes).filter(child => {
if (child.nodeType === 1) {
return true
}
return false
})
console.log('div1ChildNodesP', div1ChildNodesP)
div1.removeChild( div1ChildNodesP[0] )
DOM性能
- DOM操作非常“昂贵”,避免频繁的DOM操作
- 对DOM查询做缓存
- 将频繁操作改为一次性操作
const list = document.getElementById('list')
// 创建一个文档片段,此时还没有插入到 DOM 结构中
const frag = document.createDocumentFragment()
for (let i = 0; i < 20; i++) {
const li = document.createElement('li')
li.innerHTML = `List item ${i}`
// 先插入文档片段中
frag.appendChild(li)
}
// 都完成之后,再统一插入到 DOM 结构中
list.appendChild(frag)
console.log(list)
3.5 BOM操作
navigator
screen
location
history
如何识别浏览器的类型
分析拆解url各个部分
3.6 事件
// 通用的事件绑定函数
// function bindEvent(elem, type, fn) {
// elem.addEventListener(type, fn)
// }
function bindEvent(elem, type, selector, fn) {
if (fn == null) {
fn = selector
selector = null
}
elem.addEventListener(type, event => {
const target = event.target
if (selector) {
// 代理绑定
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
// 普通绑定
fn.call(target, event)
}
})
}
// 普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', function (event) {
// console.log(event.target) // 获取触发的元素
event.preventDefault() // 阻止默认行为
alert(this.innerHTML)
})
// 代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', function (event) {
event.preventDefault()
alert(this.innerHTML)
})
// const p1 = document.getElementById('p1')
// bindEvent(p1, 'click', event => {
// event.stopPropagation() // 阻止冒泡
// console.log('激活')
// })
// const body = document.body
// bindEvent(body, 'click', event => {
// console.log('取消')
// // console.log(event.target)
// })
// const div2 = document.getElementById('div2')
// bindEvent(div2, 'click', event => {
// console.log('div2 clicked')
// console.log(event.target)
// })
编写一个通用的事件监听函数(事件绑定)
function bindEvent(elem, type, selector, fn) {
if (fn == null) {
fn = selector
selector = null
}
elem.addEventListener(type, event => {
const target = event.target
if (selector) {
// 代理绑定
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
// 普通绑定
fn.call(target, event)
}
})
}
描写事件冒泡的流程(事件冒泡)
1. 基于DOM树形结构
2. 事件会顺着触发元素向上冒泡
3. 应用场景:代理
无限下拉的图片列表,如何监听每个图片的点击?(事件代理)
- 代码简洁
- 减少浏览器内存占用
- 不能滥用
事件代理、用e.target获取触发元素、用matches来判断是否是触发元素
如何识别浏览器的类型
分析拆解url各个部分
3.7 ajax
- XMLHttpRequest
- 状态码
- 跨域:同源策略,跨域解决方案
xhr.readyState
0(未初始化):还没有调用send()方法
1(载入):已调用send()方法,正在发送请求
2(载入完成):send()方法执行完成,已经接收到全部响应内容
3(交互):正在解析响应内容
4(完成):响应内容解析完成,可以在客户端调用
xhr.status
2xx:表示成功处理请求,如200
3xx:表示重定向,浏览器直接跳转,如301 302 304
4xx:客户端请求错误,如 404 403
5xx:服务器端错误
手写一个简易的ajax
// const xhr = new XMLHttpRequest()
// xhr.open('GET', '/data/test.json', true)
// xhr.onreadystatechange = function () {
// if (xhr.readyState === 4) {
// if (xhr.status === 200) {
// // console.log(
// // JSON.parse(xhr.responseText)
// // )
// alert(xhr.responseText)
// } else if (xhr.status === 404) {
// console.log('404 not found')
// }
// }
// }
// xhr.send(null)
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 if (xhr.status === 404 || xhr.status === 500) {
reject(new Error('404 not found'))
}
}
}
xhr.send(null)
})
return p
}
const url = '/data/test.json'
ajax(url)
.then(res => console.log(res))
.catch(err => console.error(err))
什么是浏览器的同源策略?
- ajax请求时,浏览器要求当前网页和server必须同源(安全)
- 同源:协议、域名、端口,三者必须一致
加载图片css js 可无视同源策略
<img src=跨域的图片地址 />
<link href=跨域的css地址 />
<script src=跨域的js地址 ></script>
<img />:可用于统计打点,可使用第三方统计服务
<link /><script>可使用CDN,CDN一般都是外域
<script>可实现JSONP
跨域的常用实现方式
跨域
-
所有的跨域,都必须经过server端允许和配合
-
未经server端允许就实现跨域,说明浏览器有漏洞
-
JSONP
<script>可绕过跨域限制
服务器可以在任意动态拼接数据返回
所以<script>就可以获得跨域的数据,只要服务端愿意返回
$.ajax({
url: 'http://localhost:8882/x-origin.json',
dataType: 'jsonp',
jsonpCallback: 'callback',
success: function (data) {
console.log(data)
}
})
- CORS - 服务器设置
http header
3.2.11 缓存
cookie
- 本身用于浏览器和server通讯
- 被借用到本地存储
- 可用 document.cookie = '...'来修改
缺点: 1. 存储大小,最大4kb 2. http请求时需要发送到服务端,增加请求数量 3. 只能用document.cookie = ''来修改
localStorage
和sessionStorage
- H5专门为存储而设计,最大可存5M
- API简单易用
setItem
getItem
- 不会随着
http
请求被发送出去
localStorage
:数据会永久存储,除非代码或手动删除
sessionStorage
:数据只存在于当前会话,浏览器关闭则清空
3.8 ES5和ES6继承的区别
- ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的属性和方法添加到子类实例对象的this上面(Parent.call(this))。
- ES6 的继承机制完全不同,实质是先创建父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this实现继承。
容量、API易用性、是否跟随http请求发送出去
四、Http
4.1 Http状态码
1XX:服务器收到请求()
2xx:表示成功处理请求,如200()
3xx:表示重定向,浏览器直接跳转,如301 302 304
301:永久重定向(配合location,浏览器自动处理)
302:临时重定向(配合location,浏览器自动处理)
304:资源未修改
4xx:客户端请求错误,如 404 403()
404:资源未找到
403:没有权限
5xx:服务器端错误()
500:服务器错误
504:网关超时
4.2 http methods
-
传统的methods
get
获取服务器的数据post
像服务器提交数据- 简单的网页功能,就这个两个操作
-
现在的methods
get
获取数据post
新建数据patch
/put
更新数据delete
删除数据
-
Restful API
- 一种新的API设计方法(早已被推广使用)
- 传统API设计:把每个url当做一个功能
- Restful API 设计:把每个url当做唯一的资源(尽量不用url参数、用mathod表示操作类型)
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 用method表示操作类型(Restful API设计)
+ post请求:/api/blog + patch请求:/api/blog/100 + get请求:/api/blog/100
4.3 http的header
4.3.1 Request Headers
Accept
:浏览器可接收的数据格式Accept-Encoding
:浏览器可接收的压缩算法,如gzip
Accept-Languange
:浏览器可接收的语言,如zh-CNConnection:keep-alive
:一次TCP
连接重复使用cookie
:每次同域请求都会带上cookieHost
:请求的域名是什么(例:www.baidu.com)User-Agent
:简称UA
,浏览器信息,什么浏览器什么系统(手机,安卓)Content-type
:发送数据的格式,如application
/json
(post请求有,get请求没有)
4.3.2 Response Headers
Content-type
:返回数据的格式,如application/json
(图片:image/png
,JS:application/x-javascript
)Content-length
:返回数据的大小,多少字节Content-Encoding
:返回数据的压缩算法,如gzip
Set-Cookie
:服务端修改cookie
通过这个修改
4.3.3 自定义header
axios-js.com/docs/#Request-Config
headers: {'X-Requested-With': 'XMLHttpRequest'}
4.4 http缓存
什么是缓存?
浏览器访问一个新网站,所有资源都从服务端获取。第二次访问,则没必要重新获取所有的。
为什么需要缓存?
网络请求比CPU计算、页面渲染更耗时间,尽量减少网络请求的体积和数量。而且网络请求不稳定,所以优化网络请求需要缓存
哪些资源可以被缓存?
静态资源(js css img)
html、业务数据经常变,不能缓存
4.4.1 http缓存-强制缓存
- http缓存策略(强制缓存+协商缓存)
Cache-Control
:
Response Headers
中- 控制强制缓存的逻辑:例如
Cache-Control:max-age=3153434
(单位是秒)
Expires
已被Cache-Control
代替
Cache-Control
的值:
max-age
:过期时间no-cach
:不需要本地缓存,服务端自行处理no-store
:不需要本地缓存,不用服务端的缓存private
:只能最终用户做缓存(手机、电脑)public
:允许中间路由、代理做缓存
4.4.2 http缓存-协商缓存
- 服务器端缓存策略:告诉请求可以使用本地缓存
- 服务器端判断客户端资源是否和服务器端资源一样
- 资源一样则返回304,否则返回200和最新的资源
资源标识:
- 在
Response Headers
中,有两种 Last-Modified
:资源的最后修改时间Etag
资源的唯一标识(一个字符串,类似人类的指纹)
优先使用Etag
,Last-Modified
只能精确到秒级,如果资源被重复生成,而内容不变,则Etag
更精确
4.4.3 缓存相关的headers
Cache-Control
:Expires
Last-Modified
:If-Modified-Since
Etag
:If-None-Match
4.3.4 三种刷新操作
- 正常操作:地址栏输入url,跳转链接,前进后退等
- 手动刷新:F5,点击刷新按钮,右击菜单刷新
- 强制刷新:ctrl + F5
不同刷新操作,不同的缓存策略
- 正常操作:强制缓存有效,协商缓存有效
- 手动刷新:强制缓存失效,协商缓存有效
- 强制刷新:强制缓存失效,协商缓存失效
4.4.5 面试题
Http 常见的状态码有哪些?
Http常见的header有哪些?
什么是Restful API
描述一下Http的缓存机制(重要)
五、开发环境
5.1 常见开发工具
- git
- 调试工具
- 抓包
- webpack babel
- linux 常用命令
5.1.1 git
- 最常用的代码版本管理工具
git status
git add .
git checkout xxx
git commit -m "xx"
git push origin master
git pull origin master
git branch
git checkout -b xxx/git checkout xxx
git merge xxx
git satsh
git satsh pop
5.1.2 chrome调试工具(技能)
Elements
:Console
:debugger
:Network
:Application
:
5.1.3 移动端 h5 抓包工具
- windows:fiddler
- Mac OS:charles
抓包条件:手机和电脑连同一个局域网,将手机代理到电脑上,手机浏览网页即可抓包
5.1.4 webpack babel
ES6模块化,浏览器暂不支持,ES6语法浏览器并不完全支持。
压缩代码,整合代码,以让网页加载更快
npm install webpack webpack-cli -D
npm install html-webpack-plugin -D
npm install webpack-dev-server -D //启动服务
npm install @babel/core @babel/preset-env babel-loader -D
"build": "webpack --config webpack.prod.js",
"dev": "webpack-dev-server"
.babelrc文件
{
"presets": ["@babel/preset-env"],
"plugins": []
}
//开发环境
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// mode 可选 development 或 production ,默认为后者
// production 会默认压缩代码并进行其他优化(如 tree shaking)
mode: 'development',
entry: path.join(__dirname, 'src', 'index'),
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader'],
include: path.join(__dirname, 'src'),
exclude: /node_modules/
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'index.html'),
filename: 'index.html'
})
],
devServer: {
port: 3000,
contentBase: path.join(__dirname, 'dist'), // 根目录
open: true, // 自动打开浏览器
}
}
//生产环境
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'production',
entry: path.join(__dirname, 'src', 'index'),
output: {
filename: 'bundle.[contenthash].js',
path: path.join(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader'],
include: path.join(__dirname, 'src'),
exclude: /node_modules/
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'index.html'),
filename: 'index.html'
})
]
}
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'production',
entry: path.join(__dirname, 'src', 'index'),
output: {
filename: 'bundle.[contenthash].js',
path: path.join(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.js$/,
loader: ['babel-loader'],
include: path.join(__dirname, 'src'),
exclude: /node_modules/
},
]
},
plugins: [
new HtmlWebpackPlugin({
template: path.join(__dirname, 'src', 'index.html'),
filename: 'index.html'
})
]
}
5.1.5 linux命令
5.2 运行环境
运行环境即浏览器(sercer端有nodejs)
5.2.1 网页的加载和渲染
- 加载资源的形式
- html代码
- 媒体文件:图片、视频等
- javascript css
- 加载资源的过程
- DNS解析:域名-> IP地址
- 浏览器根据IP地址向服务器发起http请求
- 服务器处理http请求,并返回给浏览器
- 渲染页面的过程
- 根据
HTML
代码生成DOM Tree
- 根据
CSS
代码生成CSSOM
- 将
DOM Tree
和CSSOM
整合形成Render Tree
- 根据
Render Tree
渲染页面 - 遇到
<script>
则暂停渲染,优先加载并执行JS代码 - 渲染出
Render Tree
- 根据
为何建议将css放在head中
为何建议将js放在body之后
面试题
从输入url到渲染出页面的整个过程
- 下载资源:各个资源类型,下载过程
- 渲染页面:结合 html css javascript 图片等
window.onload和DOMContentLoaded区别
window.addEventListener('load', function(){
//页面的全部资源加载完才会执行,包括图片、视频等
})
document.addEventListener('DOMContentLoaded', function(){
//DOM 渲染完即可执行,此时图片、视频还可能没有加载完
})
const img1 = document.getElementById('img1')
img1.onload = function () {
console.log('img loaded')
}
window.addEventListener('load', function () {
console.log('window loaded')
})
document.addEventListener('DOMContentLoaded', function () {
console.log('dom content loaded')
})
5.3 前端性能优化
原则:
-
多使用内存、缓存或其他方法
-
减少CPU计算量,减少网络加载耗时
-
空间换时间:适用于所有编程的性能优化
-
让加载更快
- 减少资源体积:压缩代码
- 减少访问次数:合并代码,SSR服务器端渲染,缓存
- 使用更快的网络:CDN
-
让渲染更快
- CSS放在head,JS放在body最下面
- 尽早开始执行JS,用DOMContentLoaded触发(没必要等到图片加载完成再渲染)
- 懒加载(图片懒加载,上滑加载更多)
- 对DOM查询进行缓存
- 频繁DOM操作,合并到一起插入到DOM结构
- 节流throttle,防抖debounce
5.3.1 资源合并
多个script文件合并成一个
5.3.2 缓存
- 静态资源加hash后缀,根据文件内容计算hash
- 文件内容不变,则hash不变,则url不变
- url和文件不变,则自动触发http缓存机制,返回304
CDN
静态文件资源
SSR
- 服务器端渲染:将网页和数据一起加载,一起渲染
- 非SSR(前后端分离):先加载网页,再加载数据,再渲染数据
懒加载
<img id="img1" src="preview.png" data-realsrc="abc.png" />
<script type="text/javascript">
var img1 = document.getElementById('img1')
img1.src = img1.getAttribute('data-realsrc')
</script>
5.3.3 防抖、节流
防抖:用户输入结束或暂停时,才会触发change事件
const input1 = document.getElementById('input1')
// let timer = null
// input1.addEventListener('keyup', function () {
// if (timer) {
// clearTimeout(timer)
// }
// timer = setTimeout(() => {
// // 模拟触发 change 事件
// console.log(input1.value)
// // 清空定时器
// timer = null
// }, 500)
// })
// 防抖
function debounce(fn, delay = 500) {
// timer 是闭包中的
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments) //箭头函数就没有arguments
// fn()也可
timer = null
}, delay)
}
}
input1.addEventListener('keyup', debounce(function (e) {
console.log(e.target)
console.log(input1.value)
}, 600))
节流:每隔一定时间渲染一次(拖拽一个元素)
const div1 = document.getElementById('div1')
// let timer = null
// div1.addEventListener('drag', function (e) {
// if (timer) {
// return
// }
// timer = setTimeout(() => {
// console.log(e.offsetX, e.offsetY)
// timer = null
// }, 100)
// })
// 节流
function throttle(fn, delay = 100) {
let timer = null
return function () {
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
div1.addEventListener('drag', throttle(function (e) {
console.log(e.offsetX, e.offsetY)
}))
div1.addEventListener('drag', function(event) {
})
5.3.4 前端安全
常见的web前端攻击方式有哪些?
XSS
跨站请求攻击
XSS攻击例子:
1. 博客网站,发表的博客被嵌入<script>脚本
2. 脚本内容:获取cookie,发送到自己的服务器(服务器配合跨域)
3. 发布这篇博客,有人查看这篇博客,就能轻松收割访问者的cookie
预防:
1. 替换特殊字符,如`<`变为`<` `>`变为`>`
2. `<script>`变为`<script>`,直接显示,而不会作为脚本执行
3. 前端要替换,后端也要替换
XSRF
跨站请求伪造
XSRF 攻击 例子1:
1. 正在购物,看中了某个商品,商品id是100
2. 付费接口是xxx.com/pay?id=100,但没有任何验证
3. 我i是攻击者,看中商品,id为200
XSRF 攻击 例子2:
1. 发送一封电子邮件,邮件标题很吸引人
2. 但邮件正文隐藏着<img src=xxx.com/pay?id=200/>
3. 一查看邮件,就购买了id是200的商品
预防:
1. 使用post接口
2. 增加验证,例如密码、短信验证码、指纹等
六、常见问题
1、var和let const的区别
- var是ES5语法,let const是ES6语法;var有变量提升
- var和let是变量,可修改;const是常量,不可修改
- let const是块级作用域,var没有
2、typeof能判断哪些类型
undefined
string
number
boolean
symbol
object
(注意,typeof null === 'object'
)function
3、列举强制类型转换和隐式类型转换
- 强制:
parseInt
parseFloat
toString
等 - 隐式:if、逻辑运算、==、+拼接字符串
4、手写深度比较,模拟lodash.isEqual
// 判断是否是对象或数组
function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
// 全相等(深度)
function isEqual(obj1, obj2) {
if (!isObject(obj1) || !isObject(obj2)) {
// 值类型(注意,参与 equal 的一般不会是函数)
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
}
// 测试
const obj1 = {
a: 100,
b: {
x: 100,
y: 200
}
}
const obj2 = {
a: 100,
b: {
x: 100,
y: 200
}
}
// console.log( obj1 === obj2 )
console.log( isEqual(obj1, obj2) )
const arr1 = [1, 2, 3]
const arr2 = [1, 2, 3, 4]
5、split()和join() 的区别
'1-2-3'.split('-') //[1,2,3]
[1,2,3].join('-') //1-2-3
6、数组的pop push unshift shift分别是什么
- 功能
- 返回值
- 是否会对原数组造成影响
pop:删除数组最后一个数据,返回删除的数据
shift:删除数组第一个数据,返回删除的数据
push:往数组尾部添加数据,返回 length
unshift:往数组头部添加数据,返回 length
7、数组slice和splice的区别
- slice 切片;splice 剪接
// slice 纯函数 不修改原数组
// const arr1 = arr.slice()
// const arr2 = arr.slice(1, 4)
// const arr3 = arr.slice(2)
// const arr4 = arr.slice(-3)
// splice 非纯函数,修改原数组
// const spliceRes = arr.splice(1, 2, 'a', 'b', 'c')
// // const spliceRes1 = arr.splice(1, 2)
// // const spliceRes2 = arr.splice(1, 0, 'a', 'b', 'c')
// console.log(spliceRes, arr)
8、[10, 20, 30].map(parseInt)返回结果是什么?
const res = [10, 20, 30].map(parseInt)
console.log(res) //[10,NaN,NaN]
// 拆解
[10, 20, 30].map((num, index) => {
return parseInt(num, index)
})
9、ajax请求get和post的区别?
- get 一般用于查询操作,post 一般用户提交操作
- get参数拼接在url上,post放在请求体内(数据体积可更大)
- 安全性:post易于防止CSRF
10、函数call和apply的区别
fn.call(this,p1,p2,p3)
fn.apply(this,arguments)
11、事件代理(委托)是什么?
12、闭包是什么,有什么特性?有什么负面影响?
- 函数作为参数被传入,作为返回值被返回
- 自由变量的查找,要在函数定义的地方(而非执行的地方)
- 变量会常驻内存,得不到释放
// // 自由变量示例 —— 内存会被释放
// let a = 0
// function fn1() {
// let a1 = 100
// function fn2() {
// let a2 = 200
// function fn3() {
// let a3 = 300
// return a + a1 + a2 + a3
// }
// fn3()
// }
// fn2()
// }
// fn1()
// // 闭包 函数作为返回值 —— 内存不会被释放
// function create() {
// let a = 100
// return function () {
// console.log(a)
// }
// }
// let fn = create()
// let a = 200
// fn() // 100
function print(fn) {
let a = 200
fn()
}
let a = 100
function fn() {
console.log(a)
}
print(fn) // 100
13、如何阻止事件冒泡和默认行为?
event.stopPropagation()
event.preventDefault()
14、查找、添加、删除、移动DOM节点的方法?
15、如何减少DOM操作?
- 缓存DOM查询结果
- 多次存DOM操作,合并到一次插入
16、解释jsonp的原理,为何它不是真正的ajax ?
17、document load 和 ready 的区别
18、==和===的不同
- == 会尝试类型转换
- === 严格相等
19、函数声明和函数表达式的区别
- 函数声明:function fn() {…}
- 函数表达式:const fn = function() {...}
- 函数声明会在代码执行前预加载,而函数表达式不会
20、new Object()和 Object.create()的区别
{}
仍等同于new Object()
,原型Object. prototype
Object.create(null)
没有原型Object create({...})
可指定原型
const obj21 = new Object(obj1) // obj1 === obj2
const obj3 = Object.create(null)
const obj4 = new Object() // {}
const obj5 = Object.create({
a: 10,
b: 20,
sum() {
return this.a + this.b
}
})
const obj6 = Object.create(obj1)
21、关于this的场景题 this在执行的时候,才知道指向谁
const User = {
count: 1,
getCount: function(){
return this.count
}
}
console.log(User.getCount()) //
const func = User.getCount
console.log(func()) //
22、关于作用域和自由变量的场景题
let i
for(i = 1; i<= 3; i++){
setTimeout(function(){
console.log(i)
},0)
}
23、判断字符串以字母开头,后面字母数字下划线,长度6-30
const reg = /^[a-zA-Z]\w{5,29}$/
// 邮政编码
/\d{6}/
// 小写英文字母
/^[a-z]+$/
// 英文字母
/^[a-zA-Z]+$/
// 日期格式 2019.12.1
/^\d{4}-\d{1,2}-\d{1,2}$/
// 用户名
/^[a-zA-Z]\w{5, 17}$/
// 简单的 IP 地址匹配
/\d+\.\d+\.\d+\.\d+/
24、关于作用域和自由变量的场景题
let a = 100
function test() {
alert(a)
a = 10
alert(a)
}
test()
alert(a)
25、手写字符串trim方法,保证浏览器兼容性
String.prototype.trim = function() {
return this.replace(/^\s+/,'').replace(/\s+$/,'')
}
26、如何获取多个数字中的最大值
function max() {
const nums = Array.prototype.slice.call(arguments)
let max = 0
nums.forEach(n => {
if(n > max)
max = n
})
return max
}
27、如何用JS实现继承?
- class 继承
- prototype
28、如何捕获JS程序中的异常?
try {
// todo
} catch (ex)[
console. error(ex)//手动捕获 catch
} finally {
// todo
}
//自动捕获
window.onerror = function (message, source, lineNom, colNom, error) {
//第一,对跨域的js,如CDN的,不会有详细的报错信息
//第二,对于压缩的js,还要配合 sourceMap反查到未压缩代码的行、列
29、什么是JSON?
- json是一种数据格式,本质是一段字符串。
- json格式和Js对象结构-致,对JS语言更友好
- window.json是一个全局对象: JSON.stringify JSON.parse
30、获取当前页面ur参数
- 传统方式,查找 location.search
- 新API, URLSearchParams
// // 传统方式
// function query(name) {
// const search = location.search.substr(1) // 类似 array.slice(1)
// // search: 'a=10&b=20&c=30'
// const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i')
// const res = search.match(reg)
// if (res === null) {
// return null
// }
// return res[2]
// }
// query('d')
// URLSearchParams
function query(name) {
const search = location.search
const p = new URLSearchParams(search)
return p.get(name)
}
console.log( query('b') )
31、将url参数解析为JS对象
32、手写数组 flaten,考虑多层级
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) // 递归
}
const res = flat( [1, 2, [3, 4, [10, 20, [100, 200]]], 5] )
console.log(res)
33、数组去重
// // 传统方式
// function unique(arr) {
// const res = []
// arr.forEach(item => {
// if (res.indexOf(item) < 0) {
// res.push(item)
// }
// })
// return res
// }
// 使用 Set (无序,不能重复)
function unique(arr) {
const set = new Set(arr)
return [...set]
}
const res = unique([30, 10, 20, 30, 40, 10])
console.log(res)
34、手写深拷贝
35、介绍一下 RAF requestAnimationFrame
- 要想动画流畅,更新频率要60帧/s,即16.67ms更新一次视图
setTimeout
要手动控制频率,而RAF
浏览会自动控制- 后台标签或隐藏
iframe
中,RAF
会暂停,而setTimeout
依然执行
36、前端性能如何优化?一般从哪几个方面考虑?
页面加载过程
window.onload
JS作用域
js创建10个a标签,点击每个跳出对应数字