博文整理自饥人谷课程
博文整理自网道(WangDoc.com)
DOM
Document Object Model 文档对象模型
网页其实是一棵树,JS如何操作这棵树,首先JS不能直接操作网页,而是用document操作网页,即浏览器往window上加了document,就可以获取整个网页了。
window.document
获取元素
// 最便捷获取元素的方法
<input type="text" id="kw">
window.kw // 可获取这个 input 元素
kw // 省略 window 也可直接获取这个 input 元素
document.querySelector('div>span:nth-child(2)')
document.querySelectorAll('div>span:nth-child(2)')[0]
// 其他获取元素的方法(兼容IE)
document.getElementById('kw')
document.getElementsByTagName('div')[0] // 所有标签名为 div
document.getElementsByClassName('red')[0] // 所有类名为 red
// 获取特定元素
document.documentElement // 获取 html 元素
document.head // 获取 head 元素
document.body // 获取 body 元素
window.onclick = () => {console.log('hi')} // 获取窗口 添加点击事件
if(document.all){console.log('ie 浏览器')} // 判别 ie 浏览器
else{console.log('其他浏览器')}
document.all // 是第六个 falsy 值
元素的6层原型链
let div = document.getElementsByTagName('div')[9]
console.dir(div) // 打印出 div 的结构
div.__proto__ === HTMLDivElement.prototype
HTMLDivElement.prototype.__proto__ === HTMLElement.prototype
HTMLElement.prototype.__proto__ === Element.prototype
Element.prototype.__proto__ === Node.prototype
Node.prototype.__proto__ === EventTarget.prototype
EventTarget.prototype__proto__ === Object.prototype
Node.nodeType // 区分不同类型的节点
// 节点Node包括:元素、文本、注释、文档、文档片段
节点的增删改查
// 增
// 创建的标签默认处于 JS线程 中
let div1 = document.createElement('div') // 创建一个 标签 节点
let text1 = document.createTextNode('你好') // 创建一个 文本 节点
div1.appendChild(text1) // 标签里插入文本 方法1
div1.innerText = '你好' // 标签里插入文本 方法2
div1.textContent = '你好' // 标签里插入文本 方法3
xx div1.appendChild('你好') // 不可以用
// 节点插入到 页面 中
document.body.appendChild(div)
已在页面中的元素.appendChild(div)
// 删
parentNode.removeChild(childNode)
childNode.remove()
// 改
div.className = 'red blue' // 改 class
div.classList.add('red')
div2.setAttribute('data-x', 'test') // 设置 div2 的data-x属性值为test
div2.getAttribute('data-x') // test
div2.dataset.x // test
div.innerText = 'xxx' // 改文本内容
div.textContent = 'xxx' // 改文本内容
// 改事件处理函数
div.onclick = null // 默认为空
div.onclick = fn
// 点击div时,浏览器调用fn.call(div, event)
// div 会被当做 this ;event包含了点击事件的所有信息
div.addEventListener // onclick 升级版
// 查
node.parentNode // 查爸爸
node.parentElement // 查爸爸
node.parentNode.parentNode // 查爷爷
node.childNodes // 查子代
node.parentNode.childNodes // 查兄弟姐妹
DOM跨线程操作
| 浏览器功能 | 内容 | 备注 |
|---|---|---|
| 渲染引擎 | 渲染HTML和CSS 不能操作JS 只能操作页面 | 线程1 |
| JS引擎 | 执行JS 不能操作页面 | 线程2 |
document.body.appendChild(div1)
// 理论上只能添加到内存中,JS 是如何添加到页面中的?
跨线程通信
当浏览器发现 JS 在body里面加了div1对象
浏览器就会通知渲染引擎在页面里也新增一个div1元素
新增的div元素所有属性都照抄div1对象
对象风格 封装DOM操作
创建window.dom全局对象
// index.html
<body>
<div id="test"></div>
<script src="dom.js"></script>
<script src="main.js"></script>
</body>
// dom.js 封装dom
window.dom = {
create(string){
const container = document.createElement('template');
container.innnerHTML = string.trim();
return container.content.firstChild;
},
after(node, node2){
node.parentNode.insertBefore(node2, node.nextSibling);
}
};
// 增
const div = dom.create(`<div>newDiv</div>`) //创建节点
dom.after(node, node2); // 用于新增弟弟
dom.append(parent, child) // 用于新增儿子
// 删
dom.remove(node) // 删除节点
// 改
dom.attr(node, 'title', ?) // 用于读写属性
dom.text(node, ?) // 用于读写文本内容
dom.on(node, 'click', fn) // 用于添加事件监听
// 查
dom.find('选择器') // 用于获取标签或标签们
dom.siblings(node) // 用于获取兄弟姐妹元素
dom.each(nodes, fn) // 用于遍历所有节点
链式风格 封装DOM操作
jQuery于2006年发布,是目前最长寿的库,也是世界上使用最广泛的库,全球80%网站在用
jQuery函数实现过程
// index.html
<body>
<div class="test">你好</div>
<script src="jquery.js"></script>
<script src="main.js"></script>
</body>
一、api做为中间对象
jQuery核心代码:根据接收的选择器,得到一些元素,但它却不返回这些元素。相反,它返回一个对象api,这个api对象有方法可以操作元素。
// jquery.js
window.jQuery = function(selector){ // window.jQuery() 是全局函数
const elements = document.querySelectorAll(selector)
// api 对象可以操作 elements
const api = {
"addClass":function(){ // api对象的 key 为addClass、value 为函数,简写为下一行
addClass(className){ // 闭包: addClass函数访问外部变量 elements
for(let i=0; i<elements.length; i++){ // 拿到className 遍历elements
elements[i].classList.add(className) // 每个element都添加className
}
return api // 这是 addClass函数的 return
}
}
return api // 这是 jQuery函数的 return
}
链式操作:用api对象调用addClass函数,这个函数返回api,于是就可以继续调用addClass函数,以上的操作是基于 addClass函数定义里面 return的是api对象。
// main.js
// jQuery是全局变量,省略window.
const api = jQuery(.test) // 先获取到 .test 对应的所有元素,jQuery 不返回元素们,而返回 api 对象
api.addClass('red') // 遍历所有刚才获取的元素,添加 .red 再添加 .blue
// addClass()函数的返回值为 api 也就可以继续 .addClass()函数
api.addClass('red').addClass('blue') // 链式操作
// 输出结果为 class为test的元素属性 都添加上了 <div class="test red blue">
二、删掉 api
函数如果用对象来调用,那么这个函数里的this就是调用这个函数的对象。
obj.fn(p1) // 函数里的 this 就是 obj
// 等价于
obj.fn.call(obj, p1)
也就是说步骤一里的addClass里面的this就是api
const api = jQuery(.test)
api.addClass('red') // this 就是 api
既然如此,就可以return this 而不 return api,这样做的目的是删掉根本没用到的中间变量api,简化步骤一中的如下代码:
// jquery.js
window.jQuery = function(selector){
const elements = document.querySelectorAll(selector)
// api 对象可以操作 elements
const api = {
addClass(className){
for(let i=0; i<elements.length; i++){
elements[i].classList.add(className)
}
return this // 原来的 api 改为 this
}
}
return api // 这是 jQuery函数的 return
}
再进一步的,声明了api,又return了这个对象,就可以把根本没用到的api删掉,直接return这个不具名的对象:
// jquery.js
window.jQuery = function(selector){
const elements = document.querySelectorAll(selector)
// api 对象可以操作 elements
return {
addClass(className){
for(let i=0; i<elements.length; i++){
elements[i].classList.add(className)
}
return this // 原来的 api 改为 this
}
}
}
使用jQuery时,就改为:
jQuery('test').addClass('red')
.addClass('blue')
.addClass('green')
jQuery对象
jQuery是一个不需要加new的构造函数,jQuery对象(api)代指jQuery函数构造出来的对象,
Object对象 表示 Object 构造出的对象
Array数组对象 表示 Array 构造出的对象
Function函数对象 表示 Function 构造出来的对象
实现find函数
// index.html
<body>
<div class="test1">
你好1
<div class="child">child1</child>
<div class="child">child2</child>
<div class="child">child3</child>
</div>
<script src="jquery.js"></script>
<script src="main.js"></script>
</body>
// jquery.js
window.jQuery = function(selector){
const elements = document.querySelectorAll(selector)
// api 对象可以操作 elements
return {
addClass(className){
for(let i=0; i<elements.length; i++){
elements[i].classList.add(className)
}
return this // 原来的 api 改为 this
},
find(selector){
let array = [] // 新数组储存新查找的元素
for(let i=0; i<elements.length; i++){
const elements2 = elements[i].querySelectorAll(selector)
}
}
}
}
const x1 = jQuery('.test1').find('.child')
console.log(x1)
事件
事件的本质是程序各个组成部分之间的一种通信方式,DOM支持大量的事件。DOM节点的事件操作(监听和触发),都定义在EventTarget接口。
JS不支持事件,而是DOM事件,只是浏览器的功能,不是JS功能,JS只是调用了DOM提供的addEventListener而已。
EventTarget接口
所有节点对象也都部署了这个接口,还有一些需要事件通信的浏览器内置对象也部署了这个接口。比如:小黄人XMLHttpRequest
EventTarget接口提供三个实例方法:
| 实例方法 | 内容 | 备注 |
|---|---|---|
| addEventListener() | 绑定事件的监听函数 | 事件绑定API |
| removeEventListener() | 移除事件的监听函数 | |
| dispatchEvent() | 触发事件 |
监听函数
浏览器的事件模型:就是通过监听函数(listener)对事件做出反应。事件发生后,浏览器监听到了这个事件,就会执行对应的监听函数。这是事件驱动编程模式的主要编程方式。
JS有三种为事件绑定监听函数的方法:
| 方法 | 内容 | 备注 |
|---|---|---|
| HTML 的 on- 属性 (不推荐使用) | 元素的事件监听属性 onclick 表示click事件的监听代码 | 属性的值为将会执行的代码 使用这个方法指定的监听代码,只会在冒泡阶段触发 |
| 元素节点的事件属性 (不推荐使用) | 属性值为函数名 | 使用这个方法指定的监听函数,也是只会在冒泡阶段触发 |
| addEventListener() (推荐使用) | 用来为该节点定义事件的监听函数 | 所有DOM节点实例和其他对象window、XMLHttpRequest都有此方法 |
// on- 属性
<div onclick="console.log(2)">
<button onclick="console.log(1)">点击</button>
</div>
// 点击结果是先输出1,再输出2,即事件从子元素开始冒泡到父元素
<Element onclick="doSomething()">
// 属性的值是将会执行的代码,而不是一个函数
// 等价于
el.setAttribute('onclick', 'doSomething()');
// 元素节点的事件属性
window.onload = doSomething;
div.onclick = function (event) {
console.log('触发事件');
};
// 在当前节点或对象上定义特定事件的监听函数,事件发生就执行
EventTarget.addEventListener('load', doSomething, false)
// 接受3个参数,'load'事件名称,'doSomething'监听函数,默认为 false
// 如果设为 true ,表示监听函数将在捕获阶段(capture)触发
事件传播
一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。
- 第一阶段:从
window对象传导到目标节点(上层传到底层),称为“捕获阶段”(capture phase)。 - 第二阶段:在目标节点上触发,称为“目标阶段”(target phase)。
- 第三阶段:从目标节点传导回
window对象(从底层传回上层),称为“冒泡阶段”(bubbling phase)。
这种三阶段的传播模型,使得同一个事件会在多个节点上触发。
事件委托
也叫事件代理。由于事件会在冒泡阶段向上传播到父节点,因此可以把子节点的监听函数定义在父节点上,由父节点的监听函数统一处理多个子元素的事件,这种方法叫做事件的委托
Event对象
事件发生以后,会产生一个事件对象,作为参数传给监听函数。浏览器原生提供一个Event对象,所有的事件都是这个对象的实例,或者说继承了Event.prototype对象。
Event对象本身就是一个构造函数,可以用来生成新的实例。
事件发生以后,会经过捕获和冒泡两个阶段,依次通过多个 DOM 节点。因此,任意事件都有两个与事件相关的节点,一个是事件的原始触发节点(Event.target),另一个是事件当前正在通过的节点(Event.currentTarget)。前者通常是后者的后代节点。
| 标题 | 内容 | 备注 |
|---|---|---|
| Event.currentTarget属性 (程序员监听的元素) | 返回事件当前所在的节点 | 随着事件传播,该属性值会变 |
| Event.target属性 (用户操作的元素) | 返回原始触发事件的节点 | 属性不会随事件传播而改变 |
<div><span>文字</span></div>
// 用户点击文字,e.target就是span e.currentTarget就是div