20.this对象
对于要绑定的多个对象的事件内容相同时可以使用循环来绑定,注意这时要使用this对象拿到当前调用函数的对象的属性和方法,不能直接使用循环变量作为角标。
1 this 对象基础内容
<!-- 大坑坑坑坑!!!!!!!用循环给对象绑定相同的事件 -->
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<script>
var divs = document.getElementsByTagName("div")
for (var i = 0; i < divs.length; i++) {
divs[i].onclick = function () {
console.log(divs[i])//输出结果:undefined
alert(divs[i].innerHTML)
}
}
//运行结果显示:类型错误,由于循环内的divs[i]不是元素对象
//1 点击事件是在循环结束后发生的
//2 循环干了什么事? 给每个元素绑定了一个事件
//3 i是全局变量 在某一时刻只能有一个值 在循环结束后i=4
//在点击的时候才赋值,在点击之前循环只是给绑定了事件,并没有完成点击这个动作。当进行点击动作时循环已完成i变成了4,而元素对象的数组中并没有角标为4的
//这时引出this对象
</script>
<!-- 解决方法1:将点击事件中函数的divs[i]改为this对象 -->
<!--解决方法二:不用var ,使用let声明,因为let作用域-->
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<script>
var divs = document.getElementsByTagName("div")
for (var i = 0; i < divs.length; i++) {
divs[i].onclick = function () {
alert(this.innerHTML)//this指向的是divs[i],i=0,1,2,3
}//原来不管谁点击,i的值都是4,alert输出的都是divs[4].innerHTML,而现在谁点击,alert输出的就是 谁的.innerHTML 点谁操作谁就用this
}
//运行结果显示:类型错误,由于循环内的divs[i]不是元素对象
//1 点击事件是在循环结束后发生的
//2 循环干了什么事? 给每个元素绑定了一个事件
//3 i是全局变量 在某一时刻只能有一个值 在循环结束后i=4
//在点击的时候才赋值,在点击之前循环只是给绑定了事件,并没有完成点击这个动作。当进行点击动作时循环已完成i变成了4,而元素对象的数组中并没有角标为4的
//这时引出this对象
</script>
this代表一个对象,this一般用在函数内部,this指向的是函数调用时所在的对象。
this对象在对一些对象绑定相同的事件时使用for循环的时候使用this对象,this对象是点谁谁做出改变,如果是点谁而另一个发生改变,这时就需要找到这二者之间的关系,找到相同的一些内容作为this对象指向的属性,通过this找到这个元素对象的属性,再通过该属性改变另一个元素对象的内容。
this指向的是函数运⾏时所在的对象(谁调⽤了函数,那么函数中的this就指谁(而不是在谁中定义的就指向谁)) ,如果调用的时没有指定谁调用,那么就是window全局对象调用。
<script>
var obj1 = {
name: "小明",
intr: intr
}
var obj2 = {
name: "小红",
intr: intr
}
function intr() {
console.log(this)
}
obj1.intr()//输出结果:小明的对象,obj1调用函数,这时的this指向的是obj1
obj2.intr()//输出结果:小红的对象,obj2调用函数,this指向obj2
intr()//输出结果:window的对象内容
var name = "我是全局"
var obj1 = {
name: "小明",
intr: function () {
console.log(this.name)
}
}
var c = obj1.intr
c()//输出结果:我是全局
/*因为obj1.intr并没有调用,只是将值取出来
var c=function(){console.log(this.name)}
等价于 function c(){console.log(this.name)}
在这时调用c()等价于window.c(),这时this指向的就是window全局变量,输出的为全局变量name的值
*/
</script>
2 window全局对象
我们定义的全局变量和全局函数本质上都是window的属性和方法,在访问的时候可以省略前缀。
var a = 1
console.log(a)//输出结果:1,二者等价
console.log(window.a)//输出结果:1
3 this对象的实战:实现选项卡之间的切换
由于四个选项卡绑定事件的内容都相同,因此使用循环对这些元素对象绑定事件,注意使用this对象拿到当前调用函数的属性和方法,否则会出现点击事件在循环完成之后进行的,这时的循环变量在结束循环之后的值作为角标在元素对象数组中找不到与之对应的元素对象。
this点谁谁改变,点击后另一个元素对象发生改变,找到二者通过角标可以联系起来,因此将角标作为该元素的属性添加进去,通过this找到该元素的角标之后,通过角标对另一个元素对象做修改内容。
方法一:使用this的指向。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
.tab {
width: 80px;
height: 50px;
background-color: gray;
float: left;
margin-right: 5px;
text-align: center;
line-height: 50px;
color: white;
}
.content {
width: 335px;
height: 300px;
background-color: pink;
display: none;
}
.act {
display: block;
}
</style>
</head>
<body>
<div class="tab">体育新闻</div>
<div class="tab">科技新闻</div>
<div class="tab">电影新闻</div>
<div class="tab">娱乐新闻</div>
<div style="clear: both;"></div>
<div>
<div class="content act">
<p>我是体育新闻1</p>
<p>我是体育新闻2</p>
<p>我是体育新闻3</p>
</div>
<div class="content">
<p>我是科技新闻1</p>
<p>我是科技新闻2</p>
<p>我是科技新闻3</p>
</div>
<div class="content">
<p>我是电影新闻1</p>
<p>我是电影新闻2</p>
<p>我是电影新闻3</p>
</div>
<div class="content">
<p>我是娱乐新闻1</p>
<p>我是娱乐新闻2</p>
<p>我是娱乐新闻3</p>
</div>
</div>
<script>
var tabs = document.getElementsByClassName("tab")
var contents = document.getElementsByClassName("content")
var index = 0
//由于这四个的绑定的事件内容相同,使用循环和this对象
for (var i = 0; i < tabs.length; i++) {
tabs[i].index = i
tabs[i].onclick = function () {
//this是点谁谁变化,而我们要求的是点谁另一个变化
//经过对比我们发现:点击的和要操作的角标相同,此时我们采用点击后拿到this.角标,通过该角标再对另一个操作
//要得到角标的方法:将角标添加为属性,通过属性拿到角标值
// contents[this.index].style.display = "block"
//这时点击后会出现相应的内容,但此时点击之后之前点击的内容也会显示,为了让只显示当前点击的那个,我们采用当点击的时候把所有的都隐藏了,再将当前的显示就行
for (var j = 0; j < contents.length; j++) {
contents[j].style.display = "none"
//点谁谁背景色变化,其余的不变,以便于能看出是点击了谁,同样是先将所有的tab变为灰色,再单独改变其中一个的背景色
tabs[j].style.backgroundColor = "gray"
}
contents[this.index].style.display = "block"
tabs[this.index].style.backgroundColor = "red"
}
}
</script>
</body>
</html>
方法二:使用let 。
let tabs = document.getElementsByClassName("tab")
let contents = document.getElementsByClassName("content")
for (let i = 0; i < tabs.length; i++) {
tabs[i].onclick = function () {
// 先把所有的隐藏,再把当前的显示
for (let j = 0; j < tabs.length; j++) {
tabs[j].style.backgroundColor = "gray"
contents[j].style.display = "none"
}
tabs[i].style.backgroundColor = "red"
contents[i].style.display = "block"
}
}
21.定时器(运用的不好重点):window对象的方法
1 基本内容
定时器是让⽹⻚⾃动运⾏的唯⼀办法
1、周期性定时器:每隔⼀段时间,做什么事
setInterval(干什么事的函数名,间隔毫秒数)
2、⼀次性定时器:等待⼀定的时间,做什么事 ,就执行一次
setTimeout(干什么事的函数名,等待毫秒数)
同步:从上往下顺序执行,前面不走完后面不能走;
异步:
定时器是⼀个异步多线程的程序,每开一个定时器都增加一个线程,再加上本来JS就有的线程。而主线程永远是最先执行的(同步代码永远是最快的)
setTimeout(function () {
console.log(123)
}, 3000)//等待3s
console.log(456)
//输出结果:先输出456,后输出123,因为定时器是异步多线程的
setTimeout(function () {
console.log(123)
}, 0)//等待0s
console.log(456)
//输出结果:先输出456.后输出123,因为主线程的同步代码永远是最先执行的。
setTimeout(function () {
console.log(123)
}, 1000)//等待1s
setTimeout(function () {
console.log(789)
}, 500)//等待0.5s
console.log(456)
//输出结果:456 789 123
3、停⽌定时器
定时器的返回值是线程号,因此在使用定时器时如果是需要关闭的常常用一个变量将定时器存储起来。
clearInterval(线程号) 后面常常紧跟着 timer=null 用于释放内存
clearTimeout(线程号) timer=null
var timer1 = setInterval(function () {
console.log("aaa")
}, 1000)
var timer2 = setInterval(function () {
console.log("bbb")
}, 3000)
clearInterval(timer1)
4、定时器的变量一般定义在全局,因为定时器的开启和关闭可能不再同一个函数中使用。
2 定时器案例
案例一:倒计时 周期性定时器
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="txt"></div>
<script>
//作业:完善倒计时,将显示的时分秒改为:,且随着每秒的时间变化闪动
/*案例一:倒计时 周期性定时器 距离下课还有多长时间
用到了日期Date对象,需要计算时间差+定时器开启和停止
注意1:开启了定时器之后要关闭定时器,当时间间隔大于0的时候才会进行单位换算并输出时间差,否则将定时器停止
注意2:时分秒的显示格式完善,如将9显示为09
注意3:将时分秒的显示改为冒号,且随着每秒的时间变化闪动
方法一:用flag 标记
方法二:用跳动的奇偶数
*/
var txt = document.getElementById("txt")
var timer = setInterval(clock, 1000)
//结束时间的日期对象
var targrt = new Date("2023-5-31 18:58:00")
var col = ":"
var flag = true
function clock() {
//当前日期对象
var now = new Date();
//时间间隔 返回的是毫秒数
var ms = targrt - now
//当时间间隔大于0时才做下面的内容,否则到了指定的时间点后应该停下来,停止定时器
if (ms > 0) {
//时间间隔时分秒换算
var h = Math.floor(ms / 1000 / 60 / 60)//向下取整
//时分秒格式完善,将9显示为09
h = h < 10 ? "0" + h : h
var min = Math.floor((ms - h * 60 * 60 * 1000) / 1000 / 60)
min = min < 10 ? "0" + min : min
var s = Math.floor((ms - h * 60 * 60 * 1000 - min * 60 * 1000) / 1000)
s = s < 10 ? "0" + s : s
if(flag){
col=":"
}else{
col=" "
}
txt.innerHTML = "距离下课还有" + h + col + min + col + s
flag = !flag
} else {
clearInterval(timer)
txt.innerHTML = "下课了"
}
}
</script>
</body>
</html>
案例二:获取验证码 周期性定时器
按钮禁用:button的属性disabled=”true”表示该按钮不可用。
按钮禁用时鼠标变为红色的圈:css属性 cursor:not-allowed(红色圈)/pointer(小手指)
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div>
<button id="btn">获取验证码</button>
</div>
<script>
/* 定时器案例2:获取验证码 周期性定时器 点击一次按钮之后显示内容变为59 58 57 秒后重新发送,在此期间该按钮是无法点击的,直到60秒后才可重新点击
补充1:按钮禁用:button的属性disabled="true"表示不可用。
补充2:当按钮禁用时,鼠标变为红色的圈,
属于css样式:cursor:not-allowed(红色圈)/pointer(小手指)/default(默认的箭头)
注意1:当点击一次按钮后就开启一个定时器,而此时的n是全局变量,会导致n的值快速变化,这时我们需要设置点击一次按钮后就将按钮禁用掉
注意2:当n的值为0停止定时器后需要将按钮的状态和内容全都恢复成开始的样子
注意3:在n的值为0后再次点击按钮,出现无法继续显示倒计时,这时因为此时n的值已经变为了0,需要将n的值恢复成初始的开始值
*/
var btn = document.getElementById("btn")
var timer;
var n = 5//起始时间
btn.onclick = function () {
//在1秒的空隙内无法进行点击按钮
btn.disabled = true
btn.style.cursor = "not-allowed"
timer = setInterval(clock, 1000)
}
function clock() {
if (n > 0) {
n--
btn.innerHTML = n + "秒后重新获取验证码"
} else {
clearInterval(timer)
btn.innerHTML = "重新获取验证码"
btn.style.cursor = "auto"
btn.disabled = false
n = 5;//需要恢复n的值
}
}
</script>
</body>
</html>
案例三:烦人的关不掉的小广告 周期性定时器+一次性定时器
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
#adv {
position: fixed;
right: 0;
/* bottom的负值要比图片的高度要大,因为还有按钮的高度 */
bottom: -335px;
}
</style>
</head>
<body>
<div id="adv">
<div>
<button id="btn">关闭广告</button>
</div>
<img src="images/adv.jpg" alt="">
</div>
<script>
/*定时器案例三:烦人的小广告 进入某个网站几秒后,右下角逐渐一点点的出来小广告,点击关闭按钮之后广告下去,之后几秒钟后就又出来了
周期性定时器+一次性定时器
注意1:将小广告放在右下角,以浏览器为参照物,使用固定定位,一开始bottom的值为负的,值为图片高度+按钮高度,让按钮和图片放在浏览器的下面不显示出来
注意2:通过修改adv类的bottom值,周期性的往外走,实现逐渐出现的效果,使用周期性定时器
注意3:通过adv.style.bottom是无法获取当前对象的bottom值的,因为通过这种方法获取的属性必须是以行内样式的形式定义的。
获取元素的所有样式的方法:doucument.defaultView.getComputedStyle(要获取的元素对象,null)
注意4:获取到当前的bottom值之后做加法时,bottom值是字符串类型,需要截取其中的数字,我们使用window的方法parseFloat()获取以数字开头的字符串的数字部分
注意5:将bottom的单位px去掉之后,在给元素对象的bottom属性重新赋值时还需要再加上px
注意6:在点击了关闭按钮之后等待三秒重新打开广告:执行的方法不能直接是moveUp,因为moveUp只执行一次是无法将小广告向上走,需要开启周期性定时器一步步向上走
注意7:在广告上升的途中点击关闭按钮,会出现颤抖的现象,这时因为广告上升的途中定时器还未关闭,bottom还在加,而此时点击关闭按钮后又开启了一个定时器,bottom处在一下加10一下减10的过程中,这才导致的颤抖
解决方法1:只能在广告全部出来以后才可以点击,在此期间不能点击。在关闭定时器后将timre=null释放内存同时做点击时能否做开启向下走的定时器的判断
解决方法2:在上升途中允许点击关闭按钮,直接向下走:点击之后直接关闭定时器,再重新开启向下走的定时器
*/
var adv = document.getElementById("adv")
var btn = document.getElementById("btn")
var timer1 = null;//存储小广告向上逐渐出现的周期性定时器,将周期性定时器的初始值设为null,等到使用的时候再用
var timer2 = null;//存储一次性定时器,当进入网站就开启一次性定时器,3秒后就将小广告向上滑动
// 小广告向上走的方法:每0.1秒向上移动一次,获取现在的bottom值,在此基础上做bottom的修改,而不是直接就给bottom赋值
function moveUp() {
//获取当前的bottom值,在此基础上每次加10
var cssStyle = document.defaultView.getComputedStyle(adv, null)
var bottom = parseFloat(cssStyle.bottom)//此时的bottom值为-325px,带有像素的单位
//由于当前的bottom值带有px的单位,是字符串类型,如果直接+10,变成了-325px10,因此需要截取其中的数字部分,使用window的方法parseFloat()
if (bottom < 0) {
bottom += 10
adv.style.bottom = bottom + "px"//给bottom再加上单位px
} else {
clearInterval(timer1)
timer1=null//释放内存
}
}
//向下走的方法
function moveDown() {
var cssStyle=document.defaultView.getComputedStyle(adv,null)
var bottom=parseFloat(cssStyle.bottom)
if(bottom>-335){
bottom-=10
adv.style.bottom=bottom+"px"
}else{
clearInterval(timer1)
timer1=null
//注意6:等待3秒钟重新打开广告,开启周期性定时器
timer2=setTimeout(function(){
timer1=setInterval(moveUp,30)
},3000)
}
}
btn.onclick = function () {
//注意7解决方法1:如果没有向上走的定时器即timer1=null,此时就可以点击关闭按钮关闭
/*if(!timer1){
timer1 = setInterval(moveDown, 30)
}*/
//注意7解决方法2:点击按钮之后直接关闭定时器,再重新开启向下走的定时器
clearInterval(timer1)
timer1 = setInterval(moveDown, 30)
}
//一打开网页并不会弹出小广告,等3秒钟再弹出
window.οnlοad=function(){
timer2=setTimeout(function(){
timer1 = setInterval(moveUp, 30)
},3000)
}
</script>
</body>
</html>
3 防抖(面试重点)⭐⭐⭐⭐⭐ 一次性定时器+闭包
发送网络请求,在短时间内连续触发多次事件(点击、鼠标移动、鼠标滚轮、键盘回车)只执行一次。
核心思想:延迟执行,把一次性定时器先停再开。只要点击的间隔小于给定的时间,就只执行一次,如果点击的间隔大于给定的时间,就会再次执行。
应用场景:购物车。
实现效果:在5秒内点击1000次,只能执行1次。
在项目中常常有很多地方都需要用到防抖,因此我们常常将防抖函数封装起来,然后把要做的事情作为参数传进去,在使用的时候只需要调用防抖函数并把要做的事情作为参数传进就行。
想要用这个变量,不能放局部否则会每次都更新,放全局的话又封装不彻底且全局污染 ,而且调用几次就需要定义几个标记,这时就可以采用闭包来实现。
防抖函数:
//防抖函数
function debounce(fn){
var timer=null;
return function(){
clearTimeout(timer)
timer=setTimeout(function(){
fn()//要做的事情
},1000)
}
}
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<button id="btn">下单</button>
<button id="load">加载更多</button>
<script>
var btn=document.getElementById("btn")
var load=document.getElementById("load")
// var timer1=null
// var timer2=null
/*多个按钮不能共享同一个定时器,否则会导致前面的刚开定时器,点击了后面的按钮之后就把人家前面的定时器停止了,
如果是在全局声明多个定时器的变量,可以,但不建议,因为要少用全局变量有风险
如果把timer声明成局部变量,这样每次在点击的时候都会重新声明一个timer=null,上一次点击使用的timer并没有被关闭,不可以,因此不能每次都重新声明timer
*/
/* btn.οnclick=function(){
//必须是先停再开,这样就保证了最后只开了一个定时器
clearTimeout(timer1)
timer1=setTimeout(function(){
console.log("下单了")
},1000)
}
load.οnclick=function(){
clearTimeout(timer2)
timer2=setTimeout(function(){
console.log("加载更多")
},1000)
}*/
//两个按钮独自占用一个timer,调用两次debounce函数,点击按钮时生成各自的timer,
// btn.οnclick=debounce(function(){console.log("下单了")})
btn.οnclick=debounce(a);//事件要么写匿名函数,要么写函数名,如果要传参数的话需要在匿名函数中重新写函数名+参数。而这里直接写的函数名+参数,这时因为debounce函数中执行结果的返回值正好是一个匿名函数
load.οnclick=debounce(function(){console.log("加载更多数据")})
/*封装防抖函数
闭包:每个事件都要有自己的timer,既想要用timer的初始值,但是又不能每次执行时都重新声明,
*/
function debounce(fn){
//返回的是匿名函数
//每个要用的变量初始值,在调用该debounce函数时只声明一次timer=null,而下次执行debounce的时候就只执行返回的匿名函数
var timer=null
return function(){
clearTimeout(timer)
timer=setTimeout(function(){
//每次都要做的事情写在这里
//由于每个按钮要做的事情不一样,把要做的事情的函数作为参数传进来,然后执行的时候直接调用函数即可
fn()
},1000)
}
}
function a(){
console.log("下单了")
}
</script>
</body>
</html>
4 节流(面试重点)⭐⭐⭐⭐⭐ 一次性定时器+闭包
在短时间内连续触发多次事件,会减少执行次数。发送了1000次请求,在1秒内最多执行一次,10s内最多执行10次,这样就由原来的1000次减少到了10次,实现了节流。
应用场景:淘宝放大镜。
实现效果:在1s内点击多次之后只能执行一次,在5秒内如果点击了1000次只能执行5次,减少了执行次数。
防抖是只执行最后一次,突然变化的;节流是减少执行次数,还是有过程 的,比如,淘宝的放大镜在鼠标滑过时会触发几百次的事件,但我们不用这么多次,只需要几十次就可以了,这时就可以采用节流。不采用防抖是因为我们还需要变化的过程,防抖是欻一下过去了,没有中间的过程变化。
关键思想:一有人进来,闸门立刻关闭,闸门每隔一定时间就会打开进下一个人,进来之后闸门再次关闭。
先判断闸门是否开着,若没开不能进,后续都不在执行直接return,若开着,进去后立刻关闭闸门 ,1秒后再打开闸门,
节流函数:
//节流函数的封装
function throttle(fn) {
var flag = true//开门
return function () {
if (!flag) {
//如果是关门,则不用再继续了,后面的额事情不用在做了
return
}
flag = false//关门
//1s之后再开门
setTimeout(function () {
fn()
flag = true
}, 1000)
}
}
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<!-- 作业1:封装节流函数 -->
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<script>
var btn1=document.getElementById("btn1")
var btn2=document.getElementById("btn2")
btn1.οnclick=throttle(function(){console.log("123")})
btn2.οnclick=throttle(function(){console.log("456")})
//节流函数
function throttle(fn){
var flag=true
return function(){
if(!flag){
return
}
flag=false
setTimeout(function(){
fn()
flag=true
},1000)
}
}
</script>
</body>
</html>
22.window对象的方法
1、定时器:setInterval(要做事情的函数,间隔时间) setTimeout(要做事情的函数,等待时间) clearInterval(线程号) clearTimeout(线程号)
2、parseInt(“数字开头的字符串”):获取字符串中的以整数开头的部分,而中间和结尾的都不行
parseFloat(“数字开头的字符串”):获取字符串当中以小数和整数开头的部分,负数也可以
3、onload() 当网页加载完毕之后再执行
我们常需要先加载网页的内容再加载网页的功能,如果想要将js代码写在head标签里面,或者写在body的前面时就需要这个方法,等到网页加载完毕之后再执js代码。
<script>
window.οnlοad=function(){
//将所有的js代码写在此处
}
</script>
Number(“数字”):将字符串类型的数字转换为数字类型的数字
23.原型、继承、原型链
1、原型(prototype):⽅法背后,专⻔保存由⽅法创建出来的对象的共有属性。
原型就是构造函数的属性,它指向的是一个对象,这个对象里放的就是共有的属性,原型的值也是一个对象。
只有函数才有原型(不管是不是构造函数都有原型)。对象都没有原型(×,函数也是对象)
使用构造函数创建的对象,其中的 属性值如果没有得到修改,那么该对象只能使用该属性而不能拥有改属性,其所有权归构造函数所有。(自己有用自己的,自己没有从父级上找)
2、创建对象的方式:
1.对象字⾯量的形式(最常用的,声明变量的形式) var obj={name:"⼩明",age:18}
2.通过构造函数的形式 new Date() new Array(1,2,3) new Object() new RegExp()
3、构造函数/对象模板:专⻔⽤来创建相同结构对象的专⻔⽅法。(对象的结构相同只是属性值不一样)
构造函数的特点:
1.返回的一定是对象;
2.构造函数的函数名的首字母大写以此来区分于普通函数;
3.构造函数的调用:new 构造函数()
//自定义构造函数
function Fn(a,b,c){
this.a=a
this.b=b
...
}
//自己构造函数,给当前对象加属性
function Student(name, age) {
//给调用函数的当前对象添加一个name和age属性,其值分别为m和n
this.name = name
this.age = age
}
//构造方法的调用
var xm = new Student("小明", 18)
var xh = new Student("小红", 21)
console.log(xm, xh)
4、在构造函数中 并没有使用return却能够返回,这是因为在使用new的时候由于new关键字的原理已经有了return。只要有new就有对象。
new关键字的原理:new关键字做了哪⼏件事?
1.创建了⼀个空对象并取出构造函数和参数 var obj={}
2.修改对象obj的隐式原型的指向为创造其的构造函数
3.改变this指向 把构造函数中的this从指向全局的window改为调用函数的对象obj,通过call、apply,因为取出来的参数的格式是数组类型的,适合用apply
4给该对象加属性 this.name="⼩ 明" this.age=18
5.返回⼀个全新的对象 return obj
5、关于属性:
共有属性:由同⼀构造函数创建出来的对象共同享有的属性。构造函数中的属性并不是共有属性
共有属性是只属于构造函数的,这些共有的属性只能存放在构造函数的原型中。
构造函数的原型指向一个对象,存储的就是共有属性。
⾃有属性:属于对象实例私有的属性
任何对象实例没有权利修改原型中的属性,只有构造函数本身能去修改原型。
共有属性只能放在原型中,其他的都不是。
//自己构造函数,给当前对象加属性
function Student(name, age) {
//给调用函数的当前对象添加一个name和age属性,其值分别为m和n
this.name = name
this.age = age
}
//添加共有属性,只有原型中的才是共有属性,自己加的只有一个共有属性car
Student.prototype.car = "bmw"
console.log(Student.prototype)//输出结果:{car: 'bmw', constructor: ƒ}
//修改原型,修改共有属性,只能通过构造函数
//Student.prototype.car="audi"
//构造方法的调用
var xm = new Student("小明", 18)
var xh = new Student("小红", 21)
xm.car = "benz"//xm的自有属性
console.log(xm)//有三个属性,其car为benz而不是bmw
console.log(xh)//有两个属性,没有car
//能用car 但不能归其所有
console.log(xm.car)//输出结果:benz,自己有先用自己的,自己没有从父级找
console.log(xh.car)//输出结果:bmw
6、继承:使⽤现有类型,创建出新的类型,新的类型可以使⽤现有类型的属性和⽅法,也可以拓展出现有类型没有的属性和⽅法。
7、原型链
原型链:一个对象的隐式原型指向创建它的父级的原型,而父级也有隐式原型,也会指向他的父级的原型,由此往上一直找到null,这样的层层指向关系构成原型链。
作用:原型链的层层指向关系可以用来实现继承的准确性。
Function 代表的是所有函数function的⽗类 Object是所有对象的父类
Function是所有函数的祖先,但是不是所有对象的祖先,所有对象的祖先是Object,Function的祖先也是Object
__proto__:隐式原型。 任何⼀个对象都有隐式原型(只有null没有隐式原型),⽤来实现继承的,这种指向关系保证继承的准确性。
⼀个对象的隐式原型默认指向创建该对象的构造函数/父级的原型。 这种指向关系保证继承的准确性。
Object.prototype就已经找到头了,如果在Object.prototype中添加属性,则所有的对象都能使用。
如果某个属性或方法想要让所有的自定义对象/所有的数组/日期都能用,那么就把该方法放在原型中,这时它下面创建的所有自定义对象/数组/日期就都能用。
上图中所有的虚线都是原型链,实线不是。
24.给元素绑定事件的三种方式及移出事件
1 绑定事件的三种方式
1、eleObj.οnclick=function(){} 相当于给元素对象加属性,也可以在html中的开始标签中写。
2、元素开始标签里面绑定,不需要获取DOM元素 onclick <div οnclick=”fn(参数)”></div>
注意:在html中函数名要加上括号,在js中函数名加括号表示立即调用,而在html中不是。
3、元素对象.addEventListener("事件名",⽅法对象,是否在捕获阶段触发) 可以给元素对象绑定多个事件。
注意:添加事件监听 事件名为click(不是onclick,onclick是事件处理函数)
var btn = document.getElementById("btn")
//要求点击之后要执行两个函数
//方法一:将两个要执行的函数放在匿名函数中,在匿名函数中依次执行这两个函数
/*btn.onclick = function () {
fn()
fn2()
}*/
//方法二:使用给元素绑定事件的第三种方式 既能同时给元素绑定多个事件、给元素绑定一个事件的多个功能。若采用绑定事件的第一种和第二种方法的方式,最后前面的事的都会被后面的覆盖
btn.addEventListener("click", fn2, false)
btn.addEventListener("click", function () { console.log(100) }, false)
function fn() {
alert(1)
}
function fn2() {
console.log(5)
}
2 删除事件
去掉点击事件:表单元素button才有的disabled=true将元素禁用,这种方法并不是所有的元素都有的。
方法一:通过第一种方式给元素绑定事件,将属性改为空null。
方法二:通过第二种方法给元素绑定事件,利用删除属性的方式,前提是必须是通过在元素开始标签中绑定的方式才能算是属性,否则在js中写的不能算是属性
方法三:通过第三种方法给元素绑定多个事件 利用removeEventListener方法。如果使用的是匿名函数,移出时方法对象如果与绑定时写的内容一样,并不能将事件移出。因为它们是对象,地址不一样,只是数据一样而已,移出的并不是原来的函数。
//去掉事件
//方法一:通过第一种方式给元素绑定事件,将属性改为空null
/* btn.onclick = fn2
btn.onclick = null*/
//方法二:通过第二种方法给元素绑定事件,利用删除属性的方式,前提是必须是通过在元素开始标签中绑定的方式才能算是属性,否则在js中写的不能算是属性
/*btn.removeAttribute("onclick")*/
//方法三:通过第三种方法给元素绑定多个事件 利用removeEventListener方法
btn.addEventListener("click", fn2, false)
btn.addEventListener("click", function () { console.log(100) }, false)
btn.removeEventListener("click", fn2, false)
// btn.removeEventListener("click", function () { console.log(100) }, false)
//如果使用的是匿名函数,移除时方法对象如果与绑定时写的内容一样,并不能将事件移出。因为它们是对象,地址不一样,只是数据一样而已,移出的并不是原来的函数
3 事件触发周期(在嵌套的情况下讨论)(面试题)
事件触发周期:一个事件发生的整个过程。
三个阶段: 事件捕获阶段(外--⾥,统计谁有该事件,直到统计到目标之后就不再继续统计)--⽬标触发阶段--事件冒泡阶段(⾥--外,把之前统计的每一个都触发了)
有多层嵌套结构,其中有某些层绑定了相同的事件,当点击里面的某个块时,通过是否允许在捕获阶段触发来决定执行顺序。但在工作过程为中用到的false为多。
<body>
<div class="d1">
<div class="d2">
<div class="d3"></div>
</div>
</div>
<script>
var divs = document.getElementsByTagName("div")
for (var i = 0; i < divs.length; i++) {
/*//采用第一种方法给元素对象绑定事件
divs[i].onclick = function () {
this.style.backgroundColor = "purple"
alert(this.className)
this.style.backgroundColor = ""//去掉行内样式的背景色,剩下的是内部样式的背景色,回归原来的背景色
}
//执行结果:点击最里面的d3块,弹出三个框,依次为d3、d2、d1。因为事件触发周期为事件捕获阶段(从外到里)、目标触发阶段、事件冒泡阶段(从里到外,最后执行的结果是从里层向外层的顺序)*/
//采用第三种方法给元素对象绑定事件,通过addEventListener的最后一个参数是否允许在捕获阶段触发为true或false来决定里外层的执行顺序。
divs[i].addEventListener("click", function () {
this.style.backgroundColor = "purple"
alert(this.className)
this.style.backgroundColor = ""
}, true)
//执行结果:如果是否允许在捕获阶段触发为false,则在事件冒泡阶段从里到外,执行结果为d3、d2、d1;
// 如果为true,则在事件触发周期的第一个阶段事件捕获阶段,从外到里的时候就要触发执行,执行结果为d1、d2、d3
}
</script>
</body>
由于事件触发周期中的事件冒泡阶段在点击里层的块时,包含其的外层块的事件也会被执行了,这就导致发生了bug,例如像表格中有一个编辑按钮,点击其可以对这一行进行编辑,该行又有点击该行可以复制的事件,这时如果点击了编辑按钮,就会出现先执行了编辑按钮的功能,之后又执行了点击该行做的操作,这两个事件都被触发了,这时就出现了一些错误,因此这就需要我们阻止事件冒泡的发生,由此引出下面的事件对象。
如: <table>
<tr id="t">
<td>
<button id="btn">按钮</button>
</td>
<td>1</td>
<td>2</td>
</tr>
</table>
<script>
var btn = document.getElementById("btn")
var t = document.getElementById("t")
btn.onclick = function () {
console.log("btn被点击了")
}
t.onclick = function () {
console.log("tr被点击了")
}
/*执行结果:点击里面的按钮,由于事件的触发周期的第三阶段事件冒泡阶段从里到外执行,button的事件被触发了,整行tr的事件也被触发了。
btn被点击了
tr被点击了
*/
</script>
4 事件对象:用于解决事件冒泡的问题
1、事件对象:默认在事件触发的时候⾃动传⼊函数的第⼀个参数,与⽣俱来的,不是后天传⼊的。
不是所有的函数都有事件对象,只有是事件绑定的函数才有事件对象,例如采用匿名函数的情况:btn.οnclick=function(e){console.log(e);fn(10)}中的事件对象不是fn函数而是匿名函数的形参e。
2、事件对象中重要的属性和方法
(1)阻⽌冒泡: e.stopPropagation() 在发生冒泡的元素对象的触发事件中添加阻止冒泡的代码。
btn.onclick = fn;//这里不能传实参,e在调用时系统自动的传过去的
function fn(e){
console.log("btn被点击了")
e.stopPropagation();//阻止冒泡,只有最内层的执行了,之前外层标记的都没有执行
}
事件委托/代理:利⽤冒泡,如果嵌套元素都有相同的事件要触发,我们只需要在父级绑定事件,内部元素就可以使用事件,内部元素可以通过事件对象来准确拿到触发事件的那个对象。
事件委托的应用场景:如淘宝筛选用户动态选择,一开始不存在,当时获取不到元素对象,无法进行绑定事件,这时我们给外层的div绑定事件即可,通过事件对象来拿到之后点击的是哪一个。
<!-- 冒泡的应用:事件委托在嵌套结构中点谁弹出谁的className -->
<div class="d1">
<div class="d2">
<div class="d3"></div>
</div>
</div>
<script>
// 以前挨个获取,循环绑定,现在利用事件冒泡只给d1自己绑定即可
var d1 = document.querySelector(".d1")
d1.onclick = function (e) {
//这里使用的是事件对象e,target,而不是this,因为this指向的是调用函数的元素对象,调用该函数的始终是d1,因此如果使用this输出的始终是d1。而e.target指向的是事件源对象,是点击了谁而导致发生的,点击谁显示谁。
alert(e.target.className)
}
</script>
(2)访问事件源对象:e.target 。
(3)鼠标相对于当前块的偏移位置:e.offsetX e.offfsetY。
(4)按键码:e.keyCode 键盘按下区分按下的是哪一个键 如回车13、删除8
例如:使用上下左右的键盘控制圆的位置。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
#bird {
position: absolute;
width: 100px;
height: 100px;
background-color: red;
border-radius: 50%;
}
</style>
</head>
<body>
<!-- 给整个页面帮绑定键盘事件 使用上下左右的键盘按键控制圆的位置 -->
<div id="bird"></div>
<script>
var bird = document.getElementById("bird")
//给整个的页面绑定键盘事件
document.onkeydown = function (e) {
//拿到当前圆的位置 拿到所有的样式
var cssStyle = document.defaultView.getComputedStyle(bird, null)
var top = parseFloat(cssStyle.top)
var left = parseFloat(cssStyle.left)
//上38 下40 左37 右39
if (e.keyCode == 38) {
top -= 10
bird.style.top = top + "px"
} else if (e.keyCode == 40) {
top += 10
bird.style.top = top + "px"
} else if (e.keyCode == 37) {
left -= 10
bird.style.left = left + "px"
} else if (e.keyCode == 39) {
left += 10
bird.style.left = left + "px"
}
}
</script>
</body>
</html>
5 e.target VS this
this指向的是调用函数的对象,会随着调用函数的变化而变化,this的指向也可以发生变化,而e.target指向的始终是事件源对象,不会发生改变。