3 WebAPI

WebAPI 概述


Application Programming Interface: 应用程序编程接口,是一些预先定义的函数,通俗的理解就是一些方法
浏览器提供的一套操作浏览器功能和页面元素的API
此处的Web API特指浏览器提供的一些方法;可分为两个部分: BOM(浏览器对象模型)、DOM(页面对象模型)

BOM (浏览器对象模型)


BOM 概述

BOM(Browser Object Model)是指浏览器对象模型,浏览器对象模型提供了独立于内容的、可以与浏览器窗口进行互动的对象结构;BOM由多个对象组成,其中代表浏览器窗口的Window对象是BOM的顶层对象,其他对象都是该对象的子对象
调用window下的属性和方法时,可以省略window
BOM 没有标准,具体实现依据各浏览器厂商指定,其顶级对象为 window

BOM Window (顶级对象)

window 对象表示一个包含 DOM 文档的窗口,其 document 属性指向窗口中载入的 DOM 文档
window作为全局变量,代表了脚本正在运行的窗口,暴露给 Javascript 代码

Window 属性

Window.document // HTML文档
Window.body // HTML文档 body 元素对象

// 事件常用
window.pageYOffset // 返回文档当前垂直滚动的像素数;只读属性pageYOffset是 scrollY 的别名;如果需要整数值,可以使用Math.round()四舍五入
window.pageXOffset // 返回文档当前水平滚动的像素数;只读属性pageXOffset是 scrollX 的别名;如果需要整数值,可以使用Math.round()四舍五入
Screen.availHeight // 返回窗口中垂直方向可用空间的像素值(不算工具栏等)
Screen.availWidth // 返回窗口中水平方向可用空间的像素值(不算滚动条等)
Screen.height // 以像素为单位返回屏幕的高度
Screen.width // 以像素为单位返回屏幕的宽度

Window 方法

// 窗口常用
window.alert() // 显示一个警告对话框,上面显示有指定的文本内容以及一个"确定"按钮
Window.confirm() // 显示一个具有一个可选消息和两个按钮 (确定和取消) 的模态对话框

// 特殊
Window.stop() // 这个方法停止窗口加载
Window.close() // 关闭窗口
window.print() // 打开打印对话框打印当前文档

// 事件常用
Window.scroll(x-coord, y-coord) // 滚动窗口至文档中的特定位置,返回一个双精度浮点数
Window.scrollTo() // 滚动到文档中的某个坐标
window.scrollTo({
    top: 1000behavior: "smooth"
})
    - x-coord 是文档中的横轴坐标
    - y-coord 是文档中的纵轴坐标
    - options 是一个包含三个属性的对象(smooth(平滑滚动),instant(瞬间滚动),默认值 auto)

定时器

window.setInterval(fn, delay) // 重复调用一个函数或执行一个代码片段,具有固定的时间间隔,也叫定时器
WindowTimers.clearInterval(fn, delay) // 取消先前通过 setInterval()设置的重复定时任务, 需interval ID
window.setTimeout(fn, delay) // 定时器到期后执行一个函数或指定的一段代码(执行一次),也叫计时器

BOM Console (控制台)

Console 对象提供了浏览器控制台调试的接口(如:Firefox 的 Web Console);在不同浏览器上它的工作方式可能不一样,但通常都会提供一套共性的功能

Console 方法

Console.log(obj1 [, obj2, ..., objN]) // 打印内容的通用方法,支持多个参数
Console.debug() // 在控制台打印一条 "debug" 级别的消息
Console.clear() // 清空控制台,并输出 Console was cleared
Console.dir() // 显示一个由特定的 Javascript 对象列表组成的可交互列表
Console.error() // 打印一条错误信息
Console.info() // 打印资讯类说明信息

BOM History (历史记录)

History 接口允许操作浏览器的曾经在标签页或者框架里访问的会话历史记录

History 方法

window.history.back() // 向后跳转(如同用户点击了后退按钮)
window.history.forward() // 向前跳转(如同用户点击了前进按钮)
window.history.go(n) // 正数向前跳转,负数向后跳转

BOM Location (地址,URL)

**Location **接口表示其链接到的对象的位置(URL);所做的修改反映在与之相关的对象上

Location 属性

Location.href // 包含整个 URL 的一个DOMString
Location.host // 包含了域名的一个DOMString,可能在该串最后带有一个":"并跟上 URL 的端口号
Location.hostname // 包含 URL 域名的一个DOMString
Location.port // 包含端口号的一个DOMString
Location.pathname // 包含 URL 中路径部分的一个DOMString,开头有一个“/"
Location.search //  包含 URL 参数的一个DOMString,开头有一个“?”
Location.hash // 包含块标识符的DOMString,开头有一个“#”

DOMstring

DOMString 是一个 UTF-16 字符串;由于 JavaScript 已经使用了这样的字符串,所以 DOMString 直接映射到一个 String

Location 方法

location.reload() // 刷新当前页面,就像刷新按钮一样
Location.toString() // 返回一个DOMString,包含整个 URL; 它和读取URLUtils.href的效果相同;但是用它不能够修改 Location 的值

BOM Navigator (用户代理)

Navigator 接口表示用户代理的状态和标识;它允许脚本查询它和注册自己进行一些活动
可以使用只读的 window.navigator属性检索 navigator 对象

Navigator 属性

Navigator.userAgent // 返回当前浏览器的用户代理字符串,以此来判断用户登录设备

Navigator.geolocation

属性返回一个 Geolocation 对象,通过这个对象可以访问到设备的位置信息。使网站或应用可以根据用户的位置提供个性化结果

Geolocation.getCurrentPosition() // 确定设备的位置并返回一个携带位置信息的 Position 对象
Geolocation.watchPosition() // 注册一个位置改变监听器,每当设备位置改变时,返回一个 long 类型的该监听器的 ID 值
Geolocation.clearWatch() // 取消由 watchPosition() 注册的位置监听器

DOM (文档对象模型)


DOM 概述

文档对象模型(Document Object Model,简称DOM), 是W3C组织推荐的处理可扩展标志语言的标准编程接口;在网页上,组织页面(或文档)的对象被组织在一个树形结构中,用来表示文档中对象的标准模型就称为DOM
Document 接口表示任何在浏览器中载入的网页,并作为网页内容的入口,也就是DOM 树
DOM 作为 w3c 的标准,其顶级对象是 document(window.document)

DOM 节点操作

DOM 增

let fragment = document.createDocumentFragment() // 创建一个新的空白的文档片段,因为文档片段存在于内存中,并不在 DOM 树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能
let element = document.createElement(tagName[, options]); // 创建一个由标签名称 tagName 指定的 HTML 元素
document.createTextNode(text) // 创建一个新的文本节点。这个方法可以用来转义 HTML 字符,清除标签

DOM 删

Node.removeChild(child) // 删除指定节点并返回此节点

DOM 改

Node.appendChild(aChild) // 将指定节点插入到指定的节点中
Node.insertBefore(newNode, referenceNode) // 在参考节点之前插入一个拥有指定父节点的子节点,返回被插入过的子节点(可以实现元素交换)
document.importNode(externalNode, deep) // 将外部文档的一个节点拷贝一份,然后可以把这个拷贝的节点插入到当前文档中
Node.cloneNode() // 返回调用该方法的节点的一个副本

DOM 查

// 查找元素对象方法
document.getElementById() // 返回符合 ID名 的元素(对象)
document.getElementsByTagName() // 返回符合 标签名 的所有元素(伪数组)
Document.getElementsByClassName() // 返回符合 class类名 的元素(伪数组)
document.querySelector() // 返回符合 css选择器 的一个元素(对象)
Document.querySelectorAll() // 返回符合 css选择器 条件的所有元素(伪数组)

// 节点属性
Node.parentNode // 返回节点的父节点(元素节点)
Element.children // 返回节点的所有子节点(元素节点)
    - parentNode.children[0] // 第一个子节点
    - parentNode.children[parentNode.children.length - 1] // 最后一个子节点
NonDocumentTypeChildNode.nextElementSibling // 返回当前元素在其父元素的子元素节点中的后一个元素节点
NonDocumentTypeChildNode.previousElementSibling // 返回当前元素在其父元素的子元素节点中的前一个元素节点

// 不推荐
Node.childNodes //返回包含指定节点的子节点的集合(包含文本节点等)
Node.firstChild // 返回树中节点的第一个子节点(包含文本节点等)
Node.lastChild // 返回当前节点的最后一个子节点(包含文本节点等)
Element.firstElementChild // 返回对象的第一个子元素,如果没有子元素,则为null
Element.lastElementChild // 返回对象的最后一个子元素,如果没有子元素,则为null
Node.previousSibling // 返回当前节点的前一个兄弟节点,没有则返回null(包含文本节点等)
Node.nextSibling // 返回其父节点的 childNodes 列表中紧跟在其后面的节点(包含文本节点等)

