【JS编程接口】DOM编程

447 阅读6分钟

一、网页其实是一棵树

二、JS用document操作网页

html内容由渲染引擎来渲染到页面上,而JS引擎负责的是JS内容,是操作不了文档内容的,咋办? 于是让浏览器来提供办法。 浏览器往window上加一个document对象,这时JS就可以用document来操作网页了,这就是document object model,即文档对象模型(把网页抽象成对象,然后来操作它)

三、获取元素,也叫标签

获取任意元素

1.偷偷用的最简便的方法

  • window.id
  • id
  • 但是如果id为保留字,比如parent,就不能用了,只能用下面的

2.兼容IE才会用的方法

  • document.getElementByld('id')
  • document.getElementsByTagName('标签名')[0](因为得到的是所有的这个标签,所以是个数组,你要自己选择需要该数组中的第几个)
  • document.getElementsByClassName('class名')[0](同上)

3.工作中的用的方法

  • doucment.qureySelector('#idxxx') 80%情况下css选择器咋写就咋写
  • doucment.qureySelectorALL('.red')[0]

获取特定元素

  • 获取html元素:document.documentElement
  • 获取head元素:document.head
  • 获取body元素:document.body
  • 获取窗口(窗口不是元素):window
  • 获取所有元素:document.all( 它是第六个false值)

四、元素是个对象--看看一个div对象的原型链

console.dir(div1)看原型链

  • 自身属性: classNameidstyle 等等
  • 第一层原型HTMLDivElement.prototype: 这里面是所有div共有的属性,不用细看
  • 第二层原型HTMLElement.prototype: 这里面是所有HTML标签共有的属性,不用细看
  • 第三层原型Element.prototype: 这里面是所有XMLHTML 标签的共有属性,你不会以为浏览器 只能展示HTML
  • 第四层原型Node.prototype: 这里面是所有节点共有的属性,节点包括XML标签文本注释、 HTML标签文本注释等等
  • 第五层原型EventTarget.prototype: 里面最重要的函数属性是addEventListener
  • 最后一层原型就是Object.prototype

五、节点的增删改查

1.创建一个标签节点

  • document.createElement('标签名')
  • let div1 = document.createElement('div')
  • 你创建的标签默认处于JS线程中,内存中

2.创建一个文本节点

  • text1 = document.createTextNode("你好")

3.标签里面插入文本

  • div1. appendChild(text1) 必须加文本节点的名字,不可以加文本的内容
  • div1.innerText='你好' (Node原型提供)
  • div1.textContent ='你好'(Element原型提供)

4.插入页面中

  • 你创建的标签默认处于JS线程中
  • 你必须把它插到head或者body里面,它才会生效
  • document.body.appendChild(div1)
  • 或者已在页面中的元素.appendChild(div1)

5.一个元素不能同时出现在两个地方

页面中有div#test1和 div#test2

let di = document.createElement('div')
test1.appendChild(div)

test2.appendChild(div)

问:test最终会出现在哪里?

  1. 出现在div1里面
  2. 出现在div2里面
  3. 同时出现在div1和div2里面

答:div2里面,因为一个元素不能同时出现在两个地方,除非复制一份

旧方法

  • parentNode.childChild(childNode)(找到它的父亲,再让父亲删掉他)

新方法

  • childNode.remove()
  • 如果一个node被移出页面(DOM树),那么它还可以再次回到页面中(appendChild())。因为他只是从页面中被删除了,他还在js引擎线程中

改标准属性

改class

  • div.className= 'red blue' (全覆盖)
  • div.className += 'black' (增加)
  • div.classList.add('black')(增加) 改style
  • div.style = 'width: 100px; color: blue;'(全覆盖)
  • 改style的一部分: div.style.width = '200px'
  • 大小写: div.style.backgroundColor = 'white' *改data-属性:
  • div.dataset.x = 'frank'(没人用了)
//在html里给元素加上属性data-xxx='yyy'
<div id="day2-meal-expense" 
  data-drink="coffee" 
  data-food="sushi" 
  data-meal="lunch">¥20.12</div>
//要想获取某个data-*属性的值,可以像下面这样使用dataset对象:
var expenseday2 = document.getElementById('day2-meal-expense');  
var typeOfDrink = expenseday2.dataset.drink; //'coffee'

读标准属性

  • div.classList / a.href(会给你把路径补充完整)
  • div.getAttribute('class') / a.getAttribute('href')(原封不动的给你)
  • 两种方法都可以,但值可能稍微有些不同

