# 前言

一起重温下 HTML,CSS 和 JS 中核心的知识点,浏览器的解析渲染原理,CSS面向对象编程,最后通过实践 Promise 封装一个Ajax请求(面试专用)。

# 前端基础知识源码地址

源码地址:github.com/dkypooh/fro…

# 通过本章读者可以学习到什么?

  1. 浏览器的解析原理是如何,服务端传输过来的 text/html字符串是如何绘制成页面?
  2. H5 的 Meta 标签配置是如何,不同属性有什么作用?
  3. CSS 面向对象编程,BEM的规范又是什么,它们两者是如何结合?
  4. CSS 主流的布局,如何轻松应对页面布局?
  5. Promise 的实现原理,应用场景,以及如何和 async await 配合使用?

# 浏览器解析原理

# 浏览器总的解析原理

浏览器的解析过程:浏览器文档流 -> html DOM结构 --> CSS结合 --> 布局 --> 绘制页面。

HTML的渲染过程中,DOM 和 CSSOM 结构可以并行渲染。

# 第一步 HTML 转换成 DOM

服务端返回 text/html 格式的文档流, HTML 字符串描述了一个页面的结构,浏览器会把 HTML 字符串解析成 DOM 树形结构。

# 第二步 生成 CSSOM 结构

CSS 样式可以在 WEB 页面里映射成 CSSOM(CSS对象模型),它和 DOM 结构比较像, 不是增量模式,而是组合模式。

# 第三步 CSSOM 树和 DOM 树合并成渲染树

# 第四步 完整 DOM 结构

DOM 结构有两个规则:一个是 HTML 文档对象,一个是通过接口获取 DOM 元素。通过 document.getElementById() 以获取元素节点

# 性能优化策略

基于上面介绍的浏览器构建原理,DOM 树型结构的构建顺序,可以对页面渲染做些优化,提升体验。

基于上面介绍的浏览器构建原理,DOM 树型结构的构建顺序,可以对页面渲染做些优化,提升体验。

  • JS优化: <script> 标签加上 defer属性 和 async属性, 不阻塞页面文档解析,控制脚本的下载和执行。
    • defer属性: 用于开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。
    • async属性: HTML5 新增属性,用于异步下载脚本文件,下载完毕立即解释执行代码
  • Preload优化: preload(预加载) 是一个声明式 fetch,可以强制浏览器在不阻塞 document 的 onload 事件的情况下请求资源

preload 有如下配置属性,<link> 为标签, ref="preload" 为预加载属性配置,href="/test.css" 为加载的资源,as="style" 为加载的资源类型。

# 用例代码

我们预加载了CSS和JavaScript文件,所以在随后的页面渲染中,一旦需要使用它们,它们就会立即可用,同时也可以预加载图片,字体等文件。

<head>
  <meta charset="utf-8">
  <title>JS and CSS preload example</title>
  // 1. 浏览器可以预先加载style.css, main.js的资源
  <link rel="preload" href="style.css" as="style">
  <link rel="preload" href="main.js" as="script">
  // 2. 使用style.css样式
  <link rel="stylesheet" href="style.css">
</head>

<body>
  <h1>bouncing balls</h1>
  <canvas></canvas>
  // 3. 使用main.js文件
  <script src="main.js"></script>
</body>

细节实践可以参考 通过rel="preload"进行内容预加载

# CSS

CSS 层叠样式表 (Cascading Style Sheets,缩写为 CSS),是一种样式表语言。

# CSS 像素

CSS像素分为了 物理像素(physical pixel) 和 设备独立(逻辑)像素(density-independent pixel)。

设备像素比(devicePixelRatio) = 物理像素 / 设备独立像素

# 历代IPhone的分辨率