DOM 属性节点

属性节点属性
Element.style // 获取或设置指定元素的style属性的值
CSSRule.cssText // 返回或设置样式规则所包含的实际文本
Element.className // 获取或设置指定元素的class属性的值
Element.value // 获取表单特有属性,所有表单属性都直接添加到了元素对象属性上

属性节点方法
Element.classList // 返回一个元素的类属性的实时 DOMTokenList 集合
    - add() // 添加指定的class属性值
    - remove() // 删除指定的class属性值
    - toggle() // 切换指定的class属性值,有则删 无则加
Element.getAttribute() // 返回元素 上一个指定的属性值
Element.setAttribute() // 设置指定元素上的某个属性值
Element.removeAttribute() // 从指定的元素中删除一个属性

自定义属性

是为了保存并使用数据;有些数据可以保存到页面中而不用保存到数据库中

H5自定义属性: H5规定自定义属性data-开头做为属性名并且赋值

element.getAttribute(‘data-index’) // 兼容性获取 H5 自定义属性
element.dataset.index // H5 新增
element.dataset[‘index’] // ie11 开始支持

Event (事件)


JavaScript 使我们有能力创建动态页面,而事件是可以被 JavaScript 侦测到的行为,简单理解: 触发 — 响应机制
网页中的每个元素都可以产生某些可以触发 JavaScript 的事件,例如,我们可以在用户点击某按钮时产生一个事件,然后去执行某些操作;

Event 三要素

  • 事件源
  • 事件类型
  • 事件处理函数(event对象:可选)

Event 事件模型

原始事件模式(DOM0级)

  • 直接在 HTML 代码中绑定
<input type="button" onclick="fun ()">
  • 通过 JS 绑定
document.getElementById(id).onclick // 对 document 对象的事件属性进行赋值操作,直接使用null覆盖就可以实现事件的解绑,触发: 冒泡阶段
  • 特性
    • 绑定速度快(DOMO 级事件具有很好的跨浏览器优势,会以最快的速度绑定,但由于绑定速度太快,可能页面还未完全加载出来,以至于事件可能无法正常运行,所以需JS文件最后执行)
    • 只支持冒泡,不支持捕获
    • 同一个类型的事件只能绑定一次
  • 解除绑定
btn.onclick = null

标准事件模式(DOM2级)

  • 事件捕获阶段: 事件从document一直向下传播到目标元素,依次检查经过的节点是否绑定了事件监听函数,如果有则执行
  • 事件处理阶段: 事件到达目标元素,触发目标元素的监听函数
  • 事件冒泡阶段: 事件从目标元素冒泡到document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行
EventTarget.addEventListener(type, listener, useCapture) // 将指定的监听器注册到 EventTarget 上
    - type: 表示监听事件类型的字符串
    - listener: 当所监听的事件类型触发时,会接收到一个事件通知(实现了 Event 接口的对象)对象,触发事件
  - [useCapture]: 是否为捕获监听器(默认为 false)

EventTarget.removeEventListener(type, listener[, useCapture]) // 删除使用 EventTarget.addEventListener()方法添加的事件(需要具名函数)
    - type: 一个字符串,表示需要移除的事件类型,如 "click"
    - listener: 需要从目标事件移除的 EventListener 函数
  - [useCapture]: 指定需要移除的 EventListener 函数是否为捕获监听器

event.stopImmediatePropagation() // 阻止监听同一事件的其他事件监听器被调用,如果在其中一个事件监听器中执行 stopImmediatePropagation() ,那么剩下的事件监听器都不会被调用

IE事件模型(基本不用,考虑兼容使用)

  • 事件处理阶段: 事件到达目标元素,触发目标元素的监听函数。
  • 事件冒泡阶段: 事件从目标元素冒泡到document,依次检查经过的节点是否绑定了事件监听函数,如果有则执行
// 兼容 IE 6-8 方法,实现上都可以为一个对象多次绑定同名事件或解绑事件,注意: 一是只此方法只支持 IE 6-8 ,二是绑定事件类型名称前加 on
addEvent('on+EventName', fn) // 绑定
detachEvent('on+eventName', fu) // 解除绑定

兼容事件模型函数(代理)

/**
 * * 通用事件监听函数
 * ? 通过传入参数判断是否需要事件代理,如有则判断触发是否为指定元素,是则触发(事件委托)
 *
 * @param {Element Object} elem
 * @param {String} type
 * @param {String} selector
 * @param {Function} fn
 */
function bindEvent(elem, type, selector, fn) {
  if (fn === null) { // 没有传入 fn 参数,证明无代理
    fn = selector
    selector = null
  }

  elem.addEventListener(type,function(e) {
    let target = {}
    if (selector) {
      target = e.target // 触发元素赋值判断变量
      if (target.matches(selector)) { // 触发事件为指定元素时触发
        fn.call(target, e)
      }
    } else {
      fn(e)
    }
  })
}

Event 事件与事件流

事件捕获 ==> 处于目标 ==> 事件冒泡

事件传播

事件捕获和事件冒泡都有事件传播阶段,传播阶段就是事件从触发开始到结束的过程
优先级: 先捕获,再冒泡

捕获 L2

自顶向下捕获 document ==> Element html ==> Element body ==> Element div
从DOM的根元素开始去执行对应的事件 (从外到里)

冒泡 L0、L2

自下向顶冒泡 document <== Element html <== Element body <== Element div
当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发;这一过程被称为事件冒泡

Event 委托

不是每个子节点单独设置事件监听器,而是事件监听器设置在其父级(上级)节点上,然后利用冒泡原理影响设置每个子节点
e.target === 当前触发的事件的节点
注意: 单个事件没有必要使用事件委托

优点:

  1. 减少事件注册,节省内存
  2. 简化了dom节点更新时,相应事件的更新操作
  3. 不用在代理的元素新添加的元素上绑定对应事件
  4. 当删除某个元素时,不用解绑上面的对应事件

缺点:

  1. 事件委托基于冒泡,对于不冒泡的事件不支持
  2. 层级过多,冒泡过程中,可能会被某层阻止掉
  3. 理论上委托会导致浏览器频繁调用处理函数,虽然很可能这些元素不需要处理。所以建议就近委托,比如在 ul 上代理 li,而不是在 document 上代理 li

备注: 把所有事件都用代理就可能会出现事件误判。比如,在 document 中代理了所有 button 的 click 事件,另外的人在引用改动 js 时,可能不知道,造成单击 button 触发了两个 click 事件

Event 对象

  • event 对象代表事件的状态,比如键盘按键的状态、鼠标的位置、鼠标按钮的状态
  • 事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象event,它有很多属性和方法
  • 当我们注册事件时, event 对象就会被系统自动创建,并依次传递给事件监听器(事件处理函数)

事件对象属性

Event.currentTarget // 标识是当事件沿着 DOM 触发时事件的当前目标;它总是指向事件绑定的元素;早期 IE 不支持
Event.target // 触发事件的对象 (某个DOM元素)的引用
Event.type // 表示该事件对象的事件类型

// 不推荐
Event.srcElement // Event.srcElement 是标准的 Event.target 属性的一个别名;它只对老版本的IE浏览器有效

事件对象方法

event.stopPropagation() // 阻止捕获和冒泡
// 封装兼容方法
function stopBubbles(e) {
  let ev = e || window.event
  if(ev && ev.stopPropagation) {
    ev.stopPropagation() // 非 IE 浏览器
  } else {
    ev.cancelBubble = true // IE11 以下兼容
  }
}

event.preventDefault() // 如果此事件没有被显式处理,它默认的动作也不应该照常执行(链接、表单域跳转)
// 兼容方法
element.onclick = function(e) {
  if(e.preventDefault) {
    e.preventDefault() // w3c 推荐
  } else {
    window.event.returnValue = false // IE 兼容写法
  }
}

Event 类型

1 鼠标事件

click // 当定点设备的按钮(通常是鼠标左键)在一个元素上被按下和放开时
dblclick // 在单个元素上单击两次鼠标的指针设备按钮 (通常是小鼠的主按钮)时
mouseenter // 当定点设备(通常指鼠标)移动到元素上时就会触发(不触发冒泡)
mouseleave // 指点设备(通常是鼠标)的指针移出某个元素时(不触发冒泡)
mousemove // 当指针设备( 通常指鼠标 )在元素上移动时(触发冒泡)
mouseout // 当移动指针设备(通常是鼠标),使指针不再包含在这个元素或其子元素中时(触发冒泡)
mouseup // 当指针在元素中时, mouseup事件在指针设备(如鼠标或触摸板)按钮放开时触发
mousedown // 在指针设备按钮按下时触发

