GithubHelp home page GithubHelp logo

javascript-knowledge's Introduction

Hi there 👋

Hello

javascript-knowledge's People

Contributors

clarencec avatar

Stargazers

 avatar

Watchers

 avatar

javascript-knowledge's Issues

JavaScript 浏览器 EventLoop

JavaScript 浏览器的 EventLoop

在浏览器的 EventLoop里面,浏览器会通过 EventLoop 来执行事件,因为 JavaScript 是单线程的,如果处理不好会阻塞线程,页面处理会卡死浏览器,那 EventLoop 事件是怎样运行的呢,作者通过最近几篇文章总结一下 EventLoop.

大家都知道 JavaScript 是单线程而且是异步的,这不是语言本身决定它拥有这些特性的是,是浏览器内核决定的,所以意味着在同一时间只能执行一个任务,如果在 JavaScript 中执行某个任务时间过长,就会导致其它任务难以执行的情况出现,最常见就是执行函数过长,导致页面渲染阻塞的情况出现,所以管理好页面 EventLoop 尤其重要。

EventLoop 的事件

在之前我们就了解过浏览器 EventLoop有, Task、Animation callbacks、Microtasks。
链接地址 Tasks, microtasks, queues and schedules

异步任务 特点 常见出处
Task(Macrotasks) - 当次事件循环执行队列内的一个任务
- 当次事件循环产生的新任务会在指定时机加入任务列表等待执行
- setTimeout
- setInterva
- setImmediate - I/O
Animation callbacks - 渲染过程(Structure-Layout-Paint)前执行
- 当次事件循环执行队列里的所有任务
- 当次事件循环产生的新任务会在下一次循环执行
requestAnimationFrame
Microtasks - 当次事件循环的结尾立即执行的任务
- 当次事件循环执行队列里的所有任务
- 当次事件循环产生的新任务会立即执行
- Promise
- Object.observe
- MutationObserver
- process.nextTick

image

image

渲染过程: 包含三个步骤:
- STRUCTURE 构建 DOM 树的结构. 
- Layout 确认每个 DOM 的大致位置(排版). 
- Paint 绘制每个 DOM 具体的内容(绘制).

主进程函数会逐层进入 Stack 里面运行,当主进程出 Stack(栈) 运行后,会计算当前压进 Queue 队列的 Macrotask, Queue队列会打开异步队列开关,进入异步队列。如果在异步队列中卡死,就会阻塞主进程,和渲染进程.

button.addEventListener('click', () => {
   while(true)
})

如果把代码更改成下面

function loop() {
 setTimeout(loop, 0)
}
loop()

就不会存在阻塞,因为会返回主进程由主进程来重新分配渲染过程或异步进程。

process.nextTick: 是加入到当前执行栈底部运行,在下一次 EventLoop 开始之前触发运行。
requestAnimationFrame :属于一个特殊异步任务,它是在渲染过程(三个步骤)前执行的,主要用来 处理渲染相关工作。

关于 setTimeout 和 requestAnimationFrame 的区别:

  1. 不是每次 setTimeout 异步队列,都会进到渲染循环,如果异步队列已经运行过很多次进行操作DOM,进入渲染过程时会把之前多次操作结果一同渲染到页面。
  2. requestAnimationFrame,我严格执行运行一次渲染一次
// method1 使用 requestAnimationFrame 方法运行会比 setTimeout  移动box速度要快很多,因为单位时间内调用的次数比 setTimeout 多很多。
function callback() {
    moveBoxForwardOnePixel()
    requestAnimationFrame(callback)
}
callback()

// method2  setTimeout后不会到渲染进程,所以渲染时会一 下子移动很多像素。
function callback() {
    moveBoxForwardOnePixel()
    setTimeout(call, 0)
}
callback()

Macrotask 和 Microtasks 的区别

MacroTask 会进入异步队列(task Queue)。

Microtasks 简单来说,在一系列的任务做出回应或者执行异步操作时,又不想重新使用一整个异步队列的代价时,就应该调用 Microtasks 在当前执行 Microtasks 队列里结束后马上执行另一个的任务。

MacrotasksMicrotasks,执行顺序如下:

  • 取出Macrotasks任务队列的一个任务,执行;
  • 取出Microtasks任务队列的所有任务,依次执行;
  • 本次事件循环结束,等待下次事件循环;
// 普通的递归,造成死循环,页面无响应
function callback() {
    console.log('callback')
    callback()
}
callback()

// Macrotasks 使用 setTimeout 递归,不会造成死循环
function callback() {
    console.log('callback')
    setTimeout(callback, 0)
}
callback()

// Microtasks,同样会造成死循环,页面无响应,因为要全部 Microtasks 执行完毕才重新进入主进程,由于 Microtasks 永远不会退出所以会导致死循环。
function callback() {
    console.log('callback')
    Promise.resolve().then(callback)
}
callback()

同步代码的合并

同步代码修改同一个元素的属性,会直接优化到最后一个代码。

// scene I
box.style.display = 'none'
box.style.display = 'block'
box.style.display = 'none' // 运行最 后一个

// scene II
box.style.transform = 'translateX(1000px)'
box.style.tranition = 'transform 1s ease' // 直接运行
box.style.transform = 'translateX(500px)' // 直接运行

解决方法:

// method 1 使用刚才的 requestAnimationFrame
box.style.transform = 'translateX(1000px)'
requestAnimationFrame(() => {
    requestAnimationFrame(() => {
        box.style.transition = 'transform 1s ease'
        box.style.transform = 'translateX(500px)'
    })
})

// method 2 重新获取一下当前的计算样式即可
box.style.transform = 'translateX(1000px)'
getComputedStyle(box) // 伪代码,获取当前的计算样式
box.style.transition = 'transform 1s ease'
box.style.transform  = 'translateX(500px)'

文章扩展
深入浏览器的事件循环 (GDD@2018)
我与Microtasks的前世今生之一眼望穿千年
45.理解事件循环二(macrotask和microtask)
JavaScript异步机制详解
从浏览器多进程到JS单线程,JS运行机制最全面的一次梳理
图解搞懂JavaScript引擎Event Loop

DOM

DOM

