dom与jq基础使用

SIMONDOMAIN 2019-12-13

js有两种运行环境,一个是浏览器,一个是服务器(NodeJS)

js的本质是es,因为运行环境的不同,为了操作环境内的api做了升级

在浏览器上js分为es + dom + bom
在服务器上js又有另外的功能,这个在NodeJS里讲

dom和bom就是环境自带的东西

在css的dom树里讲过,浏览器把标签解析成一个巨大的对象renderTree,然后js出现了能操作renderTree的功能,每一个标签都是一个独立的可以被js单独操作的对象

==注意:在html的标签,元素,在js叫节点node 都是同个意思,他们是同个东西==

就拿把一个按钮的字变成红色来说
原生的js都需要这样几个步骤
指定获取这个按钮,通过什么呢,跟css一样

//这个就是dom对象
document.querySelector("#btn")
//怎么改颜色呢?控制这个dom对象的style属性,这个属性也是一个对象,这个对象存的就是有关样式的资料,有些可以改变,有些是只读属性
document.querySelector("#btn").style.color = "red";

那我怎么知道这个对象里有一个叫style的属性对象呢
这就是写多了就知道了,那他还有什么属性对象呢?这就是dom元素的调试
按下F12,从左数第4个console就是调试窗口,这个窗口是前端工作者的家,是最常用的地方

dom与jq基础使用

自己写一个有div的页面打开后,打开console,依次输入下面三句js

// 这个只能拿到页面上的一样的标签
document.querySelector("div")
// 把上面的标签转成详细dom对象
console.dir(document.querySelector("div"))
// 上面的简化版,偷懒就这么写
[document.querySelector("div")]

然后打开返回的数据的箭头,你会看到一个特别长的对象格式的数据,里面就有上面说的style属性对象,这样的dom对象一个页面有无数个

我们去重复的写document.querySelector("xxx")是非常的恶心的,而且有个悲伤的故事不得不讲,就是不同的浏览器的dom是不一样的,因为有个叫内核的东西,内核决定了解析效果,如果接触过IE等非chorme浏览器,你就会知道什么叫内核,因为渲染的不同,属性也会不一样,比如在这个浏览器颜色是color,另一个可能叫myColor,当然这只是比喻,导致我本地运行没问题的代码,给不同的用户使用就没效果,然后被领导疯狂的怼,于是有个优秀的团队封装了一个叫jquery的插件,专门用来操作dom对象,并且做了简化和浏览器内核兼容,简称jq

下面对比原生的js-dom和jq的使用
有很多人的原生js一点都不懂,刚接触就用jq,这是非常不好的
下面的内容不是告诉你jq有多好用,而是原生应该怎么实现jq的方法
大佬对jq的源码解读

元素获取

// jq写法
$("#id")
$(".class")
$("div")
$("[name=xx]")
$("[type=radio]")
$("input[type=radio]:checked")
$("select option:selected")
$("[disabled]")
// jq把获取一个和获取多个都封装进了$()里
// 原生js就要根据自己的情况去选择
// 使用 document.querySelector()
// 还是 document.querySelectorAll()
// 括号里的写法在原生一样适用

通过name取form表单里的带有name的form表单标签

// 比如有个name属性是myform的form标签里有个name属性是name叫nameInp的输入框
document.myform.nameInp  //不放在form里是拿不到的

选择多个的第N个

// jq写法
$("div").eq(2)
// 原生js写法
document.querySelectorAll("div")[2]

jq转js,jq的核心就是把节点存进一个数组里

$("#id")[0] 跟 document.querySelector("#id") 是一样的
$(".class")[4] 跟 document.querySelectorAll(".class")[4] 是一样的
// 一旦把jq转成js就不能再使用jq的方法

节点的循环

// jq写法
$("div").each(function(index,node){ ... })
// 原生js写法
var divs = document.querySelectorAll("div")
for(var i=0;i<divs.length;i++){  ...  }

元素的样式和属性

window的高度获取

// jq写法
$(window).height();
// 原生js写法
// 含 scrollbar
window.document.documentElement.clientHeight;
// 不含 scrollbar,与 jQuery 行为一致
window.innerHeight;

document的高度

// jq写法
$(document).height();
// 原生js写法
var body = document.body;
var html = document.documentElement;
var height = Math.max(
  body.offsetHeight,
  body.scrollHeight,
  html.clientHeight,
  html.offsetHeight,
  html.scrollHeight
);

某个元素的高度