1 鼠标事件属性

MouseEvent.clientX // 单击事件发生时的应用客户端区域的水平坐标
MouseEvent.clientY // 单击事件发生时的应用客户端区域的垂直坐标
MouseEvent.pageX // 返回的相对于整个文档的x(水平)坐标以像素为单位的只读属性
MouseEvent.pageY // 返回触发事件的位置相对于整个 document 的 Y 坐标值
MouseEvent.screenX // 提供鼠标在全局(屏幕)中的水平坐标(偏移量)
MouseEvent.screenY // 提供鼠标在全局(屏幕)中的水平坐标(偏移量)

2 键盘事件

keydown // 当用户按下键盘上的按键时会触发
keyup // 在按键被松开时触发
keypress // 当按下一个键并且该键通常会产生一个字符值时触发

2 键盘事件属性

// 新版本废弃
KeyboardEvent.keyCode // 	这个只读的属性 KeyboardEvent.keyCode 代表着一个唯一标识的所按下的键的未修改值,它依据于一个系统和实现相关的数字代码;这通常是与密钥对应的二进制的ASCII (RFC 20)或Windows 1252 码;如果这个键不能被标志,这个值为0
// 兼容性问题
KeyboardEvent.code // 表示键盘上的物理键(与按键生成的字符相对);换句话说,此属性返回一个值,该值不会被键盘布局或修饰键的状态改变

3 表单事件

submit // 在提交表单时触发
input // 当元素获得用户输入时触发
change // 当用户更改<input>、<select>和<textarea> 元素的值并提交这个更改时,change 事件在这些元素上触发

4 HTML 事件

focusin // 在元素获取焦点时触发(触发冒泡)
focus // 在元素获取焦点时触发(不触发冒泡)
focusout // 当一个元素即将失去焦点(触发冒泡)
blur // 当一个元素失去焦点的时候 blur 事件被触发(不触发冒泡)

5 状态事件

DOMContentLoaded // 当纯HTML被完全加载以及解析时,而不必等待样式表,图片或者子框架完成加载;
load // 当整个页面及所有依赖资源如样式表和图片都已完成加载时
scroll // 文档视图或者一个元素在滚动时触发

6 移动端事件

touchstart // 手指触摸到一个DOM元素触发
touchmove // 手指在一个DOM元素上滑动触发
touchend // 手指从一个DOM元素上离开触发

7 移动端事件对象

TouchEvent 是一类描述手指在触摸平面(触摸屏、触摸板等)的状态变化的事件;这类事件用于描述一个或多个触点,使开发者可以检测触点的移动,触点的增加和减少,等等
touchstart、touchmove、touchend 三个事件都会各自有事件对象;

touches // 正在触摸屏幕d额所有手指的一个列表
targetTouches // 正在触摸当前DOM元素上的手指的一个列表
changedTouches // 手指状态发生了改变的列表,从无到有,从有到无

注意: 因为平时都是给元素注册触摸事件,所以重点记住 targetTocuhes

Event 三大系列

offset 系列

元素偏移量,能够获取距离带有定位父元素的距离和获取自身的大小(宽度/高度),只读,不带单位
宽/高度 = padding + border + content

HTMLElement.offsetParent // 返回一个指向最近的(指包含层级上的最近)包含该元素的定位元素或者最近的 table,td,th,body元素
HTMLElement.offsetTop // 返回当前元素相对于其 offsetParent 元素的顶部内边距的距离
HTMLElement.offsetLeft // 返回当前元素左上角相对于  HTMLElement.offsetParent 节点的左边界偏移的像素值
HTMLElement.offsetWidth // 返回一个元素的布局宽度,不带单位
HTMLElement.offsetHeight // 返回该元素的像素高度,不带单位

client 系列

元素可视区,可以动态获取到元素的边框大小(border),元素大小(content)
宽/高度 = padding + content

Element.clientTop // 返回元素顶部边框的宽度(以像素表示)
Element.clientLeft // 返回一个元素的左边框的宽度
Element.clientWidth // 返回元素的 clientWidth,包括 padding + contentWidth ,不带单位
Element.clientHeight // 返回元素的 clientHeight,包括 padding + contentHeight ,不带单位

scroll 系列

元素滚动,可以动态获取到元素的大小(实际 content + 不可见部分),滚动距离等
宽/高 = content + overflow 溢出而在屏幕上不可见的内容

Element.scrollTop // 获取或设置一个元素的内容垂直滚动的像素数
Element.scrollLeft // 读取或设置元素滚动条到元素左边的距离
Element.scrollWidth // 元素内容宽度的一种度量,包括由于 overflow 溢出而在屏幕上不可见的内容
Element.scrollHeight // 元素内容高度的度量,包括由于溢出导致的视图中不可见内容

WebAPI 特性要点


进程和线程

通俗解释

  1. 计算机的核心是 CPU,它承担了所有的计算任务;它就像一座工厂,时刻在运行
  2. 假定工厂的电力有限,一次只能供给一个车间使用;也就是说,一个车间开工的时候,其他车间都必须停工;背后的含义就是,单个 CPU 一次只能运行一个任务
  3. 进程就好比工厂的车间,它代表 CPU 所能处理的单个任务;任一时刻,CPU 总是运行一个进程,其他进程处于非运行状态
  4. 一个车间里,可以有很多工人;他们协同完成一个任务
  5. 线程就好比车间里的工人;一个进程可以包括多个线程
  6. 车间的空间是工人们共享的,比如许多房间是每个工人都可以进出的;这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享内存
  7. 可是,每间房间的大小不同,有些房间最多只能容纳一个人,比如厕所;里面有人的时候,其他人就不能进去了;这代表一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存
  8. 一个防止他人进入的简单方法,就是门口加一把锁;先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去;这就叫“互斥锁”(Mutual exclusion,缩写 Mutex),防止多个线程同时读写某一块内存区域
  9. 还有些房间,可以同时容纳 n 个人,比如厨房;也就是说,如果人数大于 n,多出来的人只能在外面等着;这好比某些内存区域,只能供给固定数目的线程使用
  10. 这时的解决方法,就是在门口挂 n 把钥匙;进去的人就取一把钥匙,出来时再把钥匙挂回原处;后到的人发现钥匙架空了,就知道必须在门口排队等着了;这种做法叫做“信号量”(Semaphore),用来保证多个线程不会互相冲突
  11. 不难看出,Mutex 是 Semaphore 的一种特殊情况(n=1 时);也就是说,完全可以用后者替代前者;但是,因为 Mutex 较为简单,且效率高,所以在必须保证资源独占的情况下,还是采用这种设计

操作系统的设计,因此可以归结为三点

  1. 以多进程形式,允许多个任务同时运行
  2. 以多线程形式,允许单个任务分成不同的部分运行
  3. 提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源

学术解释

CPU 背景
  • CPU+RAM+各种资源(比如显卡,光驱,键盘,GPS, 等等外设)构成我们的电脑,但是电脑的运行,实际就是 CPU 和相关寄存器以及 RAM 之间的事情
  • 一个最最基础的事实:CPU 实在是太快了,寄存器仅仅能够追的上他的脚步,RAM 和别的挂在各总线上的设备完全是望其项背;那当多个任务要执行的时候怎么办呢?轮流着来?或者谁优先级高谁来?不管怎么样的策略,一句话就是在 CPU 看来就是轮流着来
  • 一个必须知道的事实:执行一段程序代码,实现一个功能的过程介绍 ,当得到 CPU 的时候,相关的资源必须也已经就位,就是显卡啊,GPS 啊什么的必须就位,然后 CPU 开始执行;这里除了 CPU 以外所有的就构成了这个程序的执行环境,也就是我们所定义的程序上下文;当这个程序执行完了,或者分配给他的 CPU 执行时间用完了,那它就要被切换出去,等待下一次 CPU 的临幸;在被切换出去的最后一步工作就是保存程序上下文,因为这个是下次他被 CPU 临幸的运行环境,必须保存
  • 串联起来的事实:前面讲过在 CPU 看来所有的任务都是一个一个的轮流执行的,具体的轮流方法就是:先加载程序 A 的上下文,然后开始执行 A,保存程序 A 的上下文,调入下一个要执行的程序 B 的程序上下文,然后开始执行 B,保存程序 B 的上下文
进程线程区别

