理解事件

js的事件,是由访问 Web 页面的用户引起的一系列操作,例如:用户点击、用户在输入框输入内容。当用户执行某些操作的时候,再去执行一系列代码。页面上给予的反馈,是由于用于进行了一些操作而产生的,而这个操作就是事件。

# Ⅰ、事件基础知识

# 1、事件处理函数列表

事件处理函数 影响的元素 何时发生
onabort 图像 当图像加载被中断时
onblur 窗口、框架、所有表单对象 当焦点从对象上移开时
onchange 输入框,选择框和文本区域 当改变一个元素的值且失去焦点时
onclick 链接、按钮、表单对象、图像映射区域 当用户单击对象时
ondblclick 链接、按钮、表单对象 当用户双击对象时
ondragdrop 窗口 当用户将一个对象拖放到浏览器窗口时
onError 脚本 当脚本中发生语法错误时
onfocus 窗口、框架、所有表单对象 当单击鼠标或者将鼠标移动聚焦到窗口或框架时
onkeydown 文档、图像、链接、表单 当按键被按下时
onkeypress 文档、图像、链接、表单 当按键被按下然后松开时
onkeyup 文档、图像、链接、表单 当按键被松开时
onload 主题、框架集、图像 文档或图像加载后
onunload 主体、框架集 文档或框架集卸载后
onmouseout 链接 当图标移除链接时
onmouseover 链接 当鼠标移到链接时
onmove 窗口 当浏览器窗口移动时
onreset 表单复位按钮 单击表单的 reset 按钮
onresize 窗口 当选择一个表单对象时
onselect 表单元素 当选择一个表单对象时
onsubmit 表单 当发送表格到服务器时

说明:上表只列出了我们常见的一些事件,更多的事件处理函数,在我们后面的课程中,根据我们的项目需求,在进行扩展讲解,大家先了解这么多。

那么知道了有这些事件函数,那么怎么在我们的页面上进行使用呢?这里就要提到js三种事件模型:内联模型、脚本模型和 DOM2 模型。

# 2、内联模型

这种模型是最传统且单一的处理事件的方法。在内联模型中,事件处理函数是 HTML标签的一个属性,用于处理指定事件。虽然内联在早期使用较多,但它是和 HTML 混写的,并没有与 HTML 分离。

# ① 事件处理函数作为属性执行 JS 代码

<img src="./static/image/logo.png" onclick="alert('点击了logo图片')" />
<img src="./static/image/logo.png" onclick="doimg()" />
function doimg(){
    alert('写在函数里面,点击了logo图片');
}

在实际开发中,特别是大型项目,为了便于管理,程序代码应该分离处理,比如html就应该重点写html的代码,js文件就应该处理js代码,我们上面的这种写法,就是将js和html混写了,所以实际开发中,这种写法用得不多。那么为了解决js和html代码层次的分离原则,就会用到我们上面提到的js三种事件模型的第二种:脚本模型。

# 3、脚本模型

let img = document.querySelector('#nav img');
console.log(img);
//1、通过匿名函数,可以直接触发对应的代码
img.onclick = function(){
    alert('脚本模型点击了logo图片');
}
//2、也可以通过指定的函数名赋值的方式来执行函数(赋值的函数名不要跟着括号)
function doimg(){
    alert('脚本模型点击了logo图片');
}
let img = document.querySelector('#nav img');
img.onclick = doimg;
// 让事件处理函数执行一个函数的时候,通过赋值方式,就直接讲函数名赋值给事件处理函数即可
// 不要后面加上(),加上了()函数会自动执行,不会经过事件处理函数点击操作

# Ⅱ、事件处理函数分类

所有的事件处理函数都会都有两个部分组成,on + 事件名称,例如 click 事件的事件处理函数就是:onclick。在这里,我们主要谈论脚本模型的方式来构建事件,违反分离原则的内联模式,我们忽略掉。
事件类型可分为:鼠标事件、键盘事件、HTML 事件。

对于每一个事件,它都有自己的触发范围和方式,如果超出了触发范围和方式,事件处理将失效。

# 1、 鼠标事件

鼠标事件,页面所有元素都可触发

# ① 鼠标事件click:当用户单击鼠标按钮或按下回车键时触发

let img = document.querySelector('#nav img');
img.onclick = function(){
   alert('在函数里面点击了logo图片111');
};

# ② 鼠标事件dblclick:当用户双击主鼠标按钮时触发

let img = document.querySelector('#nav img');
img.ondblclick = function(){
   alert('双击了鼠标左键两下,才弹出');
};

# ③ 鼠标事件mousedown:当用户按下了鼠标还未弹起时触发

let img = document.querySelector('#nav img');
img.onmousedown = function(){
   alert('按下鼠标左键,还没有松开,弹出了,就是按下鼠标左键就弹出');
};

# ④ 鼠标事件mouseup:当用户释放鼠标按钮时触发

let img = document.querySelector('#nav img');
img.onmouseup = function(){
   alert('按下鼠标左键没反应,松开鼠标左键,弹出了');
};

# ⑤ 鼠标事件mouseover:当鼠标移到某个元素上方时触发

let img = document.querySelector('#nav img');
img.onmouseover = function(){
   alert('鼠标移动到图片上面就弹出了');
};

# ⑥ 鼠标事件mouseout:当鼠标移出某个元素上方时触发

let img = document.querySelector('#nav img');
img.onmouseout = function(){
   alert('鼠标移动到图片上面没有反应,离开这个图片弹出了');
};

# ⑦ 鼠标事件mousemove:当鼠标指针在元素上移动时触发

let img = document.querySelector('#nav img');
let n = 0;
img.onmousemove = function(){
   n++;
   console.log('鼠标在图片上移动就会执行' + n);
};

# 2、 键盘事件

操作键盘时候触发的事件

# ① 键盘事件keydown:当用户按下键盘上任意键触发,如果按住不放,会重复触发