// jq写法
$el.height();
// 原生js写法
function getHeight(el) {
  var styles = this.getComputedStyle(el);
  var height = el.offsetHeight;
  var borderTopWidth = parseFloat(styles.borderTopWidth);
  var borderBottomWidth = parseFloat(styles.borderBottomWidth);
  var paddingTop = parseFloat(styles.paddingTop);
  var paddingBottom = parseFloat(styles.paddingBottom);
  return height - borderBottomWidth - borderTopWidth - paddingTop - paddingBottom;
}

获得匹配元素相对上一级div的坐标位置

// jq写法
$el.position();
// 原生js写法
el.offsetTop/offsetLeft

获得匹配元素相对body的偏移

// jq写法
$el.offset();
// 原生js写法
function getOffset (el) {
  const box = el.getBoundingClientRect();
  return {
    top: box.top + window.pageYOffset - document.documentElement.clientTop,
    left: box.left + window.pageXOffset - document.documentElement.clientLeft
  }
}

获取元素滚动条垂直位置

// jq写法
$(window).scrollTop();
// 原生js写法
(document.documentElement && document.documentElement.scrollTop) || document.body.scrollTop;

标签的属性
checked,disabled,placeholder,自定义属性都可以用这个方法
自定义属性在css篇里我说用来储存数据就是这个用法

// jq获取属性
$el.attr("src")
// 原生js获取属性
el.getAttribute("src")

// jq设置属性
$el.attr("data-xx","xx")
// 原生js设置属性
el.setAttribute("data-xx","xx")
// 返回标签上所有data的自定义属性和值,格式是一个对象
el.dataset

// jq删除属性
$el.removeAttr("data-xx")
// 原生js删除属性
el.removeAttribute("data-xx")

// 原生js判断属性
el.hasAttribute("data-xx")

==关于单选多选有个注意的点==
一般我们默认选中是用 checked="checked" 这是没错的
如果选择是通过鼠标去点击选择或者取消选择,最后用 [xxx]:checked 去取值也是没错的
但是
判断有没有被选中不能通过判断节点是否有 checked 属性
想通过js去选中或者取消选中不能通过添加和移除 checked 属性去实现
复制下面的代码可以知道为什么

<input type="checkbox" checked="checked" id="aa">
document.querySelector("#aa").checked = false
console.log($("#aa").attr("checked"))
//虽然页面显示为选择,但是打印出来的属性还是选中状态,这就是BUG产生的隐患

正确操作如下

// 要判断有没有选中,true是有,false是没有
document.querySelector("#aa").checked
// 选中或者取消选中
document.querySelector("#aa").checked = true/false

form表单元素的值
包括输入框,单选多选,下拉框,大输入框textarea

// jq获取value
$el.val()
// js获取value
el.value
// jq修改value
// 下拉框select传入option的value一样的值会修改选中选项
$el.val(123456)
// js获取value
el.value = 123456

关于下拉框的操作
获取下拉框的值上面提到是value,但是value只是获取被选中的option标签里的value属性的值,那option的内容要怎么获取呢,怎么知道当前下拉框选中的是第几个option呢,怎么去修改当前选中的下拉框的值呢

//获取当前选中的下拉框内容的序号
var index = $('select').selectedIndex
// 所有options的数组
$('select').options 
// option的内容
$('select').options[index].text
//选另一个
$('select').options[index].setAttribute("selected","selected")

获取内容html
包括div,p,span等等,图片和表单元素没有这个值

// jq获取html
$el.html()
// js获取html
el.innerHTML
// jq修改html,清空填""
$el.html("<div>123456</div>")
// js修改html,清空等于""
el.innerHTML = "<div>123456</div>"

操作class

// jq添加,移除,有就移除没有就添加
$el.addClass("xxx")
$el.removeClass("xxx")
$el.toggleClass("xxx")
// 原生js添加,移除,有就移除没有就添加
el.classList.add("xxx")
el.classList.removeClass("xxx")
el.classList.toggle("xxx")

插入节点

Append 插入到子节点的末尾

// jq写法
$el.append("<div id='container'>hello</div>");
// 原生js写法
el.insertAdjacentHTML('beforeend', '<div id="container">Hello World</div>');
// 也可以先创建一个标签元素
var newEl = document.createElement("div")
el.appendChild(newEl);

Append 插入到子节点的开头