进程和线程就是这样的背景出来的,两个名词不过是对应的CPU 时间段的描述,名词就是这样的功能

  • 进程:就是包括切换上下文切换的程序执行时间总和 = CPU 加载上下文 + CPU 执行 + CPU 保存上下文
  • 线程:进程的颗粒度太大,每次都要有上下的调入,保存,调出;如果我们把进程比喻为一个运行在电脑上的软件,那么一个软件的执行不可能是一条逻辑执行的,必定有多个分支和多个程序段,就好比要实现程序 A,实际分成 a,b,c 等多个块组合而成,那么这里具体的执行就可能变成: 程序 A 得到 CPU –> CPU 加载上下文–>开始执行程序 A 的 a 小段–>然后执行 A 的 b 小段–>然后再执行 A 的 c 小段–>最后 CPU 保存 A 的上下文;这里 a,b,c 的执行的是共享了 A 的上下文,CPU 在执行的时候没有进行上下文切换的;这里的a,b,c 就是线程,也就是说线程是共享了进程的上下文环境的更为细小的 CPU 时间段

小结: 进程和线程都是一个时间段的描述,是 CPU 工作时间段的描述,不过是颗粒大小不同;

总结

看完通俗和学术的理解以后,官方的解释就应该很明白了

  • 进程是 CPU 资源分配的最小单位(是能拥有资源和独立运行的最小单位)
  • 线程是 CPU 调度的最小单位(线程是建立在进程的基础上的一次程序运行单位,一个进程中可以有多个线程)

现在,一般通用的单线程与多线程的说法,都是指在一个进程内的单和多(核心是属于一个进程)

浏览器的进程与线程

在 Chromium 系浏览器中基本上每个 tab 页都有一个自己的进程
最新的 Chrome 浏览器包括:(Browser)主进程、GPU 进程、NetWork 进程、插件进程、渲染进程
相比于单进程浏览器,多进程有如下优点

  • 避免单个 page 崩溃影响整个浏览器
  • 避免第三方插件崩溃影响整个浏览器
  • 多进程充分利用多核优势
  • 方便使用沙盒模型隔离插件等进程,提高浏览器稳定性

1. Browser Process

主进程,只有一个,控制应用中的 “Chrome” 部分,包括地址栏,书签,回退与前进按钮,,负责各个页面的管理,创建和销毁其他进程,将 Renderer 进程得到的内存中的 Bitmap,绘制到用户界面上,处理 web 浏览器中网络请求、文件访问、资源下载等不可见的特权部分

2. GPU Process

GPU 进程,最多一个,用于 3D 绘制等,处理独立于其它进程的 GPU 任务;GPU 被分成不同进程,因为 GPU 处理来自多个不同应用的请求并绘制在相同表面

3. NetWork Process

网络进程,主要负责页面的网络资源下载,加载

4. plugin Process

插件进程,每种类型的插件对应一个进程,仅当使用该插件时才创建

5. Renderer Process

渲染进程,默认每个 Tab 页面都会产生一个渲染进程,互不影响,负责页面的渲染,JS 的执行,事件的循环,都在这个进程内进行

5.1 GUI 渲染线程
  • 负责渲染浏览器界面,解析 HTML,CSS,构建 DOM 树和 RenderObject 树,布局和绘制等
  • 当界面需要重绘(Repaint)或由于某种操作引发回流(Reflow)时,该线程就会执行

注意: GUI 渲染线程与 JS 引擎线程是互斥的,当 JS 引擎线程执行时 GUI 线程会被挂起(相当于被冻结了),GUI 更新会被保存在一个队列中等到 JS 引擎空闲时立即被执行
为什么互斥: 由于 JS 是可以操作 DOM 的,如果同时修改元素属性并同时渲染界面(即 JS 线程和 UI 线程同时运行), 那么渲染线程前后获得的元素就可能不一致了(简单说就是 js 修改 dom 后没有重新渲染成功)

5.2 JS 引擎线程
  • 也称为 JS 内核,负责处理 Javascript 脚本程序;(例如V8 引擎)
  • JS 引擎线程负责解析 Javascript 脚本,运行代码
  • JS 引擎一直等待着任务队列中任务的到来,然后加以处理,一个 Tab 页(Renderer 进程)中无论什么时候都只有一个 JS 线程在运行 JS 程序

注意: GUI 渲染线程与 JS 引擎线程是互斥的,所以如果 JS 执行的时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞

5.3 事件触发线程
  • 归属于浏览器而不是 JS 引擎,用来控制事件循环(可以理解,JS 引擎自己都忙不过来,需要浏览器另开线程协助)
  • 当 JS 引擎执行代码块如 setTimeOut 时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX 异步请求等),会将对应任务添加到事件线程中
  • 当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待 JS 引擎的处理
  • 注意,由于 JS 的单线程关系,所以这些待处理队列中的事件都得排队等待 JS 引擎处理(当 JS 引擎空闲时才会去执行)
5.4 定时触发器线程
  • setIntervalsetTimeout所在线程
  • 浏览器定时计数器并不是由 JavaScript 引擎计数的,(因为 JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确)
  • 因此通过单独线程来计时并触发定时(计时完毕后,添加到事件队列中,等待 JS 引擎空闲后执行)

注意: W3C 在 HTML 标准中规定,规定要求 setTimeout 中低于 4ms 的时间间隔算为 4ms

5.5 异步 http 请求线程
  • 在 XMLHttpRequest 在连接后是通过浏览器新开一个线程请求
  • 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件,将这个回调再放入事件队列中;再由 JavaScript 引擎执行

渲染进程中的线程之间的关系

GUI 渲染线程与 JS 引擎线程互斥

由于 JavaScript 是可操纵 DOM 的,如果在修改这些元素属性同时渲染界面(即 JS 线程和 UI 线程同时运行),那么渲染线程前后获得的元素数据就可能不一致了
因此为了防止渲染出现不可预期的结果,浏览器设置 GUI 渲染线程与 JS 引擎为互斥的关系,当 JS 引擎执行时 GUI 线程会被挂起,GUI 更新则会被保存在一个队列中等到 JS 引擎线程空闲时立即被执行

JS 阻塞页面加载

从上述的互斥关系,可以推导出,JS 如果执行时间过长就会阻塞页面
譬如,假设 JS 引擎正在进行巨量的计算,此时就算 GUI 有更新,也会被保存到队列中,等待 JS 引擎空闲后执行,然后,由于巨量计算,所以 JS 引擎很可能很久很久后才能空闲,自然会感觉到巨卡无比
所以,要尽量避免 JS 执行时间过长,这样就会造成页面的渲染不连贯,导致页面渲染加载阻塞的感觉

进程间的通信过程

  1. Browser 进程收到用户请求,首先需要获取页面内容(譬如通过网络下载资源),随后将该任务通过 RendererHost 接口传递给 Render 进程
  2. Renderer 进程的 Renderer 接口收到消息,简单解释后,交给渲染线程,然后开始渲染
  3. 渲染线程接收请求,加载网页并渲染网页,这其中可能需要 Browser 进程获取资源和需要 GPU 进程来帮助渲染
  4. 当然可能会有 JS 线程操作 DOM(这样可能会造成回流并重绘)
  5. 最后 Render 进程将结果传递给 Browser 进程
  6. Browser 进程接到结果并将结果绘制出来

渲染流程(Render)

浏览器输入 URL,浏览器主进程接管,开一个下载线程,然后进行 http 请求(略去 DNS 查询,IP 寻址等等操作),然后等待响应,获取内容,随后将内容通过 RendererHost 接口转交给 Renderer 进程,浏览器渲染流程开始,首先,浏览器获取HTML并开始构建DOM(文档对象模型 - Document Object Model)。然后获取CSS并构建CSSOM(CSS对象模型 - CSS Object Model)。然后将DOM与CSSOM结合,创建渲染树(Render Tree)。然后找到所有内容都处于网页的哪个位置,也就是布局(Layout)这一步。最后,浏览器开始在屏幕上绘制像素

渲染基本步骤

1. 解析 HTML 建立 DOM 树

浏览器会遵守一套定义完善的步骤来处理HTML并构建DOM(触发 DOMContentLoaded 事件)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 转换 浏览器从磁盘或网络读取HTML的原始字节,并根据文件的指定编码(例如 UTF-8)将它们转换成字符

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. Token化 将字符串转换成Token,例如:“”、“”等。Token中会标识出当前Token是“开始标签”或是“结束标签”亦或是“文本”等信息

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 节点之间关系如何维护 通过起始标签和结束标签确定关系

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 生成节点对象并构建DOM 事实上,构建DOM的过程中,不是等所有Token都转换完成后再去生成节点对象,而是一边生成Token一边消耗Token来生成节点对象。换句话说,每个Token被生成后,会立刻消耗这个Token创建出节点对象

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