设备 逻辑像素(point) 物理像素(pixel) 屏幕尺寸 设备像素比(dpr)
iPhone 3 320 × 480 320 × 480 3.5寸 @1x
iPhone 4/4S 320 × 480 640 × 960 3.5寸 @2x
iPhone 6/7/8 375 × 667 750 × 1334 4.7寸 @2x
iPhone 6P/7P/8P 414 × 736 (1242x2208)1080x1920 5.5寸 @3x
iPhone X 375 × 812 1125 × 2436 5.8寸 @3x

解释说明:dpr = pixel / point, 由于 Plus 系列比较特殊,可以近似于3倍屏。

# 物理像素(physical pixel)

物理像素又被称为设备像素,他是显示设备中一个最微小的物理部件,retina设备像素为独立像素的2倍, 例如 iphone 6/7/8 系列

# 设备独立像素(density-independent pixel)

设备独立像素也称为密度无关像素,又称为逻辑像素,一个点代表一个可以由程序使用的虚拟像素(PX)。

缩合上述的几个概念,用一张图来解释:

# CSS布局

Web中主流的两种布局方式:Flexbox布局CSS Grid布局

# Flex布局

Flex布局 旨在提供一个更加有效的方式制定、调整和分布一个容器里的项目布局,即使他们的大小是未知或者是动态的。

Flex布局主要思想是具有伸缩性特点,可以取向改变、缩放、拉伸和收缩。

在Flexbox布局中有主轴(Main Axis)和侧轴(Cross Axis)两个概念:

Flex 参考资料下载

# 实现一个水平垂直居中

<div class="container">
    <div class="block"></div>
</div>

.container {
    display: flex;  // 默认为水平布局,主轴线为水平
    justify-content: center; // 水平居中
    align-items: center;    // 垂直居中
}

.block {
    height: 100px;
    width: 100px;
}

# CSS Grid布局

CSS Grid布局(又名"网格"),是一个基于二维网格布局的系统,主要目的是改变我们基于网格设计的用户接口方式。

Grid 参考资料下载

# CSS 面向对象

OOCSS “Object Oriented CSS(面向对象的CSS)”,是一种写 CSS 的方法,其思想就是鼓励你把样式表看作“对象”的集合:创建可重用性、可重复性的代码段让你可以在整个网站中多次使用。

基于SCSS预编译器,使用 OOCSS 和 BEM 结合的编码方式,可以让 CSS 编写更加规范,更加高效。

# BEM

BEM(Block-Element-Modifier),是一种用于 HTML 和 CSS 类名的命名约定。BEM 最初是由 Yandex 提出的,拥有巨大的代码库和可伸缩性。 BEM 是 BlockElementModifier 的缩写。

  • Block: 有实际意义的独立元素,例如:header, container, menu, checkbox, input
  • Element: Block 的子元素,没有独立的含义,例如:menu item, list item等定义成 menu__item, list_item
  • Modifier: 表示 Blcok 和 Element 元素的不同行为和状态。例如:disable, highlighted等定义成 menu-diablemenu_item-highlighted

CSS定义

.button {
	display: inline-block;
}
.button--state-success {
	color: #FFF;
	background: #569E3D linear-gradient(#79D858, #569E3D) repeat-x;
}
.button--state-danger {
	color: #900;
}

# OOCSS 和 BEM 案例结合

BEMOOCSS 不是CSS的标准,也是定义了一种 CSS 命名规范,下面就结合实际例子来分析, 安装 sass 编译器。

npm i sass -g           // 全局安装SASS
sass bem.scss bem.css   // SCSS文件编译成CSS文件

# HTML

<article class="m-card">
  <h1 class="card__title">Adorable 2BR in the sunny Mission</h1>
  <div class="card__body">
    <p>Vestibulum id ligula porta felis euismod semper.</p>
  </div>
</article>

# SCSS

结合 BEM 规范,更加清晰语义化的表达,同时配合 SCSS 级联语法,使 CSS 书写更加清晰,更加语义化。