onkeydown = function () {
    console.log('按下键盘上任意键触发,如果按住不放,会重复触发');
};

# ② 键盘事件keypress:当用户按下键盘上的字符键触发,如果按住不放,会重复触发。

onkeypress = function () {
    console.log('按了键盘字符键入字母数字回车等触发,CTRL,ALT等键则不触发');
};

# ③ 键盘事件keyup:当用户释放键盘上的键触发

onkeyup = function () {
    alert('按了键盘任何键没有反应,松开之后弹出');
};

# 3、HTML事件(包括表单事件)

# ① HTML事件load:当页面完全加载后在 window 上面触发,或当框架集加载完毕后在框架集上触发

//js文件不放在底部,就需要在这个事件里面执行
window.onload = function () {
   let img = document.querySelector('#nav img');
   console.log(img);
};

# ② HTML事件unload:当页面完全卸载后在 window 上面触发,或当框架集卸载后在框架集上触发。

window.onunload = function () {
    console.log('卸载页面如刷新触发');
};

# ③ HTML事件resize:当窗口或框架的大小变化时在 window 或框架上触发

window.onresize = function () {
    console.log('页面大小发生了变化');
};

# ④ HTML事件scroll:当用户滚动带滚动条的元素时触发

window.onscroll = function () {
    let scrolly = document.documentElement.scrollTop;
    console.log('滚动条距离顶部',scrolly);
};

关于表单事件,我们会在下一个章节具体讲,这里大家先感受一下

# ① 表单事件select:当用户选择文本框(input 或 textarea)中的一个或多个字符触发

<input type="text" class="testinput" />
let input = document.querySelector('.testinput');
input.onselect = function(){
    console.log('选中了文本框文字松开鼠标触发')
}

# ② 表单事件change:当文本框(input 或 textarea)内容改变且失去焦点后触发

let input = document.querySelector('.testinput');
input.onchange = function(){
    console.log('文字改变且失去焦点后触发')
}

# ③ 表单事件input:当文本框(input 或 textarea)内容改变就触发

let input = document.querySelector('.testinput');
input.oninput = function(){
    console.log('内容改变就触发');
}

# ④ 表单事件submit:当用户点击提交按钮在<form>元素上触发

<form >
    <input type="text" class="testinput" />
    <button type="submit">提交</button>
</form>
let form = document.querySelector('form');
form.onsubmit = function(){
    console.log('提交表单')
}

关于form表单更多知识我们会在下一章进行讲解

# ⑤ 表单事件reset:当用户点击重置按钮在<form>元素上触发

<form >
    <input type="text" class="testinput" />
    <button type="submit">提交</button>
    <button type="reset">重置</button>
</form>
let form = document.querySelector('form');
form.onreset = function(){
    console.log('重置表单')
}

# ⑥ focus:当页面或者元素获得焦点时在 window 及相关元素上面触发

let input = document.querySelector('.testinput');
input.onfocus = function(){
   console.log('鼠标左键点击一下获取光标触发');
}

# ⑦ blur:当页面或元素失去焦点时在 window 及相关元素上触发

let input = document.querySelector('.testinput');
input.onblur = function(){
    console.log('鼠标左键点击一下其他地方失去光标触发');
}

我们在实际开发中,遇到的事件远不止这些,大家先理解和掌握这些基本的事件,后面我们项目开发中,再去理解和应用更多的事件函数。

# Ⅲ、事件对象

我们通过一些例子理解事件对象

# 1、 this 关键字和上下文

//事件处理三部分组成:对象.事件处理函数=函数。例如:单击文档任意处。
document.onclick = function () {
    alert('黑丝');
};
//以上程序的名词解释:click 表示一个事件类型,单击。
//onclick 表示一个事件处理函数或绑定对象的属性(或者叫事件监听器、侦听器)。
//document 表示一个绑定的对象,用于触发某个元素区域。
//function()匿名函数是被执行的函数,用于触发后执行。

//除了用匿名函数的方法作为被执行的函数,也可以设置成独立的函数。[脚本模型的两种写法]
document.onclick = box; //直接赋值函数名即可,无须括号
function box() {
   alert('黑丝');
}

//this 关键字和上下文
function doimg(){
    console.log(this);
}
doimg();//很明显打印的this,是window【第10章作用域那里讲得很清楚了】

let img = document.querySelector('#nav img');
img.onclick = doimg;//this打印的img节点对象
//原因:因为doimg函数被onclick绑定了,所以里面的this代表的是被绑定的那个对象img对象

从上面的拆分,我们并没有发现本章的重点:事件对象。那么事件对象是什么?它在哪里呢?当触发某个事件时,会产生一个事件对象,这个对象包含着所有与事件有关的信息。包括导致事件的元素、事件的类型、以及其它与特定事件相关的信息。

那么它在哪里呢?

事件对象,我们一般称作为 event 对象,这个对象是浏览器通过函数把这个对象作为参数传递过来的。那么首先,我们就必须验证一下,在执行函数中没有传递参数,是否可以得到隐藏的参数。

function box() { //普通空参函数
    console.log(arguments.length); //获取传参的数量
}
box();//0,没有得到任何传递的参数

let img = document.querySelector('#nav img');
img.onclick = box;//1  事件绑定的执行函数,得到一个隐藏参数

//通过上面两组函数中,我们发现,通过事件绑定的执行函数是可以得到一个隐藏参数的。
//说明,浏览器会自动分配一个参数,这个参数其实就是 event 对象(事件对象)。
let img = document.querySelector('#nav img');
img.onclick = function () {
    console.log(arguments[0]); //PointerEvent,鼠标指针事件对象
};

# 2、 获取事件对象

let img = document.querySelector('#nav img');
img.onclick = function () {
    console.log(arguments[0]); //PointerEvent,鼠标指针事件对象
};

//写法比较繁琐,可以直接通过接收参数来得到即可
img.onclick = function(e){//参数可以是任意的,如:e, event, a, b 等等
    console.log(e);
}