2. 解析 CSS 构建 CSSOM 树

DOM会捕获页面的内容,但浏览器还需要知道页面如何展示。所以需要构建CSSOM(CSS对象模型 - CSS Object Model)
构建CSSOM的过程与构建DOM的过程非常相似,当浏览器接收到一段CSS,浏览器首先要做的是识别出Token,然后构建节点并生成CSSOM

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
经过一系列步骤后生成的CSSOM
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
注意: CSSOMTree需要等到完全构建后才可以被使用,因为后面的属性可能会覆盖掉前面的设置
官方解释: 未构建完的CSSOMTree是不准确的,浏览器必须等到CSSOMTree构建完毕后才能进入下一阶段。所以,CSS的加载速度与构建CSSOMTree的速度将直接影响首屏渲染速度,因此在默认情况下CSS被视为阻塞渲染的资源,需要将它尽早、尽快地下载到客户端,以便缩短首次渲染的时间。

3. 构建 Render 树

现在,我们已经拥有了完整的DOM树和CSSOM树。DOM 树上每一个节点对应着网页里每一个元素,CSSOM树上每个节点对应着网页里每个元素的样式,并且此时浏览器也可以通过 JavaScript 操作DOM/CSSOM树,动态改变它的结构。但是DOM/CSSOM树本身并不能直接用于排版和渲染,浏览器还会生成另外一棵树:Render树

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
重要概念

  1. 从 DOM 树的根节点开始遍历每个可见节点,Render 树上的每一个节点被称为:RenderObject
  2. 有些节点不可见(例如脚本Token、元Token等),因为它们不会体现在渲染输出中,所以会被忽略
  3. 某些节点被CSS隐藏,因此在渲染树中也会被忽略。例如:上图中的p > span节点就不会出现在渲染树中,因为该节点上设置了display: none属性
  4. 对于每个可见节点,为其找到适配的 CSSOM 规则并应用它们
  5. Render 树是衔接浏览器排版引擎和渲染引擎之间的桥梁,它是排版引擎的输出,渲染引擎的输入
4. 生成 layer 树

浏览器渲染引擎并不是直接使用Render树进行绘制,为了方便处理Positioning,Clipping,Overflow-scroll,CSS Transfrom/Opacrity/Animation/Filter,Mask or Reflection,Z-indexing等属性,浏览器需要生成另外一棵树:Layer树

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
浏览器会为一些特定RenderObject生成对应的RenderLayer,其中的规则是

  • 是否是页面的根节点 It’s the root object for the page
  • 是否有css的一些布局属性(relative absolute or a transform) It has explicit CSS position properties (relative, absolute or a transform)
  • 是否透明 It is transparent
  • 是否有溢出 Has overflow, an alpha mask or reflection
  • 是否有css滤镜 Has a CSS filter
  • 是否包含一个canvas元素使得节点拥有视图上下文 Corresponds to canvas element that has a 3D (WebGL) context or an accelerated 2D context
  • 是否包含一个video元素 Corresponds to a video element

当满足上面其中一个条件时,这个RrenderObject就会被浏览器选中生成对应的RenderLayer。至于那些没有被命运选中的RrenderObject,会从属与父节点的RenderLayer。最终,每个RrenderObject都会直接或者间接的属于一个RenderLayer。
浏览器渲染引擎在布局和渲染时会遍历整个Layer树,访问每一个RenderLayer,再遍历从属于这个RenderLayer的 RrenderObject,将每一个 RenderObject 绘制出来。可以理解为:Layer 树决定了网页绘制的层次顺序,而从属于RenderLayer 的 RrenderObject决定了这个 Layer 的内容,所有的 RenderLayerRrenderObject 一起就决定了网页在屏幕上最终呈现出来的内容

5. 布局 (layout)

有了渲染树之后,接下来进入布局阶段。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”
布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换为屏幕上的绝对像素

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

6. 绘制 (paint)

布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

渲染过程总结

1. DOMContentLoaded 与 onload
  • 当 DOMContentLoaded 事件触发时,仅当 DOMTree 加载完成,不包括样式表,图片
  • 当 onload 事件触发时,页面上所有的 DOM,样式表,脚本,图片都已经加载完成了; (渲染完毕了)
  • DOMContentLoaded -> load
2. JS与关键渲染路径

JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建;JavaScript对关键渲染路径的影响不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建
原本DOM和CSSOM的构建是互不影响,井水不犯河水,但是一旦引入了JavaScript,CSSOM也开始阻塞DOM的构建,只有CSSOM构建完毕后,DOM再恢复DOM构建
因为JavaScript不只是可以改DOM,它还可以更改样式,也就是它可以更改CSSOM。前面我们介绍,不完整的CSSOM是无法使用的,但JavaScript中想访问CSSOM并更改它,那么在执行JavaScript时,必须要能拿到完整的CSSOM。所以就导致了一个现象,如果浏览器尚未完成CSSOM的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建
也就是说,在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM
关键渲染路径: 构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制

  • 将 javascript 脚本放到 head 标签中

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 将 javascript 脚本放到 body 标签底部

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3. defer and async 控制渲染

<script src="script.js"></script>

  • 没有 defer 或 async,浏览器会立即加载并执行指定的脚本,也就是说不等待后续载入的文档元素,读到就加载并执行

<script defer src="script.js"></script>(延迟执行)

  • defer 属性表示延迟执行引入的 JavaScript,即这段 JavaScript 加载时 HTML 并未停止解析,这两个过程是并行的;整个 document 解析完毕且 defer-script 也加载完成之后(这两件事情的顺序无关),会执行所有由 defer-script 加载的 JavaScript 代码,然后触发 DOMContentLoaded 事件
  • defer 与相比普通 script,有两点区别:载入 JavaScript 文件时不阻塞 HTML 的解析,执行阶段被放到 HTML 标签解析完成之后

<script async src="script.js"></script> (异步下载)

  • 加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)
  • async 属性表示异步执行引入的 JavaScript
4. defer 和 async 总结
  • defer 和 async 在网络读取(下载)这块儿是一样的,都是异步的(相较于 HTML 解析)
  • defer的script.js 的执行要在所有元素解析完成之后
  • async如果已经加载好,就会开始执行;也就是加载不阻塞,执行会阻塞
  • 在加载多个 JS 脚本的时候,async 是无顺序的加载,而 defer 是有顺序的加载
  • async 对于应用脚本的用处不大,因为它完全不考虑依赖(哪怕是最低级的顺序执行),不过它对于那些可以不依赖任何脚本或不被任何脚本依赖的脚本来说却是非常合适的

浏览器渲染流程(Composite)

1. 浏览器的主要结构

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 用户界面 - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面
  2. 浏览器引擎 - 在用户界面和呈现引擎之间传送指令
  3. 呈现引擎 - 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上
  4. 网络 - 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现
  5. 用户界面后端 - 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法
  6. JavaScript 解释器。用于解析和执行 JavaScript 代码
  7. 数据存储。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库

2. 呈现引擎

  • DOM Tree:浏览器将HTML解析成树形的数据结构
  • Style Rules:浏览器将CSS解析成树形的数据结构,对应我们的CSSOM
  • Render Tree:DOM和CSSOM合并后生成Render Tree
  • layout: 布局。有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从 属关系,从而去计算出每个节点在屏幕中的位置
  • painting: 渲染。按照算出来的规则,通过显卡,把内容画到屏幕上
  • reflow(回流/重排):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,这个回退的过程叫 reflow