.m-card {
  display: flex;
  flex-direction: column;
  &__title {
    line-height: 30px
  }
  &__body {
    line-height: 20px;
    p {
      margin: 0
    }
  }
}
# SCSS编译后的CSS
.m-card {
  display: flex;
  flex-direction: column;
}
.m-card__title {
  line-height: 30px;
}
.m-card__body {
  line-height: 20px;
}
.m-card__body p {
  margin: 0;
}

# Promise原理及实践

Promise原理是通过 Promise.prototype.thenPromise.prototype.catch 方法将观察者方法注册到被观察者 Promise 对象中,同时返回一个新的 Promise 对象,以便可以链式调用。

Promise 内部进行 等待态(Pending)执行态(Fulfilled)拒绝态(Rejected) 的状态流转。

# 三种回调比较 JS Callback VS Promise VS Async await

JS Callback : 产生 地狱般的回调嵌套,一旦嵌套次数过多,就很容易使我们的代码难以理解和维护。

Promise: 通过 链式调用的方法 去解决回调嵌套的问题,使我们的代码更容易理解和维护,同时Promise 还增加了许多有用的特性,让我们处理异步编程得心应手。

Async await: 是 ES7 引入的新的异步代码 规范,它提供了一种新的编写异步代码的方式,这种方式在语法层面提供了一种形式上非常接近于 同步代码的异步非阻塞 代码风格。

# 支持情况

Promise 在 Chrome33 版本开始支持,全球用户比例89.69%, Async await 在 Chrome55 版本开始支持,全球用户比例85.87%。

S7 标准 Async 原先需要 Babel编译,在Chrome55可以直接使用,不需要Babel编译

# Promise支持情况

# Async await支持情况

# Ajax 封装案例实践

本小节会实现一个基于 XMLHttpRequest 对象实现一个 Promise 的 Ajax 方法封装(面试专用考题),再对比 PromiseAsync Await的差异,以及他们的优缺点。

# Promise实现

/**
 * 请求服务
 * @param {Object} param
 * @param {String} param.url    请求URL
 * @param {String} param.type   默认 'GET'
 * 
 * @return new Promise
 */
function fetchRequest (param) {
    const type = param.type || 'GET',
    const url = param.url;
    if (!url) {
        new TypeError('param url must be set...')
    }
    return new Promise( (resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open(type, url, true);
        // 1. 监听状态
        xhr.onreadystatechange = function () {
            // 2. readyState = 4, status = 200 是请求成功的标识
            if (xhr.readyState === 4) {
                if (xhr.status === 200) {
                    resolve(xhr.responseText, xhr);
                } else {
                    reject({
                        code: xhr.status, 
                        message: xhr.response 
                    }, xhr);
                }
            }
        }
        xhr.send();
    })
}

# Promise VS Async await 使用区别

异步调用的最大问题是不能 Catch 到错误信息,同时编码上产生大量的 回调函数 或者 链式调用。 Async await 调用是把 Promise 的链式调用同步化,同时可以 Catch 到错误栈信息。

# Promise 使用

const getGithubHooks = () => {
    return fetchRequest({
        api: 'https://github.com/xxx'
    }).then(result => {
        return result;
        // result data
    }.catch(err => {
        // error 
    })
}
    

# Async await 使用

async function getGithubHooks() {
   let result = null
   // 同步 try catch 错误
   try {
    result =  await fetchRequest({
       api: 'https://github.com/xxx'
    })
    return result
   } catch(err) {
    return err; 
   }
   
   
}

# 结语

前端开发涉及的基础知识点非常多,非常杂,本章带精选了项目开发实践中的核心知识点,带大家进行了结构化的梳理。

这是基础到进阶重要的一步,后面一章会和大家一起学习 Typescript 知识,如果对 ES6 语法 还不是很熟悉的话,可以参考阅读 ECMAScript 6 入门

# 思考题

Q: 除了Flexible通过JS方式来动态设置的方式,还有其他方案来处理多端H5适配问题?

Q: 如何实现三栏布局,中间自适应,采用 Flex 和 Grid 两种方案实现?

# 参考文献(部分需要翻墙)

阅读全文