事件对象里面包含了很多信息,我们可以利用这个对象信息获取很多我们想要的值,比如获取你鼠标点击的那个对象是什么,你鼠标点击的时候鼠标距离左上角视口的距离是多少等等。这个我们接下里会讲到。

# 3、鼠标事件

我们上一节课讲了事件对象是什么,并且学习了如何获取事件对象,在最后还提了事件对象里面有很多信息可以给我们利用,那么作为最常用的鼠标事件,事件对象给我们提供了哪些信息呢?

# ① 获取鼠标按钮(左键、右键、中间滚轮)

开发中我们有时候需要判断用户点击的是鼠标左键,还是右键,还是鼠标滚轮,在我们的事件对象中mousedown 和 mouseup 事件有一个button属性可以判断:0表示鼠标主按钮(常规一般是鼠标左键),1表示中间的鼠标按钮(鼠标滚轮按钮),2表示鼠标次按钮(常规一般是鼠标右键)

document.onmousedown = function(e){
    console.log(e);//IE是window.event, 兼容 e = e || window.event; 
    console.log(e.button);//0左键 1中间滚轮 2右键

    if(e.button == 0){
        console.log('按的鼠标左键');
    }else if(e.button == 1){
        console.log('按的鼠标滚轮');
    }else if(e.button == 2){
        console.log('按的鼠标右键');
    }
}

# 4、可视区及屏幕坐标

# ① clientX:可视区 X 坐标,距离左边框的位置

# ② clientY:可视区 Y 坐标,距离上边框的位置

# ③ screenX:屏幕区 X 坐标,距离左屏幕的位置

# ④ screenY:屏幕区 Y 坐标,距离上屏幕的位置

document.onclick = function (e) {
    //let e = e || window.event;
    console.log(e);
    console.log('可视区 X Y 坐标', e.clientX + ',' + e.clientY);
    console.log('屏幕区 X Y 坐标', e.screenX + ',' + e.screenY);
};

结合我们上面两个知识点,以及上一章节DOM的知识,我们可以模拟一个鼠标右键弹出菜单栏的功能,这个我们后面项目有需要的话,我们可以做一下,这些基础知识大家得先知道。

# ⑤ 鼠标点击位置距离页面顶部距离(带滚动条)

document.onclick = function (e) {
    console.log('可视区 X Y 坐标', e.clientX + ',' + e.clientY);
    console.log('鼠标点击位置距离页面顶部距离(带滚动条)', e.clientX + ',' + (e.clientY + document.documentElement.scrollTop));
};

# 5、修改键

有时,我们需要通过键盘上的某些键来配合鼠标来触发一些特殊的事件,比如:按上键盘CTRL+ALT+A做截图操作,这些键为:Shfit、Ctrl、Alt 和 Meat(Windows 中就是 Windows 键,苹果机中是 Cmd 键),它们经常被用 来修改鼠标事件和行为,所以叫修改键。

# ① 属性:shiftKey对应Shfit 键,ctrlKey对应 Ctrl 键,altKey 对应 Alt 键,metaKey 对应 windows 键,判断是否按下了它们

function getKey(e) {
    let keys = [];
    if (e.shiftKey) keys.push('shift'); //给数组添加元素
    if (e.ctrlKey) keys.push('ctrl');
    if (e.altKey) keys.push('alt');
    if (e.metaKey) keys.push('window');
    return keys;
}

document.onclick = function (e) {
    console.log(getKey(e));
};

# 6、键盘事件

用户在使用键盘时会触发键盘事件,现代浏览器已经支持到了'DOM3'级键盘事件,功能更加丰富。

# ① 键码(键盘上的任意键)keyCode

在发生 keydown(按下键盘任意键立即触发) 和 keyup(按下键盘任意键松开键盘键触发) 事件时,event 对象的 keyCode 属性中会包含一个代码,与键盘上一个特定的键对应。对数字字母字符集,keyCode 属性的值与 ASCII 码中对应小写字母或数字的编码相同。字母中大小写不影响。简单理解就是:键码返回的是ASCII 码的小写字母对应的,键码只是返回那个键的值,不认识字母大小写,键码在字符上和字符编码的ASCII 码是一致的。

document.onkeydown = function (e) {
    console.log(e.keyCode); //按任意键,得到相应的 keyCode
};
//ctrl:17,ctrl键不是字符编码,所以它有值,因为你现在得到的是 keyCode,键码
//shift:16,alt:18,回车键enter:13, 大小写切换键:20,等等都有值
//a:65,A:65 也就是键码可以返回任意键的编码,而且字母不区分大小写

# ② 字符编码(键盘上可以输出字符的键)charCode

这个属性只有在发生 keypress(按下键盘字符键如123,abc,标点符号,特殊字符等,shift,alt,ctrl等不是字符的键没反应) 事件时才包含值,而且这个值是按下的那个键所代表字符的 ASCII 编码。

document.onkeypress = function (e) {
    console.log(e.charCode); //按键盘字符键,得到相应的 charCode
};
//a:97,A:65

# 7、event 事件对象的常用属性和方法

# ① 属性:target(只读不可设置):事件目标(获取你所点击的那个目标元素对象)

以前IE用的是 srcElement

document.onclick = function(e){
    console.log(e);
    console.log('点击的目标元素对象是', e.target);//IE是:e.srcElement
    console.log('点击的目标元素名称', e.target.tagName);
    console.log('点击的目标元素的父元素', e.target.parentNode);
}

# ② 事件流

<div id="box" style="width: 300px;height: 300px;background-color: red;margin: 50px;">
    <input type="button" value="按钮" style="margin: 20px;">
</div>
//层级:input--->div--->body--->html--->document
//理解:如果没有input元素,你点的按钮的位置是不是相当于点的div,如果没有div,你点的按钮的区域是不是相当于点的body
//如果没有body元素你点的按钮这个区域是不是相当于点的html,如果没有html元素你点的按钮区域是不是相当于点的document
//这样一层一层下来,就是事件流,事件流包括两种模式:冒泡和捕获

