高效学习三部曲:基础

211 阅读18分钟

高效学习三部曲:找准知识体系;刻意训练;及时反馈

从哪些方面梳理:

  • W3C标准
  • ECMA 262标准
  • 开发环境
  • 运行环境

一、HTML

1. 如何理解HTML语义化?

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

2. 默认情况下,哪些HTML标签是块级元素、哪些是内联元素?

  • 块级(display: block/table):divh1h2tableulolp
  • 内联(display: inline/inline-block):spanimginputbutton

二、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-topmargin-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-topmargin-left负值,元素向上、向左移动
  • margin-right负值,右侧元素左移,自身不受影响
  • margin-bottom负值,下方元素上移,自身不受影响
  • margin值上下一正一负,间距是两个值的相加

2.1.4 BFC的理解和应用

  • Block format context:块级格式化上下文
  • 一块独立渲染区域,内部元素的渲染不会影响边界以外的元素

形成BFC的常见条件

  • float不是noneleft|right
  • positionabsolute|fixed
  • overflow不是visiblehidden|auto|scroll
  • displaytable-cell|table-caption|inline-block|inline-flex|flex

例子:一个div里包含两个div,div左浮动导致外层div高度坍塌。在外层div添加overflow: hidden; /* 触发元素 BFC */即可,便不会出现坍塌的情况

BFC布局规则

  1. 内部的Box会在垂直方向,一个接一个地放置(即块级元素独占一行)。
  2. BFC的区域不会与float box重叠(利用这点可以实现自适应两栏布局)。
  3. 内部的Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠(margin重叠三个条件:同属于一个BFC;相邻;块级元素(行内元素如a标签便不会发生重叠,而是累加))。
  4. 计算BFC的高度时,浮动元素也参与计算。(清除浮动 haslayout)
  5. BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素。反之也如此。

BFC有特性

  • BFC会阻止垂直外边距折叠
    1. 相邻兄弟元素margin重叠问题
    2. 父子元素margin重叠问题
  • BFC不会重叠浮动元素 实现两列自适应布局
  • BFC可以包含浮动----清除浮动 容器内两个div元素浮动,脱离了文档流,父容器内容宽度为零(即发生高度塌陷),未能将子元素包裹住。解决这个问题,只需要把把父元素变成一个BFC就行了。

2.1.5 float布局的问题,以及clearfix

