DOM 与 JQuery

199 阅读9分钟

jQuery是构造函数吗?

  • 可以说是,因为确实构造出了对象提供了各类方法
  • 也可以说不是,因为正常情况的构造函数是 new Function一个对象

链式风格就是jQuery风格

基本概念

  • jQuery对象指的是由jQuery构造函数构造出来的对象
  • Object对象表示Object构造函数构造出来的对象

重要理解

  • 用到fisrtchild children之类的api都会把文本节点算在内,(空格,换行都算)
    • 自己灵活的多敲.去查看节点的api列表中选择children childNodes firstElementChild firstChild 之类的多尝试即可
  • 很多dom api使用时都要搞清是在父节点还在自己节点操作,使用时遇到问题直接mdn
  • 遍历node.children时,如果涉及到remove之类的操作,children.length会变,意味着传统的for循环是不行的
    • 因为你想顺着index去取arr[0] arr[1]... 但是length也在变化,因此应该每次都取arr[0]才能顺利遍历才对

DOM操作只要是在appendChild之后的操作,都是跨线程的,也就是会直接影响页面渲染,渲染引擎

元素的attributes是指能够挂载在页面上的properties

  • properties是存在于JS线程中(JS引擎中) attributes是存在于渲染引擎中的
  • attributes指的是渲染到页面上标签具有的那些属性比如 class id title
  • properties则指的是你console.dir打印一个element对象的全部完整属性
  • attributes只支持字符串
  • properties支持布尔等类型

实例深刻体会一下

可以通过这个实例深刻感受一下

console.dir(test); 
// 可以顺利在这个元素的结构上看到id class title 与dataset 
// 但非标准属性xxxxxx不存在,由此可见通过写在标签上的非标准属性是不会作用到properties的
test.x123=100;
console.dir(test);  // 同时如果你试图在console中这么做的话,确实可以让非标准属性挂在test对象身上
// 但依旧不会出现在渲染的html页面中
console.log(typeof test1.disabled) // boolean 
// 你可以console.dir(test1) 发现此元素的disabled:true

id.可直接操纵元素

  • idxxx.style.xxx 可直接操纵的原因是写有id='idxxx'的元素被直接挂在window上
  • 但有些js关键字比如parent,就不适合做id,因为这本身就是挂在window上的,你无法直接parent.去操纵了

查询元素只用document.querySelector/All

获取特定元素

  • document.documentElement
    • 获取< html >元素及其中内容
  • document.head
    • 获取head
  • document.body
    • 获取body
  • document.all
    • 获取所有元素,这可以被理解为是一个奇葩,这可以算是第六个falsy值
Boolean(document.all)// false
  • window
    • window可不是标签/元素,window只是窗口,获取这个有时候可以window.onclick=()=>{console.log('hi')})

获取元素身上的API

console.dir(document.documentElement)

Element有着一根很长的原型链

  • < html element >---HTMLHtmlElement---HTMLElement---Element---Node---EventTarget---Object
  • 可以看到< html element >后面跟着6个原型,每一层原型都具有大量的API
  • Element可以分为xml html
  • Node大致可分为1:Element 3:Text 8:Comment 9:Document
  • 我们可以看到Element构造函数会往this(也就是即将创建的对象身上)加id

试一下如何判断nodeType的实例

// 敲一下以下代码
let div=document.querySelector('#div123')
console.log(div.nodeType)
console.log(div.childNodes[0].nodeType)

DOM增删改查

增加一个标签节点

let div=document.createElement('div')
text1=document.createTextNode('你好')
div.appendChild(text1) // 方式1
div.innerText='你好' // 方式2

克隆一个标签节点

let div=document.createElement('div')
let div1=div.cloneNode(true); // true为深拷贝

移除节点 remove

  • 移除不等于删除
  • 移除只是移出来
你要移除的节点.parentNode.removeChild(你要移除的节点) // 方式1
你要移除的节点.remove(); // 方式1 推荐

删除节点 节点=null

  • 节点=null

修改节点属性 比如id class src 等等

节点.className = 'blue';
//如果你要增加一个class你必须 追加 
节点.className += ' red'; // 这里有一个空格的,不然就和blue连在一起了

// 新api classList
节点.classList.add('blue')
节点.classList.add('red')

// 修改style样式
节点.style['background-color']='red'; // 成立
节点.style.backgroundColor='blue'; // 成立

设置与读取自定义属性

  • 元素上的属性分三类
    • 标准属性class...
    • data属性data-x...
    • 非标准属性x...
  • 这一部分内容就记住node.setAttribute() / getAttribute()这两个API是通解
  • 修改data属性用node.dataset.xxx
  • div123.id/ className/style 这种方式读取或者修改 √
  • div123.非标准属性x 这种方式去读取或者修改 ×
// 设置属性
节点.setAttribute('xxx',123) // 方式1 非标准属性xxx
节点.setAttribute('data-xxx',123) // 方式2 data属性data-xxx
节点.dataset.xxx = '123' // 方式3
// 读取属性
节点.getAttribute('xxx'/'data-xxx') // 这种读取方式不论读取 xxx还是data-xxx都可行
节点.dataset.xxx // 只能读取data-xxx
// 修改data属性
节点.dataset.xxx =  ... 
// 注意,这种方式修改非标准属性是行不通的
节点.xxx= 你想赋的值 // 这是做不到的 

HTML节点上的自定义属性这一块内容比较复杂,一起跟着敲一下例子