# ③ 事件冒泡,取消:e.stopPropagation()

//冒泡:就是从 input--->div--->body--->html--->document 往上冒泡
//如果这几个元素都有点击事件的话,先触发input点击事件,在触发div点击事件,依次往上触发
//就是从里往外触发,就是冒泡,反过来,从外向里触发,就是事件捕获

document.onclick = function(){
    alert('document')
}
document.documentElement.onclick = function(){
    alert('html')
}
document.body.onclick = function(){
    alert('body')
}
document.getElementById('box').onclick = function(){
    alert('div')
}
document.getElementsByTagName('input')[0].onclick = function(){
     alert('input')
 }
//也就是说,你虽然点的是input,实际上input,又在div,body,html,document的区域
//它就是会依次从里面往外面冒泡,当然,如果你点div,就从div往往上冒,你点body就从body往上冒
//那么问题来了,div,body,html,document它们都有点击事件,我又不能删除这些点击事件
//然后我只想点击input按钮,只想弹出input就行,就不要往外冒泡了,那该怎么做?
//那么就需要取消冒泡事件,那么该如何取消呢?

document.getElementsByTagName('input')[0].onclick = function(e){
    e.stopPropagation();//取消事件冒泡  e.cancelBubble = true;
    alert('input')
}

事件捕获我们后面讲

# IV、事件绑定

事件绑定分为两种:一种是传统事件绑定(内联模型,脚本模型),一种是现代事件绑定(DOM2/DOM3 级模型)。现代事件绑定在传统绑定上提供了更强大更方便的功能。

# ① 传统事件绑定的问题1:同名事件后者覆盖前者

传统事件绑定有内联模型和脚本模型,内联模型我们不做讨论,基本很少去用。先来看一下脚本模型,脚本模型将一个函数赋值给一个事件处理函数。

//如果一个页面有两个或者多个js,并且,第一个js是第一个程序员写的,第二个js是第二个程序员开发的
//index.javascript.js
window.onload = function(){
    let banner = document.getElementById('banner');
    console.log('banner',banner);
}
//test.js
window.onload = function(){
    let main_business = document.getElementById('main_business');
    console.log('main_business',main_business);
}
//发现只执行了第二个js里面的window.onload代码,第一个window.onload没有执行,被覆盖了
//有解决方案,但是非常麻烦
//index.javascript.js
window.onload = function(){
    let banner = document.getElementById('banner');
    console.log('banner',banner);
}

//test.js
console.log(typeof window.onload);//'function'
let savefun = null;
if(typeof window.onload == 'function'){
    savefun = window.onload; //保存上一个事件对象
}

window.onload = function(){
    savefun(); // 执行上一个js的事件对象
    let main_business = document.getElementById('main_business');
    console.log('main_business',main_business);
}

# ② 传统事件绑定的问题2:事件切换器问题

<div id="box" class="red"></div>
<style>
    .red{
        width: 300px;height: 300px;margin: 50px;
        background-color: red;
    }
    .blue{
        width: 400px;height: 400px;margin: 50px;
        background-color: blue;
    }
</style>
let box = document.getElementById('box');
// box.onclick = function(){
//     //box.className = 'blue';
//     //this.className = 'blue';
// }
//题外话:toBlue(); 就是全局执行,this就是window对象,现在是绑定事件,那么this就是box对象
box.onclick = toBlue;
//想让它来回切换,写两个函数
function toRed(){
    this.className = 'red';
    this.onclick = toBlue;
}
function toBlue(){
    this.className = 'blue';
    this.onclick = toRed;
}

问题一:同样是同名事件覆盖问题

//先弹窗在切换背景色,弹窗无法执行,虽然可以按照问题1的方法操作,但是太麻烦
box.onclick = function(){
    alert(123);
}
box.onclick = toBlue;

问题二:this指向问题

box.onclick = function(){
    alert(123);
    toBlue();//发现不切换,查看this,发现this指向window
    //通过匿名函数执行某一个函数,里面的this代表的是window

    //通过对象冒充传递this对象
    //toBlue.call(box);
    toBlue.call(this);
};
function toBlue(){
    console.log(this);
    this.className = 'blue';
    this.onclick = toRed;
}

问题三:通过对象冒充只执行了一次,第二次又被覆盖了

function toBlue(){
    console.log(this);
    this.className = 'blue';
    this.onclick = toRed;//这里的onclick又把alert(123)给覆盖了
}

综上的三个问题:覆盖问题、可读性问题、this 传递问题。我们来创建一个自定义的事件处理函数,来解决以上三个问题。

//obj相当于window, type相当于onload,fn相当于function(){}匿名函数
function addEvent(obj, type, fn) { //取代传统事件处理函数
    let saved = null; //保存每次触发的事件处理函数
    // window.onload 可以写成 window['onload'],参考:行内样式style的获取
    if (typeof obj['on' + type] == 'function') { //判断是不是事件
        saved = obj['on' + type]; //如果有,保存起来
    }
    obj['on' + type] = function () { //然后执行
        if (saved) saved(); //执行上一个
        fn.call(this); //执行函数,把 this 传递过去
    };
}
addEvent(window, 'load', function () { //执行到了
    let banner = document.getElementById('banner');
    console.log('banner',banner);
});
addEvent(window, 'load', function () { //执行到了
    let main_business = document.getElementById('main_business');
    console.log('main_business',main_business);
});


//切换器
addEvent(window, 'load', function () { //执行到了
    let box = document.getElementById('box');
    // addEvent(box,'click',function(){
    //     alert(123);
    // });
    addEvent(box,'click',toBlue);
});
function toRed(){
    this.className = 'red';
    //this.onclick = toBlue;
    addEvent(this,'click',toBlue);
}
function toBlue(){
    this.className = 'blue';
    // this.onclick = toRed;
    addEvent(this,'click',toRed);
}