改事件处理函数

  • div.onclick默认为null
  • 默认点击div不会有任何事情发生
  • 但是如果你把div.onclick改为一个函数fn,那么点击div的时候,浏览器就会调用这个函数
  • 并且是这样调用的fn.call(div, event)
  • div会被当做this
  • event则包含了点击事件的所有信息,如坐标
  • div.addEventListenerdiv.onclick的升级版

改文本内容

  • div.innerText = 'xxx'
  • div.textContent= 'xxx'
  • `两者几乎没有区别

改HTML内容

  • div.innerHTML = '<strong>重要内容</strong>'

改标签

  • div.innerHTML = ''//先清空
  • div.appendChild(div2) //再加内容

改爸爸

  • 想找个新爸爸,让新爸爸把自己加进去就完事了
  • newParent.appendChild(div)
  • 直接这样就可以了,直接从原来的地方消失

查爸爸

  • node.parentNode或者node.parentElement

查爷爷

  • node.parentNode.parentNode

查子代

  • node.childNodes会把你不想要的空格也当成一个文本子节点
  • node.children这个不会,只是元素节点,优先使用
  • 当子代变化时,两者也会实时变化
  • document.querySelectorAll('.red')[0]不会实时更新

查兄弟姐妹

  • node.parentNode.childNodes但自己也在里面,要排除自己和所有文本节点
  • node.parentNode.children但自己也在里面,要排除自己
  • 排除自己:要用for循环遍历所有子节点,然后把自己排除出去
let siblings = []
let arr = div2.parentElement.children  //找出所有子节点形成一个数组
for(let i=0;i<c.length;i++){  //遍历数组中每个元素
    if(arr[i] !== div2){  //如果不是自己
        sibling.push(arr[i])  //就加入到新数组中
    }
}

查老大

  • node.firstChild

查老小

  • node.lastChild

查看上一个哥哥/姐姐

  • node.previousSibling

查看下一个弟弟/妹妹

  • node.nextSibling

遍历一个div里面的所有元素(查看一个节点里所有的元素(儿子、孙子、曾孙子,,,))

let travel = (node,fn) => {
    fn(node)
    if(node.children){
        for(let i = 0;i<node.children.length;i++){
            travel(node.children[i],fn)
        }
    }
}

travel(d1,(node)=>{console.log(node)})

六、DOM操作是跨线程的

跨线程操作

各线程各司其职

  • JS引擎不能操作页面,只能操作JS
  • 渲染引擎不能操作JS,只能操作页面

跨线程通信

  • document.body.appendChild(div1)---这是一句跨线程的DOM操作
  • 当浏览器发现JS在body里面加了个div1对象
  • 浏览器就会通知渲染引擎在页面里也新增一个div元素
  • 新增的div元素所有属性都照抄div1对象

插入新标签的完整过程

在div1放入页面之前

  • 你对div1所有的操作都属于JS线程内的操作
let div1 = document.createElement('div')
div1.textContent = 'hi'

把div1放入页面之时

  • 浏览器会发现JS的意图,就会通知渲染线程在页面中渲染div1对应的元素
  • document.body.appendChild(div1)

把div1放入页面之后

  • 你对div1的操作都有可能会触发重新渲染
  • div1.id = 'newid'可能会重新渲染,也可能不会
  • div1.title= 'new'可能会重新渲染,也可能不会
  • 如果你连续对div1多次操作,浏览器可能会合并成一次操作,也可能不会(之前在动画里提到过)

属性同步

标准属性

  • div1的标准属性的修改,会被浏览器同步到页面中
  • 比如idclassNametitle

data-*属性 同上

非标准属性

  • 对非标准属性的修改,则只会停留在JS线程中,不会同步到页面里
  • 比如x属性,示例代码

启示

  • 如果你有自定义属性,又想被同步到页面中,请使用
  • data-作为前缀

Property V.S. Attribute

  • property属性:JS线程中div1对象的所有属性,叫做div1的property
  • attribute也是属性:渲染引擎中div1元素的属性,叫做attribute
  • 区别:
  1. 大部分时候,同名的property和attribute值相等
  2. 但如果不是标准属性(把div1放入页面之后只有标准属性会同步),那么它俩只会在一开始时相等(把div1放入页面之时,全过去)
  3. 但注意attribute只支持字符串,而property支持字符串、布尔等类型