如何实现圣杯布局和双飞翼布局?

  1. 圣杯布局和双飞翼布局的目的
  • 三栏布局,中间一栏最先加载和渲染(内容最重要)
  • 两侧内容固定,中间内容随着宽度自适应
  • 一般用于PC网页
  1. 总结
  • 使用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根据最近一层元素的定位(定位元素:absoluterelativefixedbody

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元素: topleftbottomright = 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

  1. rem的弊端:阶梯性
  2. 网页视口尺寸
    • window.screen.height //屏幕高度
    • window.innerHeight //网页视口高度
    • document.body.clientHeight //body高度
  3. vw/vh
    • vh网页视口高度的1/100
    • vw网页视口宽度的1/100
    • vmax取两者最大值;vmin取两者最小值

2.5 CSS3(flex、动画)

三、JS

3.1 JS变量类型

3.1.1 原始类型和对象类型

常见的值有哪些

  1. 7 种原始类型(不可变):Boolean、Undefined、Number、String、Null、Symbol(ES6)、BigInt(ES6)

  2. 1 种对象类型Object: • 包含了Function、Array、特殊对象(RegExp、Date)

typeof能判断那些类型 number、string、boolean、undefined、object、function、symbol、bigint

识别所有值类型、识别函数、判断是否是引用类型(不可再细分)

值类型和引用类型的区别 值类型:

  1. 占用空间固定,保存在
  2. 保存与复制的是值本身
  3. 使用typeof检测数据的类型
  4. 基本类型数据是值类型
  5. 在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了

引用类型:

  1. 占用空间不固定,保存在堆中
  2. 保存与复制的是指向对象的一个指针
  3. 使用instanceof检测数据类型
  4. 只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它

微信截图_20210319112107.png

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语句和逻辑运算
    1. truly变量:!!a === true的变量
    2. 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__指向对应classprototype

如何判断一个变量是数组?

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 作用域和闭包

知识点

  • 作用域
    1. 全局作用域
    2. 函数作用域
    3. 块级作用域(ES6新增,if/while/for {}里的作用域)
  • 自由变量
    1. 一个变量在当前作用域没有定义,但被使用了
    2. 向上级作用域,一层一层依次寻找,直至找到为止
    3. 如果到全局作用域都没找到,则报错xx is not defined
  • 闭包
    1. 作用域应用的特殊情况,有两种表现:
    2. 函数作为参数被传递
    // 函数作为返回值
    function create() {
        const a = 100
        return function () {
            console.log(a)
        }
    }
    
    const fn = create()
    const a = 200
    fn() // 100
    
    1. 函数作为返回值被返回
    // 函数作为参数被传递
    function print(fn) {
        const a = 200
        fn()
    }
    const a = 100
    function fn() {
        console.log(a)
    }
    print(fn) // 100
    
    所有的自由变量的查找,是在函数定义的地方,向上级作用域查找
    不是在执行的地方!!!
  • this
    1. 作为普通函数
    2. 使用 call、apply、bind
    3. 作为对象方法被调用
    4. 在class方法中调用
    5. 箭头函数 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的不同应用场景,如何取值?

  1. 作为普通函数
  2. 使用 call、apply、bind
  3. 作为对象方法被调用
  4. 在class方法中调用
  5. 箭头函数

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

  • 单线程和异步
    1. JS是单线程语言,只能同时做一件事
    2. 浏览器和nodejs已经支持JS启动进程,如Web Worker
    3. JS和DOM渲染共用同一个线程,因为JS可修改DOM结构
    4. 遇到等待(网络请求,定时任务)不能卡住
    5. 需要异步
    6. 回调callback函数形式
    // 异步 (callback 回调函数)
    console.log(100)
    setTimeout(() => {
        console.log(200)
    }, 1000)
    console.log(300)
    console.log(400)
    
    // 同步
    console.log(100)
    alert(200)
    console.log(300)
    
    1. 基于JS是单线程语言,异步不会阻塞代码执行,同步会阻塞代码执行
  • 应用场景
    1. 网络请求,如ajax图片加载
    2. 定时任务,如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
    1. JS是单线程运行的
    2. 异步(setTimeout,ajax)要基于回调来实现
    3. event loop 就是异步回调的实现原理
    4. 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`执行
+ 然后继续轮询查找(永动机一样)
  1. 自行回顾Event Loop的过程
  2. 和DOM渲染的关系
  3. 微任务和宏任务在Event Loop过程中的不同处理

什么是宏任务和微任务,两者有什么区别?

  • 二、promise 进阶

Promise有哪三种状态?如何变化?

  1. pending(过程中)、resolved(已解决,成功)、rejected(已拒绝,失败)
  2. pending -> resolvedpending -> 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对状态的影响(重要)

状态的表现:

  1. pending状态,不会触发thencatch
  2. resolved状态,会触发后续的then回调函数
  3. rejected状态,会触发后续的catch回调函数

then catch 会继续返回 Promise ,此时可能会发生状态变化!!! thencatch改变状态

  1. then正常返回resolved,里面有报错则返回rejected
  2. 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 thencatch的链接调用(常考)

// 第一题
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 语法

  1. 异步回调 callback hell
  2. Promise then catch 链式调用,但也是基于回调函数
  3. 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/awaitPromise的关系:

  1. 执行async函数,返回的是Promise对象
  2. await相当于Promisethen
  3. try……catch可捕获异常,代替了Promisecatch
  • 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()
  • 四、微任务、宏任务

什么是宏任务,什么是微任务?宏任务有哪些,微任务有哪些?微任务触发时机更早

  1. 宏任务:setTimeoutsetIntervalAjaxDOM事件
  2. 微任务:Promise async/await
  3. 微任务执行时机比宏任务要早
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的过程

  1. JS是单线程的,而且和DOM渲染共用一个线程
  2. 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 渲染前会触发

先后触发的原因:

  1. 微任务ES6语法规定的
  2. 宏任务是由浏览器规定的(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

  1. JS基础知识,规定语法(ECMA 262标准)
  2. JS WEB API,网页操作的APIW3C标准)
  3. 前者是后者的基础,两者的结合才能真正实际应用

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') )
  1. property:修改对象属性,不会体现到html结构中
  2. attribute:修改html属性,会改变html结构
  3. 两者都可能引起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性能

  1. DOM操作非常“昂贵”,避免频繁的DOM操作
  2. 对DOM查询做缓存
  3. 将频繁操作改为一次性操作
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操作

  1. navigator
  2. screen
  3. location
  4. 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. 应用场景:代理

无限下拉的图片列表,如何监听每个图片的点击?(事件代理)

  1. 代码简洁
  2. 减少浏览器内存占用
  3. 不能滥用

事件代理、用e.target获取触发元素、用matches来判断是否是触发元素

如何识别浏览器的类型

分析拆解url各个部分

3.7 ajax

  1. XMLHttpRequest
  2. 状态码
  3. 跨域:同源策略,跨域解决方案
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))

什么是浏览器的同源策略?

  1. ajax请求时,浏览器要求当前网页和server必须同源(安全)
  2. 同源:协议、域名、端口,三者必须一致

加载图片css js 可无视同源策略

<img src=跨域的图片地址 />
<link href=跨域的css地址 />
<script src=跨域的js地址 ></script>

<img />:可用于统计打点,可使用第三方统计服务
<link /><script>可使用CDN,CDN一般都是外域
<script>可实现JSONP

跨域的常用实现方式

跨域

  1. 所有的跨域,都必须经过server端允许和配合

  2. 未经server端允许就实现跨域,说明浏览器有漏洞

  3. JSONP

<script>可绕过跨域限制
服务器可以在任意动态拼接数据返回
所以<script>就可以获得跨域的数据,只要服务端愿意返回
$.ajax({
	url: 'http://localhost:8882/x-origin.json',
    dataType: 'jsonp',
    jsonpCallback: 'callback',
    success: function (data) {
    	console.log(data)
    }
})
  1. CORS - 服务器设置http header

3.2.11 缓存

  • cookie
    1. 本身用于浏览器和server通讯
    2. 被借用到本地存储
    3. 可用 document.cookie = '...'来修改

缺点: 1. 存储大小,最大4kb 2. http请求时需要发送到服务端,增加请求数量 3. 只能用document.cookie = ''来修改

  • localStoragesessionStorage
    1. H5专门为存储而设计,最大可存5M
    2. API简单易用setItem getItem
    3. 不会随着http请求被发送出去

localStorage:数据会永久存储,除非代码或手动删除
sessionStorage:数据只存在于当前会话,浏览器关闭则清空

3.8 ES5和ES6继承的区别

  • ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的属性和方法添加到子类实例对象的this上面(Parent.call(this))。
  • ES6 的继承机制完全不同,实质是先创建父类的实例对象this(所以必须先调用super方法),然后再用子类的构造函数修改this实现继承。
  1. 面试题:JS中实现继承的方式有哪些?
  2. ES5 继承 与 ES6 继承的区别

容量、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

    1. get 获取服务器的数据
    2. post 像服务器提交数据
    3. 简单的网页功能,就这个两个操作
  • 现在的methods

    1. get获取数据
    2. post新建数据
    3. patch/put更新数据
    4. delete删除数据
  • Restful API

    1. 一种新的API设计方法(早已被推广使用)
    2. 传统API设计:把每个url当做一个功能
    3. Restful API 设计:把每个url当做唯一的资源(尽量不用url参数、用mathod表示操作类型)

url当做唯一的资源

  1. 不使用url参数
    • 传统API设计:/api/list?pageIndex=2
    • Restful API设计(资源标识):/api/list/2

  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-CN
  • Connection:keep-alive:一次TCP连接重复使用
  • cookie:每次同域请求都会带上cookie
  • Host:请求的域名是什么(例: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 imghtml、业务数据经常变,不能缓存

4.4.1 http缓存-强制缓存

  • http缓存策略(强制缓存+协商缓存) 微信截图_20210319112255.png Cache-Control
  1. Response Headers
  2. 控制强制缓存的逻辑:例如 Cache-Control:max-age=3153434(单位是秒)

Expires已被Cache-Control代替

Cache-Control的值:

  • max-age:过期时间
  • no-cach:不需要本地缓存,服务端自行处理
  • no-store:不需要本地缓存,不用服务端的缓存
  • private:只能最终用户做缓存(手机、电脑)
  • public:允许中间路由、代理做缓存

4.4.2 http缓存-协商缓存

微信截图_20210319112353.png

  • 服务器端缓存策略:告诉请求可以使用本地缓存
  • 服务器端判断客户端资源是否和服务器端资源一样
  • 资源一样则返回304,否则返回200和最新的资源

资源标识:

  1. Response Headers中,有两种
  2. Last-Modified:资源的最后修改时间
  3. Etag资源的唯一标识(一个字符串,类似人类的指纹)

优先使用EtagLast-Modified只能精确到秒级,如果资源被重复生成,而内容不变,则Etag更精确

微信截图_20210319112457.png

微信截图_20210319112539.png

微信截图_20210319112626.png

微信截图_20210319112710.png

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 网页的加载和渲染

  • 加载资源的形式
    1. html代码
    2. 媒体文件:图片、视频等
    3. javascript css
  • 加载资源的过程
    1. DNS解析:域名-> IP地址
    2. 浏览器根据IP地址向服务器发起http请求
    3. 服务器处理http请求,并返回给浏览器
  • 渲染页面的过程
    1. 根据HTML代码生成DOM Tree
    2. 根据CSS代码生成CSSOM
    3. DOM TreeCSSOM整合形成Render Tree
    4. 根据Render Tree渲染页面
    5. 遇到<script>则暂停渲染,优先加载并执行JS代码
    6. 渲染出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计算量,减少网络加载耗时

  • 空间换时间:适用于所有编程的性能优化

  • 让加载更快

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

    1. CSS放在head,JS放在body最下面
    2. 尽早开始执行JS,用DOMContentLoaded触发(没必要等到图片加载完成再渲染)
    3. 懒加载(图片懒加载,上滑加载更多)
    4. 对DOM查询进行缓存
    5. 频繁DOM操作,合并到一起插入到DOM结构
    6. 节流throttle,防抖debounce

5.3.1 资源合并

多个script文件合并成一个

5.3.2 缓存

微信截图_20210319112757.png

  • 静态资源加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. 替换特殊字符,如`<`变为`&lt;` `>`变为`&gt;`
2. `<script>`变为`&lt;script&gt`,直接显示,而不会作为脚本执行
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的区别?

  1. get 一般用于查询操作,post 一般用户提交操作
  2. get参数拼接在url上,post放在请求体内(数据体积可更大)
  3. 安全性: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标签,点击每个跳出对应数字

性能优化

JS异步