//以上函数其实也是有很多漏洞的,比如我们多次点击会报错,因为积累了太多的保存事件
//解决的话,就是用完的事件,立刻移除掉释放内存,写一个移除事件函数
//移除事件函数
function removeEvent(obj, type) {
    if (obj['on'] + type) obj['on' + type] = null; //删除事件处理函数
}

function toRed(){
    this.className = 'red';
    //添加事件之前,移除之前的事件
    removeEvent(this, 'click');//方法比较粗暴
    addEvent(this,'click',toBlue);
}
function toBlue(){
    this.className = 'blue';
    removeEvent(this, 'click');//方法比较粗暴,存在误杀,重新弹窗123无法执行
    addEvent(this,'click',toRed);
}

上面这个函数不需要大家记忆,而且我们写的这个函数也存在一些问题,理解一下即可。讲传统事件绑定的问题主要是为了让大家更好的理解,我们接下来讲的现代事件绑定就是为了解决上面的问题及其优越性。

# ③ 事件绑定处理函数:添加事件addEventListener(),删除事件removeEventListener()

所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数;事件名、函数、冒泡或捕获的布尔值(true 表示捕获,false 表示冒泡)。

//现代浏览器自带这两个事件处理函数,添加事件addEventListener()删除removeEventListener()
//所有 DOM 节点中都包含这两个方法,并且它们都接受 3 个参数;事件名、函数、冒泡或捕获的布尔值(true 表示捕获,false 表示冒泡)。
//1、解决同名事件覆盖问题
window.addEventListener('load', function () {
    let banner = document.getElementById('banner');
    console.log('banner',banner);
}, false);
window.addEventListener('load', function () {
    let main_business = document.getElementById('main_business');
    console.log('main_business',main_business);
}, false);


//2.相同函数屏蔽问题
window.addEventListener('load', init, false); //第一次执行了
window.addEventListener('load', init, false); //第二次被屏蔽了
function init() {
   alert('黑丝');
}


//3、是否可以传递this,移除指定事件
window.addEventListener('load',function(){
    let box = document.getElementById('box');
    box.addEventListener('click',toBlue,false);
},false);
function toBlue(){
    console.log(this);
    this.className = 'blue';
    this.removeEventListener('click',toBlue,false);
    this.addEventListener('click',toRed,false);
}
function toRed(){
    this.className = 'red';
    this.removeEventListener('click',toRed,false);
    this.addEventListener('click',toBlue,false);
}


//4.添加一个额外的方法,会不会被覆盖,或者只执行一次
window.addEventListener('load',function(){
   let box = document.getElementById('box');
   box.addEventListener('click',function(){
       alert('黑丝');
   },false);
   box.addEventListener('click',toBlue,false);
},false);
function toBlue(){
    console.log(this);
    this.className = 'blue';
    this.removeEventListener('click',toBlue,false);
    this.addEventListener('click',toRed,false);
}
function toRed(){
    this.className = 'red';
    this.removeEventListener('click',toRed,false);
    this.addEventListener('click',toBlue,false);
}

可以发现现代事件绑定函数addEventListener(),removeEventListener()能够解决上一节我们碰到的问题,推荐大家使用这两个事件绑定处理函数!

# ④ 冒泡和捕获

window.addEventListener('load',function(){
    let box = document.getElementById('box');
    //冒泡
    // box.addEventListener('click',function(){
    //     alert('box');
    // },false);
    // document.addEventListener('click',function(){
    //     alert('document');
    // },false);
    //发现点击div, 先执行弹出box, 后执行弹出document

    //捕获
    box.addEventListener('click',function(){
        alert('box');
    },true);
    document.addEventListener('click',function(){
        alert('document');
    },true);
    //发现点击div, 先执行弹出document, 后执行弹出box


 },false);

 //冒泡从里面往外进行,捕获从外面往里面进行
 //我们的addEventListener()事件绑定函数可以设置冒泡和捕获方式
 //一般情况下,我们用冒泡比较多些

# Ⅴ、事件对象其他属性方法

# ① 属性:relatedTarget;这个属性可以在 mouseover 和 mouseout 事件中获取从哪里移入和从哪里移出的 DOM 对象。

<style>
    .red{
        width: 300px;height: 300px;margin: 50px;
        background-color: red;
    }
    .blue{
        width: 400px;height: 400px;margin: 50px;
        background-color: blue;
    }
</style>
<div id="pox" class="blue">
   <div id="box" class="red"></div>
</div>


let box = document.getElementById('box');
box.onmouseover = function (e) { //鼠标移入 box
    console.log(e);
    console.log('获取移入 box 最近的那个元素对象', e.relatedTarget); 
} 
box.onmouseout = function (e) { //鼠标移出 box
    console.log('获取移出 box 最近的那个元素对象', e.relatedTarget); 
}

window.addEventListener('load',function(e){
    let box = document.getElementById('box');
    box.onmouseover = function (e) { //鼠标移入 box
        console.log(e);
        console.log('获取移入 box 最近的那个元素对象', e.relatedTarget); 
    } 
    box.onmouseout = function (e) { //鼠标移出 box
        console.log('获取移出 box 最近的那个元素对象', e.relatedTarget); 
    }
},false);

# ② 阻止事件的默认行为:e.preventDefault()

比如a标签,天生就有链接的功能,表单天生就有提交数据的功能

<a href="https://www.baidu.com" id="link">去百度</a>

let link = document.getElementById('link');
//之前学过终止程序return false;不让它执行看能不能阻止跳转
//发现可以,但是发现几个问题
//1. 为了保证程序执行,return false;必须放在最后 alert('黑丝');
//2. 如果中途有返回如 return true;这直接跳转了,写在最后无法阻止默认行为 return true;
//3. 如果放在前面,后面的代码又无法执行了
link.onclick = function(e){
    alert('黑丝');
    return true;
    return false;
}
//4.放在现代绑定事件里面不起作用
link.addEventListener('click',function(){
    return false;
},false);


