title 与 h1 的区别、b 与 strong 的区别、i 与 em 的区别?
- strong 标签有语义,是起到减轻语气的成果,而 b 标签是没有的,b 标签只是一个简略加粗标签。b 标签之间的字符都设为粗体,strong 标签增强字符的语气都是通过粗体来实现的,而搜索引擎更偏重 strong 标签。
- title 属性没有明确意义只示意是个题目,H1 则示意档次明确的题目,对页面信息的抓取有很大的影响
- i 内容展现为斜体,em 示意强调的文本
为什么有时候⽤ translate 来扭转地位⽽不是定位?
translate 是 transform 属性的⼀个值。扭转 transform 或 opacity 不会触发浏览器从新布局(reflow)或重绘(repaint),只会触发复合(compositions)。⽽扭转相对定位会触发从新布局,进⽽触发重绘和复合。transform 使浏览器为元素创立⼀个 GPU 图层,但扭转相对定位会使⽤到 CPU。因而 translate()更⾼效,能够缩短平滑动画的绘制工夫。⽽ translate 扭转地位时,元素仍然会占据其原始空间,相对定位就不会发⽣这种状况。
暗藏元素的办法有哪些
- display: none:渲染树不会蕴含该渲染对象,因而该元素不会在页面中占据地位,也不会响应绑定的监听事件。
- visibility: hidden:元素在页面中仍占据空间,然而不会响应绑定的监听事件。
- opacity: 0:将元素的透明度设置为 0,以此来实现元素的暗藏。元素在页面中依然占据空间,并且可能响应元素绑定的监听事件。
- position: absolute:通过应用相对定位将元素移除可视区域内,以此来实现元素的暗藏。
- z-index: 负值:来使其余元素遮盖住该元素,以此来实现暗藏。
- clip/clip-path:应用元素裁剪的办法来实现元素的暗藏,这种办法下,元素仍在页面中占据地位,然而不会响应绑定的监听事件。
- transform: scale(0,0):将元素缩放为 0,来实现元素的暗藏。这种办法下,元素仍在页面中占据地位,然而不会响应绑定的监听事件。
HTTP 状态码
状态码的类别:
类别 | 起因 | 形容 |
---|---|---|
1xx | Informational(信息性状态码) | 承受的申请正在解决 |
2xx | Success(胜利状态码) | 申请失常处理完毕 |
3xx | Redirection(重定向状态码) | 须要进行附加操作一实现申请 |
4xx | Client Error (客户端谬误状态码) | 服务器无奈解决申请 |
5xx | Server Error(服务器谬误状态码) | 服务器解决申请出错 |
1. 2XX (Success 胜利状态码)
状态码 2XX 示意申请被失常解决了。
(1)200 OK
200 OK 示意客户端发来的申请被服务器端失常解决了。
(2)204 No Content
该状态码示意客户端发送的申请曾经在服务器端失常解决了,然而没有返回的内容,响应报文中不蕴含实体的主体局部。个别在只须要从客户端往服务器端发送信息,而服务器端不须要往客户端发送内容时应用。
(3)206 Partial Content
该状态码示意客户端进行了范畴申请,而服务器端执行了这部分的 GET 申请。响应报文中蕴含由 Content-Range 指定范畴的实体内容。
2. 3XX (Redirection 重定向状态码)
3XX 响应结果表明浏览器须要执行某些非凡的解决以正确处理申请。
(1)301 Moved Permanently
永恒重定向。 该状态码示意申请的资源曾经被调配了新的 URI,当前应应用资源指定的 URI。新的 URI 会在 HTTP 响应头中的 Location 首部字段指定。若用户曾经把原来的 URI 保留为书签,此时会依照 Location 中新的 URI 从新保留该书签。同时,搜索引擎在抓取新内容的同时也将旧的网址替换为重定向之后的网址。
应用场景:
- 当咱们想换个域名,旧的域名不再应用时,用户拜访旧域名时用 301 就重定向到新的域名。其实也是通知搜索引擎收录的域名须要对新的域名进行收录。
- 在搜索引擎的搜寻后果中呈现了不带 www 的域名,而带 www 的域名却没有收录,这个时候能够用 301 重定向来通知搜索引擎咱们指标的域名是哪一个。
(2)302 Found
长期重定向。 该状态码示意申请的资源被调配到了新的 URI,心愿用户(本次)能应用新的 URI 拜访资源。和 301 Moved Permanently 状态码类似,然而 302 代表的资源不是被永恒重定向,只是长期性质的。也就是说已挪动的资源对应的 URI 未来还有可能产生扭转。若用户把 URI 保留成书签,但不会像 301 状态码呈现时那样去更新书签,而是仍旧保留返回 302 状态码的页面对应的 URI。同时,搜索引擎会抓取新的内容而保留旧的网址。因为服务器返回 302 代码,搜索引擎认为新的网址只是临时的。
应用场景:
- 当咱们在做流动时,登录到首页主动重定向,进入流动页面。
- 未登陆的用户拜访用户核心重定向到登录页面。
- 拜访 404 页面从新定向到首页。
(3)303 See Other
该状态码示意因为申请对应的资源存在着另一个 URI,应应用 GET 办法定向获取申请的资源。
303 状态码和 302 Found 状态码有着类似的性能,然而 303 状态码明确示意客户端该当采纳 GET 办法获取资源。
303 状态码通常作为 PUT 或 POST 操作的返回后果,它示意重定向链接指向的不是新上传的资源,而是另外一个页面,比方音讯确认页面或上传进度页面。而申请重定向页面的办法要总是应用 GET。
留神:
- 当 301、302、303 响应状态码返回时,简直所有的浏览器都会把 POST 改成 GET,并删除申请报文内的主体,之后申请会再次主动发送。
- 301、302 规范是禁止将 POST 办法变成 GET 办法的,但理论大家都会这么做。
(4)304 Not Modified
浏览器缓存相干。 该状态码示意客户端发送附带条件的申请时,服务器端容许申请拜访资源,但未满足条件的状况。304 状态码返回时,不蕴含任何响应的主体局部。304 尽管被划分在 3XX 类别中,然而和重定向没有关系。
带条件的申请(Http 条件申请):应用 Get 办法 申请,申请报文中蕴含(if-match
、if-none-match
、if-modified-since
、if-unmodified-since
、if-range
)中任意首部。
状态码 304 并不是一种谬误,而是通知客户端有缓存,间接应用缓存中的数据。返回页面的只有头部信息,是没有内容局部的,这样在肯定水平上进步了网页的性能。
(5)307 Temporary Redirect
307 示意长期重定向。 该状态码与 302 Found 有着雷同含意,只管 302 规范禁止 POST 变成 GET,然而理论应用时还是这样做了。
307 会恪守浏览器规范,不会从 POST 变成 GET。然而对于解决申请的行为时,不同浏览器还是会呈现不同的状况。标准要求浏览器持续向 Location 的地址 POST 内容。标准要求浏览器持续向 Location 的地址 POST 内容。
3. 4XX (Client Error 客户端谬误状态码)
4XX 的响应结果表明客户端是产生谬误的起因所在。
(1)400 Bad Request
该状态码示意申请报文中存在语法错误。当谬误产生时,需批改申请的内容后再次发送申请。另外,浏览器会像 200 OK 一样看待该状态码。
(2)401 Unauthorized
该状态码示意发送的申请须要有通过 HTTP 认证 (BASIC 认证、DIGEST 认证) 的认证信息。若之前已进行过一次申请,则示意用户认证失败
返回含有 401 的响应必须蕴含一个实用于被申请资源的 WWW-Authenticate 首部用以质询 (challenge) 用户信息。当浏览器首次接管到 401 响应,会弹出认证用的对话窗口。
以下状况会呈现 401:
- 401.1 – 登录失败。
- 401.2 – 服务器配置导致登录失败。
- 401.3 – 因为 ACL 对资源的限度而未取得受权。
- 401.4 – 筛选器受权失败。
- 401.5 – ISAPI/CGI 应用程序受权失败。
- 401.7 – 拜访被 Web 服务器上的 URL 受权策略回绝。这个错误代码为 IIS 6.0 所专用。
(3)403 Forbidden
该状态码表明申请资源的拜访被服务器回绝了,服务器端没有必要给出具体理由,然而能够在响应报文实体的主体中进行阐明。进入该状态后,不能再持续进行验证。该拜访是永恒禁止的,并且与应用逻辑密切相关。
IIS 定义了许多不同的 403 谬误,它们指明更为具体的谬误起因:
- 403.1 – 执行拜访被禁止。
- 403.2 – 读拜访被禁止。
- 403.3 – 写访问被禁止。
- 403.4 – 要求 SSL。
- 403.5 – 要求 SSL 128。
- 403.6 – IP 地址被回绝。
- 403.7 – 要求客户端证书。
- 403.8 – 站点拜访被回绝。
- 403.9 – 用户数过多。
- 403.10 – 配置有效。
- 403.11 – 明码更改。
- 403.12 – 回绝拜访映射表。
- 403.13 – 客户端证书被撤消。
- 403.14 – 回绝目录列表。
- 403.15 – 超出客户端拜访许可。
- 403.16 – 客户端证书不受信赖或有效。
- 403.17 – 客户端证书已过期或尚未失效
- 403.18 – 在以后的应用程序池中不能执行所申请的 URL。这个错误代码为 IIS 6.0 所专用。
- 403.19 – 不能为这个应用程序池中的客户端执行 CGI。这个错误代码为 IIS 6.0 所专用。
- 403.20 – Passport 登录失败。这个错误代码为 IIS 6.0 所专用。
(4)404 Not Found
该状态码表明服务器上无奈找到申请的资源。除此之外,也能够在服务器端拒绝请求且不想阐明理由时应用。
以下状况会呈现 404:
- 404.0 -(无)– 没有找到文件或目录。
- 404.1 – 无奈在所申请的端口上拜访 Web 站点。
- 404.2 – Web 服务扩大锁定策略阻止本申请。
- 404.3 – MIME 映射策略阻止本申请。
(5)405 Method Not Allowed
该状态码示意客户端申请的办法尽管能被服务器辨认,然而服务器禁止应用该办法。GET 和 HEAD 办法,服务器应该总是容许客户端进行拜访。客户端能够通过 OPTIONS 办法(预检)来查看服务器容许的拜访办法, 如下
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
4. 5XX (Server Error 服务器谬误状态码)
5XX 的响应结果表明服务器自身产生谬误.
(1)500 Internal Server Error
该状态码表明服务器端在执行申请时产生了谬误。也有可能是 Web 利用存在的 bug 或某些长期的故障。
(2)502 Bad Gateway
该状态码表明表演网关或代理角色的服务器,从上游服务器中接管到的响应是有效的。留神,502 谬误通常不是客户端可能修复的,而是须要由途经的 Web 服务器或者代理服务器对其进行修复。以下状况会呈现 502:
- 502.1 – CGI(通用网关接口)应用程序超时。
- 502.2 – CGI(通用网关接口)应用程序出错。
(3)503 Service Unavailable
该状态码表明服务器临时处于超负载或正在进行停机保护,当初无奈解决申请。如果当时得悉解除以上情况须要的工夫,最好写入 RetryAfter 首部字段再返回给客户端。
应用场景:
- 服务器停机保护时,被动用 503 响应申请;
- nginx 设置限速,超过限速,会返回 503。
(4)504 Gateway Timeout
该状态码示意网关或者代理的服务器无奈在规定的工夫内取得想要的响应。他是 HTTP 1.1 中新退出的。
应用场景:代码执行工夫超时,或者产生了死循环。
5. 总结
(1)2XX 胜利
- 200 OK,示意从客户端发来的申请在服务器端被正确处理
- 204 No content,示意申请胜利,但响应报文不含实体的主体局部
- 205 Reset Content,示意申请胜利,但响应报文不含实体的主体局部,然而与 204 响应不同在于要求申请方重置内容
- 206 Partial Content,进行范畴申请
(2)3XX 重定向
- 301 moved permanently,永久性重定向,示意资源已被调配了新的 URL
- 302 found,临时性重定向,示意资源长期被调配了新的 URL
- 303 see other,示意资源存在着另一个 URL,应应用 GET 办法获取资源
- 304 not modified,示意服务器容许拜访资源,但因产生申请未满足条件的状况
- 307 temporary redirect,长期重定向,和 302 含意相似,然而冀望客户端放弃申请办法不变向新的地址发出请求
(3)4XX 客户端谬误
- 400 bad request,申请报文存在语法错误
- 401 unauthorized,示意发送的申请须要有通过 HTTP 认证的认证信息
- 403 forbidden,示意对申请资源的拜访被服务器回绝
- 404 not found,示意在服务器上没有找到申请的资源
(4)5XX 服务器谬误
- 500 internal sever error,示意服务器端在执行申请时产生了谬误
- 501 Not Implemented,示意服务器不反对以后申请所须要的某个性能
- 503 service unavailable,表明服务器临时处于超负载或正在停机保护,无奈解决申请
setState
在理解 setState 之前,咱们先来简略理解下 React 一个包装构造: Transaction:
事务 (Transaction)
是 React 中的一个调用构造,用于包装一个办法,构造为: initialize – perform(method) – close。通过事务,能够对立治理一个办法的开始与完结;处于事务流中,示意过程正在执行一些操作
- setState: React 中用于批改状态,更新视图。它具备以下特点:
异步与同步: setState 并不是单纯的异步或同步,这其实与调用时的环境相干:
-
在 合成事件 和 生命周期钩子 (除 componentDidUpdate) 中,setState 是 ” 异步 ” 的;
-
起因: 因为在 setState 的实现中,有一个判断: 当更新策略正在事务流的执行中时,该组件更新会被推入 dirtyComponents 队列中期待执行;否则,开始执行 batchedUpdates 队列更新;
- 在生命周期钩子调用中,更新策略都处于更新之前,组件仍处于事务流中,而 componentDidUpdate 是在更新之后,此时组件曾经不在事务流中了,因而则会同步执行;
- 在合成事件中,React 是基于 事务流实现的事件委托机制 实现,也是处于事务流中;
- 问题: 无奈在 setState 后马上从 this.state 上获取更新后的值。
- 解决: 如果须要马上同步去获取新值,setState 其实是能够传入第二个参数的。setState(updater, callback),在回调中即可获取最新值;
-
-
在 原生事件 和 setTimeout 中,setState 是同步的,能够马上获取更新后的值;
- 起因: 原生事件是浏览器自身的实现,与事务流无关,天然是同步;而 setTimeout 是搁置于定时器线程中延后执行,此时事务流已完结,因而也是同步;
- 批量更新 : 在 合成事件 和 生命周期钩子 中,setState 更新队列时,存储的是 合并状态(Object.assign)。因而后面设置的 key 值会被前面所笼罩,最终只会执行一次更新;
-
函数式 : 因为 Fiber 及 合并 的问题,官网举荐能够传入 函数 的模式。setState(fn),在 fn 中返回新的 state 对象即可,例如 this.setState((state, props) => newState);
- 应用函数式,能够用于防止 setState 的批量更新的逻辑,传入的函数将会被 顺序调用;
注意事项:
- setState 合并,在 合成事件 和 生命周期钩子 中屡次间断调用会被优化为一次;
-
当组件已被销毁,如果再次调用 setState,React 会报错正告,通常有两种解决办法
- 将数据挂载到内部,通过 props 传入,如放到 Redux 或 父级中;
- 在组件外部保护一个状态量 (isUnmounted),componentWillUnmount 中标记为 true,在 setState 前进行判断;
总结
setState 并非真异步,只是看上去像异步。在源码中,通过
isBatchingUpdates
来判断
setState
是先存进state
队列还是间接更新,如果值为 true 则执行异步操作,为 false 则间接更新。- 那么什么状况下
isBatchingUpdates
会为true
呢?在 React 能够管制的中央,就为 true,比方在 React 生命周期事件和合成事件中,都会走合并操作,提早更新的策略。 - 但在 React 无法控制的中央,比方原生事件,具体就是在
addEventListener
、setTimeout
、setInterval
等事件中,就只能同步更新。
个别认为,
做异步设计是为了性能优化、缩小渲染次数
,React 团队还补充了两点。
- 放弃外部一致性。如果将 state 改为同步更新,那只管 state 的更新是同步的,然而 props 不是。
- 启用并发更新,实现异步渲染。
setState
只有在 React 本身的合成事件和钩子函数中是异步的,在原生事件和 setTimeout 中都是同步的setState
的异步并不是说外部由异步代码实现,其实自身执行的过程和代码都是同步的,只是合成事件和钩子函数中没法立马拿到更新后的值,造成了所谓的异步。当然能够通过 setState 的第二个参数中的 callback 拿到更新后的后果setState
的批量更新优化也是建设在异步(合成事件、钩子函数)之上的,在原生事件和 setTimeout 中不会批量更新,在异步中如果对同一个值进行屡次 setState,setState 的批量更新策略会对其进行笼罩,去最初一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新- 合成事件中是异步
- 钩子函数中的是异步
- 原生事件中是同步
- setTimeout 中是同步
这是一道常常会呈现的 React setState 口试题:上面的代码输入什么呢?
class Test extends React.Component {
state = {count: 0};
componentDidMount() {this.setState({count: this.state.count + 1});
console.log(this.state.count);
this.setState({count: this.state.count + 1});
console.log(this.state.count);
setTimeout(() => {this.setState({count: this.state.count + 1});
console.log(this.state.count);
this.setState({count: this.state.count + 1});
console.log(this.state.count);
}, 0);
}
render() {return null;}
};
咱们能够进行如下的剖析:
- 首先第一次和第二次的
console.log
,都在 React 的生命周期事件中,所以是异步的解决形式,则输入都为0
; - 而在
setTimeout
中的console.log
处于原生事件中,所以会同步的解决再输入后果,但须要留神,尽管count
在后面通过了两次的this.state.count + 1
,然而每次获取的this.state.count
都是初始化时的值,也就是0
; - 所以此时
count
是1
,那么后续在setTimeout
中的输入则是2
和3
。
所以残缺答案是 0,0,2,3
同步场景
异步场景中的案例使咱们建设了这样一个认知:setState 是异步的,但上面这个案例又会颠覆你的认知。如果咱们将 setState 放在 setTimeout 事件中,那状况就齐全不同了。
class Test extends Component {
state = {count: 0}
componentDidMount(){this.setState({ count: this.state.count + 1});
console.log(this.state.count);
setTimeout(() => {this.setState({ count: this.state.count + 1});
console.log("setTimeout:" + this.state.count);
}, 0);
}
render(){...}
}
那这时输入的应该是什么呢?如果你认为是 0,0,那么又错了。
正确的后果是 0,2
。因为 setState
并不是真正的异步函数,它实际上是通过队列提早执行操作实现的,通过 isBatchingUpdates 来判断 setState 是先存进 state 队列还是间接更新。值为 true 则执行异步操作,false 则间接同步更新
接下来这个案例的答案是什么呢
class Test extends Component {
state = {count: 0}
componentDidMount(){
this.setState({count: this.state.count + 1}, () => {console.log(this.state.count)
})
this.setState({count: this.state.count + 1}, () => {console.log(this.state.count)
})
}
render(){...}
}
如果你感觉答案是 1,2
,那必定就错了。这种迷惑性极强的考题在面试中十分常见,因为它反直觉。
如果从新认真思考,你会发现以后拿到的 this.state.count
的值并没有变动,都是 0
,所以输入后果应该是 1,1
。
当然,也能够在 setState
函数中获取批改后的 state
值进行批改。
class Test extends Component {
state = {count: 0}
componentDidMount(){
this.setState(
preState=> ({count:preState.count + 1}),()=>{console.log(this.state.count)
})
this.setState(
preState=>({count:preState.count + 1}),()=>{console.log(this.state.count)
})
}
render(){...}
}
这些统统是异步的回调,如果你认为输入后果是 1,2,那就又错了,实际上是 2,2
。
为什么会这样呢?当调用 setState
函数时,就会 把以后的操作放入队列中
。React 依据队列内容,合并 state 数据,实现后再逐个执行回调,依据后果更新虚构 DOM,触发渲染。所以 回调时,state 曾经合并计算实现了
,输入的后果就是 2,2
了。
垃圾回收
- 对于在 JavaScript 中的字符串,对象,数组是没有固定大小的,只有当对他们进行动态分配存储时,解释器就会分配内存来存储这些数据,当 JavaScript 的解释器耗费完零碎中所有可用的内存时,就会造成零碎解体。
- 内存透露,在某些状况下,不再应用到的变量所占用内存没有及时开释,导致程序运行中,内存越占越大,极其状况下能够导致系统解体,服务器宕机。
- JavaScript 有本人的一套垃圾回收机制,JavaScript 的解释器能够检测到什么时候程序不再应用这个对象了(数据),就会把它所占用的内存开释掉。
- 针对 JavaScript 的来及回收机制有以下两种办法(罕用):标记革除,援用计数
- 标记革除
v8 的垃圾回收机制基于分代回收机制,这个机制又基于世代假说,这个假说有两个特点,一是新生的对象容易早死,另一个是不死的对象会活得更久。基于这个假说,v8 引擎将内存分为了新生代和老生代。
- 新创建的对象或者只经验过一次的垃圾回收的对象被称为新生代。经验过屡次垃圾回收的对象被称为老生代。
- 新生代被分为 From 和 To 两个空间,To 个别是闲置的。当 From 空间满了的时候会执行 Scavenge 算法进行垃圾回收。当咱们执行垃圾回收算法的时候应用逻辑将会进行,等垃圾回收完结后再继续执行。
这个算法分为三步:
- 首先查看 From 空间的存活对象,如果对象存活则判断对象是否满足降职到老生代的条件,如果满足条件则降职到老生代。如果不满足条件则挪动 To 空间。
- 如果对象不存活,则开释对象的空间。
- 最初将 From 空间和 To 空间角色进行替换。
新生代对象降职到老生代有两个条件:
- 第一个是判断是对象否曾经通过一次 Scavenge 回收。若经验过,则将对象从 From 空间复制到老生代中;若没有经验,则复制到 To 空间。
- 第二个是 To 空间的内存应用占比是否超过限度。当对象从 From 空间复制到 To 空间时,若 To 空间应用超过 25%,则对象间接降职到老生代中。设置 25% 的起因次要是因为算法完结后,两个空间完结后会替换地位,如果 To 空间的内存太小,会影响后续的内存调配。
老生代采纳了标记革除法和标记压缩法。标记革除法首先会对内存中存活的对象进行标记,标记完结后革除掉那些没有标记的对象。因为标记革除后会造成很多的内存碎片,不便于前面的内存调配。所以了解决内存碎片的问题引入了标记压缩法。
因为在进行垃圾回收的时候会暂停利用的逻辑,对于新生代办法因为内存小,每次进展的工夫不会太长,但对于老生代来说每次垃圾回收的工夫长,进展会造成很大的影响。为了解决这个问题 V8 引入了增量标记的办法,将一次进展进行的过程分为了多步,每次执行完一小步就让运行逻辑执行一会,就这样交替运行
参考 前端进阶面试题具体解答
事件机制
涉及面试题:事件的触发过程是怎么样的?晓得什么是事件代理嘛?
1. 简介
事件流是一个事件沿着特定数据结构流传的过程。冒泡和捕捉是事件流在
DOM
中两种不同的流传办法
事件流有三个阶段
- 事件捕捉阶段
- 处于指标阶段
- 事件冒泡阶段
事件捕捉
事件捕捉(
event capturing
):艰深的了解就是,当鼠标点击或者触发dom
事件时,浏览器会从根节点开始由外到内进行事件流传,即点击了子元素,如果父元素通过事件捕捉形式注册了对应的事件的话,会先触发父元素绑定的事件
事件冒泡
事件冒泡(dubbed bubbling):与事件捕捉恰恰相反,事件冒泡程序是由内到外进行事件流传,直到根节点
无论是事件捕捉还是事件冒泡,它们都有一个独特的行为,就是事件流传
2. 捕捉和冒泡
<div id="div1">
<div id="div2"></div>
</div>
<script>
let div1 = document.getElementById('div1');
let div2 = document.getElementById('div2');
div1.onClick = function(){alert('1')
}
div2.onClick = function(){alert('2');
}
</script>
当点击
div2
时,会弹出两个弹出框。在ie8/9/10
、chrome
浏览器,会先弹出”2”再弹出“1”,这就是事件冒泡:事件从最底层的节点向上冒泡流传。事件捕捉则跟事件冒泡相同W3C 的规范是先捕捉再冒泡,
addEventListener
的第三个参数决定把事件注册在捕捉(true
)还是冒泡(false
)
3. 事件对象
4. 事件流阻止
在一些状况下须要阻止事件流的流传,阻止默认动作的产生
event.preventDefault()
:勾销事件对象的默认动作以及持续流传。event.stopPropagation()/ event.cancelBubble = true
:阻止事件冒泡。
事件的阻止在不同浏览器有不同解决
- 在
IE
下应用event.returnValue= false
, - 在非
IE
下则应用event.preventDefault()
进行阻止
preventDefault 与 stopPropagation 的区别
preventDefault
通知浏览器不必执行与事件相关联的默认动作(如表单提交)stopPropagation
是进行事件持续冒泡,然而对 IE9 以下的浏览器有效
5. 事件注册
- 通常咱们应用
addEventListener
注册事件,该函数的第三个参数能够是布尔值,也能够是对象。对于布尔值useCapture
参数来说,该参数默认值为false
。useCapture
决定了注册的事件是捕捉事件还是冒泡事件 - 一般来说,咱们只心愿事件只触发在指标上,这时候能够应用
stopPropagation
来阻止事件的进一步流传。通常咱们认为stopPropagation
是用来阻止事件冒泡的,其实该函数也能够阻止捕捉事件。stopImmediatePropagation
同样也能实现阻止事件,然而还能阻止该事件指标执行别的注册事件
node.addEventListener('click',(event) =>{event.stopImmediatePropagation()
console.log('冒泡')
},false);
// 点击 node 只会执行下面的函数,该函数不会执行
node.addEventListener('click',(event) => {console.log('捕捉')
},true)
6. 事件委托
- 在
js
中性能优化的其中一个次要思维是缩小dom
操作。 - 节俭内存
- 不须要给子节点登记事件
假如有
100
个li
,每个li
有雷同的点击事件。如果为每个 Li
都增加事件,则会造成dom
拜访次数过多,引起浏览器重绘与重排的次数过多,性能则会升高。应用事件委托则能够解决这样的问题
原理
实现事件委托是利用了事件的冒泡原理实现的。当咱们为最外层的节点增加点击事件,那么外面的
ul
、li
、a
的点击事件都会冒泡到最外层节点上,委托它代为执行事件
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
window.onload = function(){var ulEle = document.getElementById('ul');
ul.onclick = function(ev){
// 兼容 IE
ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase() == 'li'){alert( target.innerHTML);
}
}
}
</script>
HTTPS是如何保障平安的?
先了解两个概念:
- 对称加密:即通信的双⽅都使⽤同⼀个秘钥进⾏加解密,对称加密尽管很简略性能也好,然而⽆法解决⾸次把秘钥发给对⽅的问题,很容易被⿊客拦挡秘钥。
- ⾮对称加密:
- 私钥 + 公钥 = 密钥对
- 即⽤私钥加密的数据, 只有对应的公钥能力解密, ⽤公钥加密的数据, 只有对应的私钥能力解密
- 因为通信双⽅的⼿⾥都有⼀套⾃⼰的密钥对, 通信之前双⽅会先把⾃⼰的公钥都先发给对⽅
- 而后对⽅再拿着这个公钥来加密数据响应给对⽅, 等到到了对⽅那⾥, 对⽅再⽤⾃⼰的私钥进⾏解密
⾮对称加密尽管安全性更⾼,然而带来的问题就是速度很慢,影响性能。
解决⽅案:
联合两种加密⽅式,将对称加密的密钥使⽤⾮对称加密的公钥进⾏加密,而后发送进来,接管⽅使⽤私钥进⾏解密失去对称加密的密钥,而后双⽅能够使⽤对称加密来进⾏沟通。
此时⼜带来⼀个问题,两头⼈问题:
如果此时在客户端和服务器之间存在⼀个两头⼈, 这个两头⼈只须要把本来双⽅通信互发的公钥, 换成⾃⼰的公钥, 这样两头⼈就能够轻松解密通信双⽅所发送的所有数据。
所以这个时候须要⼀个平安的第三⽅颁发证书(CA),证实身份的身份,防⽌被两头⼈攻打。证书中包含:签发者、证书⽤途、使⽤者公钥、使⽤者私钥、使⽤者的 HASH 算法、证书到期工夫等。
然而问题来了,如果两头⼈篡改了证书,那么身份证明是不是就⽆效了?这个证实就⽩买了,这个时候须要⼀个新的技术,数字签名。
数字签名就是⽤ CA ⾃带的 HASH 算法对证书的内容进⾏ HASH 失去⼀个摘要,再⽤ CA 的私钥加密,最终组成数字签名。当别⼈把他的证书发过来的时候, 我再⽤同样的 Hash 算法, 再次⽣成音讯摘要,而后⽤ CA 的公钥对数字签名解密, 失去 CA 创立的音讯摘要, 两者⼀⽐, 就晓得两头有没有被⼈篡改了。这个时候就能最⼤水平保障通信的平安了。
变量晋升
当执行
JS
代码时,会生成执行环境,只有代码不是写在函数中的,就是在全局执行环境中,函数中的代码会产生函数执行环境,只此两种执行环境。
b() // call b
console.log(a) // undefined
var a = 'Hello world'
function b() {console.log('call b')
}
想必以上的输入大家必定都曾经明确了,这是因为函数和变量晋升的起因。通常晋升的解释是说将申明的代码移动到了顶部,这其实没有什么谬误,便于大家了解。然而更精确的解释应该是:在生成执行环境时,会有两个阶段。第一个阶段是创立的阶段,
JS
解释器会找出须要晋升的变量和函数,并且给他们提前在内存中开拓好空间,函数的话会将整个函数存入内存中,变量只申明并且赋值为undefined
,所以在第二个阶段,也就是代码执行阶段,咱们能够间接提前应用
- 在晋升的过程中,雷同的函数会笼罩上一个函数,并且函数优先于变量晋升
b() // call b second
function b() {console.log('call b fist')
}
function b() {console.log('call b second')
}
var b = 'Hello world'
var
会产生很多谬误,所以在 ES6 中引入了let
。let
不能在申明前应用,然而这并不是常说的let
不会晋升,let
晋升了,在第一阶段内存也曾经为他开拓好了空间,然而因为这个申明的个性导致了并不能在申明前应用
Iterator 迭代器
Iterator
(迭代器)是一种接口,也能够说是一种标准。为各种不同的数据结构提供对立的拜访机制。任何数据结构只有部署Iterator
接口,就能够实现遍历操作(即顺次解决该数据结构的所有成员)。
Iterator 语法:
const obj = {[Symbol.iterator]:function(){}
}
[Symbol.iterator]
属性名是固定的写法,只有领有了该属性的对象,就可能用迭代器的形式进行遍历。
- 迭代器的遍历办法是首先取得一个迭代器的指针,初始时该指针指向第一条数据之前,接着通过调用 next 办法,扭转指针的指向,让其指向下一条数据
-
每一次的
next
都会返回一个对象,该对象有两个属性value
代表想要获取的数据done
布尔值,false 示意以后指针指向的数据有值,true 示意遍历曾经完结
Iterator 的作用有三个:
- 创立一个指针对象,指向以后数据结构的起始地位。也就是说,遍历器对象实质上,就是一个指针对象。
- 第一次调用指针对象的 next 办法,能够将指针指向数据结构的第一个成员。
- 第二次调用指针对象的 next 办法,指针就指向数据结构的第二个成员。
- 一直调用指针对象的 next 办法,直到它指向数据结构的完结地位。
每一次调用 next 办法,都会返回数据结构的以后成员的信息。具体来说,就是返回一个蕴含 value 和 done 两个属性的对象。其中,value 属性是以后成员的值,done 属性是一个布尔值,示意遍历是否完结。
let arr = [{num:1},2,3]
let it = arr[Symbol.iterator]() // 获取数组中的迭代器
console.log(it.next()) // {value: Object { num: 1}, done: false }
console.log(it.next()) // {value: 2, done: false}
console.log(it.next()) // {value: 3, done: false}
console.log(it.next()) // {value: undefined, done: true}
对象没有布局 Iterator 接口,无奈应用
for of
遍历。上面使得对象具备 Iterator 接口
- 一个数据结构只有有 Symbol.iterator 属性,就能够认为是“可遍历的”
- 原型部署了 Iterator 接口的数据结构有三种,具体蕴含四种,别离是数组,相似数组的对象,Set 和 Map 构造
为什么对象(Object)没有部署 Iterator 接口呢?
- 一是因为对象的哪个属性先遍历,哪个属性后遍历是不确定的,须要开发者手动指定。然而遍历遍历器是一种线性解决,对于非线性的数据结构,部署遍历器接口,就等于要部署一种线性转换
- 对对象部署
Iterator
接口并不是很必要,因为Map
补救了它的缺点,又正好有Iteraotr
接口
let obj = {
id: '123',
name: '张三',
age: 18,
gender: '男',
hobbie: '睡觉'
}
obj[Symbol.iterator] = function () {let keyArr = Object.keys(obj)
let index = 0
return {next() {
return index < keyArr.length ? {
value: {key: keyArr[index],
val: obj[keyArr[index++]]
}
} : {done: true}
}
}
}
for (let key of obj) {console.log(key)
}
作用域
- 作用域:作用域是定义变量的区域,它有一套拜访变量的规定,这套规定来治理浏览器引擎如何在以后作用域以及嵌套的作用域中依据变量(标识符)进行变量查找
- 作用域链:作用域链的作用是保障对执行环境有权拜访的所有变量和函数的有序拜访,通过作用域链,咱们能够拜访到外层环境的变量和 函数。
作用域链的实质上是一个指向变量对象的指针列表。变量对象是一个蕴含了执行环境中所有变量和函数的对象。作用域链的前 端始终都是以后执行上下文的变量对象。全局执行上下文的变量对象(也就是全局对象)始终是作用域链的最初一个对象。
- 当咱们查找一个变量时,如果以后执行环境中没有找到,咱们能够沿着作用域链向后查找
- 作用域链的创立过程跟执行上下文的建设无关 ….
作用域能够了解为变量的可拜访性,总共分为三种类型,别离为:
- 全局作用域
- 函数作用域
- 块级作用域,ES6 中的
let
、const
就能够产生该作用域
其实看完后面的闭包、this
这部分外部的话,应该根本能理解作用域的一些利用。
一旦咱们将这些作用域嵌套起来,就变成了另外一个重要的知识点「作用域链」,也就是 JS 到底是如何拜访须要的变量或者函数的。
- 首先作用域链是在定义时就被确定下来的,和箭头函数里的 this 一样,后续不会扭转,JS 会一层层往上寻找须要的内容。
- 其实作用域链这个货色咱们在闭包小结中曾经看到过它的实体了:
[[Scopes]]
图中的 [[Scopes]]
是个数组,作用域的一层层往上寻找就等同于遍历 [[Scopes]]
。
1. 全局作用域
全局变量是挂载在 window 对象下的变量,所以在网页中的任何地位你都能够应用并且拜访到这个全局变量
var globalName = 'global';
function getName() {console.log(globalName) // global
var name = 'inner'
console.log(name) // inner
}
getName();
console.log(name); //
console.log(globalName); //global
function setName(){vName = 'setName';}
setName();
console.log(vName); // setName
- 从这段代码中咱们能够看到,globalName 这个变量无论在什么中央都是能够被拜访到的,所以它就是全局变量。而在 getName 函数中作为局部变量的 name 变量是不具备这种能力的
- 当然全局作用域有相应的毛病,咱们定义很多全局变量的时候,会容易引起变量命名的抵触,所以在定义变量的时候应该留神作用域的问题。
2. 函数作用域
函数中定义的变量叫作函数变量,这个时候只能在函数外部能力拜访到它,所以它的作用域也就是函数的外部,称为函数作用域
function getName () {
var name = 'inner';
console.log(name); //inner
}
getName();
console.log(name);
除了这个函数外部,其余中央都是不能拜访到它的。同时,当这个函数被执行完之后,这个局部变量也相应会被销毁。所以你会看到在 getName 函数里面的 name 是拜访不到的
3. 块级作用域
ES6 中新增了块级作用域,最间接的体现就是新增的 let 关键词,应用 let 关键词定义的变量只能在块级作用域中被拜访,有“暂时性死区”的特点,也就是说这个变量在定义之前是不能被应用的。
在 JS 编码过程中 if 语句
及 for
语句前面 {...}
这外面所包含的,就是 块级作用域
console.log(a) //a is not defined
if(true){
let a = '123';console.log(a);// 123
}
console.log(a) //a is not defined
从这段代码能够看出,变量 a 是在
if 语句 {...}
中由let 关键词
进行定义的变量,所以它的作用域是 if 语句括号中的那局部,而在里面进行拜访 a 变量是会报错的,因为这里不是它的作用域。所以在 if 代码块的前后输入 a 这个变量的后果,控制台会显示 a 并没有定义
DNS 残缺的查问过程
DNS 服务器解析域名的过程:
- 首先会在 浏览器的缓存 中查找对应的 IP 地址,如果查找到间接返回,若找不到持续下一步
- 将申请发送给 本地 DNS 服务器,在本地域名服务器缓存中查问,如果查找到,就间接将查找后果返回,若找不到持续下一步
- 本地 DNS 服务器向 根域名服务器 发送申请,根域名服务器会返回一个所查问域的顶级域名服务器地址
- 本地 DNS 服务器向 顶级域名服务器 发送申请,承受申请的服务器查问本人的缓存,如果有记录,就返回查问后果,如果没有就返回相干的下一级的权威域名服务器的地址
- 本地 DNS 服务器向 权威域名服务器 发送申请,域名服务器返回对应的后果
- 本地 DNS 服务器将返回后果保留在缓存中,便于下次应用
- 本地 DNS 服务器将返回后果返回给浏览器
比方要查问 IP 地址,首先会在浏览器的缓存中查找是否有该域名的缓存,如果不存在就将申请发送到本地的 DNS 服务器中,本地 DNS 服务器会判断是否存在该域名的缓存,如果不存在,则向根域名服务器发送一个申请,根域名服务器返回负责 .com 的顶级域名服务器的 IP 地址的列表。而后本地 DNS 服务器再向其中一个负责 .com 的顶级域名服务器发送一个申请,负责 .com 的顶级域名服务器返回负责 .baidu 的权威域名服务器的 IP 地址列表。而后本地 DNS 服务器再向其中一个权威域名服务器发送一个申请,最初权威域名服务器返回一个对应的主机名的 IP 地址列表。
继承
涉及面试题:原型如何实现继承?
Class
如何实现继承?Class
实质是什么?
首先先来讲下 class
,其实在 JS
中并不存在类,class
只是语法糖,实质还是函数
class Person {}
Person instanceof Function // true
组合继承
组合继承是最罕用的继承形式
function Parent(value) {this.val = value}
Parent.prototype.getValue = function() {console.log(this.val)
}
function Child(value) {Parent.call(this, value)
}
Child.prototype = new Parent()
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
- 以上继承的形式外围是在子类的构造函数中通过
Parent.call(this)
继承父类的属性,而后扭转子类的原型为new Parent()
来继承父类的函数。 - 这种继承形式长处在于构造函数能够传参,不会与父类援用属性共享,能够复用父类的函数,然而也存在一个毛病就是在继承父类函数的时候调用了父类构造函数,导致子类的原型上多了不须要的父类属性,存在内存上的节约
寄生组合继承
这种继承形式对组合继承进行了优化,组合继承毛病在于继承父类函数时调用了构造函数,咱们只须要优化掉这点就行了
function Parent(value) {this.val = value}
Parent.prototype.getValue = function() {console.log(this.val)
}
function Child(value) {Parent.call(this, value)
}
Child.prototype = Object.create(Parent.prototype, {
constructor: {
value: Child,
enumerable: false,
writable: true,
configurable: true
}
})
const child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
以上继承实现的外围就是将父类的原型赋值给了子类,并且将构造函数设置为子类,这样既解决了无用的父类属性问题,还能正确的找到子类的构造函数。
Class 继承
以上两种继承形式都是通过原型去解决的,在 ES6 中,咱们能够应用 class 去实现继承,并且实现起来很简略
class Parent {constructor(value) {this.val = value}
getValue() {console.log(this.val)
}
}
class Child extends Parent {constructor(value) {super(value)
this.val = value
}
}
let child = new Child(1)
child.getValue() // 1
child instanceof Parent // true
class
实现继承的外围在于应用extends
表明继承自哪个父类,并且在子类构造函数中必须调用super
,因为这段代码能够看成Parent.call(this, value)
。
ES5 和 ES6 继承的区别:
- ES6 继承的子类须要调用
super()
能力拿到子类,ES5 的话是通过apply
这种绑定的形式 - 类申明不会晋升,和
let
这些统一
function Super() {}
Super.prototype.getNumber = function() {return 1}
function Sub() {}
Sub.prototype = Object.create(Super.prototype, {
constructor: {
value: Sub,
enumerable: false,
writable: true,
configurable: true
}
})
let s = new Sub()
s.getNumber()
以下具体解说几种常见的继承形式
1. 形式 1: 借助 call
function Parent1(){this.name = 'parent1';}
function Child1(){Parent1.call(this);
this.type = 'child1'
}
console.log(new Child1);
这样写的时候子类尽管可能拿到父类的属性值,然而问题是父类原型对象中一旦存在办法那么子类无奈继承。那么引出上面的办法。
2. 形式 2: 借助原型链
function Parent2() {
this.name = 'parent2';
this.play = [1, 2, 3]
}
function Child2() {this.type = 'child2';}
Child2.prototype = new Parent2();
console.log(new Child2());
看似没有问题,父类的办法和属性都可能拜访,但实际上有一个潜在的有余。举个例子:
var s1 = new Child2();
var s2 = new Child2();
s1.play.push(4);
console.log(s1.play, s2.play);
能够看到控制台:
明明我只扭转了 s1 的 play 属性,为什么 s2 也跟着变了呢?很简略,因为两个实例应用的是同一个原型对象。
那么还有更好的形式么?
3. 形式 3:将前两种组合
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
function Child3() {Parent3.call(this);
this.type = 'child3';
}
Child3.prototype = new Parent3();
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);
能够看到控制台:
之前的问题都得以解决。然而这里又徒增了一个新问题,那就是
Parent3
的构造函数会多执行了一次(Child3.prototype = new Parent3();
)。这是咱们不愿看到的。那么如何解决这个问题?
4. 形式 4: 组合继承的优化 1
function Parent4 () {
this.name = 'parent4';
this.play = [1, 2, 3];
}
function Child4() {Parent4.call(this);
this.type = 'child4';
}
Child4.prototype = Parent4.prototype;
这里让将父类原型对象间接给到子类,父类构造函数只执行一次,而且父类属性和办法均能拜访,然而咱们来测试一下:
var s3 = new Child4();
var s4 = new Child4();
console.log(s3)
子类实例的构造函数是 Parent4,显然这是不对的,应该是 Child4。
5. 形式 5(最举荐应用): 组合继承的优化 2
function Parent5 () {
this.name = 'parent5';
this.play = [1, 2, 3];
}
function Child5() {Parent5.call(this);
this.type = 'child5';
}
Child5.prototype = Object.create(Parent5.prototype);
Child5.prototype.constructor = Child5;
这是最举荐的一种形式,靠近完满的继承,它的名字也叫做寄生组合继承。
6. ES6 的 extends 被编译后的 JavaScript 代码
ES6 的代码最初都是要在浏览器上可能跑起来的,这两头就利用了 babel 这个编译工具,将 ES6 的代码编译成 ES5 让一些不反对新语法的浏览器也能运行。
那最初编译成了什么样子呢?
function _possibleConstructorReturn(self, call) {
// ...
return call && (typeof call === 'object' || typeof call === 'function') ? call : self;
}
function _inherits(subClass, superClass) {
// ...
// 看到没有
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
enumerable: false,
writable: true,
configurable: true
}
});
if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
}
var Parent = function Parent() {
// 验证是否是 Parent 结构进去的 this
_classCallCheck(this, Parent);
};
var Child = (function (_Parent) {_inherits(Child, _Parent);
function Child() {_classCallCheck(this, Child);
return _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).apply(this, arguments));
}
return Child;
}(Parent));
外围是
_inherits
函数,能够看到它采纳的仍然也是第五种形式————寄生组合继承形式,同时证实了这种形式的胜利。不过这里加了一个Object.setPrototypeOf(subClass, superClass)
,这是用来干啥的呢?
答案是用来继承父类的静态方法。这也是原来的继承形式忽略掉的中央。
诘问: 面向对象的设计肯定是好的设计吗?
不肯定。从继承的角度说,这一设计是存在微小隐患的。
DNS 记录和报文
DNS 服务器中以资源记录的模式存储信息,每一个 DNS 响应报文个别蕴含多条资源记录。一条资源记录的具体的格局为
(Name,Value,Type,TTL)
其中 TTL 是资源记录的生存工夫,它定义了资源记录可能被其余的 DNS 服务器缓存多长时间。
罕用的一共有四种 Type 的值,别离是 A、NS、CNAME 和 MX,不同 Type 的值,对应资源记录代表的意义不同:
- 如果 Type = A,则 Name 是主机名,Value 是主机名对应的 IP 地址。因而一条记录为 A 的资源记录,提供了标 准的主机名到 IP 地址的映射。
- 如果 Type = NS,则 Name 是个域名,Value 是负责该域名的 DNS 服务器的主机名。这个记录次要用于 DNS 链式 查问时,返回下一级须要查问的 DNS 服务器的信息。
- 如果 Type = CNAME,则 Name 为别名,Value 为该主机的标准主机名。该条记录用于向查问的主机返回一个主机名 对应的标准主机名,从而通知查问主机去查问这个主机名的 IP 地址。主机别名次要是为了通过给一些简单的主机名提供 一个便于记忆的简略的别名。
- 如果 Type = MX,则 Name 为一个邮件服务器的别名,Value 为邮件服务器的标准主机名。它的作用和 CNAME 是一 样的,都是为了解决标准主机名不利于记忆的毛病。
定时器与 requestAnimationFrame、requestIdleCallback
1. setTimeout
setTimeout 的运行机制:执行该语句时,是立刻把以后定时器代码推入事件队列,当定时器在事件列表中满足设置的工夫值时将传入的函数退出工作队列,之后的执行就交给工作队列负责。然而如果此时工作队列不为空,则需期待,所以执行定时器内代码的工夫可能会大于设置的工夫
setTimeout(() => {console.log(1);
}, 0)
console.log(2);
输入 2,1;
setTimeout
的第二个参数示意在执行代码前期待的毫秒数。下面代码中,设置为 0,外表意思为 执行代码前期待的毫秒数为 0,即立刻执行。但实际上的运行后果咱们也看到了,并不是外表上看起来的样子,千万不要被坑骗了。
实际上,下面的代码并不是立刻执行的,这是因为 setTimeout
有一个最小执行工夫,HTML5 标准规定了 setTimeout()
的第二个参数的最小值(最短距离)不得低于 4 毫秒
。当指定的工夫低于该工夫时,浏览器会用最小容许的工夫作为setTimeout
的工夫距离,也就是说即便咱们把 setTimeout
的延迟时间设置为 0,实际上可能为 4 毫秒后才事件推入工作队列
。
定时器代码在被推送到工作队列前,会先被推入到事件列表中,当定时器在事件列表中满足设置的工夫值时会被推到工作队列,然而如果此时工作队列不为空,则需期待,所以执行定时器内代码的工夫可能会大于设置的工夫
setTimeout(() => {console.log(111);
}, 100);
下面代码示意 100ms
后执行 console.log(111)
,但实际上履行的工夫必定是大于 100ms 后的,100ms 只是示意 100ms 后将工作退出到 ” 工作队列 ” 中,必须等到以后代码(执行栈)执行完,主线程才会去执行它指定的回调函数。要是以后代码耗时很长,有可能要等很久,所以并没有方法保障,回调函数肯定会在setTimeout()
指定的工夫执行。
2. setTimeout 和 setInterval 区别
setTimeout
: 指定延期后调用函数,每次setTimeout
计时到后就会去执行,而后执行一段时间后才持续setTimeout
, 两头就多了误差,(误差多少与代码的执行工夫无关)。setInterval
:以指定周期调用函数,而setInterval
则是每次都准确的隔一段时间推入一个事件(然而,事件的执行工夫不肯定就不精确,还有可能是这个事件还没执行结束,下一个事件就来了).
btn.onclick = function(){setTimeout(function(){console.log(1);
},250);
}
击该按钮后,首先将
onclick
事件处理程序退出队列。该程序执行后才设置定时器,再有250ms
后,指定的代码才被增加到队列中期待执行。如果下面代码中的onclick
事件处理程序执行了300ms
,那么定时器的代码至多要在定时器设置之后的300ms
后才会被执行。队列中所有的代码都要等到 javascript 过程闲暇之后能力执行,而不论它们是如何增加到队列中的。
如图所示,只管在 255ms
处增加了定时器代码,但这时候还不能执行,因为 onclick
事件处理程序仍在运行。定时器代码最早能执行的机会是在 300ms
处,即 onclick
事件处理程序完结之后。
3. setInterval 存在的一些问题:
JavaScript 中应用 setInterval
开启轮询。定时器代码可能在代码再次被增加到队列之前还没有实现执行,后果导致定时器代码间断运行好几次,而之间没有任何进展。而 javascript 引擎对这个问题的解决是:当应用 setInterval()
时,仅当没有该定时器的任何其余代码实例时,才将定时器代码增加到队列中。这确保了定时器代码退出到队列中的最小工夫距离为指定距离。
然而,这样会导致两个问题:
- 某些距离被跳过;
- 多个定时器的代码执行之间的距离可能比预期的小
假如,某个 onclick
事件处理程序应用 setInterval()
设置了 200ms
距离的定时器。如果事件处理程序花了 300ms
多一点工夫实现,同时定时器代码也花了差不多的工夫,就会同时呈现跳过某距离的状况
例子中的第一个定时器是在 205ms
处增加到队列中的,然而直到过了 300ms
处能力执行。当执行这个定时器代码时,在 405ms 处又给队列增加了另一个正本。在下一个距离,即 605ms 处,第一个定时器代码仍在运行,同时在队列中曾经有了一个定时器代码的实例。后果是,在这个工夫点上的定时器代码不会被增加到队列中
应用 setTimeout
结构轮询能保障每次轮询的距离。
setTimeout(function () {console.log('我被调用了');
setTimeout(arguments.callee, 100);
}, 100);
callee
是arguments
对象的一个属性。它能够用于援用该函数的函数体内以后正在执行的函数。在严格模式下,第 5 版 ECMAScript (ES5) 禁止应用arguments.callee()
。当一个函数必须调用本身的时候, 防止应用arguments.callee()
, 通过要么给函数表达式一个名字, 要么应用一个函数申明.
setTimeout(function fn(){console.log('我被调用了');
setTimeout(fn, 100);
},100);
这个模式链式调用了 setTimeout()
,每次函数执行的时候都会创立一个新的定时器。第二个setTimeout()
调用以后执行的函数,并为其设置另外一个定时器。这样做的益处是,在前一个定时器代码执行完之前,不会向队列插入新的定时器代码,确保不会有任何缺失的距离。而且,它能够保障在下一次定时器代码执行之前,至多要期待指定的距离,防止了间断的运行。
4. requestAnimationFrame
4.1 60fps
与设施刷新率
目前大多数设施的屏幕刷新率为60 次 / 秒
,如果在页面中有一个动画或者突变成果,或者用户正在滚动页面,那么浏览器渲染动画或页面的每一帧的速率也须要跟设施屏幕的刷新率保持一致。
卡顿:其中每个帧的估算工夫仅比 16 毫秒
多一点( 1 秒 / 60 = 16.6 毫秒
)。但实际上,浏览器有整顿工作要做,因而您的所有工作是须要在10 毫秒
内实现。如果无奈合乎此估算,帧率将降落,并且内容会在屏幕上抖动。此景象通常称为卡顿,会对用户体验产生负面影响。
跳帧: 如果动画切换在 16ms, 32ms, 48ms 时别离切换,跳帧就是如果到了 32ms,其余工作还未执行实现,没有去执行动画切帧,等到开始进行动画的切帧,曾经到了该执行 48ms 的切帧。就好比你玩游戏的时候卡了,过了一会,你再看画面,它不会停留你卡的中央,或者这时你的角色曾经挂掉了。必须在下一帧开始之前就曾经绘制结束;
Chrome devtool 查看实时 FPS, 关上 More tools => Rendering, 勾选 FPS meter
4.2 requestAnimationFrame
实现动画
requestAnimationFrame
是浏览器用于定时循环操作的一个接口,相似于 setTimeout,主要用途是按帧对网页进行重绘。
在 requestAnimationFrame
之前,次要借助 setTimeout/ setInterval
来编写 JS 动画,而动画的关键在于动画帧之间的工夫距离设置,这个工夫距离的设置有考究,一方面要足够小,这样动画帧之间才有连贯性,动画成果才显得平滑晦涩;另一方面要足够大,确保浏览器有足够的工夫及时实现渲染。
显示器有固定的刷新频率(60Hz 或 75Hz),也就是说,每秒最多只能重绘 60 次或 75 次,requestAnimationFrame
的根本思维就是与这个刷新频率放弃同步,利用这个刷新频率进行页面重绘。此外,应用这个 API,一旦页面不处于浏览器的以后标签,就会主动进行刷新。这就节俭了 CPU、GPU 和电力。
requestAnimationFrame
是在主线程上实现。这意味着,如果主线程十分忙碌,requestAnimationFrame
的动画成果会大打折扣。
requestAnimationFrame
应用一个回调函数作为参数。这个回调函数会在浏览器重绘之前调用。
requestID = window.requestAnimationFrame(callback);
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(callback){window.setTimeout(callback, 1000 / 60);
};
})();
下面的代码依照 1 秒钟 60 次(大概每 16.7 毫秒一次),来模仿requestAnimationFrame
。
5. requestIdleCallback()
MDN 上的解释:
requestIdleCallback()
办法将在浏览器的闲暇时段内调用的函数排队。这使开发者可能在主事件循环上执行后盾和低优先级工作,而不会影响提早要害事件,如动画和输出响应。函数个别会按先进先调用的程序执行,然而,如果回调函数指定了执行超时工夫 timeout,则有可能为了在超时前执行函数而打乱执行程序。
requestAnimationFrame
会在每次屏幕刷新的时候被调用,而 requestIdleCallback
则会在每次屏幕刷新时,判断以后帧是否还有多余的工夫,如果有,则会调用 requestAnimationFrame
的回调函数,
图片中是两个间断的执行帧,大抵能够了解为两个帧的持续时间大略为 16.67,图中黄色局部就是闲暇工夫。所以,requestIdleCallback
中的回调函数仅会在每次屏幕刷新并且有闲暇工夫时才会被调用.
利用这个个性,咱们能够在动画执行的期间,利用每帧的闲暇工夫来进行数据发送的操作,或者一些优先级比拟低的操作,此时不会使影响到动画的性能,或者和 requestAnimationFrame
搭配,能够实现一些页面性能方面的的优化,
react 的
fiber
架构也是基于requestIdleCallback
实现的, 并且在不反对的浏览器中提供了polyfill
总结
- 从
单线程模型和工作队列
登程了解setTimeout(fn, 0)
,并不是立刻执行。 - JS 动画, 用
requestAnimationFrame
会比setInterval
成果更好 requestIdleCallback()
罕用来切割长工作,利用闲暇工夫执行,防止主线程长时间阻塞
computed 的实现原理
computed
实质是一个惰性求值的观察者computed watcher
。其外部通过this.dirty
属性标记计算属性是否须要从新求值。
- 当 computed 的依赖状态产生扭转时, 就会告诉这个惰性的 watcher,
computed watcher
通过this.dep.subs.length
判断有没有订阅者, - 有的话, 会从新计算, 而后比照新旧值, 如果变动了, 会从新渲染。(Vue 想确保不仅仅是计算属性依赖的值发生变化,而是当计算属性
最终计算的值
发生变化时才会触发渲染 watcher
从新渲染,实质上是一种优化。) - 没有的话, 仅仅把
this.dirty = true
(当计算属性依赖于其余数据时,属性并不会立刻从新计算,只有之后其余中央须要读取属性的时候,它才会真正计算,即具备 lazy(懒计算)个性。)
说说 Vue2.0 和 Vue3.0 有什么区别
-
重构响应式零碎,应用
Proxy
替换Object.defineProperty
,应用Proxy
劣势:- 可间接监听数组类型的数据变动
- 监听的指标为对象自身,不须要像
Object.defineProperty
一样遍历每个属性,有肯定的性能晋升 - 可拦挡
apply、ownKeys、has
等 13 种办法,而Object.defineProperty
不行 - 间接实现对象属性的新增 / 删除
- 新增
Composition API
,更好的逻辑复用和代码组织 -
重构
Virtual DOM
- 模板编译时的优化,将一些动态节点编译成常量
slot
优化,将slot
编译为lazy
函数,将slot
的渲染的决定权交给子组件- 模板中内联事件的提取并重用(本来每次渲染都从新生成内联函数)
- 代码结构调整,更便于 Tree shaking,使得体积更小
- 应用 Typescript 替换 Flow
如何设计 React 组件
React 组件应从 设计与工程实际
两个方向进行探讨
从设计上而言,社区支流分类的计划是展现组件与乖巧组件
展现组件外部没有状态治理,仅仅用于最简略的展现表白
。展现组件中最根底的一类组件称作代理组件。代理组件罕用于封装罕用属性、缩小反复代码。很经典的场景就是引入 Antd 的 Button 时,你再本人封一层。如果将来须要替换掉 Antd 或者须要在所有的 Button 上增加一个属性,都会十分不便。基于代理组件的思维还能够持续分类,分为款式组件与布局组件两种,别离是将款式与布局内聚在本人组件外部。- 从工程实际而言,通过文件夹划分的形式切分代码。我初步罕用的宰割形式是将页面独自建设一个目录,将复用性略高的 components 建设一个目录,在上面别离建设 basic、container 和 hoc 三类。这样能够保障无奈复用的业务逻辑代码尽量留在 Page 中,而能够形象复用的局部放入 components 中。其中 basic 文件夹放展现组件,因为展现组件自身与业务关联性较低,所以能够应用 Storybook 进行组件的开发治理,晋升我的项目的工程化治理能力
盒模型
content(元素内容)+ padding(内边距)+ border(边框)+ margin(外边距)
延长:box-sizing
content-box
:默认值,总宽度 =margin
+border
+padding
+width
border-box
:盒子宽度蕴含padding
和border
,总宽度 = margin + width
inherit
:从父元素继承box-sizing
属性
如何防止 React 生命周期中的坑
16.3 版本
>=16.4 版本
在线查看:https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram(opens new window)
- 防止生命周期中的坑须要做好两件事:不在失当的时候调用了不该调用的代码;在须要调用时,不要忘了调用。
-
那么次要有这么 7 种状况容易造成生命周期的坑
getDerivedStateFromProps
容易编写反模式代码,使受控组件与非受控组件辨别含糊componentWillMount
在 React 中已被标记弃用,不举荐应用,次要起因是新的异步渲染架构会导致它被屡次调用
。所以网络申请及事件绑定代码应移至componentDidMount
中。componentWillReceiveProps
同样被标记弃用,被getDerivedStateFromProps
所取代,次要起因是性能问题shouldComponentUpdate
通过返回true
或者false
来确定是否须要触发新的渲染。次要用于性能优化componentWillUpdate
同样是因为新的异步渲染机制,而被标记废除,不举荐应用,原先的逻辑可联合getSnapshotBeforeUpdate
与componentDidUpdate
革新应用。- 如果在
componentWillUnmount
函数中遗记解除事件绑定,勾销定时器等清理操作,容易引发 bug - 如果没有增加谬误边界解决,当渲染产生异样时,用户将会看到一个无奈操作的白屏,所以肯定要增加
“React 的申请应该放在哪里,为什么?”这也是常常会被诘问的问题。你能够这样答复。
对于异步申请,应该放在 componentDidMount
中去操作。从工夫程序来看,除了 componentDidMount
还能够有以下抉择:
- constructor:能够放,但从设计上而言不举荐。constructor 次要用于初始化 state 与函数绑定,并不承载业务逻辑。而且随着类属性的风行,constructor 曾经很少应用了
- componentWillMount:已被标记废除,在新的异步渲染架构下会触发屡次渲染,容易引发 Bug,不利于将来 React 降级后的代码保护。
- 所以 React 的申请放在
componentDidMount 里是最好的抉择
。
透过景象看实质:React 16 缘何两次求变?
Fiber 架构简析
Fiber 是 React 16 对 React 外围算法的一次重写。你只须要 get 到这一个点:
Fiber 会使本来同步的渲染过程变成异步的
。
在 React 16 之前,每当咱们触发一次组件的更新,React 都会构建一棵新的虚构 DOM 树,通过与上一次的虚构 DOM 树进行 diff,实现对 DOM 的定向更新。这个过程,是一个递归的过程。上面这张图形象地展现了这个过程的特色:
如图所示,同步渲染的递归调用栈是十分深的,只有最底层的调用返回了,整个渲染过程才会开始逐层返回
。这个漫长且不可打断的更新过程,将会带来用户体验层面的微小危险: 同步渲染一旦开始,便会牢牢抓住主线程不放,直到递归彻底实现
。在这个过程中,浏览器没有方法解决任何渲染之外的事件,会进入一种无奈解决用户交互的状态。因而若渲染工夫略微长一点,页面就会面临卡顿甚至卡死的危险。
而 React 16 引入的 Fiber 架构,恰好可能解决掉这个危险:Fiber 会将一个大的更新工作拆解为许多个小工作
。 每当执行完一个小工作时,渲染线程都会把主线程交回去
,看看有没有优先级更高的工作要解决,确保不会呈现其余工作被“饿死”的状况,进而防止同步渲染带来的卡顿。在这个过程中,渲染线程不再“一去不回头”,而是能够被打断的,这就是所谓的“异步渲染”,它的执行过程如下图所示:
换个角度看生命周期工作流
Fiber 架构的重要特色就是能够被打断的异步渲染模式。但这个“打断”是有准则的,依据“是否被打断”这一规范,React 16 的生命周期被划分为了 render 和 commit 两个阶段
,而 commit 阶段又被细分为了 pre-commit 和 commit
。每个阶段所涵盖的生命周期如下图所示:
咱们先来看下三个阶段各自有哪些特色
render 阶段
:污浊且没有副作用,可能会被 React 暂停、终止或重新启动。pre-commit 阶段
:能够读取 DOM。commit 阶段
:能够应用 DOM,运行副作用,安顿更新。
总的来说,render 阶段在执行过程中容许被打断,而 commit 阶段则总是同步执行的。
为什么这样设计呢?简略来说,
因为 render 阶段的操作对用户来说其实是“不可见”的,所以就算打断再重启,对用户来说也是零感知
。而commit 阶段的操作则波及实在 DOM 的渲染
,所以这个过程必须用同步渲染来求稳
。