// jq写法
$el.prepend("<div id='container'>hello</div>");
// 原生js写法
el.insertAdjacentHTML('afterbegin', '<div id="container">Hello World</div>');
// 也可以先创建一个标签元素
var newEl = document.createElement("div")
el.insertBefore(newEl, el.firstChild);

在选中元素前插入新节点

// jq写法
$el.insertBefore("<div id='container'>hello</div>");
// 原生js写法
el.insertAdjacentHTML('beforebegin ', '<div id="container">Hello World</div>');
// 也可以
const el = document.querySelector(selector);
if (el.parentNode) {
  var newEl = document.createElement("div")
  el.parentNode.insertBefore(newEl, el);
}

在选中元素后插入新节点

// jq写法
$el.insertAfter("<div id='container'>hello</div>");
// 原生js写法
el.insertAdjacentHTML('afterend', '<div id="container">Hello World</div>');
// 也可以
const el = document.querySelector(selector);
if (el.parentNode) {
  var newEl = document.createElement("div")
  el.parentNode.insertBefore(newEl, el.nextSibling);
}

替换元素

// jq写法
$el.replaceWith("<b>Paragraph. </b>");
// 原生js写法
var newEl = document.createElement("div");
el.parentNode.replaceChild(newEl,el);

移除一个元素

// jq写法
$el.remove()
// 原生js写法
el.parentNode.removeChild(el)
//现在也是可以直接el.remove()的,只是不知道兼容性如何

在html的第一篇里说标签除了属性就是方法,dom节点如何绑定一个方法
让dom响应方法的方式有3种

  1. 在标签上去添加全局暴露的方法
  2. 通过选择器添加方法(这个又分为直接赋值和订阅发布)

在标签上去添加全局暴露的方法
关键词是全局,下面写的所有方法,必须是全局的,如果被私有的作用域保护,是找不到的,什么叫全局的方法,就是window.xx()可以执行的,或者在F12的调试框输入方法名找得到的,这个写法很不安全,因为可以被随意被调用,但是快,简单

<div onclick="click()">点击时</div>
<input type="text" onblur="blur()" oninput="input()" />光标选择时,输入时
<input type="checkbox" onchange="change()" />选中或者取消选中
<input type="file" onchange="change(this.files[0])" />传入文件时

// 讲个特别的,阻止a标签的路径跳转
// 但是为什么要用a标签,还要专门去阻止跳转,这就是傻逼行为
// 知识点 void(0) 就是undefined的意思,很老很装逼的写法
<a href="javascript:void(0)">跳转不了</a>

通过选择器添加方法(这个又分为直接赋值和订阅发布)
先说直接赋值,一个标签的同个方法只能赋值一次,新的会替换掉旧的

//原生js写法,jq没有
document.querySelector(el).onclick = function(){ ... }
document.querySelector(el).onchange = function(){ ... }
window.onscroll = function(){ ... }
window.onload = function(){ ... }

再说说订阅发布
下面的写法不会因为新添加方法移除上一个方法,
添加多少次方法就会执行多少次,会引起多次执行
支持移除,但需要方法名作为标识

//jq写法
//这种写法不支持移除
$el.click(function(){ ... })
$el.change(function(){ ... })
//这种写法可以移除
$el.on("click",functionName)
$el.remove("click",functionName)
//这种写法不能移除,没有标识
$el.on("click",function(){ ... })
// 原生js写法
document.querySelector(el).addEventListener("input",functionName)
document.querySelector(el).removeEventListener("input",functionName)
//这种写法不能移除,没有标识
document.querySelector(el).addEventListener("input",function(){ ... })

虽然上面有很多绑定方法的写法,但是通过id和class等标识去绑定的方式我不是很喜欢,我常用的写法有两种

  • 直接把方法写在标签里,方法写成全局的方法
  • 创建标签不用字符串,用creatElment,然后把创建好的dom元素直接addEventListener绑定方法

注意
上面的所有的事件绑定都必须保持原标签不改变的前提,一旦标签被父元素innerHTML=""清空,或者本身remove,之后就算再添加一个一样id一样class的标签,他也不是原来的标签了,因为他原本被赋予的方法被删除时已经消失了,需要重新添加方法

dom方法的event对象
任何关于dom元素的方法,他的作用域里都有一个隐藏的叫做event的对象
event的对象有很多种,
最原始的window的onload的Event,
比如输入框失去光标的FocusEvent,
比如键盘按下的KeyboardEvent,
比如鼠标的MouseEvent,
还有手机屏幕滑动的TouchEvent,
等等