let link = document.getElementById('link');
link.onclick = function(e){
    e.preventDefault(); //阻止默认行为,放哪里都可以
    alert('黑丝');
}
//那么有同学就问,它天生有这个功能,你为何要阻止它?
//有时候,比如说我们用第三方工具jquery(下一季课程我们会讲到)提交我们的表单数据
//因为jquery提交表单非常好用,这个时候,你得先阻止表单默认提交功能先屏蔽,才能用jquery
//这个时候就需要用个这个方法,阻止默认行为

# ③ 上下文菜单事件:contextmenu,取消鼠标右击弹出的菜单

当我们右击网页的时候,会自动出现 windows 自带的菜单。那么我们可以使用 contextmenu 事件来修改我们指定的菜单,但前提是把右击的默认行为取消掉。

#menu{
    width: 50px;
    background-color: #cccccc;
    position: absolute;
    display: none;
}
<ul id="menu">
    <li>菜单1</li>
    <li>菜单2</li>
    <li>菜单3</li>
</ul>
window.addEventListener('load',function(e){
   document.addEventListener('contextmenu',function(e){
        e.preventDefault();
        let menu = document.getElementById('menu');
        menu.style.display = 'block';
        menu.style.left = e.clientX + 'px';//网页左边距离
        menu.style.top = (e.clientY + document.documentElement.scrollTop) + 'px';//网页顶部距离
        
        //在点击一下鼠标左键消失
        document.addEventListener('click',function(){
            document.getElementById('menu').style.display = 'none';
        },false);

   },false);
},false);

# ④ 鼠标滚轮:mousewheel事件对象,用于获取鼠标上下滚轮的距离,事件作用在document上

document.addEventListener('mousewheel',function(e){
    console.log(e.wheelDelta);//鼠标滚轮滚一次的距离
},false);







# 课程其它章节

# 章节1.课程介绍

# 章节2.javascript基础

# 1、变量

# 2、关键保留字

# 3、语法构成

# 4、数据类型

# 章节3.javascript运算符

# 1、一元运算符

# 2、算术运算符

# 3、赋值运算符

# 4、关系运算符

# 5、逻辑运算符

# 6、字符串、逗号、三元条件运算符

# 7、运算符优先级

# 章节4.流程控制语句

# 1、if语句

# 2、switch 语句

# 3、循环语句

# 4、for...in 语句 枚举对象的属性

# 5、break 和 continue 语句 退出循环语句

# 6、with语句

# 章节5.javascript函数

# 1、函数声明

# 2、函数 return 返回值

# 3、函数的arguments 对象

# 章节6.javascript对象

# 1、创建对象

# 2、对象属性输出、方法(函数)调用

# 3、对象中的方法及应用

# 章节7.javascript数组

# 1、创建及读取数组

# 2、数组中的属性和内置方法:toLocaleString()、valueOf()和 toString()

# 3、数组中的方法:join()、push()、pop()、shift()、unshift()、reverse()、sort()、concat()、slice()、splice()方法

# 4、数组更多操作方法

# 章节8.Date类型:时间和日期

# 1、Date 类型

# 2、格式化日期和时间

# 章节9.Function类型:函数进阶

# 1、函数的声明

# 2、作为值的函数

# 3、函数内部属性

# 4、函数的属性和方法

# 章节10.内置对象:Global、Math对象,变量、作用域和内存问题

# 1、内置对象: Global对象

# 2、内置对象: Math 对象

# 3、变量

# 4、作用域(执行环境)

# 5、 内存问题

# 章节11.匿名函数和闭包

# 1、匿名函数如何自动执行-()()方式

# 2、通过闭包实现局部变量的累加

# 3、循环里的匿名函数取值问题:任何变量都是最后一个值

# 4、闭包中的this对象问题

# 5、 匿名函数私有化:匿名函数自我执行模仿块级作用域,将变量私有化保护数据

# 章节12.javascript基本包装类型

# 1、Number 类型

# ① Number类型静态属性:MAX_VALUE、MIN_VALUE、NaN、NEGATIVE_INFINITY、POSITIVE_INFINITY、prototype
# ② Number类型方法:toString()、toLocaleString()、toFixed()、toExponential()、toPrecision()

# 2、String类型

# ① String类型属性:length、constructor、prototype
# ② String类型通用方法:valueOf()、toLocaleString()和 toString()方法
# ③ String类型方法:charAt(n)、charCodeAt(n)、数组方式截取字符串、concat(str1...str2)、slice(n,m)、substring、substr、indexOf、lastIndexOf、toLowerCase、toUpperCase、toLocaleLowerCase、toLocaleUpperCase、match、search、replace、replaceAll、split、fromCharCode、localeCompare、startsWith、endsWith、includes、trimStart、trimEnd、trim、repeat、魔法字符串

# 3、Array数组的常用方法

# ① 数组中的属性length,数组内置方法toLocaleString()、valueOf()和 toString()
# ② join、push、pop、shift、unshift、reverse、sort、concat、slice、splice
# ③ includes、reduce、find、findIndex、filter、map、forEach
# ④ indexOf、lastIndexOf、at、fill、copyWithin、isArray、es6的扩展运算符、every、some、for...of遍历、keys()、values()、entries()、reduceRight、flat、flatMap、from、of
# ⑤ 集合类型Set()属性方法:size属性、add、has、delete、clear方法,迭代遍历数据:keys()、values()、entries()、for...of遍历,forEach遍历
# ⑥ 小结拓展:获取两个数组交集、并集、差集,获取数组随机元素(验证码)

# 章节13.函数对象数组小结