Dirty 位系统
为了避免所有细小的更改都要从根节点进行整体布局,浏览器采用了一种“dirty 位”系统。如果某个RenderTree上的Node发生了更改,便将自身标注为“dirty”,表示需要进行布局
有两种标记:“dirty”和“children are dirty”。“children are dirty”表示尽管呈现器自身没有变化,但它至少有一个子代需要布局reflow时,浏览器会对Render Tree进行遍历,而仅仅只对标注了“dirty “的Node进行布局

  • repaint(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变

上面的过程可以合并成
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 正常情况:JS/CSS -> 计算样式 -> 布局 -> 绘制 -> 渲染层合并

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 不需要重新布局(仅触发重绘):JS/CSS -> 绘制 -> 渲染层合并

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 既不需要重新布局,也不需要重新绘制(不触发回流):JS/CSS -> 渲染层合并

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3. Compsite

浏览器渲染时,对渲染树有着一个RenderObject -> RenderLayer 的转换过程

  • Chrome 拥有两套不一样的渲染路径(rendering path):硬件加速路径<硬件加速>和旧软件路径(older software path)<软件渲染引擎>
  • Chrome 中有不一样类型的层: RenderLayers (渲染层)和 GraphicsLayers (合成层),只有 GraphicsLayer 是做为纹理(texture)上传给GPU的。
  • 什么是纹理?能够把它想象成一个从主存储器(例如 RAM)移动到图像存储器(例如 GPU 中的 VRAM)的位图图像(bitmapimage)
  • Chrome 使用纹理来从 GPU上得到大块的页面内容。经过将纹理应用到一个很是简单的矩形网格就能很容易匹配不一样的位置(position)和变形(transformation)。这也就是3DCSS 的工做原理,它对于快速滚动也十分有效
合成层概念

在浏览器渲染流程中提到了composite概念,在 DOM 树中每一个节点都会对应一个 LayoutObject,当他们的 LayoutObject 处于相同的坐标空间时,就会造成一个 RenderLayers ,也就是渲染层。RenderLayers 来保证页面元素以正确的顺序合成,这时候就会出现层合成(composite),从而正确处理透明元素和重叠元素的显示。
某些特殊的渲染层会被认为是合成层(Compositing Layers),合成层拥有单独的 GraphicsLayer,而其余不是合成层的渲染层,则和其第一个拥有 GraphicsLayer 父层公用一个。
而每一个GraphicsLayer(合成层单独拥有的图层) 都有一个 GraphicsContext,GraphicsContext 会负责输出该层的位图,位图是存储在共享内存中,做为纹理上传到 GPU 中,最后由 GPU 将多个位图进行合成,而后显示到屏幕上。

如何变成合成层
  • 3D 或透视变换(perspective transform) CSS 属性
  • 使用加速视频解码的 元素 拥有 3D
  • (WebGL) 上下文或加速的 2D 上下文的 元素
  • 混合插件(如 Flash)
  • 对本身的 opacity 作 CSS动画或使用一个动画变换的元素
  • 拥有加速 CSS 过滤器的元素
  • 元素有一个包含复合层的后代节点(换句话说,就是一个元素拥有一个子元素,该子元素在本身的层里)
  • 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)
合成层的优势
  • 合成层的位图,会交由 GPU 合成,比 CPU 处理要快
  • 当须要 repaint 时,只须要 repaint 自己,不会影响到其余的层
  • 对于 transform 和 opacity 效果,不会触发 layout 和 paint
合成层注意
  1. 提高到合成层后合成层的位图会交GPU处理,但请注意,仅仅只是合成的处理(把绘图上下文的位图输出进行组合)须要用到GPU,生成合成层的位图处理(绘图上下文的工做)是须要CPU
  2. 当须要repaint的时候能够只repaint自己,不影响其余层,可是paint以前还有style, layout,那就意味着即便合成层只是repaint了本身,但style和layout自己就很占用时间
  3. 仅仅是transform和opacity不会引起layout 和paint,那么其余的属性不肯定
性能优化点
  1. 提高动画效果的元素 合成层的好处是不会影响到其余元素的绘制,所以,为了减小动画元素对其余元素的影响,从而减小paint,咱们须要把动画效果中的元素提高为合成层。 提高合成层的最好方式是使用 CSS 的 will-change属性。从上一节合成层产生缘由中,能够知道 will-change 设置为opacity、transform、top、left、bottom、right 能够将元素提高为合成层。
  2. 使用 transform 或者 opacity 来实现动画效果, 这样只须要作合成层的合并就行了。
  3. 减小绘制区域 对于不须要从新绘制的区域应尽可能避免绘制,以减小绘制区域,好比一个 fix 在页面顶部的固定不变的导航header,在页面内容某个区域 repaint 时,整个屏幕包括 fix 的 header 也会被重绘。而对于固定不变的区域,咱们指望其并不会被重绘,所以能够经过以前的方法,将其提高为独立的合成层。减小绘制区域,须要仔细分析页面,区分绘制区域,减小重绘区域甚至避免重绘。
利用合成层可能踩到的坑
  1. 合成层占用内存的问题
  2. 层爆炸,因为某些缘由可能致使产生大量不在预期内的合成层,虽然有浏览器的层压缩机制,可是也有不少没法进行压缩的状况,这就可能出现层爆炸的现象(简单理解就是,不少不须要提高为合成层的元素由于某些不当操做成为了合成层)。
  3. 解决层爆炸的问题,最佳方案是打破 overlap 的条件,也就是说让其余元素不要和合成层元素重叠。简单直接的方式:使用3D硬件加速提高动画性能时,最好给元素增长一个z-index属性,人为干扰合成的排序,能够有效减小chrome建立没必要要的合成层,提高渲染性能,移动端优化效果尤其明显。
  4. 如何查看合成层 Chrome DevTools -> Rendering -> Layer borders

浏览器的回流与重绘 (Reflow & Repaint)

浏览器使用流式布局模型 (Flow Based Layout);有了 RenderTree,我们就知道了所有节点的样式,然后计算他们在页面上的大小和位置,最后把节点绘制到页面上

回流

当 Render Tree 中部分或全部元素的尺寸、结构、或某些属性发生改变时,浏览器重新渲染部分或全部文档的过程称为回流

  • 页面首次渲染
  • 浏览器窗口大小发生改变
  • 元素尺寸或位置发生改变
  • 元素内容变化(文字数量或图片大小等等)
  • 元素字体大小变化
  • 添加或者删除可见的 DOM 元素
  • 激活 CSS 伪类(例如::hover)
  • 查询某些属性或调用某些方法

重绘

当页面中元素样式的改变并不影响它在文档流中的位置时(例如:color、background-color、visibility 等),浏览器会将新样式赋予给元素并重新绘制它,这个过程称为重绘;

如何避免回流

CSS
  • 避免使用 table 布局
  • 尽可能在 DOM 树的最末端改变 class
  • 避免设置多层内联样式
  • 将动画效果应用到 position 属性为 absolute 或 fixed 的元素上
  • 避免使用 CSS 表达式(例如:calc())
Javascript
  • 避免频繁操作样式,最好一次性重写 style 属性,或者将样式列表定义为 class 并一次性更改 class 属性
  • 避免频繁操作 DOM,创建一个 documentFragment,在它上面应用所有 DOM 操作,最后再把它添加到文档中
  • 也可以先为元素设置 display: none,操作结束后再把它显示出来;因为在 display 属性为 none 的元素上进行的 DOM 操作不会引发回流和重绘
  • 避免频繁读取会引发回流/重绘的属性,如果确实需要多次使用,就用一个变量缓存起来

注意: 对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流

attribute and property 的区别

attribute 是 dom 元素在文档中作为 html 标签拥有的属性
property 是 dom 元素在 js 中作为对象拥有的属性

1. 定义

  • 对于html的标准属性来说,attribute和property是同步的,是会自动更新的
  • 但是对于自定义的属性来说,他们是不同步的.(自定义属性不会自动添加到property)
  • property 的值可以改变;attribute的值不能改变

2. 创建

  • DOM对象初始化时会在创建默认的基本property
  • 只有在HTML标签中定义的attribute才会被保存在property的attributes属性中
  • attribute会初始化property中的同名属性,但自定义的attribute不会出现在property中
  • attribute的值都是字符串

3. 数据绑定

  • attributes的数据会同步到property上,然而property的更改不会改变attribute
  • 对于valueclass这样的属性/特性,数据绑定的方向是单向的,attribute->property
  • 对于id而言,数据绑定是双向的,attribute<=>property
  • 对于disabled而言,property上的disabled为false时,attribute上的disabled必定会并存在,此时数据绑定可以认为是双向的

4. 使用

  • 可以使用DOM的setAttribute方法来同时更改attribute
  • 直接访问attributes上的值会得到一个Attr对象,而通过getAttribute方法访问则会直接得到attribute的值

CSS 动画与 JS 动画的区别

  1. 兼容性: CSS代码存在兼容性,JS代码则不存在
  2. 代码复杂度: CSS代码简单但功能实现代码臃肿,JS代码实现复杂但代码精简
  3. 动画运行时: CSS无法实现特定时间点操作,添加事件,回调等,JS可以简单实现
  4. 动画性能上: CSS动画属于 GUI 渲染线程,JS动画多了一个JS解析过程,而JS线程执行与GUI渲染线程互斥,性能相比之下较差
  5. 总结: 需要精细控制动画情况下使用 JS 动画,其他情况优先使用 CSS 动画