$el.click(function(){ console.log(event) })
window.onload = function(){ console.log(event) }
document.querySelector(el).onclick = function(){ console.log(event) }
window.ontouchmove = function(){  console.log(event)  }

当给window添加点击事件时认真的去看那个event对象,会发现,event对象里有一个target属性,这个属性里的值就是被鼠标点到的节点,有了这个节点就可以进行节点的判断了,如果点击到的节点的className是aa执行aa方法,如果点到的节点是个img,就如何如何,event就是这样一个俯视节点的上帝的存在,这也是一种绑定事件的方法,叫做==事件代理==,除了点击事件外,其他事件做不出同样的效果

我用到event的三个地方

  1. 一是写一个打气球的游戏;
  2. 还有解决苹果手机ios系统页面不会弹的BUG;
  3. 手机滑动手势计算

TouchEvent
? clientX:触摸目标在视口中的x坐标。
? clientY:触摸目标在视口中的y坐标。
? pageX:触摸目标在页面中的x坐标。
? pageY:触摸目标在页面中的y坐标。
? screenX:触摸目标在屏幕中的x坐标。
? screenY:触摸目标在屏幕中的y坐标。

事件冒泡和阻止冒泡
在页面上我们会遇到这样的情况,divA有个点击事件A,还有个子元素divB,divB也有个点击事件B,这时鼠标点击B会执行什么事件呢?
点击事件的执行是一种冒泡的模式,从最里面往外面执行,也就是先执行B,然后执行A,但是我们点击B却不想执行A,怎么办
两种方法
一种是把divB从divA里移出来
另一种是阻止冒泡行为,就是在B方法里面写上

function B(){  event.stopPropagation()  }

event的其他事件

// 阻止默认行为
event.preventDefault()

// 阻止剩余的事件处理函数执行并且防止事件冒泡到DOM树上
// 这个方法不接受任何参数。
// 例如注册了A、B两个 click 事件,在 A 的方法中阻止后,不会执行 B 的方法
event.stopImmediatePropagation()

dom方法的this对象
==每个function都有执行者,function的执行者就是function作用域内的this==,这句概念使用在整个js领域,function的执行者就是一个对象,可以是构造对象,可以是dom对象,最常见的是全局对象(全局对象在浏览器端是window对象,在服务器端叫global对象)

上面的几种事件绑定方式
第一种标签绑定事件的默认this是window,需要让标签把自己传过来

<div onclick="aa(this)"></div>

最后一种event.target就是this
其他几种就是直接

function x(){ console.log(this) }

》》其他
这里的内容都很少用到,要么找插件,要么找插件

画布/视频/音频

// 画布api,画布比较常用会单独做一篇
var canvas = document.getElementById("myCanvas");
var cxt = canvas.getContext("2d");

// 视频api
var mp4 = document.getElementById("myVideo");
mp4.onplay = function() {  alert("The video has started to play") }

// 音频api
var mp3 = document.getElementById("myAudio");
mp3.onplay = function() {  alert("The Audio has started to play") }

还有文件读取fileReader,文件详情DataView,IntersectionObserver是否在可见区域,文件容器FromData,iframe,富文本,拖拽上传,复制上传,地址,摄像头,录音,websocket,打印机

// 文件读取会跟画布做在一期
var fr = new FileReader()

// iframe的高度等于内容的高度
document.querySelector('#iframe').onload = function () {
    this.height = this.contentWindow.top.innerHeight + "px";
    if(this.contentDocument){
        // 不跨域的情况 
    }else{
        // 跨域的情况
        this.height = this.contentWindow.top.innerHeight + "px";
        this.style.marginLeft = "16px";
        document.body.style.overflowX = "hidden";
    }
};

// 富文本有相关3个api,会专门总结一期
var selection = window.getSelection();
var range = selection.getRangeAt;
document.execCommand("Copy");

// 拖拽上传
$("#id")
  .on("dragover", function (event) {
    event.preventDefault();
  })
  .on("drop", function(event) {
    event.preventDefault();
    // 数据在event的dataTransfer对象里
    let file = event.originalEvent.dataTransfer.files[0];
    console.log(file)
    // 然后就可以使用FileReader进行操作
    // var fr = new FileReader();
    // fr.readAsDataURL(file);
    // 或者是添加到一个FormData
    // let formData = new FormData();
    // formData.append("file", file);
  })

// 复制上传,查看富文本篇

// IntersectionObserver查看面试题二

==后续会继续补充==

相关推荐