# ① 对象的方法:for...in(遍历对象)、Object.assign()、Object.create()、Object.freeze()、Object.keys()、Object.values()、Object.entries()
# ② 数组解构:基础用法、省略元素、设置数组元素默认值、嵌套数组(多维数组)的结构赋值、不定元素的解构赋值
# ③ 对象解构:基础用法、重命名解构变量、对象解构时的默认值
# ④ var和let、const的区别
# ⑤ 函数参数默认值:对arguments的影响、对函数length的影响、函数默认表达式、参数默认值的暂时性死区
# ⑥ 函数剩余参数:剩余参数只能出现一个、剩余参数只能放在最后
# ⑦ 展开运算符:针对可迭代对象展开、字面量形式的对象做对象克隆和对象合并

# 章节14.浏览器对象模型BOM及浏览器检测

# ① window 对象:属性和方法、系统对话框、调出打印机print()、网页新建窗口open()、窗口页面的位置:screenX(screenLeft)和 screenY(screenTop)、窗口页面的大小:innerWidth和 innerHeight,outerWidth 和 outerHeight,document.documentElement.clientWidth和document.documentElement.clientHeight
# ② window对象:超时调用:setTimeout()方法、取消超时调用:clearTimeout()方法、间歇调用:setInterval()方法、取消间歇调用:clearInterval()方法、模拟定时器功能,超时调用模拟间歇
# ③ location 对象:获取网址相关信息、assign()方法跳转到指定页面、reload()方法、repalce()方法、获取网址url参数中的键值对、history 对象 : 属性:length,方法:back()、forward()、go(num)
# ④ 获取浏览器相关信息、浏览器插件检测、MIME 类型检测、判断设备是安卓、苹果、是否在微信上面浏览网页、用户查看网页的设备是电脑设备,还是手机设备

# 章节15.网页文档对象模型DOM

# 1、理解DOM

# ① DOM节点树,节点种类:元素节点、文本节点、属性节点

# 2、查找节点信息方法

# ① getElement系列: getElementById()方法,getElementById()方法获取的元素节点属性:tagName、innerHTML等, 获取及设置元素html属性:id,title,style,className等,getElementsByTagName()方法,getElementsByName()方法,getElementsByClassName()方法
# ② querySelector系列: querySelector()方法,querySelectorAll()方法
# ③ 属性操作系列: getAttribute()方法,setAttribute()方法,removeAttribute()方法
# ④ node节点属性: nodeName、nodeType、nodeValue
# ⑤ 层次节点属性: childNodes属性,firstChild 和 lastChild 属性,ownerDocument 属性,parentNode 属性,previousSibling 属性,nextSibling 属性,忽略空白文本节点,attributes 属性

# 3、操作节点

# ① document.write()方法,createElement()方法,appendChild()方法,createTextNode()方法, insertBefore()方法,模拟在指定节点的后面添加一个节点,repalceChild()方法,cloneNode()方法,removeChild()方法

# 4、DOM 类型、扩展、操作内容

# ① DOM 类型: Document类型补充(获取html节点对象,获取body节点对象,获取文档声明:DOCTYPE对象,document.title,document.images,document.links等),Text类型补充(normalize()方法,splitText(num)方法,deleteData删除字符,insertData插入字符,replaceData替换字符,substringData获取字符)
# ② DOM 扩展: scrollIntoView(), children 属性,contains()方法
# ③ DOM操作网页内容:innerText、innerHTML、outerText、outerHTML

# 5、DOM实战:操作表格

# ① DOM创建表格
# ② HTML DOM 来操作表格: 获取表格元素对象, 获取表格的标题对象<caption>,获取表头表尾<thead>、<tfoot>、<tbody>,获取表格的行数,获取表格主体里的行数,获取表格主体内第一行的单元格数量(tr),获取表格主体内第一行第一个单元格的内容(td),删除标题、表头、表尾、行、单元格,HTML DOM 创建一个表格

# 6、操作CSS样式

# ① 行内样式style的获取、赋值、移除属性removeProperty,计算后的样式获取(行内、内联、外联样式):window 对象下getComputedStyle()方法

# 7、操作页面样式

# ① className关键字设置样式,创建函数:hasClass() 判断是否存在某个类名,addClass() 如果不存在的这个类名,添加这个类名,removeClass() 如果存在的这个类名,删除这个类名

# 8、操作CSS外联样式表.css文件

# ① 获取CSSStyleSheet,外联的css样式表对象

# 9、DOM元素尺寸(元素大小)和位置(元素位置)

# ① 获取元素 CSS 大小回顾:通过 style 内联获取元素大小,通过计算getComputedStyle()方法获取元素大小,通过 CSSStyleSheet 对象中的 cssRules属性获取元素大小(需将网页放到服务器上查看)
# ② 获取元素实际大小:clientWidth 和 clientHeight:获取可视区的元素大小,可以得到元素内容及内边距所占据的空间大小,scrollWidth 和 scrollHeight:获取滚动内容的元素大小,offsetWidth 和 offsetHeight:获取元素大小,包含边框、内边距和滚动条
# ③ 获取元素周边大小位置:clientLeft 和 clientTop:获取元素设置了左边框和上边框的大小;offsetLeft 和 offsetTop:获取当前元素相对于父元素的位置;scrollTop 和 scrollLeft:这组属性可以获取滚动条被隐藏的区域大小(滚动条滚动高度宽度),也可设置定位到该区域(定位滚动条); getBoundingClientRect()方法:返回一个矩形对象,包含四个属性:left、top、right和 bottom,分别表示元素各边与页面上边和左边的距离

# 10、动态加载脚本

# ① 动态加载js文件,动态加载样式表

# 章节16.事件

# 1、事件基础知识

# ① 事件处理函数列表
# ② 内联模型
# ③ 脚本模型

# 2、事件处理函数分类

# ① 鼠标事件:click,dblclick,mousedown,mouseup,mouseover,mouseout,mousemove
# ② 键盘事件:keydown,keypress,keyup
# ③ HTML事件(包括表单事件):load,unload,resize,scroll,select,change,input,submit,reset,focus,blur

# 3、事件对象