document.write() and innerHTML 的区别

  1. document.write(): document.write 是直接写入到页面的内容流,如果在写之前没有调用document.open(),浏览器会自动调用open(),每次写完关闭之后重新调用该函数,会导致页面被重写
  2. innerHTML: innerHTML是DOM页面元素的一个属性,代表该元素的html内容,innerHTML将内容写入某个DOM节点,不会导致页面全部重绘
  3. 总结: innerHTML很多情况下都优于document.write,其原因在于其允许更精确的控制要刷新页面的那一个部分

本地存储

1. Cookie

Cookie,类型为「小型文本文件」,指某些网站为了辨别用户身份而储存在用户本地终端上的数据。是为了解决HTTP无状态导致的问题,作为一段一般不超过4KB 的小型文本数据,它由一个名称(Name)、一个值(Value)和其它几个用于控制cookie有效期、安全性、使用范围的可选属性组成

// 设置 Cookie
function setCookie(cname, cvalue, exdays) {
  let d = new Date()
  d.setTime(d.getTime() + exdays * 24 * 60 * 60 * 1000)
  let expires = 'expires=' + d.toUTCString()
  document.cookie = cname + '=' + cvalue + ';' + expires + ';path=/'
}
// 获取 Cookie
function getCookie(cname) {
  let name = cname + '='
  let decodedCookie = decodeURIComponent(document.cookie)
  let ca = decodedCookie.split(';')
  for (var i = 0; i < ca.length; i++) {
    let c = ca[i]
    while (c.charAt(0) == ' ') {
      c = c.substring(1)
    }
    if (c.indexOf(name) == 0) {
      return c.substring(name.length, c.length)
    }
  }
  return ''
}
// 检测 Cookie
function checkCookie() {
  var username = getCookie('username')
  if (username != '') {
    alert('Welcome again ' + username)
  } else {
    username = prompt('Please enter your name:''')
    if (username != '' && username != null) {
      setCookie('username', username, 365)
    }
  }
}
// 删除 Cookie
function delCookie(cname) {
  let exp = new Date()
  exp.setTime(exp.getTime() - 1)
  let cval = getCookie(cname)
  if (cval != null) {
    document.cookie = name + '=' + cval + ';expires=' + exp.toUTCString() + ';path=/'
  }
}

2. Window.localStorage

只读的localStorage 属性允许你访问一个Document源(origin)的对象 Storage;存储的数据将保存在浏览器会话中;localStorage 类似 sessionStorage,但其区别在于:存储在 localStorage 的数据可以长期保留;而当页面会话结束——也就是说,当页面被关闭时,存储在 sessionStorage 的数据会被清除

  • 应注意,无论数据存储在 localStorage 还是 sessionStorage它们都特定于页面的协议
  • 另外,localStorage 中的键值对总是以字符串的形式存储; (需要注意,和 js 对象相比,键值对总是以字符串的形式存储意味着数值类型会自动转化为字符串类型)

特点

  • 生命周期:持久化的本地存储,除非主动删除数据,否则数据是永远不会过期的
  • 存储的信息在同一域中是共享的
  • 当本页操作(新增、修改、删除)了localStorage的时候,本页面不会触发storage事件,但是别的页面会触发storage事件。
  • 大小: 5M(跟浏览器厂商有关系)
  • localStorage 本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡

缺点

  • 无法像Cookie一样设置过期时间
  • 只能存入字符串,无法直接存对象

3. Window.sessionStorage

sessionStorage 属性允许你访问一个,对应当前源的 session Storage对象;它与 localStorage相似,不同之处在于 localStorage 里面存储的数据没有过期时间设置,而存储在 sessionStorage 里面的数据在页面会话结束时会被清除(存储一般为 5 M)

  • 页面会话在浏览器打开期间一直保持,并且重新加载或恢复页面仍会保持原来的会话
  • **在新标签或窗口打开一个页面时会复制顶级浏览会话的上下文作为新会话的上下文,**这点和 session cookies 的运行方式不同
  • 打开多个相同的 URL 的 Tabs 页面,会创建各自的 sessionStorage
  • 关闭对应浏览器标签或窗口,会清除对应的 sessionStorage

特点,缺点

  • localStorage 基本相同,唯一不同的是sessionStorage 在页面关闭后就会清除数据

4. web Storage 使用语法

setItem('myCat', 'Tom'); // 增加了一个数据项目
getItem('myCat'); // 读取数据项目
removeItem('myCat') // 移除数据项目
clear() // 移除所有项目!!!

特定于页面的协议

  • 应该注意,存储在 sessionStorage 或 localStorage 中的数据特定于页面的协议;也就是说 http://example.comhttps://example.com 的 sessionStorage 相互隔离
  • 被存储的键值对总是以 UTF-16 DOMString的格式所存储,其使用两个字节来表示一个字符;对于对象、整数 key 值会自动转换成字符串形式

5. indexDB

indexedDB是一种低级API,用于客户端存储大量结构化数据(包括,文件 / blobs)。该API使用索引来实现对该数据的高性能搜索
虽然web Storage对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB提供了一个解决方案

优点

  • 储存量理论上没有上限
  • 所有操作都是异步的,相比LocalStorage同步操作性能更高,尤其是数据量较大时
  • 原生支持储存Js的对象
  • 是个正经的数据库,意味着数据库能干的事它都能干

缺点

  • 操作非常繁琐
  • 本身有一定门槛

indexedDB 基本使用步骤

  • 打开数据库并且开始一个事务
  • 创建一个 object store
  • 构建一个请求来执行一些数据库操作,像增加或提取数据等
  • 通过监听正确类型的DOM事件以等待操作完成
  • 在操作结果上进行一些操作(可以在 request 对象中找到)

备注: 通过使用 Godb.js 库进行缓存,最大化的降低操作难度

6. 本地存储之间的区别

Cookie 与 web Storage api接口的共同点:
保存时以键值对的形式进行存储:都是保存在浏览器端、且同源的
Cookie 与 web Storage api接口的区别:

  1. cookie数据始终在同源的http请求中携带(即使不需要),即cookie在浏览器和服务器间来回传递,而sessionStorage和localStorage不会自动把数据发送给服务器,仅在本地保存。cookie数据还有路径(path)的概念,可以限制cookie只属于某个路径下
  2. 存储大小限制也不同, cookie数据不能超过4K,同时因为每次http请求都会携带cookie、所以cookie只适合保存很小的数据,如会话标识。sessionStorage和localStorage虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大
  3. 数据有效期不同, sessionStorage:仅在当前浏览器窗口关闭之前有效; localStorage:始终有效,窗口或浏览器关闭也一直保存,因此用作持久数据; cookie:只在设置的cookie过期时间之前有效,即使窗口关闭或浏览器关闭
  4. 作用域不同, sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;localstorage在所有同源窗口中都是共享的; cookie也是在所有同源窗口中都是共享的
  5. web Storage支持事件通知机制,可以将数据更新的通知发送给监听者
  6. web Storage api接口使用更方便

7. 使用场景

  • 标记用户与跟踪用户行为的情况,推荐使用cookie
  • 适合长期保存在本地的数据(令牌),推荐使用localStorage
  • 敏感账号一次性登录,推荐使用sessionStorage
  • 存储大量数据的情况、在线文档(富文本编辑器)保存编辑历史的情况,推荐使用indexedDB

WebAPI 手写实现


拖拽函数

/**
 * * JS拖拽函数,设置安全边界
 *
 * @param {Element Object} elem
 * @param {Element Object} borderElem
 * @param {Function} [callback]
 */
function dragAnimation(elem, borderElem, callback) {
  borderElem = borderElem || document.documentElement
  // 鼠标按下
  elem.addEventListener(
    'mousedown'function (e) {
      let x = e.clientX // 鼠标 X 坐标
      let y = e.clientY // 鼠标 Y 坐标
      let elemLeft = x - parseInt(elem.offsetLeft) // 鼠标在元素内 X 坐标
      let elemTop = y - parseInt(elem.offsetTop) // 鼠标在元素内 Y 坐标
      
      // 设置移动函数
      function moving(e) {
        let movedX = e.clientX - elemLeft // 移动 X 坐标
        let movedY = e.clientY - elemTop // 移动 Y 坐标
        // 判断是否超过安全边界
        let windowWith = borderElem.clientWidth
        let windowHeight = borderElem.clientHeight
        movedX = range(movedX, 0, windowWith - elem.offsetWidth) // 父元素宽度 - 子元素宽度 = 鼠标坐标可以移动的最大 X 值
        movedY = range(movedY, 0, windowHeight - elem.offsetHeight) // 父元素高度 - 子元素高度 = 鼠标坐标可以移动的最大 Y 值
        // 设置移动
        elem.style.marginLeft = movedX + 'px'
        elem.style.marginTop = movedY + 'px'
      }
      
      // 鼠标移动
      elem.addEventListener('mousemove', moving, false)

      // 鼠标抬起
      window.addEventListener(
        // 全局,防止卡顿造成鼠标抬起动作在元素外部触发
        'mouseup'function (e) {
          elem.removeEventListener('mousemove', moving, false)
          callback && callback(e)
        }false
      )
    }false
  )
}

