前言
前端的发展史中,我们逻辑语言,曾经一度以“js”(javascript )称呼。后来,随着前端的发展以及单页面架构到来,前端的逻辑逐渐以“es”(ECMAScript)称呼。
那么,js跟es(或es6)之间,有什么关系呢?答案是:js = es + bom + dom。
我们的文本节点之类,包括nodeType,nodeList,childNodes都属于dom的范畴。 可能你会觉得nodeType,nodeList,childNodes等知识点,他并不是那么重要。日常工作中几乎没用到过。笔者就在学习vue源码,虚拟dom转换成html时候遇到一些问题,都是通过本文的要点去解决的,还是需要能深刻分析。
笔者之前已经汇总了es6差不多2W字的总结,本文再对曾经js的一部分,bom跟dom进行详细的总结。
概念
我们先来看看他们的概念:
DOM:文档对象模型(Document Object Model,简称DOM),描述了处理网页内容的方法和接口。最根本对象是document(window.document)。
BOM:浏览器对象模型(Browser Object Model),描述了与浏览器进行交互的方法和接口。由navigator、history、screen、location、window五个对象组成的,最根本对象是window。
字面意思相对好理解。如果还是对概念模棱两可,好吧,这就是我写汇总的意义,汇总成自己方便理解的语言,也许我的理解有利于你对知识点的加深。
DOM就是我们日常对Html所有文本节点,元素,属性等操作,访问,修改等,都在dom的范畴。 其中,我们日常用的HTML DOM,他包括HTML的标准对象模型,W3C 标准等规范。
而BOM,就是跟浏览器有关的相关是属性,如浏览器的窗口呀,浏览器的页面跳转等。
看到这里,入行不深的小伙伴,可能都觉得非常简单。其实不然,很有学问。我们继续往下看。
知识点
节点
首先我们谈谈节点。
HTML文档中的所有内容都是节点,比如body之间就是一个节点,div与/div之间也是一个节点,甚至注释也算是注释节点。 一般在最外层,所以html也称根节点。
那么可见一个常规页面的节点是非常多的,而且他们还各自有交互关系,比如子父节点,兄弟节点,那么WC3定义如何来访问,修改他们呢。下面列举常用方法:
-
getElementById() 返回带有指定 ID 的元素。
-
getElementsByTagName() 返回包含带有指定标签名称的所有元素的节点列表(集合/节点数组)。
-
getElementsByClassName() 返回包含带有指定类名的所有元素的节点列表。
-
appendChild() 把新的子节点添加到指定节点。
-
removeChild() 删除子节点。
-
replaceChild() 替换子节点。
-
insertBefore() 在指定的子节点前面插入新的子节点。
-
createAttribute() 创建属性节点。
-
createElement() 创建元素节点。
-
createTextNode() 创建文本节点。
-
getAttribute() 返回指定的属性值。
-
setAttribute() 把指定属性设置或修改为指定的值。
-
innerHTML - 节点(元素)的文本值
-
parentNode - 节点(元素)的父节点
-
childNodes - 节点(元素)的子节点
-
attributes - 节点(元素)的属性节点
这是关于最简单的访问或者修改的方法。
那么节点本身包含了什么呢
这里包含三个内容。nodeType、nodeName、nodeValue。
首先,nodeType,是属性返回节点的类型。nodeType 是只读的。我们列举一下文本的类型。
元素类型 | NodeType |
---|---|
元素 | 1 |
属性 | 2 |
文本 | 3 |
注释 | 8 |
文档 | 9 |
需要普及一下,这里每个节点都有一个childNodes属性,里边是一个NodeList对象(类数组),它是基于DOM结构动态执行查询的结果,因此 DOM 结构的变化能够自动反映在NodeList对象中。
同样每个节点还有一个children的属性,它与childNodes的区别就是,childNodes存储的是NodeList对象,而children返回的是HTMLCollection对象。NodeList存储的不只是元素节点,也有文本节点、注释节点等等,而HTMLCollection存储的只有元素节点。
我们通过案例来分析:
<div id="test">
hello <span>word</span>
</div>
<script>
let btn = document.getElementById("test");
console.log(btn.childNodes, btn.children)
//NodeList(3) [text, span, text]
//HTMLCollection(1) [span]
</script>
复制代码
还有一点需要普及的是,无论是children还是childNodes,他都不是真正意义的数组(关于数组,笔者ES6的总结array中已经详细介绍过,本文不再详写),是个类数组。
如果需要转换成数组,我们只能把获取到的NodeList或者HTMLCollection手动转换成数组。
我们NodeList还是HTMLCollection都不是数组,如果我们需要转化为数组,我们可以使用扩展运算符来操作:
const array = [...NodeList]
const array2 = [...HTMLCollection]
复制代码
介绍到这里,你能想象一下vue的虚拟dom是怎么操作的吗?简单的来说,他就是利用object对象去模拟NodeList。
属性
属性名nodeName
属性名并不只是存在与属性节点,任何节点都有属性名。nodeName 属性规定节点的名称。他们的属性名如下:
- 元素节点的 nodeName 与标签名相同
- 属性节点的 nodeName 与属性名相同
- 文本节点的 nodeName 始终是 #text
- 文档节点的 nodeName 始终是 #document
属性值nodeValue
nodeValue 属性规定节点的值,也就是每个属性名,就有会他对应的属性值。
- 元素节点的 nodeValue 是 undefined 或 null
- 文本节点的 nodeValue 是文本本身
- 属性节点的 nodeValue 是属性值
属性节点
我们再来看一下属性节点。什么是属性?我们元素中的内置对象,就是属性节点。比如style,class, id,都称为元素的属性节点。
- Element.getAttributeNames();//获取属性名
- Element.getAttribute("class");//获取class的属性值
- Element.attributes.item(0);//获取元素第一个属性键值对
- Element.hasAttribute("class");//判断是否有class属性
- Element.setAttribute("class", "active");//设置class属性
比如我们需要重置元素class的值,我们就可以通过setAttribute重新给class赋值。 当然还有一个内置class对象可以操作:Element.classList.add("active", "show")
介绍到这里,你能想象一下vue的的v-if或者v-on是怎么实现的吗?简单的来说,他就是把他当成一个元素属性,获取到对应的标识,再去解析。
事件
再普及一下dom的事件,我们日常用到的点击,鼠标等事件,就是dom事件。onclick,onchange,onmousedown, onmouseup,onfocus等等,他都是一个dom事件。
这里需要给大家普及一下关于dom事件的一些冷知识点或者细节:
-
js如果定制了onclick事件,会直接覆盖掉原来标签的onclick事件。如下边栗子,test就失效了。
<div id="btn" onclick="test(this)"> 按钮 </div> let btn = document.getElementById("btn"); btn.onclick = function (e) { alert("2"); } function test(){ alert("1"); } 复制代码
2.onclick的触发来自监听本身,而监听事件addEventListener还需要检测事件的回流,所以优先级永远都是:onclick>addEventListener, 如下边栗子:
<div id="btn" onclick="test(this)"> 按钮 </div>
<script>
let btn = document.getElementById("btn");
btn.onclick = function (e) {
alert("1");
}
btn.addEventListener("click", function (e) {
alert("2");
})
</script>
复制代码
执行1再执行2.
3.获取的对象中,currentTarget表示当前对象,而target表示事件的对象。 如下边栗子:
let btn_box = document.getElementById("btn_box");
btn_box.addEventListener("click", function (e) {
consloe.log( e.currentTarget);
consloe.log( e.target);
})
复制代码
点击按钮0, currentTarget对象指的是btn_click_0,而target对象是btn_box
自定义事件
除了官方自带的click,focus等事件可以监听之外,我们还可以自定义事件进行监听。举个栗子:
// createEvent创建事件
let myEvent = document.createEvent(‘Event‘);
// 定义事件名为‘build‘.
myEvent.initEvent(‘‘, true, true);
//eventType:事件名称
//canBubble:是否支持冒泡
//cancelable:是否可以用 preventDefault() 方法取消事件。
new3Event.name = "document.createEvent创建的自定义事件"
// 如果需要监听事件
document.addEventListener(‘myEventName‘, function (e) {
// e.target matches elem
alert(e.name)
}, false);
// 触发对象可以是任何元素或其他事件目标
document.dispatchEvent(myEvent);
复制代码
这是最简单的创建”自定义事件“的方法,document.createEvent。
如果你还需要自定义传参数的话,可以使用CustomEvent或者Event.这里不做详细介绍。
看完上边的demo,我们貌似知道了大概的用法。但是他的带来的好处是什么?
自定义事件的优缺点
优缺点有点类似传统的监听事件。其优点就是,各模块之间低耦合,互不影响。
缺点的话,不好定位问题,容易导致诡秘的错误。大项目出错时,连入口都很难定位在哪里。
BOM
接下来谈谈bom的知识点。
bom的知识点,相对的话只是知识面的了解。因为属于应用开发范畴(浏览器开发),我们只需要对他的知识面有所了解,建议不深入。
该部分,如有面试或者日常也不会挖深,只考虑是否了解知识点
他可以由下边6部分组成:
Location
下边为Location的属性
属性 | 描述 | 栗子 |
---|---|---|
hash | 获取锚点,简单来说就是url的#后边 | #detail?a=1 |
host | url的端口 + 端口 | www.baidu.com:8080 |
hostname | 主机路径 | www.baidu.com |
href | 完整url | www.baidu.com?a=1 |
pathname | 返回当前 URL 的路径部分 | /index.html |
port | 端口 | 8080 |
protocol | 协议 | http或htttps |
search | 协议 | 获取参数,简单来说就是url的?后边 |
下边为Location的方法
方法 | 描述 |
---|---|
assign | 加载新的文档 |
reload | 重新刷新页面 |
replace | 用新的文档替换当前文档 |
Navigator
属性 | 说明 |
---|---|
appCodeName | 返回浏览器的代码名 |
appName | 返回浏览器的名称 |
appVersion | 返回浏览器的平台和版本信息 |
cookieEnabled | 返回指明浏览器中是否启用 cookie 的布尔值 |
platform | 返回运行浏览器的操作系统平台 |
userAgent | 返回由客户机发送服务器的user-agent 头部的值 |
Screen
属性 | 说明 |
---|---|
availHeight | 返回屏幕的高度(不包括Windows任务栏) |
availWidth | 返回屏幕的宽度(不包括Windows任务栏) |
colorDepth | 返回目标设备或缓冲器上的调色板的比特深度 |
height | 返回屏幕的总高度 |
pixelDepth | 返回屏幕的颜色分辨率(每象素的位数) |
width | 返回屏幕的总宽度 |
History
属性 | 说明 |
---|---|
back | 返回上一页 |
forward | 返回下一页 |
go | 加载 history 列表中的某个具体页面 |
结语
原文转载:https://juejin.im/post/5efef0a3e51d4534640ea205