DOM 是针对 HTML 和 XML 文档的一个 API应该程序编程接口。DOM 用来描绘了一个可添加、移除和修改的层次的节点树。DOM 是由 DHTML (动态 HTML)开始创立的,现在都是跨平台、语言中立的。

DOM 是一个树形结构的根节点集合组成的。

1. Node 类型

DOM1 级里面定义了一个 Node 接口, 所有节点类型都继承自 Node 类型, 所有的节点类型都有着相同的基本属性和方法。

Node 常量数值 Node 节点类型
1 ELEMENT_NODE
2 ATTRIBUTE_NODE
3 TEXT_NODE
4 CDATA_SECTION_NODE
5 ENTITY_REFERENCE_NODE
6 ENTITY_NODE
7 PROCESSING_INSTRUCTION_NODE
8 COMMENT_NODE
9 DOCUMENT_NODE
10 DOCUMENT_TYPE_NODE
11 DOCUMENT_FRAGMENT_NODE
12 DOCUMENT_NOTATION_NODE

1. nodeType Node节点的类型

nodeType 可以用来显示当前 Node节点类型。判断的时候可以用类型 Node.ELEMENT_NODE 或者常量数值也行,最好使用常量数值进行比较为了确保浏览器兼容。

    if (someNode.nodeType === Node.ELEMENT_NODE) // 用来判断 Element 节点
    // or
    if (someNode.nodeType === 1 ) // 用来判断 Element 节点

2. nodeName Node节点的元素标签名

    var element = document.getElementByID("testName")
    console.log(element.nodeName) // "DIV"

3. nodeValue Node节点元素的值

对于元素节点 nodeName 中保存的是元素的标签名,而 nodeValue 值为 null 。

1. NodeList

在 DOM 中每个节点都存在一个childNodes的属性, childNodes 属性保存的是 NodeList 的对象, NodeList 对象是一个拥有 length 属性的类数组,并不是一个真正的数组。 DOM 的结构变化能自动反映在 NodeList 对象中的, NodeList 是一个动态的对象。

如果想访问,NodeList 元素可以使用下面的方法:

    //1
    var firstChild = someNode.childNodes[0] // 使用数组标,因为更贴近数组,所以更多开发人员在使用
    //2
    var secondCHild = someNode.childes.item(1) // 使用 item()方法

或者把 NodeList 转变为数组

    // 转换数组
    function convertToArray(nodes){
        var array = null
        try { // 通过 try catch 来捕获错误
            array = Array.prototype.slice.call(nodes, 0) // 非 IE8以下浏览器
        } catch(ex) {
            array = new Array()
            for(var i = 0, len = nodes.length; i < len; i++){
                array.push(nodes[i])
            }
        }
        return array
    }

2. 关于 parentNode 属性和 previousSibling、nextSibling属性。

parentnode_childnodes_method

一图解释全部关系 Node 节点下面的子节点,有一个 parentNode 属性对回父类属性,如果 nextSiblingpreviousSibling 为最尾或者最头节点会返回 null

someNode.ownerDocument 每个节点都有这个属性,指向最顶层的 HTML 节点。
someNode.hasChildNodes() 返回该节点是否有 childNodes。

3.操作节点

  1. appendChild() 在指定的节点 childNodes 内追加一个元素节点在 childNodes 末尾。

someNode.appendChild(someELement) 添加子节点到 someNode 的 childNodes 最后面。如果节点已经存在在节点树中那么,就会从原来的位置移动到操作后的位置。

  1. insertBefore(newNode, someNode) 把 newNode 插入到 someNode 节点之前。

如果传入的第二个参数为 null 则会和 appendChild() 操作效果一样。

  1. replaceChild(newNode, someNode) 用 newNode节点把 someNode 节点替换了,replaceChild 可以删除节点。

  2. removeChild() 移除节点

    var formerFirstChild = someNode.removeChild(someNode.firstChild) // 移除第一个子节点

删除和移除节点后,节点并不是消失了,只是不在 DOM 树中,如果需要彻底删除需要 formerFirstChild = null,释放节点那么引用节点的事件才会跟着释放内存。

  1. cloneNode() 创建复制副本
    var deepList = myList.cloneNode(true) // 深度拷贝
    console.log(deepList.childNodes.length) // 3
    var shalloList = myList.cloneNode(false) // 浅拷贝
    console.log(shallowList.childNodes.length) // 0

Document 类型

Document 代表整个文档。document 对象是 window对象的一个属性。

    console.log(window.document.nodeType) // 9
    console.log(window.document.nodeName) // #document
    var html = document.querySelector("html") // 获取 html
    console.log(html.parentNode.nodeType) // 9 html的父类就是 document 节点
    console.log(document.parentNode) // document 的父类为 null
    console.log(document.childNodes) // NodeList(2) [<!DOCTYPE html>, html.module-default] 

对于 document 的直接子元素属性可以直接属性访问

    console.log(document.doctype) // <!DOCTYPE html>
    console.log(document.doctype.nodeType) // 这能获得 10  DOCUMENT_TYPE_NODE 值
    console.log(document.body) // <body id="body" style>...</body>
    console.log(document.title) // 标题

document 里面还自带了,几个 BOM 对象属性非常实用

    console.log(document.URL) // 取得连接
    console.log(document.domain) // 取得域名
    console.log(document.location) // 获得地址信息
    console.log(document.images) // 同样这方法也能获取全部文档 img 元素的节点
    console.log(document.anchors) // 包含文档中所有带 name 特性的 <a> 元素
    console.log(document.forms) // 包含文件中所有 <form> 元素

检测 DOM 的功能类型

可以通过 document.implementation.hasFeature 来判断是否包含 Dom 的部分功能。

    var haxXmlDom = document.implementation.hasFeature("XML","1.0") 

DOM写入流

除了操作 DOM 方法外,还可以使用写入流来插入 node 元素节点。有以下几个方法

  1. write() 方法,把传入的字符串写入到 dom 流中。
    document.write("<strong>" + (new Date()).toString() + "</strong>")
  1. writeln() 方法,把传入的字符串写入到 dom 流中并最后换行。
  2. open() 方法,用于打开写入流。页面加载其间不需要使用这方法。
  3. close() 方法,用于关闭写入流。页面加载其间不需要使用这方法。

