# 音乐播放器案例
// 加muted为静音播放 loop为循环播放 controls为显示控件
<audio src="./music/Alan Walker - Faded.mp3" controls muted loop></audio>
// 背景音乐 自动播放但是有些浏览器不支持,比如谷歌
<audio src="./music/Alan Walker - Faded.mp3" autoplay></audio>
// 监听事件 ontimeupdate
audio.ontimeupdate事件在视频/音频(audio/video)当前的播放位置发生改变时触发。
进度条动态实现:(现在播放时间audio.currentTime/音乐总时间audio.duration) * 进度条总长度 +"px"
或者 (现在播放时间/音乐总时间) * 100 +"%"
鼠标点到进度条上播放哪:音乐总时间 * (百分比)
音乐总时间 * (进度条的现在偏移量(点击到哪的距离)/进度条总长度)
durationchange 音乐准备好了就自动触发(进入页面就且执行一次)
音乐总时长改变的时候触发 进入页面执行一次
一般会在 durationchange 里拿到 duration,不在 ontimeupdate(监听事件) 反复拿到
canplay 视频能播放的时候
audio.ended ended属性返回音频是否已结束
audio.currentTime 设置或返回音频中的当前播放位置(以秒计)。
audio.duration 返回音频的长度(以秒计)。只能在各种事件中拿,否则拿不到
# this 关键字
this 概念:只要封装函数,任何一个函数系统都会内置一个叫做 this 的变量,
this 变量存储的是当前函数主人的地址。
【注】this 永远指向当前函数的主人 。函数的主人要通过当前上下文去判断。
# 强制改变 this 指向的方法
call
格式:函数名.call();
参数:
第一个参数:传入该函数this指向的对象,传入什么强制指向什么
第二个参数开始:将原函数的参数往后顺延一位。
xxFun.call(newThis,参数一,参数二...)
show.call("call", 20, 40);
apply
格式:函数名.apply()
参数:
第一个参数:传入该函数this指向的对象,传入什么强制指向什么
第二个参数:数组(放入我们原有所有的参数)
xxFun.apply(newThis, [参数1,参数2,...]);
show.apply("apply", [20, 40]);
var obj = {b: 100};
var b = 200;
var fun1 = function (x, y, z) {
console.log(this);
console.log(x + y + z);
console.log(this.b);
};
fun1.call(obj, 1, 2, 3); //{b: 100} 6 100
fun1.apply(obj, [1, 2, 3]); //{b: 100} 6 100
apply 小技巧 利用 apply 可以替代函数执行并且传参方式换成数组
Math.min(); //传入所有参数中的最小值
Math.max(); //传入所有参数中的最大值
alert(Math.min(10, 20, 30, 40, 50));
alert(Math.max(10, 20, 30, 40, 50));
var arr = [10, 20, 30, 40, 50];
alert(Math.min.apply(null, arr));//其中null可以随便写
alert(Math.max.apply(null, arr));//其中null可以随便写
bind 预设this指向
bind方法的作用是用来生成一个和某一个函数一摸一样的函数(拷贝函数) 并且可以修改函数内的 this 指向
无论什么时候调用该函数 this 指向基本上不会改变了
用法:xxx.bind(newThis) xxx 代表的是要拷贝的函数 newThis 代表的是替换成别的对象
var a = 10;
function fun() {
console.log(this.a);
}
var obj = {a: 100};
var obj1 = {a: 200};
var newFun = fun.bind(obj);
newFun(); //100
console.log(newFun);
/* ƒ fun() {
console.log(this.a);
} */
obj1.fun = newFun;
obj1.fun(); //100
# this 案例
var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
},
};
console.log(foo.bar()); // 2
案例一:
var length = 10;
function fn() {
console.log(this.length); // 10 2
}
var obj = {
length: 5,
method: function (fn) {
fn();
arguments[0]();
},
};
obj.method(fn, 1);
在我们这道题中,虽然 fn 作为 method 的参数传了进来
但它的调用者并不受影响,仍然是 window,所以输出了 10。
arguments[0]();这条语句并不常见,可能大家有疑惑的点在这里。
其实,arguments 是一种特殊的对象。在函数中,我们无需指出参数名,就能访问。
可以认为它是一种,隐式的传参形式。
当执行 arguments[0](); 时,其实调用了 fn()。
而这时,fn 函数中 this 就指向了 arguments这个特殊的对象。
obj.method 方法接收了 2 个参数,所以 arguments 的 length,很显然就是 2 了。
案例二:
window.val = 1;
var obj = {
val: 2,
dbl: function () {
this.val *= 2;
val *= 2;
console.log('val:', val);
console.log('this.val:', this.val);
}
};
// 说出下面的输出结果
obj.dbl();
var func = obj.dbl;
func();
// 2 、 4 、 8 、 8 。
执行 obj.dbl(); 时, this.val 的 this 指向 obj,而下一行的 val 指向 window。
所以,由 window.val 输出 2,obj.val 输出 4 。
最后一行 func(); 的调用者是 window。 所以,现在的 this.val 的 this 指向 window。
别忘了刚才的运算结果,window.val 已经是 2 了,所以现在 this.val *= 2; 的执行结果就是 4。
val *= 2; 的执行结果,就是 8 了。 所以,最终的结果就是输出 8 和 8 。
# Offset 系列
offsetWidth;
offsetHeight;
// 以上: width + border + padding(眼睛能看到的实际的宽);
// 以下:是一个只读属性,到最近的(有定位的)父元素的 左侧/顶部的距离
// 如果父级没有定位则以body为准;
offsetLeft;
offsetTop;
// left是获取该dom元素的css,所以如果是固定值则带上'px',可读写
# 事件
# 事件类型的种类
一、鼠标事件(可以绑定在任意的元素节点上)
click 单击
dblclick 双击
mouseover 鼠标移入(经过子节点会重复触发)
mouseout 鼠标移出(经过子节点会重复触发)
mousemove 鼠标移动(会不停的触发)
mousedown 鼠标按下
mouseup 鼠标抬起
mouseenter 鼠标移入(经过子节点不会重复触发)
mouseleave 鼠标移出(经过子节点不会重复触发)
二、键盘事件(表单元素,全局window)
keydown 键盘按下(如果按下不放手,会一直触发)
keyup 键盘抬起
keypress 键盘按下(只支持字符键)
三、HTML事件
表单元素的单击事件绑定要用 onchange
表单元素上的属性在js中基本都可以直接 .XX(className,checked,value……)
1、window事件
load 当页面加载完成以后会触发
unload 当页面解构的时候触发(刷新页面,关闭当前页面) IE浏览器兼容
scroll 页面滚动
resize 窗口大小发生变化的时候触发
2、表单事件
blur 失去焦点
focus 获取焦点
select 当我们在输入框内选中文本的时候触发
change 当我们对输入框的文本进行修改并且失去焦点时触发,一般用在表单元素上(单击事件之类)
【注】以下两个必须添加在form元素上
submit 当我们点击submit上的按钮才能触发
reset 当我们点击reset上的按钮才能触发
var oBtn = document.getElementById("btn1");
oBtn.ondblclick = function () {
alert("双击");
};
# 事件对象
事件对象触发的时候默认会接受一个参数 这个参数就是事件对象
事件绑定:
元素节点.on + 事件类型 = 匿名函数;
【注】系统会在事件绑定一旦完成的时候,生成一个事件对象。
【注】触发事件的时候,系统会自动去调用事件绑定的函数。将事件对象当做第一个参数传入。
var e = ev || window.event;
// 解决浏览器兼容问题
# 鼠标事件对象的属性
事件对象:
button 的属性
0 左键
1 滚轮
2 右键
获取当前鼠标位置:(原点位置不一样)
clientX clientY 原点位置:可视窗口的左上角(浏览器的有效区域)为原点
pageX pageY 原点位置:整个页面的左上角(包含滚出去的滚动距离)
screenX screenY 原点位置:电脑屏幕的左上角
document.onmousedown = function (ev) {
var e = ev || window.event;
// alert(e.button);
alert(e.clientX + ", " + e.clientY);
// alert(e.pageX + "," + e.pageY);
alert(e.screenX + ", " + e.screenY);
};
document.oncontextmenu = function () {
return false;
};//阻止默认操作点击右键时不出现本身鼠标右键的菜单
# 修改键
事件对象的四个修改键:
shiftKey 如果按下shift键,值就是true,否则是false
ctrlKey
altKey
metakey (windows键 mac电脑下command键)
# 键码和字符码
键码:只在keydown下支持。
【注】不管是在大写字母还是小写字母的情况下,返回的统一都是大写字母的ASIIC码值。
keyCode
which
字符码: 只在keypress下支持
【注】区分大小写,并且按下的是我当前按下这个键的ASCII码值。
【注】
charCode
which
var e = ev || window.event;
var which = e.charCode || e.which;
window.onkeydown = function (ev) {
var e = ev || window.event;
var which = e.keyCode || e.which;
alert(which); //回车=13
};
window.onkeypress = function (ev) {
var e = ev || window.event;
var which = e.charCode || e.which;
alert(which);
};
# target 目标对象和 this
target:目标对象/触发对象 事件对象的属性
【注】这个事件是由谁而起的。
IE8以下不兼容 window.event.srcElement;
var target = e.target || window.event.srcElement;
this 永远指向当前函数的主人。
# 事件冒泡
事件冒泡: 由里向外逐级触发 。
阻止事件冒泡:浏览器兼容问题
// 事件对象的属性和方法:
cancelBubble=true stopPropagation()
var e = ev || window.event;
e.cancelBubble = true;
e.stopPropagation();
# 事件委托
A 委托 B 去买饭。
A:发布任务 委托方
B:执行任务 代理方
var oUl = document.getElementById("ul1");
oUl.onclick = function (ev) {
var e = ev || window.event;
var target = e.target || window.event.srcElement;
if (target.nodeName.toLowerCase() == "li") {
target.style.backgroundColor = "red";
}
};
var btn = document.querySelector(".btn");
var list = document.querySelector(".list");
btn.onclick = function () {
var li = document.createElement("li");
li.innerHTML = "666<button class='del'>删除</button>";
list.appendChild(li);
};
list.onclick = function (event) {
var element = event.target;
// dom.tagName; 元素的标签名 且是大写的字符串
if (element.tagName === "BUTTON") {
element.parentNode.remove();
}
};
# 事件监听器
addEventListener()
格式:node.addEventListener("click")
参数:
第一个参数 事件类型
第二个参数 绑定函数
第三个参数 布尔值 true 事件捕获
false 事件冒泡 默认
// 从最外面开始到最里边叫做事件捕获
// 从最里边开始到最外面叫做事件冒泡
// 事件监听不会屏蔽之前的,有几个事件监听就会执行几个
// 如果之前有点击事件,之前的点击事件也会执行,这里的事件监听也执行
btn.addEventListener("click", function(){});
var fun = function () {
console.log("事件监听");
};
btn.addEventListener("click", fun);
removeEventListener()
格式:node.removeEventListener("click")
参数:
第一个参数 事件类型
第二个参数 删除函数名字
btn.removeEventListener("click", fun);
# 本地存储技术
localStorage(IE8以下不兼容)
1、永久存储
2、最大可以存储5M 客户端的一个微型数据库
3、只能存储string
sessionStorage(结合后台使用)
仅在当前浏览器窗口关闭前有效,自然也就不可能持久保持
localStorage.setItem(name, value); //将value存储到name字段(name要加引号)
localStorage.getItem(name); //获取指定name本地存储的值
localStorage.removeItem(name); //删除指定name本地存储的值
localStorage.setItem("a", "1");
localStorage.b = "2";
localStorage["c"] = "3";
alert(localStorage.getItem("b"));
alert(localStorage.c);
alert(localStorage["a"]);
# Cookie
cookie
1、可以设置过期时间
2、最大可以存4KB
3、每一个域名下面最多可以存储50条数据
【注】只能字符串。一般只存储一些重要的信息。登录,购物车信息,是否点赞,视频播放进度等。
cookie的语法
格式: name=value;[expires=date];[path=path];[domain=somewhere.com];[secure],
name 键 value 值 都是自定义
【注】后续中括号的内容,都是可选项。
【注】火狐支持本地加载的文件缓存cookie,谷歌只支持服务器加载文件缓存cookie
//设置cookie
document.cookie = "username=xxx";
console.log(document.cookie);//username=xxx
encodeURIComponent 将中文编译成对应的字符
decodeURIComponent 将对应的字符编译成中文
//设置cookie
document.cookie = "username=" + encodeURIComponent("钢铁侠");//%E9%92%A2%E9%93%81%E4%BE%A0
console.log(decodeURIComponent(document.cookie));//username=钢铁侠
expires:过期时间
必须填写,日期对象
【注】系统会自动清除过期的cookie。
//获取n天后的日期
function afterOfDate(n) {
var d = new Date();
var day = d.getDate();
d.setDate(n + day);
return d;
}
document.cookie = "username=xxx;expires=" + afterOfDate(7); //将cookie的过期时间变成7天后
//快速获取过去时间的方法
alert(new Date(0));//这个时间是1970的时间
window.onload = function () {
var oBtn = document.getElementById("btn1");
oBtn.onclick = function () {
document.cookie = "username=;expires=" + new Date(0);//点击按钮的时候删除cookie
};
};
path 限制访问路径
如果不去设置,默认是加载当前.html文件的路径
【注】我们设置的cookie的路径,和加载当前文件的路径,必须一致,如果不一致,cookie访问失败。
document.cookie = 'username=xxx;path=' + '/code14/cookie';
domain 限制访问域名
如果不去设置,默认是加载当前.html文件的服务器域名/ip
【注】如果加载当前文件域名和设置的域名不一致,设置cookie失败。
secure 安全
如果不设置,设置cookie,可以通过http协议加载文件设置,也可以通过https协议加载文件设置
【注】设置这个字段以后,只能设置https协议加载cookie.
# 集合
集合:
1、不重复
2、无序
Set 类似于数组
Map 类似于对象
# Set 集合
let imgs = new Set();
imgs.add(100);
imgs.add(100);
imgs.add("hello");
imgs.add("hello");
imgs.add(true);
imgs.add(new String("world"));
imgs.add(new String("world"));
console.log(imgs); //100,"hello",true,String,String
# Set 集合遍历
for…of 遍历集合
for (let item of imgs.keys()) {
console.log(item);
}
for (let item of imgs.values()) {
console.log(item);
}
imgs.entries;
for (let item of imgs.entries()) {
console.log(item);
}
# 数组和集合之间的相互转换
//数组变集合
var set = new Set([10, 20, 30, 40, 50, 40, 30, 20, 10]);
// console.log(set); //Set(5) {10, 20, 30, 40, 50}
//集合变数组 将数据结构展开成数组
var arr = [...set];
alert(arr); // 10,20,30,40,50
//对数组去重
var arr = [10, 20, 30, 40, 50, 40, 30, 20, 10];
arr = [...new Set(arr)];
alert(arr); // 10,20,30,40,50
# Map 集合
//添加数据
map.set("张三", "打渔的");
map.set("李四", "种地的");
map.set("王五", "挖煤的");
map.set("李四", "打猎的");
// 取值
alert(map.get("王五"));
# Map 遍历
// 通过for of
for (let [key, value] of map) {
console.log(key, value);
}
# prototype 原型对象
概念:每一个函数上,都有一个原型对象 prototype
我们可以给构造函数的原型 prototype,添加方法
如果我们将方法添加到构造函数的原型 prototype 对象上,构造函数构造出来的对象共享原型上所有的方法。
var arr1 = [10, 20, 30, 40, 50];
var arr2 = [1, 2, 3, 4, 5];
Array.prototype.sum = function () {
var res = 0;
//this指的是调用函数的数组(arr1,arr2)
for (var i = 0; i < this.length; i++) {
res += this[i];
}
return res;
};
alert(arr1.sum()); //150
alert(arr2.sum()); //15
alert(arr1.sum == arr2.sum); //true
# 构造函数
// 构造函数创建对象
// 构造函数里的是私有的,原型上的是公有的
function CreateCat(name, age) {
// 构造函数内部使用 this 向创建好的对象内部添加属性或方法
// this 指向的就是 new CreateCat 的时候的实例化对象
this.name = name;
this.age = age;
}
// 当实例化对象访问或者获取公共的方法或属性的时候,
// 一直会在原型对象内查找,如果原型对象修改了,实例化对象的公共属性和方法也变了
// 访问公共的属性或方法的时候永远去最新的构造函数原型对象内查找
// 构造函数的原型对象内默认会有一个 constructor 方法,
// 该方法指的就是构造函数本身 console.log(CreateCat.prototype.constructor);
// 创建好的实例化对象下 默认存在一个 __proto__ 属性,
// 该属性指的是 创建该实例化对象的构造函数的原型对象(CreateCat.prototype)
// 原型内的属性和方法会在实例化对象下的 _proto_ 属性内找到
CreateCat.prototype.say = function () {
// this 指向的就是 new CreateCat 的时候的实例化对象
console.log(this.name);
};
var catOne = new CreateCat("大白", 2);
var catTwo = new CreateCat("小黑", 1);
console.log(catOne);
catOne.say(); //大白
console.log(catTwo);
catTwo.say(); //小黑
# 构造函数的继承
function Animal(type) {
this.type = type;
}
Animal.prototype.say = function () {
console.log("我是" + this.type + "动物");
};
var catAnimal = new Animal("猫科类");
console.log(catAnimal); //Animal {type: "猫科类"}
catAnimal.say(); //我是猫科类动物
// Cat 构造函数继承 Animal 的某一个实例化对象下的所有内容
function Cat(name, age) {
this.name = name;
this.age = age;
}
Cat.prototype = catAnimal;
// Object.assign(Cat.prototype, catAnimal);
// Object.assign 合并对象的时候并不会合并对象内的 __proto__
// Object.assign 只会合并表层的属性和方法
// 深浅拷贝
var xiaoHua = new Cat("小花", 2);
xiaoHua.say(); //我是猫科类动物
console.log(Cat.prototype); //Animal {type: "猫科类"}
console.log(xiaoHua); //Cat {name: "小花", age: 2}
# 继承常用写法
function Parent(name) {
this.name = name;
this.colors = ["red", "blue", "green"];
}
Parent.prototype.getName = function () {
console.log(this.name);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
Child.prototype.constructor = Child;
var child1 = new Child("kevin", "18");
child1.colors.push("black");
console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
var child2 = new Child("daisy", "20");
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]
# __proto__
当对对象进行拷贝的时候对象内的 __proto__ 属性是不可以拷贝的
只要是对象,内部都会有一个 __proto__ 属性,如果这个对象是你的某个构造函数创建出来的
那么这个 __proto__ 指的就是你的构造函数的原型对象,
如果不是 那么这个 __proto__ 指的就是 js 内置的构造函数 Object 的原型对象
如果一个对象一直查找 __proto__ 那么最后的 __proto__ 指的是null
# Object.defineProperty 方法
可以使用 Object.defineProperty 方法:给对象添加新的属性或者对已有的属性进行修改
js 创建对象的时候可以对对象的属性 设置一些规则
Object.defineProperty () 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
第一个参数:要定义或修改属性的对象。第二个参数:要定义或修改的属性的名称或 Symbol 。第三个参数:要定义或修改的属性描述符。
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。
var obj = {
name: "小a",
age: 10,
};
// 数据描述符:value writable enumerable configurable
Object.defineProperty(obj, "name", {
value: "小b", //等于 obj.name = "小b"
// 不可修改
writable: false,
// 是否可梅举(遍历) 不可枚举就是不可使用 for...in 语句遍历
enumerable: false,
// 定义 属性是否可以被删除以及是否可以重新定义 除了 value 和 writable 之外的其他规则
configurable: false,
});
// obj.name = "小c";
for (const key in obj) {
console.log(key); //age
}
// 删除 obj 内的 name 属性
delete obj.name;
console.log(obj); //{age: 10, name: "小b"},因为configurable为false
// 存取描述符: get set enumerable configurable
Object.defineProperty(obj, "name", {
// get 函数必须返回一个值 作为 name 属性的属性值
get: function () {
return "小b";
},
// set 当通过 obj 修改 name 属性时,set 会被触发,修改的属性值会被当作参数传递给 set 函数
set: function (newName) {
// 做自己想做的事
console.log(newName); //小c
},
// 是否可梅举(遍历) 不可枚举就是不可使用 for...in 语句遍历
enumerable: false,
// 定义属性是否可以被删除以及是否可以重新定义 除了 value 和 writable 之外的其他规则
configurable: true,
});
obj.name = "小c";
# hasOwnProperty () 方法
hasOwnProperty (属性名) 方法会返回一个布尔值,检测对象自身属性中是否具有指定的属性
Object.hasOwnProperty.call (对象,属性名) 检测某个属性是否是某个对象的自身属性
var user = {
name: "小张",
age: 20,
say: function () {
console.log("我是普通用户");
},
};
user.say();
console.log(Object.prototype === user.__proto__); //true
for (var key in user) {
// 为什么要先判断再遍历
// 给 for in 语句做解释,我只能遍历对象自身的属性
if (user.hasOwnProperty(key)) {
console.log(key); //name age say
}
}
# isPrototypeOf(Object)
用于检查传入的对象是否是当前对象的原型
# 深浅拷贝
分为基本数据类型 (string, number, boolean, undefind, null, symbol) 和引用数据类型 (obj, array, function)
基本类型其实不分深浅拷贝;深浅拷贝只针对引用数据类型
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。
但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
引用类型的赋值,因为是内存,所以都发生变化
# 浅拷贝的实现
// 1.Object.assign() 注意:当object只有一层的时候,是深拷贝
var obj = { a: { a: "kobe", b: 39 } };
var initalObj = Object.assign({}, obj);
initalObj.a.a = "wade";
console.log(obj.a.a);
//wade
let obj = {
username: "kobe",
};
let obj2 = Object.assign({}, obj);
obj2.username = "wade";
console.log(obj); //{username: "kobe"}
// 2、Array.prototype.concat() 数组的拼接,返回拼接之后数组,原数组不变
let arr = [1, 3, { username: "kobe" }];
let arr2 = arr.concat();
arr2[2].username = "wade";
console.log(arr); // 修改新对象会改到原对象:username变成了wade
// 3、Array.prototype.slice() 数组的截取,返回截取后的数组,不修改原数组
let arr = [1, 3, { username: " kobe" }];
let arr3 = arr.slice();
arr3[2].username = "wade";
console.log(arr); // 同样修改新对象会改到原对象:username变成了wade
// 4.[...] 展开运算符,相当于把数组或对象外边的[]或{}给去掉,只剩里边的内容
// 扩展运算符里边只有一层就是深拷贝,否则就是浅拷贝
var arr = [1, 2, 3, 4, 5, 6, [7, 8, 9]];
var arr1 = [...arr];
arr1[6][1] = 0;
console.log(arr1, arr); //arr中的8也变成了0
# 深拷贝的实现
var post = {
title: "vue入门",
date: "2020-12-20",
author: {
username: "zzt",
avatar_url: "https://www.xxx.com/1.jpg",
},
};
// 1.手写递归方法
// 递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
function deepCope(obj) {
// 先判断 obj 是 array 还是 object
// Array.isArray(值) 检测这个值是否是数组
// var newObj = Array.isArray(obj) ? [] : {}
// 值 instanceof Array 检测这个值是否是由 Array 构造函数创建的
// Array 可以替换成 RegExp Function Object
var newObj = obj instanceof Array ? [] : {};
for (var key in obj) {
var val = obj[key];
// 这个判断主要是判断 val 是不是原始类型 number string boolean undefined null
if (typeof val === "object") {
newObj[key] = deepCope(val);
} else {
newObj[key] = val;
}
}
return newObj;
}
// 2、函数库lodash 该函数库也有提供 _.cloneDeep
// lodash链接
// <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
# 函数节流和函数防抖
- 函数节流 第一次操作的东西 在第二次执行的时候要有一定的时间间隔
保证如果电梯第一个人进来后,10 秒后准时运送一次,这个时间从第一个人上电梯开始计时,不等待,如果没有人,则不运行。
高频事件触发,但在 n 秒内只会执行一次,所以节流会稀释函数的执行频率
思路:每次触发事件时都判断当前是否有等待执行的延时函数
function throttle(fn) {
let canRun = true; // 通过闭包保存一个标记
return function () {
if (!canRun) return; // 在函数开头判断标记是否为true,不为true则return
canRun = false; // 立即设置为false
setTimeout(() => {
// 将外部传入的函数的执行放在setTimeout中
fn.apply(this, arguments);
// 最后在setTimeout执行完毕后再把标记设置为true(关键)表示可以执行下一次循环了。
// 当定时器没有执行的时候标记永远是false,在开头被return掉
canRun = true;
}, 500);
};
}
function sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener("resize", throttle(sayHi));
- 函数防抖 一个需要频繁触发的函数,在规定时间内,只让最后一次生效,前面的不生效
比如搜索,键盘输入的时候不搜索,输入完之后才搜索
假设现在坐公交车,很多人不断在刷卡,那么此时司机是不能开车的,等到乘客都刷卡完毕了,司机需要等待一会儿(延迟时间)确认乘客都做好之后准备开车,此时正好又有一个乘客上车,那么司机又要重新等待刷卡上人再次坐稳之后再开车。
触发高频事件后 n 秒内函数只会执行一次,如果 n 秒内高频事件再次被触发,则重新计算时间
思路:每次触发事件时都取消之前的延时调用方法
function debounce(fn) {
let timeout = null; // 创建一个标记用来存放定时器的返回值
return function () {
clearTimeout(timeout); // 每当用户输入的时候把前一个 setTimeout clear 掉
timeout = setTimeout(() => {
// 然后又创建一个新的 setTimeout
// 这样就能保证输入字符后的 interval 间隔内如果还有字符输入的话,就不会执行 fn 函数
fn.apply(this, arguments);
}, 500);
};
}
function sayHi() {
console.log("防抖成功");
}
var inp = document.getElementById("inp");
inp.addEventListener("input", debounce(sayHi)); // 防抖