div123.setAttribute('non-dataset',123);
console.log(div123); // 可以看到这种非标准自定义属性是ok的
div123.setAttribute('data-123',234);
console.log(div123); // 可以看到这标准自定义属性是ok的
div123.dataset.xxx = 345;
console.log(div123); // 可以看到此方式也可以自定义属性
div123.dataBBB=101;
console.log(div123); // 可以看到此方式是不可以的

读取a标签的href属性

a123.href // 会被浏览器动手脚
a123.getAttribute('href') // 这才确保所得的是你想要的

事件处理函数

每个node自己都有类似onclick这样的事件对象,只不过一开始都是null
以下代码点击此处

console.log(div123.onclick) // null
console.dir(div123) // 可以观察到div123这个对象上是有onclick属性的

//要注意的是
div123.onclick() //这种方式在console中敲入的也可以触发onclick事件
// 但是这样子是获取不到event参数的
// 只有当用户去点击div123这个元素了浏览器才会传递event参数,当然这个传递是隐式传递而非用户可见
div123.onclick=function(x){
  console.log(this);
  console.log(x);
};
// 为什么在里面this是指向div123?
// 第一种理解,因为onclick事件是由div123调用的 div123.onclick
// 第二种理解: div123.onclick() ======被JS转化成====> div123.onclick.call(div123,event) 

改内容

改文本内容

div.innerText=
div.textContent= 

改HTML内容

div.innerHTML=

改标签

div.innerHTML='' // 清空
div.appendChild(div1) // 再追加

为本元素修改父节点

newParentNode.appendChild(div) // div这个节点会直接从现在的地方消失去到新的父节点下面

查看元素的节点

node.parentNode   node.parentElement // 查爸爸
node.parentNode.parentNode // 查爷爷
node.childNodes // 查子节点 注意 就算是空格 换行也都会算在childNodes数目中 因为是node的计算
node.children // 符合人类的直觉 

node.childNodes里面会保留空格

// 敲一下以下的代码
parentDiv.childNodes // 可以看到数组中包含七个元素
// 并且不论空格回车还是文本123 都算作nodeType:3 都是text类型的node
childDiv1.childNodes // 可以看到值是一个空格 这是符合预期的
childDiv2.childNodes // 可以看到值是一个空格后面是换行符再是多个空格 这是符合预期的
childDiv3.childNodes // 可以看到值是一个换行符再是多个空格 这是符合预期的
parentDiv.children.length // 3

查兄弟姐妹

node.parentNode.childNodes // 查找得到父节点下的所有子节点,因此还需排除自己
node.parentNode.children // 查找得到父节点下的所有子节点,因此还需排除自己

查第一个/最后一个节点

node.firstChild / lastChild // 注意 这会查找到text文本节点哦
node.firstElementChild / lastElementChild // 这只会查找到element节点

查找比自己大一点的兄弟节点

node.previousSibling / nextSibling // 注意 这会查找到text文本节点哦
node.previousElementSibling / nextElementSibling // 这只会查找到element节点

获取元素节点的长度(有坑,无法自动获取更新)

  • 这里算是一个避坑,某些写法会引起length不会实时更新
  • 这里列举了三种方式获取长度然后进行remove操作再获取一下长度

敲一下下面的代码配合这个实例

方式1: 每次都重新获取长度 这是肯定对的

// 每次都重新获取长度的方式 如下
// 初次获取
document.querySelectorAll('div').length // 6
parentDiv.childNodes.length // 7
parentDiv.children.length // 3
childDiv1.remove() // 删一个节点

// 再次获取
document.querySelectorAll('div').length // 5
parentDiv.childNodes.length // 6
parentDiv.children.length // 2

方式2: 存一下.length前的对象 这就有问题了

// 存一下.length前的对象 如下
// 初次获取
let a=document.querySelectorAll('div')
console.log(a.length) // 6
let b=parentDiv.childNodes
console.log(b.length) // 7
let c=parentDiv.children
console.log(c.length) // 3
childDiv1.remove() // 删一个节点

// 再次获取
console.log(a.length) // 6 可以看到此方式预先存一下对象之后,.length也不会自动更新
console.log(b.length) // 6 但是下面两种方式是可以的
console.log(c.length) // 2

利用数据结构遍历一个div里的所有元素

详解:为什么说dom操作是跨线程的, 以及如何影响的?

  • 只要dom操作是发生在appendChild这类挂载元素操作前的,就不会影响,在appendChild之后的,那肯定会影响渲染线程
  • 每个浏览器有两个引擎: 渲染引擎和 JS执行引擎
  • 这两个引擎分别在不同的线程中,两者互不相干,并且各司其职
    • JS引擎不能操纵页面只能操作JS
    • 渲染引擎不能操作JS只能操纵页面
  • 效率低在于浏览器需要付出时间去感知变化再修改
  • 在div1放入页面之前指的是:node.appendChild()这样的操作
  • node.appendChild()之前也就是说元素还未挂载到页面上,所有操作都是JS引擎内部发生的
  • 浏览器修改页面时也就是渲染时,才会用到渲染引擎
  • 元素已经挂载到页面后,如果对element.id element.title进行修改的话也可能引起对页面的重新渲染((JS修改=>渲染引擎再次触发)

点击查看实例

// 在上面实例敲入以下代码以体会什么叫修改id可能会影响渲染引擎
id4test1.id='id4test';
// 在上面实例敲入以下代码以体会什么叫修改title也会重新影响渲染引擎
div123.title='我在修改title,也会影响页面渲染'

transition过渡期动画时的例子

transition接连两句的时候浏览器会自动合并 不产生过渡效果,但是你办当中读一下相关属性的话他就会分开执行

  • 除非你中间有强制要求分开渲染的操作

jQuery的设计模式