查找元素的方法

  • document.getElementById() 通过节点 id 属性查找 ELement 节点,返回 ELEMENT_NODE
var div = document.getElmentById('myDiv')
  • document.getElementsByTagName() 通过元素的标签名返回找到的元素列表 HTMLCollection
var images = document.getELementsByTagName("img")
// 访问 HTMLCollection 方法
console.log(images.length) // 图像数量
console.log(images[0]) // 数组访问
console.log(images.item(0)) // item 形式访问
console.log(images.namedItem('myImage')) // 访问 name 属性为 myImage 的元素
var allElements = document.getElementsByTagName("*") // 获取所有元素
  • document.getElementsByName 通过元素的 name 特性,返回 HTMLCollection

通常会使用在单选按钮上面,获取同一类组别的元素。

创建元素节点

  1. createElement()创建元素节点
  2. createComment 创建解释节点

Element 类型

Element 是 Web 编程中最常用的类型,用于表现 XML 或 HTML 元素,提供对元素的标签名,子节点特性访问等。

在Element的 nodeName 属性中,保存的是元素标签名, nodeValue 返回的是 null。

关于 Element 和 Node 的区别

很多人都会混淆了 Element 和 Node 的区别,其实两个是完全不一样的东西,Node 是属于 HTML DOM 中的基类,所有像 Element ,Attribut Text,都是 Node 的实例对象,
是特殊的 Node 分别叫做, Element_Node, Text_Node, Comment_Node , 所以 Element_Node 都会继承 Node 的属性。

NodeList vs HTMLCollection

  • NodeList 是由 Node 类型组成的列表
  • HTMLCollection 是由 Element 节点组成的列表,操用 DOM 的时候都会操作 ElementList 即是 HTMLCollection ,而不会去操作 NodeList。
  • 特别要注意的是 NodeList 和 HTMLCollection 都不是真正的数组,都是类数组。
  • NodeList 会包含文本节点和空格节点
  • NodeList 和 HTMLCollection 都是 live 的,会动态变化。
  • NodeList 没有 nameditem() 方法,而 HTMLCollection 有。

怎样获取 NodeList 和 HTMLCollection

    // 获取 HTMLCollection
    const div = document.getElementById('Id') // HTMLCollection
    document.getElementsByName('Name') // HTMLCollection
    document.getElementsByTagName('TagName') // HTMLCollection
    document.getElementsByClassName('style') // HTMLCollection

    document.querySelectorAll('style') // 返回的是 nodelist,但是实际上的是 元素集合,并且是静态的

    div.item(0) // HTMLCollection 可以通过 item 方法访问单独 node 节点
    div[0] // HTMLCollection 也可以通过直接数组的形式访问

    node.childNodes // 返回 NodeList 
    div.item(0).childNodes // 返回 NodeList 

Element 的属性

所有 HTML 元素都是由 HTMLElement 类型来表示的,HTMLElement 是直接继承自 Element 一些属性包括:

  • id
  • className
  • title
  • lang
  • name
  • ref

访问的时候可以直接获得一个node节点,再直接访问属性

    var div = document.getElementById('myDiv')
    div.id
    div.className
    div.lang
    div.ref

或者可以使用 dom 方法来操作属性值

    var div = document.getElementyById('myDiv')
    div.getAttribute('id')
    div.setAttribute('id',value)

有三种操作特性的方法

    div.getAttribute('id') // 获取 属性 ID 的值
    div.setAttribue('id',value) // 设置 ID 的值
    div.removeAttribute('id') // 删除 ID 的属性
    // attributes 属性
    element.attributes.lenght // 通过 Element_node 的 attributes 属性访问

创建元素

创建元素节点可以通过下面方法创建

    // 传参数是 tagName
    var div = document.createElement('div')
    // or
    var div = document.createElement("<div id=\"myNewDiv\" class=\"box\"></div>")

创建元素节点后是单独存在的,还需要添加到文档树中,才会在页面显示出来。

    node.appendChild() // 在末尾添加子节点
    node.insertBefore() // 在某节点前添加子节点
    node.replaceChild() // 替换子节点

Text 类型

文本节点由 Text 类型表示,是用来保存纯文本的内容,一般包含在 Element 节点里面。

    <div></div> <!--没有文本节点-->
    <div> </div><!--空格也算文本节点-->
    <div>Hello World!</div> <!--文本节点-->

文本节点可以直接通过 nodeValue 赋值,不过记着是要对 TextNode 的 nodeValue 赋值,别对其它 Node 节点赋值是没什么作用的,比如 ElementNode。

    const head = document.getElementById('head')
    head.childNodes[0].nodeValue = 'Text_Node'

创建文本节点

    var element = document.createElmenet('div')
    var textNode = document.createTextNode('<strong>Hello</strong> World!')
    element.appendChild(textNode)
    document.body.appendChild(element)

Text类型节点的方法

  • normalize() 能把元素节点下多个文本子节点合并
  • splitText() 能把文本子节点分拆,正好和 normalize 相反

Comment 类型

注释节点,注释节点是通过 Comment 来表示。

CDATASection 类型

用于针对基于 XML 的文档描述

DocumentType 类型

用于描述页面整个文档的 doctype 有关信息

DocumentFragment 类型

用于包含document 片块节点信息

Attr 类型

用来表示元素节点里面的属性,属性都由 AttrNode 节点表示。
操作属性节点的方法有:

    node.getAttribute('name') // 获取 属性节点
    node.setAttribute('name','value') // 更改属性节点
    node.removeAttribute('name') // 移动属性节点

    document.createAttribute('align') // 创建属性节点

动态脚本

