目录
- 1 循环语法补充
- 2 var、let、const关键字的特性
- 3 JS实现一键复制功能
- 4 call和apply
- 5 深拷贝
- 6 fetch实现网络请求
- 7 ...展开语法
- 8 eslint
- 9 原型链继承规则
- 10 console.dir
- 11 操作cookie
- 12 encodeURI和encodeURIComponent
- 13 DOM右键菜单事件
- 14 事件冒泡&捕获+事件绑定&取消
- 15 获取焦点
- 16 键盘事件
- 17 类型判断
- 18 操作浏览器会话存储和本地存储
- 19 url被#截断
- 20 js支持连等
- 21 js实现sleep方法
- 22 模拟类的私有属性
- 23 模拟类的静态变量
- 24 XMLHttpRequest
- 25 预解析/预编译/状态提升
- 26 调试信息stalled waiting
- 27 JS获取标签样式信息
- 28 JS两个定时器
- 29 获取鼠标点击坐标
- 30 标签宽高和窗口宽高
- 31 滑动加载(上拉触底加载)
- 32 判断元素是否在可见区域(定位元素)
- 33 图片加载完成事件
- 34 多次触发防抖节流(debounce/throttle)
- 35 显示打断点
- 36 call apply bind用法
- 37 include和indexof区别
- 38 搜索框内容匹配高亮
- 39 Object.assign
- 40 delete删除对象属性
- 41 Object.keys
- 42 变量提升和函数提升
- 43 JavaScript事件循环Event Loop
- 44 给对象加属性
- 45 装箱操作
1 循环语法补充
循环有多种,普通循环:for
forEach
map
while
等都很熟悉了。
for
和while
都是基础的循环方式
forEach
多用于无需返回结果的循环遍历
map
多用于返回结果的循环
特殊的:数组的some和every会跳过空元素!
for in 循环
for in 循环枚举对象的属性,这里体现了属性的 enumerable 特征。用于Object对象而不是数组!!
let o = { a: 10, b: 20}
Object.defineProperty(o, "c", {enumerable:false, value:30})
for(let p in o)
console.log(p);
这段代码中,定义了一个对象 o,给它添加了不可枚举的属性 c,之后用 for in 循环枚举它的属性,输出时得到的只有 a 和 b。
如果定义 c 这个属性时,enumerable 为 true,则 for in 循环中也能枚举到它。
与Object.keys()类似,都可以获取到对象的可枚举属性,但是,Object.keys只获取对象本身的枚举属性,不获取其原型上的,而for in全都获取
判断对象的一个属性是否是其本身的:hasOwnPropery
for of 循环
for of 循环主要是用来遍历可迭代对象,即iterator迭代器
for(let e of [1, 2, 3, 4, 5])
console.log(e);
手动实现一个迭代器:
let o = {
[Symbol.iterator]:() => ({
_value: 0,
next(){
if(this._value == 10)
return {
done: true
}
else return {
value: this._value++,
done: false
};
}
})
}
for(let e of o)
console.log(e);
实现一个迭代器主要通过next()方法返回一个iterator对象
- 当iterator对象的done为true时表示迭代结束
- 当为false时表示继续等待下一轮迭代,且value为本轮迭代值
生成器函数迭代
生成器函数可以使用yield来控制下次进入函数时的入口位置:
function* foo(){
yield 0;
yield 1;
yield 2;
yield 3;
}
for(let e of foo())
console.log(e);
上面过程中,四次过程,从0到4,每次yield跳出,下次从跳出位置继续执行!
异步生成器函数迭代
function sleep(duration) {
return new Promise(function(resolve, reject) {
setTimeout(resolve,duration);
})
}
async function* foo(){
i = 0;
while(true) {
await sleep(1000);
yield i++;
}
}
for await(let e of foo())
console.log(e);
循环与异步
常用循环中,对异步支持不同:
for
:正常执行
while
:正常执行
map
:直接返回promise数组,可用:Promise.all
进行处理
forEach
:不支持异步循环
参考:https://juejin.cn/post/6844903860079738887
2 var、let、const关键字的特性
var声明的变量会挂在window上,而let和const的不会
window.abc
> undefined
var abc = 123
window.abc
> 123
let def = 123
window.def
> undefined
const g = 123
window.g
> undefined
var存在变量提升,而const和let不会
var关键字有个特性,在js预处理阶段会进行作用提升,即无论声明在使用语句的前还是后,都会提升到最前面,但注意:仅仅只是声明提升了,赋值并没有,所以在赋值前访问会得到undefined
console.log(a);
var a=1;
作用提升的时候,var除了脚本和函数体都会穿透,所以容易产生一些奇怪的错误
var a=[];
for(var i=0;i<3;i++){
setTimeout(()=>{console.log(i)},1000);
}
此时会打印出3个3,究其原因就是for循环结束,在执行回调函数时,每个回调函数访问的都是同一个i,且i=3
在看一个例子:
var a=1;
function foo(){
console.log(a);
if(false){
var a=2;
}
}
foo();
分析该例子,很容易根据上面的知识知道,函数体内部无法穿透所以 a=2 不会覆盖 a=1,同时作用提升到函数体最前面。在执行阶段:函数体内输出a时由于函数体内部存在a的声明,所以不会访问外部a,但是赋值在调用后,所以此时输出undefined
var作用域是函数作用域,let作用域是{}
同一作用域下let和const不能声明同名变量,而var可以
暂存死区
块作用域外存在同名变量,在当前块作用域中存在某个变量使用let/const声明的情况下,在声明行前面使用该变量则报错,无法访问块外的同名变量
const一旦赋值,则指向的内容不可改变
内容不可改变指的是:引用地址不可改变,如果是基本类型,则基本类型值不可改变
在实际开发中,建议使用let来取代var,let只在当前作用域内有效,且不会进行作用提升!!
参考: https://www.cnblogs.com/zhaoxiaoying/p/9031890.html
3 JS实现一键复制功能
copy = () => {
var copyDOM = document.getElementById("xxx"); //需要复制文字的节点
var range = document.createRange(); //创建一个range
window.getSelection().removeAllRanges(); //清楚页面中已有的selection
range.selectNode(copyDOM); // 选中需要复制的节点
window.getSelection().addRange(range); // 执行选中元素
var successful = document.execCommand('copy'); // 执行 copy 操作
if(successful){
message.success('复制成功!')
}else{
message.warning('复制失败,请手动复制!')
}
// 移除选中的元素
window.getSelection().removeAllRanges();
}
4 call和apply
在全局上下文中,this指的是window
在函数上下文中,this指的是该函数所属的对象
this指向更改:
方法1:函数.call(需要更改为的对象,参数1,参数2)
方法2:函数.apply(需要更改为的对象,[参数1,参数2])
eg: fn.call(btn,1,2,3,4)
eg: fn.apply(btn,[1,2,3,4])
function fn(a,b,c,d){
alert(a+b+c+d);
}
5 深拷贝
在引用对象的使用过程中,对其操作大多数是浅拷贝,即只是将新变量指向原对象地址,原对象改变后,新引用可以观察到。
如果想获取原对象的一个完整的拷贝,可以使用深拷贝:json解析实现深拷贝
let x =JSON.parse(JSON.stringify(a))
6 fetch实现网络请求
es6提供的一个异步请求方法:
注意:该方法不能跨域请求!,如果需要跨域,可以使用第三方包fetch-jsonp
fetch(url).then(response => response.json())
.then(data => console.log(data))
.catch(e => console.log("Oops, error", e))
7 …展开语法
在对象上使用展开语法会把当前对象中所有属性取出来平铺
var a = {"name":"pp","age":12}
var b = {...a,"sex":"male"}
在数组上使用会展开成一串数字平铺
var a = [1,2,3,4]
var b= [...a,0]
注意:展开式后的同名属性会覆盖展开值
注意:展开语法中,对象可以接受数组和对象展开,数组只能接受含有迭代器属性的对象展开!
其中:对于含有迭代器的对象进行展开时,相当于将迭代器跑一遍,获取其所有值!(并非对象中所有属性)
// a是类数组
let a = {
0: "a",
1: "b",
2: "c",
length: 3
};
let b = { name: "pp", age: 11 };
let c = [1,2,3,4]
function l(...props) {
console.log(props);
console.log(arguments)
ll(...arguments)
}
function ll(...props) {
console.log(props)
}
// l(...a); 展开失败,虽然是类数组,但没有迭代器无法展开
// l(...b); 展开失败,无迭代器对象(展开到数组中需要迭代器对象)
l(...c); // 展开成功,数组对象展开到数组参数中,输出为:
// > (4) [1, 2, 3, 4]
// > Arguments(4) [1, 2, 3, 4, callee: (...), Symbol(Symbol.iterator): ƒ]
// > (4) [1, 2, 3, 4]
arguments 为类数组对象,包含迭代器Symbol(Symbol.iterator),对其展开为数组部分值
8 eslint
如果不想当前文件被语法检查,在文件顶部添加:/* eslint-disable */
9 原型链继承规则
访问一个对象的某个属性,查找步骤:
- 从当前对象中搜索
- 从当前对象的原型中搜索
- 从Object对象的原型中搜索
- 都没查到则:undefined
类对象的原型可以用来修改或增加对象方法:
let person = new Person(name);
person.prototype.show = function(name){console.log(name)}
// 此时person对象具备了show方法
person.show('pp')
10 console.dir
该方法可以遍历并打印对象的属性信息
11 操作cookie
设置cookie
: document.cookie='key=value'
,这个方式会在当前环境cookie中添加一个新的
设置过期:
document.cookie='key=value;expires='+d
// 其中:d为日期对象Date的实例
// 日期操作:
var d=new Date()
// 加三天日期:
d.setDate(d.getDate()+3)
设置为0的变为session
过期,即会话结束过期
若设置为-1,则立马过期,浏览器会删掉这个数据
对于中文和特殊符号的存储,建议编码后再存储: encodeURI() decodeURI() 只会对中文和特殊符号进行编码
获取cookie:
document.cookie
中是所有的cookie,用:;
分号空格 来分割,即可得到每一条数据,若编码了,需要先解码再分割
12 encodeURI和encodeURIComponent
前者对于url中可识别的特殊字符不编码,如数字、字母、=、?等
后者对除了数字和字母的所有符号进行编码,一般而言,后者使用较多
解码:decodeURI
和decodeURIComponent
13 DOM右键菜单事件
document.oncontextmenu=func
鼠标右击页面需要执行的功能
14 事件冒泡&捕获+事件绑定&取消
事件捕获(event capturing):
当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件。
点击p标签,依次弹出:a b p,属于事件捕获
<body>
<div id='a'>
<div id='b'>
<p id="p">111</p>
</div>
</div>
</body>
// 同样是上面的标签组合方式
// 这次使用addEventListener绑定事件click,第三个参数true为绑定捕获,false绑定冒泡
a.addEventListener('click',function (){
alert('a');
},true);
b.addEventListener('click',function(){
alert('b')
},true)
p.addEventListener('click',function(){
alert('p')
},true)
注意:如果true和false共存的话,就会先执行true后执行false
事件冒泡(event capturing) :
当一个元素接收到事件的时候 会把他接收到的事件传给自己的父级,一直到window 。(注意这里传递的仅仅是事件 并不传递所绑定的事件函数。所以如果父级没有绑定事件函数,就算传递了事件 也不会有什么表现 但事件确实传递了。)
如果a b p三个标签都绑定了onclick
事件的话,点击p标签,依次弹出:p b a,属于事件冒泡
阻止事件冒泡:
事件冒泡本身是一种机制没有好坏之分,如果不需要冒泡可以阻止,有时候我们也可以利用这种冒泡机制来实现一些效果
删除留言案例:
给父级ul添加单击事件在里面通过事件对象寻找事件源然后去到除每一条留言
事件委托应用场景.内部子元素会动态变化,适合将子元素身上的事件娄托给父级元素代
理抉行(性能更好一些)
捕获:从最外层开始触发
冒泡:从最内层开始触发
在需要阻止事件继续传播,无论是冒泡还是捕获,在处理函数中调用:event.stopPropagation()
事件绑定:
对于同一对象的事件绑定,如果采用普通的如x.onclick=function(){}
这种方式,多次绑定会被覆盖,最终只有最后一个生效!如果想多次都生效:
addEventListener(事件名, 事件处理函数, 捕获 与否
:该方法可以用于绑定事件,且多次绑定多次生效,按顺序执行。如果捕获了就不会冒泡了
ie 6 7 8支持:元素.attachEvent(‘on’+事件名, 事件处理函数)
传统绑定事件绑定的是冒泡阶段:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="a" style="width: 500px;height: 500px;background-color: red;">
<div id="b" style="width: 300px;height: 300px;background-color: yellow;">
<div id="c" style="width: 100px;height: 100px;background-color: green;">
abc
</div>
</div>
</div>
</body>
<script>
document.getElementById('a').onclick=()=>alert('a')
document.getElementById('b').onclick=()=>alert('b')
document.getElementById('c').onclick=()=>alert('c')
</script>
</html>
点击c,则弹出c b a
注意:addEventListener
方法是按照绑定顺序先后执行,且其中this指向触发事件的元素,而attachEvent
为逆序,this指向window
事件解绑:
传统解绑方式:x.onclick=null
监听方式解绑:x.removeEventListener(事件名, 事件处理函数)
封装兼容性函数:
15 获取焦点
让一个可以参与用户交互的元素获取焦点的方式有三种
1 鼠标直接点击
2 键盘的Tab
3 js获取焦点
关于焦点的两个方法.
A.focus()
让A获取焦点
A.blur()
让众失去焦点
关于焦点的两个事件
//当A获取焦点的时候触发一个事件处理函数
A.onfocus:function(){}
//当A失去焦点的时候触发一个事件处理函数
A.onblur:function(){}
16 键盘事件
鼠标事件:click dblclick mouseover mouseout mousedown mouseup mousemove
键盘事件:keydown keyup keypress
触发过程:keydown-keypress-keyup
键盘中有专门的功能键:
在js事件对象中存在:ctrlKey shiftKey altKey 这三个为辅助功能键属性,默认为false,按下为true
17 类型判断
instanceof
:判断某个变量是否是某个对象的派生:a instanceof b
,a是否是b的一种
注意:数组即是Array类型,也是Object类型,除数组和对象外的所有类型如果需要正确判断,则必须使用构造方法传值生成对象才行。即new出来
typeof
:判断某个变量的类型,结果有string, object, boolean, number
注意:该方法将数组判断为对象object类型,且一般用来判断基本类型,判断new对象都为object类型
总结:new对象使用instanceof
判断,基本类型使用typeof
判断,大多数情况需要两者结合判断
18 操作浏览器会话存储和本地存储
localStorage 和 sessionStorage 属性允许在浏览器中存储 key/value 对的数据。
sessionStorage 用于临时保存同一窗口(或标签页)的数据,在关闭窗口或标签页之后将会删除这些数据。提示: 如果你想在浏览器窗口关闭后还保留数据,可以使用 localStorage 属性, 该数据对象没有过期时间,今天、下周、明年都能用,除非你手动去删除。
设置:sessionStorage.setItem("flg","1");
获取:var index=sessionStorage.getItem("flg");
删除:sessionStorage.removeItem("flg");
清空:sessionStorage.clear();
19 url被#截断
在发送请求时,url中#
后面的字符会被截断,常用方法是使用encodeURIComponent
进行编码
20 js支持连等
var a=b=c=d=1
则,a b c d都是1
21 js实现sleep方法
方法:
sleep(time) { return new Promise((resolve) => setTimeout(resolve, time))}
调用:
await this.sleep(1000)
注意:await
必须在异步函数中,即函数必须声明async
22 模拟类的私有属性
由于js类没有所谓的私有属性,但是可以通过函数作用域来模拟,函数作为一个独立的作用域,在函数外除了闭包,无法访问函数内部的变量
class Person{
constructor(name){
this.name = name
}
getAge(){
var age = 12
return age
}
}
let person = new Person('pp')
console.log(person.name)
console.log(person.getAge())
23 模拟类的静态变量
有时候,我们需要所有实例对象,能够读写同一项内部数据。这个时候,只要把这个内部数据,封装在类对象的里面、createNew()方法的外面即可。
var Cat = {
sound : "喵喵喵",
createNew: function(){
var cat = {};
cat.makeSound = function(){ alert(Cat.sound); };
cat.changeSound = function(x){ Cat.sound = x; };
return cat;
}
};
然后,生成两个实例对象:
var cat1 = Cat.createNew();
var cat2 = Cat.createNew();
cat1.makeSound(); // 喵喵喵
这时,如果有一个实例对象,修改了共享的数据,另一个实例对象也会受到影响。
cat2.changeSound("啦啦啦");
cat1.makeSound(); // 啦啦啦
24 XMLHttpRequest
具有功能:XMLHttpRequest
- 上传下载文件
- 跨域请求
- 设置超时时间
- 设置各种事件的回调函数
- 模拟表单数据
但是建议使用第三方封装好的框架来做http请求
详细XMLHttpRequest
25 预解析/预编译/状态提升
申明的变量和函数会在运行期前进行位置提升:
变量只提升定义,而函数提升整个函数
当出现重名变量和函数时,函数会覆盖变量
在执行期间不看函数声明了,因为整个函数已经预解析过了,只看变量赋值!!
无论在全局作用域还是局部作用域都会预解析
26 调试信息stalled waiting
stalled:TCP建立连接到可以传递数据的时间
1.单一服务stalled过长,被阻塞
2.多个服务并发stalled过长,是浏览器对同一域名的并发限制
waiting:发出请求到刚接收到响应的时间
content download:收到响应的第一字节到最后一字节时间
27 JS获取标签样式信息
读取样式表样式或行内样式:getComputedStyle(对象).属性
读取行内样式:对象.style.属性
在js中样式名中的-
改为名字大写
ie7以下:对象名.currentStyle.属性名
28 JS两个定时器
第一个定时器叫循环定时器setlnterval()
清除循环定时器clearlnterval()
第二个定时器延迟定时器setTimeout()
清延迟定时器clearTimeout()
循环定时器如果不用clear清楚则一直存在!!清除定时器必须靠返回的句柄,如果该句柄被覆盖则无法清除。清除定时器后最好让定时器变量=null
,有利于后续判断
29 获取鼠标点击坐标
(clientX , clientY)
为当前事件中的鼠标坐标,远点为左上角,页面中都为正值
30 标签宽高和窗口宽高
网上内容都不一样,所以该部分仅供参考,运行环境edge版本 88.0.705.50 (官方内部版本) (64 位),测试页面:bilibili主页
对于一个元素标签来说:
// offset表示元素整体大小,包括:内容+padding+border
offsetWidth: css height(固定) + padding*2 + border*2
offsetTop: 返回当前元素相对于其父元素的顶部距离
// client表示元素显示的大小,即可见部分:内容+padding-滚动条遮挡大小
clientWidth: css height(固定) + padding*2 - 滚动条宽度
clinetTop: border-top-width,相当于就是顶部的border大小
// scroll表示内容完全撑开的client大小
scrollWidth: contentWidth(随内容宽度变化) + padding*2
scrollTop: 元素的滚动距离,不滚动时为0,不可滚动时为0
// 当contentWidth内容没有出现横向滚动条时,clientWidth和scrollWidth相等,如果出现滚动条则scrollWidth为实际内容宽度
注意:document.body
和document.documentElement
有区别!
在xhtml
标准网页或者带<!DOCTYPE>
标签的网页中,document.body.scrollTop=0
恒成立
窗口 可见区高:document.documentElement.clientHeight
=document.body.clientHeight
=window.innerHeight
-滚动条
body总高(不包括margin):document.documentElement.scrollHeight
=document.body.scrollHeight
html总高(包括margin):document.documentElement.scrollHeight
即:documentElement
和body
几乎可以等同!
31 滑动加载(上拉触底加载)
明确三个距离:
滑动距离:document.documentElement.scrollTop
窗口高度:document.documentElement.clientHeight
文档高度:document.documentElement.scrollHeight
判断触底方法:窗口高度 + 滑动距离 > 文档高度
可以考虑预留一部分余量:即提前加载
窗口高度 + 滑动距离 > 文档高度 - [余量]
32 判断元素是否在可见区域(定位元素)
同样明确三个距离:
滑动距离:window.scrollY
或 document.documentElement.scrollTop
窗口高度:document.documentElement.clientHeight
元素顶部距文档顶部距离:可以使用浏览器提供的getBoundingClientRect()
方法获取目标元素的定位信息。如果不支持该api只能使用offsetTop获取当前元素到父级定位元素的顶部距离,如果父级定位元素不是body,则需要循环遍历:
function getElementTop(element) {
// 方式一:使用浏览器api
if(element.getBoundingClientRect){
var rect = element.getBoundingClientRect();
return rect.top
}
// 方式二:低效率方式
var actualTop = element.offsetTop
var current = element.offsetParent
while (current !== null) {
actualTop += current.offsetTop
current = current.offsetParent
}
return actualTop
}
判断方法:窗口高度+滑动距离 > 元素距离顶部距离
同样可以预留余量:提前加载
窗口高度 + 滑动距离 > 元素距离顶部距离 - [余量]
function isInViewPortOfOne (el) {
// viewPortHeight 兼容所有浏览器写法
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const offsetTop = getElementTop(el)
const scrollTop = document.documentElement.scrollTop
const top = offsetTop - scrollTop
console.log('top', top)
// 这里有个+100是为了提前加载+ 100
return top <= viewPortHeight + 100
}
32.1 getBoundingClientRect
该方法是浏览器所拥有的,DOM元素调用该方法后,返回的为当前元素的定位信息:
// 调用后返回的结果格式如下:
DOMRect {x: 50, y: 50, width: 230, height: 230, top: 50, …}
bottom: 280
height: 230
left: 50
right: 280
top: 50
width: 230
x: 50
y: 50
[[Prototype]]: DOMRect
注意:x和y表示从border外边左上角开始算起的位置,height和width表示从border外边算起的尺寸
32.2 使用新api:IntersectionObserver
该API提供根据指定的区域判定内部元素是否可见的功能,并且可以定义可见百分比
常用方法:
- observe() 将内部该元素添加到观察对象中
- unobserve() 取消内部该元素的观察
- isIntersecting 判定当前元素是否可见
- intersectionRatio 判定当前元素可见比例
<div id="imgContent" style="width: 350px;height:350px;overflow-y: scroll;">
<img data-src="./二选/711A0879.jpg" height="400"><br>
<img data-src="./二选/711A0886.jpg" height="400"><br>
<img data-src="./二选/711A0887.jpg" height="400"><br>
<img data-src="./二选/711A0901.jpg" height="400"><br>
</div>
<script>
const imgContent = document.getElementById("imgContent");
const imgList = Array.from(imgContent.getElementsByTagName('img'));
const io = new IntersectionObserver((entries) => {
entries.forEach(item => {
console.log(item,item.isIntersecting,item.intersectionRatio)
if (item.isIntersecting) {
item.target.src = item.target.dataset.src
io.unobserve(item.target)
}
})
}, { root: imgContent })
imgList.forEach(item => io.observe(item))
</script>
注意:图片的高度要设置,否则在加载时会判定全部可见
33 图片加载完成事件
原生JS监听图片加载完成:img.onload=function(){}
vue监听:@load=’方法'
34 多次触发防抖节流(debounce/throttle)
防抖
防抖即多次触发的函数,会有一个超时限制,满足超时才能执行该函数。
当一个事件多次发生时,为了提高性能,可以做防抖处理,即在一定时间内,如果有再次触发的情况,则重新计时并重新获取输入值,当超时未发生时,再接着处理下去。
这样限制有利于提高处理性能减轻计算压力
防抖函数有多种写法:
方式一
debounce(func,delay){
let timer = null
return function (...args) {
if(timer) clearTimeout(timer) // 只要有timer就清空,第二次后每次都会执行清空
timer = setTimeout(() => {
func.apply(this,args)
}, delay);
}
}
调用示例
const f1 = ()=>{console.log('f1')} // f1函数
let f1_deb = debounce(f1,250) // 获得f1的防抖函数
f1_deb()
f1_deb() // 只会执行一次输出
注意:使用的是闭包,即返回的函数访问了函数内部的变量,相当于将函数内部变量当作共用变量使用!
方式二
let db_timer = null
function debounce(func,delay,...args){
if(db_timer) clearTimeout(db_timer)
db_timer = setTimeout(()=>func(...args),delay)
}
调用示例
const f1 = ()=>{console.log('f1')} // f1函数
debounce(f1,250)
debounce(f1,250) // 只会执行一次输出
注意:虽然方式二比方式一易于理解,但是不建议使用,因为方式二依赖于一个外部变量db_timer,对于每个传入函数,都要存在一个与之对应的db_timer变量,方式二仅作为理解。方式一采用闭包的方式,同时每个函数又都是一个作用域,封装到位。
节流
节流就是说,多次触发的函数,在一定时间内只会执行一次。针对高频触发的函数非常有效,显著降低消耗,提升性能。
debounce(func,delay){
let timer = null
return function (...args) {
if(!timer) { // 只要不存在timer表示当前没有触发,所以就新建一个触发,否则啥也不做,等待触发结束
timer = setTimeout(() => {
func.apply(this,args)
timer = null
}, delay);
}
}
}
注意:节流和防抖的逻辑就是timer的判断不同,节流是只要timer存在就啥也不做否则设定定时器,而防抖是无论存在与否都直接清掉定时器重新定时。同时节流和防抖也可以使用时间戳来写逻辑。
节流时间戳写法:
var throttle = function (func, delay) {
var prev = Date.now();
return function () {
var context = this;
var args = arguments;
var now = Date.now();
if (now - prev >= delay) {
func.apply(context, args);
prev = Date.now();
}
}
}
function handle() {
console.log(Math.random());
}
window.addEventListener('scroll', throttle(handle, 1000));
35 显示打断点
在<script>
标签中,有时候浏览器中无法打断点的地方,可以使用:debugger
关键字来显示打断点,只需要写完后进入chrome浏览器的F12,然后刷新即可!
36 call apply bind用法
用于重新定义this的指向,前两者之前已经说了:
- call 第一个参数表示this指向,第二个到第N个表示调用函数的入参
- apply 第一个参数表示this指向,第二个表示函数入参,多个参数可以用数组
- bind 和call入参一样,但是返回一个函数,需要再调用一下
()
37 include和indexof区别
相同点都可以用来判断一个元素在数组中是否存在,区别在于前者返回布尔值,后者返回索引(不存在为-1)
如果不关心索引,则使用include比较好!
特别的:include认为NaN===NaN,即使结果是false
38 搜索框内容匹配高亮
思路:使用正则将输入与所有数据进行匹配,如果找到匹配,则在目标两侧加上标签并附加样式
function handleInput(value) {
const reg = new RegExp('(' + value + ')')
const search = data.reduce((res, cur) => {
if (reg.test(cur)) {
const match = RegExp.$1;
res.push(`<li>${cur.replace(match, '<span>$&</span >')}</li>`);
}
return res;
}, []);
return search;
}
39 Object.assign
该方法可以动态给一个对象添加属性,或覆盖某个属性
var a={"a":1,"b":2}
Object.assign(a,{"c":3}) // {a: 1, b: 2, c: 3}
Object.assign(a,{"a":3}) // {a: 3, b: 2, c: 3}
40 delete删除对象属性
var a={"a":1,"b":2}
delete a.a //{"b":2}
41 Object.keys
返回一个数组,数组里是该obj可被枚举的所有属性。
var a = {"a":1}
Object.keys(a)
42 变量提升和函数提升
正常情况下,变量和函数都会提升:
- 函数和变量同名,函数提升优先于变量
foo();
var foo= function() {
console.log("2");
};
function foo() {
console.log("1");
}
函数foo和变量foo同时提升,不过函数优先级高,所以调用的是函数
- 函数会提升但是函数表达式不会
foo();
var foo = function fun() {
console.log("fun");
};
这种情况下,会报foo不是函数!函数表达式当作变量提升,不会识别为函数
43 JavaScript事件循环Event Loop
首先明确三个概念:
- 调用栈call stack:在解释器执行到非延时语句或函数时会将其放入调用栈,调用栈会依次从栈顶执行直到清空栈
- 消息队列message queue:当解释器执行到
setTimeout
或setInterval
或fetch
语句时会将其内部的回调函数放入消息队列中称为宏任务!当调用栈清空且无微任务时,消息会依次压入调用栈 - 微任务队列microtask queue:
promise
async
await
的回调函数会放入该处称为微任务,当调用栈清空,微任务会立即压入调用栈执行,且执行期间如果有新微任务会一同执行
帧:函数在调用栈中的称呼
入参为函数时,执行器会进入函数执行
调用栈中压入函数,然后执行器会执行到该函数内部
宏任务:setTimeout
,setInterval
,DOM事件
,AJAX请求
微任务:Promise
,async/await
微任务与宏任务之间间隔了DOM渲染!!微任务-->DOM渲染-->宏任务
var p = new Promise((resolve) => {
console.log(4);
resolve(5);
});
function fun1() {
console.log(1);
}
function fun2() {
setTimeout(() => {
console.log(2);
});
fun1();
console.log(3);
p.then((resolve) => {
console.log(resolve);
}).then(() => {
console.log(6);
});
}
fun2();
// 4 1 3 5 6 2
分析:
- 执行器执行到第一行,new Promise传入一个函数作为构造,构造函数会被放入调用栈,然后进入该函数内部执行打印出4
- 执行器执行完毕跳出该函数,调用栈清空
- 执行器执行到最后一行进入fun2函数
- 碰到setTimeout函数,将该函数放入调用栈,将其回调放入消息队列,弹出setTimeout,调用栈清空
- 打印3
- then函数放入调用栈,内部的回调放入微任务,弹出then函数
- 同上
- 弹出fun2函数,当前调用栈清空
- 微任务按照顺序放入调用栈执行,打印5 6
10.微任务和调用栈都清空,将消息队列依次放入调用栈并执行,打印2
44 给对象加属性
给对象加属性:obj.xxx=xxx
但是给基本类型加属性:10.xxx=xxx也不会报错,只是访问时10.xxx为undefined
45 装箱操作
给基本数据类型包装一下变成对象类型:Object(10)
变成了Number(10)