# ① this 关键字和上下文
# ② 获取事件对象
# ③ 获取鼠标按钮(左键、右键、中间滚轮)
# ④ 可视区及屏幕坐标:clientX,clientY,screenX,screenY,鼠标点击位置距离页面顶部距离(带滚动条)
# ⑤ 修改键:shiftKey对应Shfit 键,ctrlKey对应 Ctrl 键,altKey 对应 Alt 键,metaKey 对应 windows 键,判断是否按下了它们
# ⑥ 键盘事件:键码(键盘上的任意键)keyCode,字符编码(键盘上可以输出字符的键)charCode
# ⑦ 事件对象的常用属性和方法:target,事件冒泡,取消:e.stopPropagation()
# ⑧ 事件对象其他属性方法:relatedTarget(在 mouseover 和 mouseout 事件中获取),e.preventDefault(),contextmenu取消鼠标右击弹出的菜单,鼠标滚轮:mousewheel

# 4、事件绑定

# ① 剖析传统事件绑定问题:同名事件后者覆盖前者,this 传递问题
# ② 现代事件绑定处理函数:添加事件addEventListener(),删除事件removeEventListener(),设置冒泡和捕获

# 章节17.表单处理及错误处理与调试

# 1、表单基础知识

# ① 获取form对象的方法
# ② 提交表单:默认提交form表单按钮type="submit",js中表单提交及阻止提交,让没有提交功能的按钮(其它元素)完成表单提交使用的是form.submit()方法,实现使用键盘 CTRL + enter(回车键)提交数据
# ③ 预防表单数据重复提交,表单重置:按钮重置type="reset",js重置 form.reset()方法

# 2、使用HTML DOM获取表单控件元素

# ① elements属性获取表单控件元素集合
# ② 多个表单字段都使用同一个 name,如单选框多选框,常用属性
# ③ 表单共有字段方法:foucs(),blur(),表单共有的字段事件:blur,change,input,focus

# 3、表单中的文本框

# ① input.value 和 textarea.value 获取输入的内容,defaultValue获取默认设置的value值
# ② 选择文本:使用 select()方法,可以将文本框里的文本选中,并且将焦点设置到文本框中
# ③ 选择部分文本:setSelectionRange()方法,这个方法接受两个参数:索引和长度
# ④ select 事件,选中文本框文本后触发
# ⑤ 获取选择的文本,两个属性:selectionStart 和 selectionEnd
# ⑥ 文本过滤输入(表单验证,文本验证,输入框验证)
# ⑦ 剪切事件:cut,复制事件:copy,粘贴事件:paste,禁用输入法
# ⑧ 利用正则表达式将用户输入的非数字字符替换成空,只留下数字
# ⑨ 输入框自动切换焦点到下一个输入框(类似Tab键),maxlength属性可输入的最大字符长度

# 4、表单中的下拉列表(选择框)

# ① 获取下拉列表对象
# ② 下拉列表多选属性 multiple="multiple"(按住键盘CTRL键进行多选),size显示的行数
# ③ 属性 options(下拉选项的集合)
# ④ 选择选项:选择一项的使用selectedIndex属性,监听的是下拉列表的change事件
# ⑤ 下拉列表动态添加选择项: 使用Option 构造函数,add方法添加选项
# ⑥ 下拉列表动态移除选择项:三种方式(removeChild移除、remove()方法移除和 null 移除)
# ⑦ 下拉列表动态移动选择项(两个下拉列表选项移动)
# ⑧ 下拉列表动态排序选择项(选项排序):属性index
# ⑨ 获取单选框或者多选框选择的值: 判断checked属性是否为真判断选中的项,defaultChecked判断项目原本设置的默认值

# 5、错误与调试

# ① 浏览器错误报告,错误处理:try-catch 语句,finally 子句,错误类型
# ② try-catch使用场景,抛出错误,错误事件error:处理DOM对象产生错误时候使用,比如加载图片失败,错误处理策略,调试技术和调试工具

# 章节18.数据Cookie、XML、JSON

# 1、Cookie、sessionStorage、localStorage

# ② 封装cookie(创建,获取,删除)
# ③ sessionStorage、localStorage

# 2、XML

# ① 创建 XMLDOM 对象(document.implementaion,DOMParser类型创建XMLSerializer类型进行序列化成字符串),解析错误
# ② 加载读取外部xml文件
# ③ XPath操作XML
# ④ 封装XPath操作xml文件

# 3、JSON

# ①JSON 语法,可以表示三种类型的值
# ② 获取外部json文件
# ③ 解析json字符串(将json字符串转成数组对象——json结构数据):JSON.parse()方法
# ④ js原生值数组、对象转成json字符串--JSON.stringify()
# ⑤ toJSON



# 其它学期课程

# 第一学期(学习顺序:01)

第一学期课程专为零基础的学员定制录制的,纯html+css做企业网站的网页,主讲html和css的相关基础知识,flex布局相关知识,封装css基础样式库,引入字体图标及网页开发基础布局思维,完成企业网站网页的开发过程。

[第一学期学习视频]

# 第二学期【第1季】(学习顺序:02)

主讲JavaScript的基础,建议所有学员观看。
[第1季学习文档] [第1季学习视频]

# 第二学期【第2季】(学习顺序:03)

JavaScript中的面向对象,类,ajax,封装js库过渡到jQuery, vue.js基础配置网站页面,建议所有学员观看。
[第2季学习文档] [第2季学习视频]

# 第二学期【第3季】(学习顺序:04)

egg.js基础,响应式网页布局,Bootstrap框架,响应式后台系统管理,完整企业网站前后台开发,建议所有学员观看。
[第3季学习文档] [第3季学习视频]

# 第二学期【第4季】(学习顺序:05)

主要对第三季,同学们开发的企业网站,进行一个完整的上线运维流程的一个讲解,同学们将网站开发完成之后,如何进行上线运维,将项目交付给客户。
[第4季学习文档] [第4季学习视频]

更新时间: 2024年11月19日星期二中午11点54分