// 安全边界函数
/**
 * @param {Number} loc
 * @param {Number} min
 * @param {Number} max
 * @return {Number} 
 */
function range(loc, min, max) {
  if (loc > max) {
    return max
  } else if (loc < min) {
    return min
  } else {
    return loc
  }
}

动画函数

/**
 * 动画函数,传入 对象、长度,可以让对象按设置的速度进行变换
 *
 * @param {*} obj 要操作的对象
 * @param {*} target 最终值
 * @param {*} callback 回调函数
 */
function animate(obj, target, callback) {
  // 先清除以前的定时器,只保留当前的一个定时器执行
  clearInterval(obj.timer)
  obj.timer = setInterval(function () {
    // 步长值写到定时器的里面
    var step = (target - obj.offsetLeft) / 10
    // 把步长值改为整数 不出现小数的问题
    step = step > 0 ? Math.ceil(step): Math.floor(step) // 大往右,小往左
    if (obj.offsetLeft == target) {
      // 停止动画 本质是停止定时器
      clearInterval(obj.timer)
      // 回调函数写到定时器结束里面
      callback && callback()
    }
    // 将步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10, 实现缓动的效果
    obj.style.left = obj.offsetLeft + step + 'px'
  }15)
}

电梯导航

/**
 * 动画函数,传入 对象、长度,可以让对象按设置的速度进行变换(实现电梯导航)
 *
 * @param {*} obj 要操作的对象
 * @param {*} target 最终值
 * @param {*} callback 回调函数
 */
function animate(obj, target, callback) {
  // 先清除以前的定时器,只保留当前的一个定时器执行
  clearInterval(obj.timer)
  obj.timer = setInterval(function () {
    // 步长值写到定时器的里面
    // 把我们步长值改为整数 不要出现小数的问题
    // var step = Math.ceil((target - obj.offsetLeft) / 10);
    var step = (target - window.scrollY) / 10
    step = step > 0 ? Math.ceil(step): Math.floor(step)
    if (window.scrollY == target) {
      // 停止动画 本质是停止定时器
      clearInterval(obj.timer)
      // 回调函数写到定时器结束里面
      callback && callback()
    }
    // 把每次加1 这个步长值改为一个慢慢变小的值  步长公式:(目标值 - 现在的位置) / 10
    window.scroll(0, window.scrollY + step) // X Y 值
  }15)
}

轮播图

window.addEventListener('load'function () {
  // ! 1.实现鼠标经过箭头显示隐藏功能
  // 1.1 获取元素
  let arrow_left = document.querySelector('.icon-arrow-left')
  let arrow_right = document.querySelector('.icon-arrow-right')
  let focus = document.querySelector('.container')
  // 设置图片宽度数值
  let fucusWidth = focus.offsetWidth
  // 1.2 事件绑定并添加处理程序
  // 鼠标经过显示箭头
  focus.addEventListener('mouseenter'function () {
    arrow_left.style.display = 'block'
    arrow_right.style.display = 'block'
    clearInterval(timer)
    timer = null // 释放内存
  })
  // 鼠标离开隐藏箭头
  focus.addEventListener('mouseleave'function () {
    arrow_left.style.display = 'none'
    arrow_right.style.display = 'none'
    // 鼠标离开开启定时器
    timer = setInterval(function () {
      // 调用事件
      arrow_right.click()
    }2000)
  })

  // ! 2.动态添加小圆点
  // 2.1 获取元素
  let ul = document.querySelector('.carousel')
  let ol = document.querySelector('.lines')
  // 2.2 事件绑定并添加处理程序
  for (let i = 0; i < ul.children.length; i++) {
    let li = document.createElement('li')
    // 添加自定义属性以便计算
    li.setAttribute('index', i)
    ol.appendChild(li)
    // 在添加元素时绑定处理程序
    li.addEventListener('click'function () {
      for (let j = 0; j < ol.children.length; j++) {
        ol.children[j].className = ''
      }
      // 设置索引
      let index = this.getAttribute('index')

      // 点击时将索引给全局控制变量
      num = circle = index

      // 设置动画函数
      animate(ul, -index * fucusWidth)

      // 当前小圆点被选中
      this.className = 'current'
    })
  }
  ol.children[0].className = 'current'

  // ! 3.点击箭头切换图片和小圆点
  // 克隆节点
  // 实现无缝切换需将总先显示的图片克隆到最后
  let first = ul.children[0].cloneNode(true)
  ul.appendChild(first)

  // 控制变量
  let num = 0
  let circle = 0
  // 节流设置
  let flag = true
  // 右侧点击模块
  arrow_right.addEventListener('click'function () {
    if (flag) {
      flag = false
      if (num === ul.children.length - 1) {
        ul.style.left = 0
        num = 0
      }
      num++
      animate(ul, -num * fucusWidth, ()=> (flag = true))

      // 控制小圆点滚动
      circle++

      if (circle === ol.children.length) {
        circle = 0
      }

      circleChange()
    }
  })

  // 左侧点击模块
  arrow_left.addEventListener('click'function () {
    if (flag) {
      flag = false
      if (num === 0) {
        num = ul.children.length - 1
        ul.style.left = -num * fucusWidth + 'px'
      }
      num--
      animate(ul, -num * fucusWidth, ()=> (flag = true))

      // 控制小圆点滚动
      circle--

      if (circle < 0) {
        circle = ol.children.length - 1
      }
      // circle = circle < 0 ? ol.children.length - 1 : circle

      circleChange()
    }
  })
  // 排他思想设置小圆点显示
  function circleChange() {
    for (let i = 0; i < ol.children.length; i++) {
      ol.children[i].className = ''
    }
    ol.children[circle].className = 'current'
  }

  // ! 4.设置自动轮播
  let timer = setInterval(function () {
    // 调用事件
    arrow_right.click()
  }2000)
})

懒加载

当页面被请求时,只加载可视区域的图片,其它部分的图片/资源则不加载,只有这些图片/资源出现在可视区域时才会动态加载这些图片/资源,从而节约了网络带宽和提高了初次加载的速度

懒加载的核心:是如何在适当的时候加载用户需要的资源(这里用户需要的资源指该资源呈现在浏览器可视区域)

实现: 页面渲染时将src路径放到自定义属性中去,这样页面加载时图片就不会去请求服务器,当图片滚动到可视区内时,获取它的自定义属性并赋值给src

// 获取所有需要懒加载的元素
let imgs = document.querySelectorAll('img')

// 页面滚动时触发,利用节流减少触发频率
window.onscroll = throttle(()=> {
  let viewHeight = document.documentElement.clientHeight // 获取可视区高度
  let scrollHeight = document.documentElement.scrollTop || document.body.scrollTop // 获取页面滑动距离 || 0 v_v
  for (let i = 0; i < imgs.length; i++) {
    // 判断条件触发
    if (!imgs[i].isShow && getTop(imgs[i]) < viewHeight + scrollHeight) { // 断言,第一个判断后如果为 false 取反 为 true 判断后面,否则直接跳过
      imgs[i].src = imgs[i].getAttribute('data-url')
      imgs[i].isShow = true
    }
  }
}500)
// 进入页面执行一次资源加载
window.onscroll.call()

// 获得传入元素距离顶部的距离
function getTop(e) {
  let elTop = e.offsetTop // offsetTop 获取的是元素顶部距离 offsetParent 的距离
  while ((e = e.offsetParent)) {
    // offsetParent 返回一个指向最近的(指包含层级上的最近)包含该元素的定位元素或者最近的 table,td,th,body元素,没有则返回 null
    elTop += e.offsetTop // 将获取等最新高度加到返回高度上
  }
  return elTop
}
// 节流函数
function throttle(callback, wait) {
  // 间隔判断值
  let start = 0
  // 返回值为一个函数
  return function (e) {
    // 函数执行时时间
    let now = Date.now()
    // 触发时间判断
    if (now - start >= wait) {
      callback.call(this, e)
      start = now
    }
  }
}

节流阀

设置一个判断数,满足特定条件下程序才能执行,否则不执行

let flag = true
function getData() {
  if (flag) {
    flag = false
    ...
    callback( flag = true )
  }
}

获取页面滚动元素

function fn(element) {
  element.onscroll = function () {
    console.log(element)
  }
  Array.from(element.children).forEach(fn)
}
fn(document.body)
  • 19
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

taciturn丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值