有了上面操作 DOM 的操作方法,就可以动态地做很多事情比如说动态加载脚本

    const script = document.createElmenet('script') // 创建元素节点
    script.type = 'text/javascript' // 设置忏悔
    script.src = 'client.js'
    script.appendChild( // 在脚本里面添加 sayHi 方法
        document.createTextNode(" 
            function sayHi()(
                console.log('hi')
            )
        ")
    )
    document.body.appendChild(script) // 添加到文档树中

动态样式

同样也能添加动态样式

    const link = document.createElement('link')
    link.rel = 'stylesheet'
    link.type = 'text/css'
    link.appendChild(document.createTextNode("body{background-color:red}"))
    link.href = 'style.css'
    let head = document.getElementsByTagName('head')[0]
    head.appendChild(link)

关于 NodeList 的使用

NodeList 、NamedNodeMap、 HTMLCollection 三个集合都是动态变化的,每当文档结构发生变化的时候,它们都会更新 live 状态。所以在遍历操作 NodeList 的时候得小心,最好把 NodeList.length 单独赋变量,以至不会让更改 NodeList 的时候无限变化。DOM 操作会花费比较大的资源,访问 NodeList 都会运行一次查询,尽量减少 DOM 操作会比较好。

DOM Level

DOM

DOM(文档对象模型) 是针对 HTML 和 XML 文档的一个API(应用程序编程接口),DOM 描绘了一个层次化的节点树,允许开发人员添加、移除和修改页面的某一部分。DOM 定义了访问 HTML 和 XML 文档的标准,可以允许脚本动态地更新文档内容、结构和样式, 简单点说就是提供一个文档对象模型给 JS 动态修改文档状态。 DOM 是一个共 W3C 公发布的标准,所以可以在所有浏览器上使用 DOM 的这些 API。

1200px-dom-model svg

浏览器渲染

566688fa0001312112800720
这图很清楚地描述了整个页面渲染过程,而 DOM 标准是基于 HTML 上解释成文档对象模型,之后再和 CSS 树合并,画出来的。

DOM 的发展史

DOM | DOM0

最初 DOM 还没有被标准化,还只是在 Netscape Navigator3 上的模型,结果 IE3 和 Netscape Navigator3 上的模型都各自不同,相互冲突,那个时候被叫 DOM Level 0。

DOM 1

在浏览器各厂商进行大战的同时,W3C 结合大家的优点推出了一个标准化的 DOM ,被叫为 DOM1。 DOM1 被定义为一个与平台和编程语言无关的接口,可以通过这些 API 接口访问和修改文档的内容、结构和样式。DOM1 是比较基础的,主要定义了 HTML 和 XML 文档的底层结构,DOM1 由三部分组成,包括: 核心 (core)、 HTML接口。

  • DOM Core (DOM 核心):DOM Core 规定了基于 XML 的文档结构标准,通过这个标准简化了对文档中 XML 的访问和操作。

  • DOM HTML :DOM HTML 则在 DOM 核心的基础上加以扩展,添加了针对 HTML 的对象和方法。

DOM 2

DOM2 在 DOM1 的基础上引入了更多的交互能力,支持更高级的 XML 特性。 DOM2 扩充了,鼠标、用户界面事件、范围、遍历等细分模块,而且增加了对 CSS 的支持。

  • DOM2核心(DOM2 Core): 为节点添加更多方法和属性。
  • DOM级视图(DOM2 HTML): 在 DOM1 上添加更多属性、方法和新接口。
  • DOM视图 (DOM2 Views): 为文档定义了基于样式信息的不同视图。
  • DOM事件 (DOM2 Events): 说明了如何使用事件与 DOM 文档交互。
  • DOM样式 (DOM2 Style): 定义了如何方式访问和改变 CSS 样式的信息。
  • DOM遍历和范围 (DOM2 Traversal and Range): 引入了遍历 DOM 文档和选择其特定部分的新接口。

DOM2 样式,围绕外部样式表,<style/>样式表,和针对特定元素的样式,提交API。

style

利用节点的 style 属性,可以访问任何该节点元素的样式信息。

    // 可以直接对 Node_Element 节点的 style属性进行添加样式
    const myDiv = document.getElementById('myDiv')
    myDiv.style.backgroundColor = 'red'
    myDiv.style.width = '100px'
    myDiv.style.border = '1px solid black'

    // 还可以直接设置 style.cssText,直接设置能大量修改样式特性
    myDiv.style.cssText = 'width: 25px; height: 100px; background-color: green;'

    //访问 style 的时候和访问 Node 节点一样
    for(const i = 0 ,len=myDiv.style.length; i < len; i++) {
        const divStyle =  myDiv.style[i] // 数组形式访问, 获取 CSS 属性名
        const divItemStyle = myDiv.style.item(i) // item函数访问, 获取 CSS 属性名
        // 通过 getPropertyValue 获取属性的值
        const value = myDiv.style.getPropertyValue(divStyle)
        // 如果访问cssText
        const value = myDiv.style.getPropertyCSSSValue(prop)
    }
    // 删除style属性
        myDiv.style.removeProperty('border')

getComputedStyle()

利用 getComputedStyle() 能计算出从其他样式表层叠而来影响到当前元素的样式信息。

const myDiv = document.getElementById('myDiv')
// 传入两个参数, Node 节点,伪元素字符串(没有可以传 null 
// 计算出这个元素的最终样式
const computedStyle = document.defaultView.getComputedStyle(myDiv, null)
computedStyle.backgroundColor // "red"
computedStyle.width // "100px"
computedStyle.height // "200px"
computedStyle.border 

document.styleSheets && sheet.cssRules && sheet.rules

document.styleSheets可以返回当前文档的样式表集合。
sheet.cssRulessheet.rules 可以访问样式元素里面的 CSS 元素。

    console.log(document.styleSheets) // StyleSheetList {0: CSSStyleSheet, 1: CSSStyleSheet, 2: CSSStyleSheet, 3: CSSStyleSheet, length: 4}

    const sheet = document.styleSheets[0] // 获取文档第一个样式表
    const rules = sheet.cssRules || sheet.rules // 获取样式表里面的 CSS列表
    const rule = rules[0] // 获取第一条
    console.log(rule.selectorText) // "div.box"
    console.log(rule.style.cssText) // "全部css样式"
    console.log(rule.style.backgroundColor) // "blue"
    console.log(rule.style.width) // "100px"
    console.log(rule.style.height) // "200px"

    rule.style.backgroundColor = "red" // 修改样式属性
    sheet.insertRule("body { background-color: sliver }",0) // 在样式表插入样式 在 0 行插入
    sheet.deleteRule(0) // 删除 0 行
    sheet.removeRule(0) // 兼容 IE

DOM2 偏移量

在日常网页开发中偏移量在经常使用在横福,广告轮转,页面滚动当中啊。理解根本的概念非常重要。

offsetParent

Node.offsetTop 直接访问距离属性

Node.offsetParent 对象可以统一包含下面属性

  • offsetHeight:元素在垂直方向上占用的空间大小。
  • offsetWidth: 元素在水平方向 上占用的空间大小。
  • offsetLeft: 元素的左外边框至包含元素的左内边框之前的像素距离。
  • offsetTop: 元素的上外边框至包含元素的上内边框之间的像素距离。

test_choiye84

客户区大小

客户区大小指元素内容及其内边距所占据的空间大小。

Node.offsetParent 对象可以统一包含下面属性

  • clientWidth: 是元素内容区宽度边距宽度。
  • clientHeight: 属性是元素内容区高度边距高度。

滚动大小

滚动大小是指包含滚动内容的元素的大小。

  • scrollHeight: 在没有滚动条的情况下,元素内容的总高度。

  • scrollWidth: 在没有滚动条的情况下,元素内容的总宽度。

  • scrollLeft: 被隐藏在内容区域左侧的像素数。通过设置这个属性可以改变元素的滚动位置。

  • scrollTop: 被隐藏在内容区域上方的像素数。通过设置这个属性可以改变元素的滚动位置。

  • getBoundingClientRect(): 返回一个矩形对象包含4个属性: left、top、right、bottom.

DOM2 遍历

DOM2 提供了下面两个 DOM 类型来遍历 DOM 结构,这两个类型都是基于深度优先算法遍历的。就是遇到兄弟节点先把兄弟节点遍历到底再遍历另一边的兄弟子点。

  • NodeIterator
const filter = function(node) {
    return node.tagName.toLowerCase() == 'p' ?
           NodeFilter.FILTER_ACCEPT :
           NodeFilter.FILTER_SKIP
}
const iterator = document.createNodeIterator( // 创建遍历节点
    root, // 从那个父节点开始遍历
    NodeFilter.SHOW_ELEMENT, // 节点数字代码
    filter, // NodeFilter 对象,接受某中特定节点的函数
    false // 在 HTML 页面返回 false
)

iterator.nextNode() // 下一个节点
iterator.previousNode() // 上一个节点
  • TreeWalker

TreeWalker 和 NodeIterator 使用方式很相似

    const div = document.getElementById('div')
    const filter = function(node) {
        return node.tagName.toLowerCase() === "li"?
               NodeFilter.FILTER_ACCEPT :
               NodeFilter.FILTER_SKIP
    }
    const walker = document.createTreeWalker( // createTreeWalker
        div,
        NodeFilter,
        SHOW_ELEMENT,
        filter,
        false
    )
    walker.firstChild() // 第一个节点
    walker.nextSibling() // 下一个字点
    walker.currentNode // 当前的节点
    let node = iterator.nextNode()

    while(node !== null) {
        console.log(node.tagName)
        node = iterator.nextNode()
    }

DOM2 中的范围

  • createRange()
    const range1 = document.createRange() // 创建范围
    const range2 = document.createRange()
    const p1 = document.getELmenetById('p1')
    range1.selectNode(p1) // 选择整个节点,包括其子节点
    range2.selectNodeContents(p1) // 只选择节点的子节点

DOM2 范围还有增删查改的操作,不一一详解了。

DOM 3

DOM3 扩展了 DOM,增加了一些扩展模块:

  • DOM3 加载和保存模块(DOM Load and Save): 引入了以统一方式加载和保存文档的方法。
  • DOM3 验证模块 (DOM Validation): 定义了难文档的方法
  • DOM3 核心的扩展 (DOM Style): 支持 XML 1.0规范,涉及 XML Infoset、XPath 和 XML Base
  • DOM3 事件模块在 DOM2 事件模块上重新定义了这些事件

DOM2 和 DOM3 都不同程度对 XML 命名空间有扩展,比较少使用到就不展开讨论了。

DOM 接口

DOM 模型的接口有30多个,但常用的只有4~5个

Document

Document接口是对文档进行操作的入口,它是从Node接口继承过来的。

Node

  Node接口是其他大多数接口的父类。在DOM树中,Node接口代表了树中的一个节点。

NodeList

  NodeList接口是一个节点的集合,它包含了某个节点中的所有子节点。它提供了对节点集合的抽象定义,并不包含如何实现这个节点集的定义。NodeList用于表示有顺序关系的一组节点,比如某个节点的子节点序列。在DOM中,NodeList的对象是live的,对文档的改变,会直接反映到相关的NodeList对象中。

NamedNodeMap

  NamedNodeMap接口也是一个节点的集合,通过该接口,可以建立节点名和节点之间的一一映射关系,从而利用节点名可以直接访问特定的节点,这个接口主要用在属性节点的表示上。尽管NamedNodeMap所包含的节点可以通过索引来进行访问,但是这只是提供了一种枚举方法,NamedNodeMap所包含的节点集中节点是无序的。与NodeList相同,在DOM中,NamedNodeMap对象也是live的。

DOM 事件

DOM 事件流

JavaScript 和 HTML 之间的交互是通过事件实现的。最早的 DOM 事件是在 DOM2开始标准化的。

  • DOM2 实现了浏览器事件的基础标准,但没有涵盖所有事件类型。

  • DOM3 增加了 DOM事件 API,增加了包含 BOM 的事件操作。

在事件当中,有一个事件流的概念像下图。

  • IE 事件冒泡

IE的事件流叫做 事件冒泡(event bubbling),即触发事件开始时由最里面的元素接收,之后逐级向上传播到最上层节点。像上图从 6 传到 10。

  • Netscap 事件捕获

Netscape 团队提出的另一种叫做 事件捕获(event capturing),即角发事件开始从元素的最顶层父节点,一层层向下捕获,直到找到该解发事件的该元素。即上图从 1 捕获到 5。

由于老板本的不支持,因此很相对少人使用事件捕获,一般使用事件冒泡。

DOM2级事件流 包括三个阶段: 事件捕获阶段、处于目标阶段和事件冒泡阶段。

事件处理程序

事件处理程序包括事件程序的名字和事件处理程序。

// 给元素 input 绑定 onclick 事件
<input type="button" value="Click Me" onclick="showMessage(event, this)">
<script type="text/javascript">
    function showMessage(event, input) { // 定义事件处理程序
        console.log('Hello world!')
        console.log(event) // 事件对象
        console.log(input) // 解发事件的元素本身,可以通过元素本身获取属性。
    }
</script>

这样定义处理程序,会创建一个封装着元素属性的函数,函数中会有一个局部变量事件对象 eventthis 是指向当前事件的目标元素。不过这样定义 JavaScript 事件也是有缺点的,就是 HTML 和 JavaScript 代码耦合度很高,如果要更改代码就要更改 HTML 代码和 JavaScript 代码。

DOM0 级事件处理程序

比事件绑定好的原因是,耦合度低,跨浏览器,脚本就能实现。缺点是:必须要完成加载 JS 脚本,才会有事件效果,不然未加载完成是触发不了事件。

    const btn = document.getElementById('myBtn')
    btn.onclick = function() {  // 给 btn 添加 onclick 事件
        console.log('Clicked')
        console.log(this.id) // 'myBtn'
    }
    btn.onclick = null // 删除onclick 事件

DOM2 级事件处理程序

DOM2 定义了两个方法用于处理指定和删除事件处理程序的操作,所有的 DOM 节点中都包含这两个方法,并接受3个参数。

  • addEventListener()
    const btn = document.getElementById('myBtn')
    btn.addEventListener('click', function() { // 添加 click 事件, 处理事件是匿名函数
        console.log(this.id)
    },false) // false 表示冒泡阶段才调用事件处理程序, true 表示在捕获阶段调用处理程序
    const btn = document.getElementById('myBtn')
    btn.addEventListener('click', function(){ // 添加 click 事件1
        console.log(this.id)
    }, false)
    btn.addEventListener('click', function(){ // 添加 click 事件2
        console.log('Hello world!')
    }, false)

这里添加了两个 click 事件,解发的时候会按添加的顺序触发。

  • removeEventListener()

removeEventListener 中是移除添加的解发事件,但是有一个问题如果是按上面方法,添加匿名函数来触发事件是没办法移除的。像下面例子

    const btn = document.getElmentById('myBtn')
    btn.addEventListener('click', function(){
        console.log(this.id)
    },false)
    btn.removeEventListener('click', function(){ // 没有作用
        console.log(this.id)
    },false)

可以改成实名函数的方式

    const btn = document.getElementById('myBtn')
    const handler = function(){ // handler 事件处理
        console.log(this.id)
    }
    btn.removeEventListener('click', handler, false) // work

如果不是特别需要,我们不建议在事件捕获阶段注册事件处理程序。

IE 事件处理程序

  • attachEvent()
  • detachEvent()

attachEventdetachEventaddEventListenerremoveEventListener 使用上很像,不过要注意一点 attachEvent 上的 this 指向的是 window,addEventListener 指向上的是元素本身。

DOM 中的事件对象 event

在 DOM0 或者 DOM2级中,绑定事件都会传入 event 对象。

    // DOM2
    const btn = document.getElementById('myBtn')
    btn.onclick = function(event) {
        console.log(event.type) // 'click'
    }
    btn.addEventListener('click', function(event) {
        console.log(event.type) // 'click'
    }, false)

    // DOM0
    const handleOnClick = function(event) {
        console.log(event.type)
    }
    <input type="button" value="Click Me" onclick="handleOnCLick()" />

event 几个重要的属性与方法

  • event.eventPhase 属性判断调事事件处于那个阶段 1 表示捕获阶段 2表示'处于目标'阶段 3 表示冒泡阶段

  • event.preventDefault() 取消事件默认行为。

  • event.stopImmediatePropagation() 取消事件的进一步捕获或冒泡,同时阻止任何DOM3事件处理程序被调用

  • event.stopPropgation() 取消事件的进一步捕获或冒泡

  • event.target 属性表示为当前事件的触发目标元素。

  • event.currentTarget 属性为当前事件的注册元素。

  • event.type 被触发的事件类型

  • event.cancelable 表示是否可以取消事件的默认行为,如果为 true 则可以取消, false 则没有默认动作,或者不能阻止默认动作。

关于 event.currentTargetevent.targetthis 的区别。

    document.body.onclick = function(event) {
        console.log(event.currentTarget) // 注册事件的元素
        console.log(this) // 注册事件的元素
        console.log(event.target) // 触发事件的元素
    }

关于 event.preventDefault()event.stopPropgationreturn false 很容易分不清除区别

  • event.preventDefault() 是取消阻止默认动作,比如表单事件的 submit。
  • event.stopPropgation 是阻止继续冒泡或者阻止继续捕获。
  • return false 是上面两个都会执行,还可以返回对象,跳出循环。

IE 中的事件对象

在 IE中 event 对象会作为 window 对象的一个属性存在。

    const btn = document.getElementById('myBtn')
    btn.onclick = function() {
        const event = window.event
        console.log(event.type) // 'click'
    }
  • IE 的 returnValue 属性和 DOM 中的preventDefault() 作用相同,默认为true,设为 false 就可以取消事件的默认行为。

  • IE 的 event.cancelBubble 属性和 DOM 中的 stopPropagation() 作用相同。默认为 false, 设为 true 就可以取消事件冒泡。

事件类型

DOM 里面有很多种类的事件类型,而 DOM3 中重新定义了事件模块。

  • UI(User Interface, 用户界面)事件,当用户与页面上的元素交互时触发。

    • load:当页面完全加载后触发。
    • abort:用户停止下载过程时触发。
    • unload:当页面完全卸载后在 window 上面触发。
    • resize: 当窗口大小变化时在 window 上面触发。
    • scroll: 当用户滚动带条内容时在元素上面触发。
  • 焦点事件,当元素获得或失去焦点时触发。

    • blur: 在元素失去焦点时触发,(事件不冒泡
    • focus:在元素获得售楼 点时触发,(事件不冒泡
  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发。

    • click: 在用户单击鼠标按钮时,或者按下回车键时触发。
    • dblclick:在用户双击鼠标按钮时触发。
    • mousedown:在用户按下了任意鼠标按钮时触发。
    • mouseenter:在鼠标从元素外部首次移动到元素范围内时触发,移动到后代元素上不触发。(事件不冒泡
    • mouseleave: 在元素上方的鼠标光标移动到元素范围之外时触发,移动到后代元素上不触发。(事件不冒泡
    • mousemove:鼠标在元素内部移动时重复地触发。
    • mouseout:鼠标指针位于一个元素的上方,然后移入另一个元素时触发。
    • mouseover:鼠标在元素外部首次移入另一个元素边界内时触发。
    • mouseup:在用户释放鼠标按钮时触发。
  • 滚轮事件,当使用鼠标滚轮或设备时触发。

    • mousewheel: 当用户通过滚轮与页面交互就会触发。
  • 文本事件&键盘事件,当在文档中输入文本时触发,当用户通过键盘在页面上执行操作时触发。

    • keydown:当用户按下键盘上的任意键时触发。
    • keypress:当用户按下键盘上的字twfy键时触发。
    • keyup: 当用户释放键盘上的键时触发。
    • textInput:当用户在可编辑区域中输入字符时,就会触发这个事件。(DOM3)
  • 复合事件,当为 IME(Input Method Editor,输入法编辑器)输入字符时触发.

    • compositionupdate: 在向输入字段中插入新字符时触发。
    • compositionend: 在 IME 的文本复合系统关闭时触发,表示返回正常键盘输入状态。
  • 变动事件,当底层 DOM 结构发生变化时触发。

    • DOMSubtreeModified:在DOM结构中发生任何变化时触发。这个事件在其他任何事件触发后都会触发。
    • DOMNodeInserted: 在一个节点作为子节点被插入到另一个节点中时触发。
    • DOMNODERemoved: 在节点从其父节点中被移除时触发。
    • DOMNodeInsertedIntoDocument: 在一个节点被直接插入文档或通过子树间接插入文档之后触发。
    • DOMNodeRemovedFormDocument: 在一个节点被直接从文档中移除或通过子树间接从文档中移除之前触发。
    • DOMAttrModified:在特性被修改之后触发。
    • DOMCharacterDataModified:在文本节点的值发生变化时触发。
  • HTML5 事件,DOM规范里没有涵盖所有浏览器支持的所有事件。

    • contextmenu 事件,右键调出上下文,触发事件。
    • beforeunload 事件,在页面卸载前触发该事件。
    • DOMContentLoaded 事件,会在页面一切都加载完毕时触发,在形成完整的 DOM树之后就会触发,不理会图像,JavaScript CSS 其它资源文件。
    • readystatechange 事件,是用来提供与文档或元素的加载状态有关信息。
    • hashchange 事件,在 URL 的参数发生变化时通知开发人员。

事件如何管理内存和性能

过多的事件绑定会存在内存性能问题,如何管理内存和性能就变成是一个学术上的问题了。怎样解决呢?

事件季托

在多个需要事件处理的元素上层只注册一个处理事件,利用冒泡上传到父类组件时再处理。

    <ul id="myLinks">
        <li id="goSomewhere">Go somewhere</li>
        <li id="doSomething">Do something</li>
        <li id="sayHi">Say hi</li>
    </ul>

    const list = document.getElmenetById('myLinks')
    list.addEventListener('click', function(event) {
        let event = event || window.event
        const target = event.target
        switch(target.id) {
            case 'doSomething':
                document.title = 'I changed the document\'s title'
                break
            case 'goSomewhere':
                location.href = 'http://www.wrox.com'
                break
            case 'sayHi':
                console.log('hi')
                break
        }
    },false)

移除事件处理程序

在错误移除事件处理程序,就会产生 空事件处理程序(dangling event handler),在残留在内存中,对内存性能造成影响。

对将会被removeChild()replaceChild()innerHTML移除或删除的元素,提前做好删除事件。

    <div id="myDiv">
        <input type="button" value="Click Me" id="myBtn">
    </div>
    <script type="text/javascript">
        const btn = document.getElementById('myBtn')
        btn.onclick = function() {
            btn.onclick = null // 先移除事件处理程序
            document.getElementById('myDiv').innerHTML = 'Processing...' // 再对组件进行操作
        }
    </script>

另一种空事件处理程序情况发生在页面卸载,在页面卸载之前也最好调用 onunload 事件进行处理。

模拟 DOM 事件

DOM 事件一般是由 DOM 元素组件触发的,但是在某些时候也能通过 JavaScript 来触发特定的事件。

在触发事件之前我们先要创建这个事件对象 event。创建 event,可以在 document 对象上使用 createEvent() 来创建 event 对象,createEvent() 需要传事件字符参数。在 DOM3 中传参的字符串有所更改 DOM3 是单数形式。

事件描述 DOM2 DOM3
一般化UI事件,在DOM3级中把鼠标和键盘都继承自UI事件。 UIEvents UIEvent
一般化的鼠标事件 MouseEvents MouseEvent
一般化的 DOM 变动事件 MutationEvents MutationEvent
一般化的 HTML 事件 HTMLEvents 没有对应的DOM3级事件,被分散到其他类别中
    const btn = document.getElementById('myBtn')
    // 创建事件对象
    const event = document.createEvent('MouseEvents')
    // 初始化事件对象, 传入模拟事件的参数
    event.initMouseEvent('click',true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
    // 通过 btn 触发事件
    btn.dispatchEvent(event)

其它类型创建触发也是这样,主要是创建事件对象和初始化事件对象有所不同。在 DOM3 能自定义事件。

    const div = document.getElmentById('myDiv')
    // 创建处理函数
    const myeventHandle = function(event) {
        console.log('DIV: ' + event.detail)
    }
    // 添加处理事件
    div.addEventListener(myevent,myeventHandle,false)
    // 创建自定义事件对象
    const event = document.createEvent('CustomEvent')
    // 初始化自定义对象
    event.initCustomEvent('myevent', true, false, 'Hello world')
    // 传递对象触发事件
    div.dispatchEvent(event)

在 IE 中的模拟事件也有不同,兼容 IE 的时候需要注意了。

    const textbox = document.getElementById('myTextbox')
    // 创建 IE 事件对象
    const event = document.createEventObject()
    // 初始化事件对象
    event.altKey = false
    event.ctrlKey = false
    event.shiftKey = false
    event.keyCode = 65
    // IE 下触发事件
    textbox.fireEvent('onkeypress',event)

关于作用域

JS作用域

作用域

先来看看作用域的定义。

作用域就是变量或函数,可以在整个程序中可以访问和修改的范围,变量或函数的生存周期。

如果访问不到或者使用不到,访问时候为 undefined 值,基本上是超出了变量或函数的作用域了。
在ES5 之前 JS 设计得非常奇怪,变量只有两种 全局变量局部变量 ,即是 全局变量 对应的是 全局作用域局部变量 对应的是 局部作用域 。之所以说 JS 设计得非常奇怪是因为 JS 是没有级块作用域的,而其它语言是有的,所以其它语言转过来学习 JS 的时候会有多种不习惯,级块作用域 是什么意思?就是一般的程序逻辑里面都不能限定变量或函数的范围。这奇怪的情况直到出现了 ES6 才有所改观,有级块作用域 和 级块变量。

  • 全局变量 是定义在函数之外的声明变量是全局变量。
  • 局部变量 是定义在函数之内的声明变量是局部变量。
  • 级块变量 是{...} 是大括号内所声明的变量。

变量声明

变量声明有三种语法方式:

  1. var
// 声明在最全局环境下全局变量
var ab = 'string'  // 全局变量
function foo() {
   var c = 8 //声明在函数里面,局部变量
}
function foo(){
    c = 9 // 如果在函数内声明没带 var 会自动变成全局变量
}

function foo(){
   var a = b = 6 // 这样声明也是不靠谱的
   // 编译器会理解为下面这种方式
   var a // 局部变量
   b = 6 // 全局变量
}

但正如上面所说的 var 声明的变量是没有级块作用域的,

    var color = 'blue'
    if(true) {
        var color = 'red'
    }
    console.log(color) // red if 语句修改了全部变量

经典的问题:

 for(var i =0 ; i < 5; i++) {
    setTimeout(function(){
         console.log(i)
    })
}
// 最后输出的全是 5

解决方法:

  • IIFE 使用立刻即行函数
    for(var i = 0; i < 5; i++) {
        (function(i){ // IIFE 模拟级块作用域
            setTimeout(function(){
                console.log(i)
            })
        })(i)
    }
  • 使用函数作用域
    function foo(num) {
        console.log(num)
    }
    for(var i = 0; i < 5; i++) {
        setTimeout(foo(i))
    }
  1. let [ES6]

let 是 ES6 里面的一个新的声明命令,letvar最大的不同是, let 称名的变量命令在代码块内有效。

    {
        let a = 10
        var b = 5
    }
    console.log(a) // ReferenceError: a is not defined.
    console.log(b) // 5

还有个特点 let 命令,不会变量提升在区域内如果在未声明前会进行暂时性死区调用会报错。

    function foo(){
        console.log(arg) // 锁区报错
        let arg = 'string'
    }
  1. const [ES6]

const 也是 ES6 里面的一个新声明命,和 let 很像,但是不同的是,一但声明这个常量值就不能改变了。但是对象值就不一样了,对象值保存的是内存地址,可以更改内存地址里面的值。

    const math = "3.14"
    math = "3.1425" // 报错
    const obj = {
        name: 'clarence'
    }
    obj.name = 'clarencec'

嵌套的作用域

作用域是局部作用域可以访问全局的作用域的,最里面一层能访问外面一层的,像陶瓷娃娃一样一层一层的。如下图:

fig2

  1. 在函数foo 下 a 能在函数内被访问到。
  2. bac 函数 里面能访问外层 foo函数的作用域变量 a。

减少污染全局作用域的方法

  1. 函数作为作用域
    通过函数来限定函数内变量与方法不污染到全局。
var a = 2
function foo() {
     var a = 3
     console.log( a )//3
}
foo()
console.log(a) //2
  1. 通过立即执行函数(IIFE)
  2. 通过匿名函数
  3. 模块化

JavaScript 基础类型转换

Null Undefined Boolean(true) Boolean(false) Number String Symbol Object "" 0
Boolean False False - - true 1
false 0
true true true False False
Number 0 NaN 1 0 - StringToNumber TypeError 拆箱转换 0 0
String "null" "undefined" "true" "false" NumberToString - "Symbol(***)" 拆箱转换 "" "0"
Object {} {} 装箱转换 装箱转换 装箱转换 装箱转换 装箱转换 - 装箱转换

StringToNumber

  1. 字符串到数字的类型转换,存在一个语法结构,支持十进制、二进制、八进制和十六进制比如:
"30"  "0b111"  "0o13" "0xFF"
  1. 判断是否 NaN ,如果不是 NaN.
  2. 判断是否有科学计数法.
"1e3" "-1e-2"
  1. 判断是否有正负号.

在字符串转换数字的过程中最好使用 parseInt,parseFloat 来转换其它进制。

NumberToString

  1. 判断是否十进制、二进制、八进制和十六进制.
    2.判断符号.
    3.判断科学质法.
    4.参照日常理解.

装箱转换 和 拆箱转换

装箱转换

如果在基础类型直接执行方法时,因为每一种基本类型 Number、String、Boolean、Symbol 对有对应的类,所以装箱时会把基本类型转换为对应的对象类型,再进行方法执行,称为装箱转换。

拆箱转换

  1. 拆箱转换即把对象类型转换为基本类型转换。

在 JavaScript 标准中,规定了 ToPrimitive 函数,它是对象类型到基本类型转换的实现者(即,拆箱转换);但这是一个内部算法,是编程语言在内部执行时遵循的一套规则。

对象到 String 和 Number 的转换都遵循“先拆箱再转换”的规则。通过拆箱转换,把对象变成基本类型,再从基本类型转换为对应的 String 或者 Number。

  1. 在拆箱转换的过程中,在ES6后会检查对象中是否有显式定义的[Symbol.toPrimitive] 方法,如果有直接调用。
  2. 如果没有 ToPrimitive 则判断调用对象的 toStringvalueOf 方法,来转化为 Primitive Value
  3. 由于不同基础类型调用的 toStringvalueOf 顺序不同,具体可以查阅 ECMA 规范。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.