1. 防抖和节流
防抖则延迟,节流则无视。
防抖
防抖(debounce),频繁触发某事件则为抖动,防抖则字面理解,防止频繁触发某事件。即创建一个函数,将要执行的函数(callback函数)延迟到某间隔时间之后。
假设你的输入框有模糊查询功能,每输入一个字符就会向服务器发送请求,频繁快速的请求再加之回调可能有复杂的逻辑处理,则会给浏览器和服务器则会造成巨大负担。
面对这样的问题,显然,我们不能去掉对用户输入事件的监听。然我们可以通过防抖函数,来调节方法触发的时间。
手写防抖函数:
先分析想如何调用这个防抖函数?
const onSearch = debounce((param)=>{
// 函数体,输入事件的监听,输入字符的后续操作
console.log(`执行防抖...,param为${param}`)
}, 2000)
-
首先,这个函数必要两个参数,一个回调函数,另一个为时间间隔。
function debounce(cb, timeout = 0){ } -
这时候,需要让回调函数(cb)每隔timeout触发一次
function debounce(cb, timeout = 0){ setTimeout(() => { cb() }, timeout) }但这样写由于函数无返回值,我们的onSearch拿到的是 undefined ,后续再怎么输入字符,都不会执行后续操作。那就需要,防抖函数有个返回值且是个函数,可以让onSearch在输入字符之后执行。
function debounce(cb, timeout = 0){ return ()=>{ setTimeout(() => { cb() }, timeout) } } -
代码写到这个时候,键盘快速输入几个字符,还是会执行多次。防抖函数内部需要把 timeout 清除
function debounce(cb, timeout = 0){ let timer = null return ()=>{ clearTimeout(timer) timer = setTimeout(() => { cb() }, timeout) } }let timer = null 能否被写进 return 的函数体里呢?答案为不能。我们知道,通过return 返回的timer变量没有被销毁, timeout 时间内函数执行则会被 clearTimeout,以达到 timeout 时间内多次执行,只触发一次目的。若将 timer 变量定义在 return 函数内部,则一执行回调就会重新创建变量timer,还是会执行多次。
-
这时候,还有一个很大的不足:不能传参数。
onSearch执行的时候,不管参数传几,打印出来都是undefined,接下来兼容传参。首先,debounce方法内部return的方法要接收参数,再把参数传给回调
function debounce(cb, timeout = 0){ let timer = null return (arg)=>{ clearTimeout(timer) timer = setTimeout(() => { cb(arg) }, timeout) } }假设
cb回调有多个参数,则用扩展运算符接收全部参数,再将全部参数给到cb回调function debounce(cb, timeout = 0){ let timer = null return (...arg)=>{ clearTimeout(timer) timer = setTimeout(() => { cb(...arg) }, timeout) } }
节流
节流(throttle),字面理解, 每隔一定时间触发一次,节约资源。在一定时间内, 无视 后续的触发。
试想,假设有个提交表单的功能,用户短时间内重复点击提交按钮,则会重复调用提交接口,这时给服务器带来压力不说,可能也会触发接口的错误提示一类。那解决这个问题,可以用到节流函数,提交点击完,再一定时间内再次点击提交则 无视 不做处理。
手写节流函数:
节流函数对调用和防抖函数是一样的?
const onSearch = throttle((param)=>{
// 函数体,输入事件的监听,输入字符的后续操作
console.log(`执行节流...,param为${param}`)
}, 2000)
-
和防抖函数一样,节流函数也是需要两个参数,且需要返回函数。
function throttle(cb, timeout = 0){ return ()=>{ cb() } } -
内部需要一个变量控制 timeout 时间内,是否可执行cb回调。处理参数与防抖函数一致
function throttle(cb, timeout = 0){ let flag = true return (...arg)=>{ if(flag){ flag = false cb(...arg) setTimeout(() => { flag = true }, timeout) } } }
当然防抖/节流函数还有很多优化空间,比如内部对参数 cb 进行判断处理,timeout进行类型判断或类型转换等。
2. 从输入url到页面渲染发生了什么
-
DNS解析,将域名地址解析为IP地址
在浏览器中输入www.baidu.com, 操作系统会先检查本地hosts文件是否有该域名与IP的映射,如果没有,则查找本地DNS解析器缓存。
若还没查到,则去本地DNS服务器查询,若查询的域名包含在本地配置区域资源中,则完成域名解析。若不由本地DNS服务器区域解析,但该服务器已缓存了网址映射关系,则直接用这个映射。
若以上都没拿到IP地址,则由DNS服务器查询,若DNS服务器的设置为转发模式,则向上一级DNS服务器转发,若上一级没能解析,则转发至上上一级,以此循环;若DNS服务器的设置未用转发模式,则本地DNS服务器则把请求发至根DNS,根DNS回根据.com是谁来授权管理,并返回一个负责该顶级域名服务器的IP。本地DNS服务器收到这个IP后,会联系这台服务器,若这台服务器不能解析则找一个管理.com域的下一台DNS服务器地址(baidu.com)给本地服务器,本地服务器拿到这个IP重复上面动作,直至找到。
客户端和本地DNS服务器之间查询是递归查询,DNS服务器之间的交互查询是迭代查询
-
TCP链接,TCP三次握手
(1)第一次握手,由浏览器发起,告知服务器要发送请求
(2)第二次握手,由服务器发起,告知浏览器准备接收了
(3)第三次握手,由浏览器发送,告知服务器,我马上就发送请求了,准备接收(确认要发送请求,避免浏览器不发送请求情况下,服务器干等着的情况) -
发送请求报文
-
接收响应
-
浏览器渲染页面
遇见HTML标记,则调用HTML解析器解析成Token并构建dom树
遇见style/link标记,则调用css解析器,构建cssom树
遇见script标记,则调用javascript解析器处理script代码
将dom树和cssom树合并成一个渲染树。
根据渲染树计算布局
渲染 -
断开链接,TCP四次挥手
(1)第一次挥手,由浏览器发起,告知服务器,请求报文发送完了
(2)第二次挥手,由服务器发起,告知浏览器,请求报文接收完毕
(3)第三次挥手,由服务器发起,告知浏览器,响应报文发送完了
(4)第四次挥手,由浏览器发起,告知服务器,响应报文接收完毕
3. 原型链
每个函数都有一个 prototype 属性,默认指向一个object空对象,也就是原型对象
假设如下代码:
function Fn(){
this.a = 1
}
Fn.prototype.test = function(){
console.log('test')
}
var fn = new Fn()
console.log(fn.tostring())
代码里创建了构造函数 Fn,函数内部添加了属性a,并在Fn的 prototype 上添加了方法 test。创建了Fn的实例对象fn,这时候a就是fn的自有属性。
当通过fn查找属性 a 的时候,先在自身找,找到了属性a。
当通过fn查找属性 test 的时候,先在自身找,没有找到,这时候通过 __proto__ 找test。
当通过fn查找属性 tostring 的时候,先在自身找,没有找到,这时候通过 __proto__ 找,还没有找到则通过 __proto__ 再往上找,直至一个对象的原型对象为 null 找到该属性
每个实例对象(object)都有一个私有属性(称之为 __ proto__ )指向它的构造函数的原型对象(prototype)。该原型对象也有一个自己的原型对象(__ proto__),层层向上直到一个对象的原型对象为
null。根据定义,null没有原型,并作为这个原型链中的最后一个环节。
4. Promise
Promise 可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。
实例对象中有一个属性[[PromiseState]] 来代表 Promise的状态。
它有三个状态:pending(进行中),fulfilled(成功),rejected(失败)。而这些状态只能通过异步操作的结果来决定,其他手段无法改变。
Promise是一个构造函数。实例化参数为函数类型,该函数接收两个函数类型的参数,通常命名为resolve和reject。resolve 函数在成功时调用,reject在失败时调用。
Promise实例生成以后,可以用then方法分别指定resolved状态和rejected状态的回调函数。then方法可以链式调用
const promise = new Promise((resolve, reject)=>{
if(Math.random() * 10 > 5){
resolve()
}else {
reject()
}
})
promise.then((value)=>{
// 这里为成功的回调
},(reason)=>{
//这里为失败的回调
}).then((value)=>{
// 这里为成功的回调,链式调用
})
5. 用reduce实现map
Array.prototype.mymap = function(cb,initval){
return this.reduce((acc,cur,index,ary)=>{
acc[index] = cb.call(initval,cur,index,ary)
return acc
},[])
}