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就是调试窗口,这个窗口是前端工作者的家
,是最常用的地方
自己写一个有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种
在标签上去添加全局暴露的方法
关键词是全局
,下面写的所有方法,必须是全局的,如果被私有的作用域保护,是找不到的,什么叫全局的方法,就是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等标识去绑定的方式我不是很喜欢,我常用的写法有两种
注意
上面的所有的事件绑定都必须保持原标签不改变的前提,一旦标签被父元素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的三个地方
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查看面试题二
==后续会继续补充==
Vue和React是数据驱动视图,如何有效控制DOM操作?能不能把计算,更多的转移为js计算?因为js执行速度很快。patch函数-->patch,对比tag,对比tag与key,对比children