网上找到的各类面试题整理,长期更新。大部分答案整理来自网络,有问题的地方,但愿你们能指出,及时修改;技术更新迭代,也会及时更新
博客原地址: https://finget.github.io/2019...
1.清理 HTML 文档javascript
HTML,即超文本标记语言,几乎是全部网站的支柱。HTML 为网页带来标题、子标题、列表和其它一些文档结构的格式。在最近更新的 HTML5 中,甚至能够建立图表。css
HTML 很容易被网络爬虫识别,所以搜索引擎能够根据网站的内容在必定程度上实时更新。在写 HTML 的时候,你应该尝试让它简洁而有效。此外,在 HTML 文档中引用外部资源的时候也须要遵循一些最佳实践方法。html
a.恰当放置 CSS前端
Web 设计者喜欢在网页创建起主要的 HTML 骨架以后再来建立样式表。这样一来,网页中的样式表每每会放在 HTML 的后面,接近文档结束的地方。然而推荐的作法是把 CSS 放在 HTML 的上面部分,文档头以内,这能够确保正常的渲染过程。vue
这个策略不能提升网站的加载速度,但它不会让访问者长时间看着空白屏幕或者无格式的文本(FOUT)等待。若是网页大部分可见元素已经加载出来了,访问者才更有可能等待加载整个页面,从而带来对前端的优化效果。这就是知觉性能html5
b.正确放置 Javascriptjava
另外一方面,若是将 JavaScript 放置在 head 标签内或 HTML 文档的上部,这会阻塞 HTML 和 CSS 元素的加载过程。这个错误会致使页面加载时间增加,增长用户等待时间,容易让人感到不耐烦而放弃对网站的访问。不过,您能够经过将 JavaScript 属性置于 HTML 底部来避免此问题。node
此外,在使用 JavaScript 时,人们一般喜欢用异步脚本加载。这会阻止<script>
标签在 HTML 中的呈现过程,如,在文档中间的状况。react
虽然对于网页设计师来讲, HTML 是最值得使用的工具之一,但它一般要与 CSS 和 JavaScript 一块儿使用,这可能会致使网页浏览速度减慢。 虽然 CSS 和 JavaScript 有利于网页优化,但使用时也要注意一些问题。使用 CSS 和 JavaScript 时,要避免嵌入代码。由于当您嵌入代码时,要将 CSS 放置在样式标记中,并在脚本标记中使用 JavaScript,这会增长每次刷新网页时必须加载的 HTML 代码量。jquery
2.优化 CSS 性能
CSS,即级联样式表,能从 HTML 描述的内容生成专业而又整洁的文件。不少 CSS 须要经过 HTTP 请求来引入(除非使用内联 CSS),因此你要努力去除累赘的 CSS 文件,但要注意保留其重要特征。
若是你的 Banner、插件和布局样式是使用 CSS 保存在不一样的文件内,那么,访问者的浏览器每次访问都会加载不少文件。虽然如今 HTTP/2 的存在,减小了这种问题的发生,可是在外部资源加载的状况下,仍会花费较长时间。要了解如何减小 HTTP 请求以大幅度缩减加载时间,请阅读WordPress 性能。
此外,很多网站管理员在网页中错误的使用 @import 指令 来引入外部样式表。这是一个过期的方法,它会阻止浏览并行下载。link 标签才是最好的选择,它也能提升网站的前端性能。多说一句,经过 link 标签请求加载的外部样式表不会阻止并行下载。
3.减小外部HTTP请求
在不少状况下,网站的大部分加载时间来自于外部的 Http 请求。外部资源的加载速度随着主机提供商的服务器架构、地点等不一样而不一样。减小外部请求要作的第一步就是简略地检查网站。研究你网站的每一个组成部分,消除任何影响访问者体验很差的成分。这些成分多是:
在你去掉这些多余的成分以后,再对剩下的内容进行整理,如,压缩工具、CDN 服务和预获取(prefetching)等,这些都是管理 HTTP 请求的最佳选择。除此以外,减小DNS路由查找教程会教你如何一步一步的减小外部 HTTP 请求。
4.压缩 CSS, JS 和 HTML
压缩技术能够从文件中去掉多余的字符。你在编辑器中写代码的时候,会使用缩进和注释,这些方法无疑会让你的代码简洁并且易读,但它们也会在文档中添加多余的字节。
预先获取能够在真正须要以前经过取得必需的资源和相关数据来改善访问用户的浏览体验,主要有3类预先获取:
在你离开当前 web 页面以前,使用预先获取方式,对应每一个连接的 URL 地址,CSS,图片和脚本都会被预先获取。这保证了访问者能在最短期内使用连接在画面间切换。
幸运的是,预先获取很容易实现。根据你想要使用的预先获取形式,你只需在网站 HTML 中的连接属性上增长 rel=”prefetch”,rel=”dns-prefetch”,或者 rel=”prerender” 标记。
6.使用 CDN 和缓存提升速度
内容分发网络能显著提升网站的速度和性能。使用 CDN 时,您能够将网站的静态内容连接到全球各地的服务器扩展网络。若是您的网站观众遍及全球,这项功能十分有用。 CDN 容许您的网站访问者从最近的服务器加载数据。若是您使用 CDN,您网站内的文件将自动压缩,以便在全球范围内快速分发。
CDN 是一种缓存方法,可极大改善资源的分发时间,同时,它还能实现一些其余的缓存技术,如,利用浏览器缓存。
合理地设置浏览器缓存,能让浏览器自动存储某些文件,以便加快传输速度。此方法的配置能够直接在源服务器的配置文件中完成。
7.压缩文件
虽然许多 CDN 服务能够压缩文件,但若是不使用 CDN,您也能够考虑在源服务器上使用文件压缩方法来改进前端优化。 文件压缩能使网站的内容轻量化,更易于管理。 最经常使用的文件压缩方法之一是 Gzip。 这是缩小文档、音频文件、PNG图像和等其余大文件的绝佳方法。
Brotli 是一个比较新的文件压缩算法,目前正变得愈来愈受欢迎。 此开放源代码算法由来自 Google 和其余组织的软件工程师按期更新,现已被证实比其余现有压缩方法更好用。 这种算法的支持目前还比较少,但做为后起之秀指日可待。
8.使用轻量级框架
除非你只用现有的编码知识构建网站,否则,你能够尝试使用一个好的前端框架来避免许多没必要要的前端优化错误。虽然有一些更大,更知名的框架能提供更多功能和选项,但它们不必定适合你的 Web 项目。
因此说,不只肯定项目所需功能很重要,选择合适的框架也很重要——它要在提供所需功能的同时保持轻量。最近许多框架都使用简洁的 HTML,CSS 和 JavaScript 代码。
参考连接:
详细解读https://segmentfault.com/a/1190000006879700
详细解读https://mp.weixin.qq.com/s/jjL4iA7p6aYEAQyWhn4QbQ
输入地址
1.浏览器查找域名的 IP 地址
2.这一步包括 DNS 具体的查找过程,包括:浏览器缓存->系统缓存->路由器缓存…
3.浏览器向 web 服务器发送一个 HTTP 请求
4.服务器的永久重定向响应(从 http://example.com 到 http://www.example.com)
5.浏览器跟踪重定向地址
6.服务器处理请求
7.服务器返回一个 HTTP 响应
8.浏览器显示 HTML
9.浏览器发送请求获取嵌入在 HTML 中的资源(如图片、音频、视频、CSS、JS等等)
10.浏览器发送异步请求
URL(Uniform Resource Locator),统一资源定位符,用于定位互联网上资源,俗称网址。
好比 http://www.w3school.com.cn/ht...,遵照如下的语法规则:
scheme://host.domain:port/path/filename
各部分解释以下:
scheme - 定义因特网服务的类型。常见的协议有 http、https、ftp、file,其中最多见的类型是 http,而 https 则是进行加密的网络传输。
host - 定义域主机(http 的默认主机是 www)
domain - 定义因特网域名,好比 w3school.com.cn
port - 定义主机上的端口号(http 的默认端口号是 80)
path - 定义服务器上的路径(若是省略,则文档必须位于网站的根目录中)。
filename - 定义文档/资源的名称
客服端和服务端在进行http请求和返回的工程中,须要建立一个TCP connection(由客户端发起),http不存在链接这个概念,它只有请求和响应。请求和响应都是数据包,它们之间的传输通道就是TCP connection。
位码即tcp标志位,有6种标示:SYN(synchronous创建联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)Sequence number(顺序号码) Acknowledge number(确认号码)
第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求创建联机;(第一次握手,由浏览器发起,告诉服务器我要发送请求了)
第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包;(第二次握手,由服务器发起,告诉浏览器我准备接受了,你赶忙发送吧)
第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则链接创建成功;(第三次握手,由浏览器发送,告诉服务器,我立刻就发了,准备接受吧)
谢希仁著《计算机网络》中讲“三次握手”的目的是“为了防止已失效的链接请求报文段忽然又传送到了服务端,于是产生错误。
这种状况是:一端(client)A发出去的第一个链接请求报文并无丢失,而是由于某些未知的缘由在某个网络节点上发生滞留,致使延迟到链接释放之后的某个时间才到达另外一端(server)B。原本这是一个早已失效的报文段,可是B收到此失效的报文以后,会误认为是A再次发出的一个新的链接请求,因而B端就向A又发出确认报文,表示赞成创建链接。若是不采用“三次握手”,那么只要B端发出确认报文就会认为新的链接已经创建了,可是A端并无发出创建链接的请求,所以不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉不少资源。若是采用“三次握手”的话就不会出现这种状况,B端收到一个过期失效的报文段以后,向A端发出确认,此时A并无要求创建链接,因此就不会向B端发送确认,这个时候B端也可以知道链接没有创建。
问题的本质是,信道是不可靠的,可是咱们要创建可靠的链接发送可靠的数据,也就是数据传输是须要可靠的。在这个时候三次握手是一个理论上的最小值,并非说是tcp协议要求的,而是为了知足在不可靠的信道上传输可靠的数据所要求的。
这个网上转载的例子不错:
三次握手:
A:“喂,你听获得吗?”A->SYN_SEND
B:“我听获得呀,你听获得我吗?”应答与请求同时发出 B->SYN_RCVD | A->ESTABLISHED
A:“我能听到你,今天balabala……”B->ESTABLISHED
四次挥手:
A:“喂,我不说了。”A->FIN_WAIT1
B:“我知道了。等下,上一句还没说完。Balabala…..”B->CLOSE_WAIT | A->FIN_WAIT2
B:”好了,说完了,我也不说了。”B->LAST_ACK
A:”我知道了。”A->TIME_WAIT | B->CLOSED
A等待2MSL,保证B收到了消息,不然重说一次”我知道了”,A->CLOSE
在实现websocket连线过程当中,须要经过浏览器发出websocket连线请求,而后服务器发出回应,这个过程一般称为“握手” (handshaking)。
客户端请求web socket链接时,会向服务器端发送握手请求
请求头大体内容:
请求包说明:
服务端响应以下:
应答包说明:
*必须包括Upgrade头域,而且其值为”websocket”;
*必须包括Connection头域,而且其值为”Upgrade”;
*必须包括Sec-WebSocket-Accept头域,其值是将请求包“Sec-WebSocket-Key”的值,与”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接,而后对拼接后的字符串进行sha-1运算,再进行base64编码,就是“Sec-WebSocket-Accept”的值;
*应答包中冒号后面有一个空格;
*最后须要两个空行做为应答包结束
参考连接:
Websocket协议之握手链接
符合”协议+域名+端口”三者相同,就是同源
同源策略,其初衷是为了浏览器的安全性,经过如下三种限制,保证浏览器不易受到XSS、CSFR等攻击。
- Cookie、LocalStorage 和 IndexDB 没法读取 - DOM 和 Js对象没法得到 - AJAX 请求不能发送
跨域解决方案
最容易想到的解决方案是:
1.使用前端cookie技术来保存本地化数据,如jquery.cookie.js;
2.使用html5提供的Web Storage技术来提供解决方案;
用cookie存储永久数据存在如下几个问题:
1.大小:cookie的大小被限制在4KB。
2.带宽:cookie是随HTTP事务一块儿被发送的,所以会浪费一部分发送cookie时使用的带宽。
3.复杂性:要正确的操纵cookie是很困难的。
针对这些问题,在HTML5中,从新提供了一种在客户端本地保存数据的功能,它就是Web Storage。
具体来讲,Web Storage又分为两种:
1.sessionStorage:将数据保存在session对象中。所谓session,是指用户在浏览某个网站时,从进入网站到浏览器关闭所通过的这段时间,也就是用户浏览这个网站所花费的时间。session对象能够用来保存在这段时间内所要求保存的任何数据。
2.localStorage:将数据保存在客户端本地的硬件设备(一般指硬盘,也能够是其余硬件设备)中,即便浏览器被关闭了,该数据仍然存在,下次打开浏览器访问网站时仍然能够继续使用。
这二者的区别在于,sessionStorage为临时保存,而localStorage为永久保存。
Http 2.0协议简介
HTTP 2.0 详细介绍,http2.0详细介绍
HTTP/2.0 相比1.0有哪些重大改进
我能想到的只有Promise.all()
,欢迎补充
<b>
粗体文本,<strong>
用于强调文本,他们的样式是同样的
有一种说法,是<strong>
貌似在盲人用的机器上会读两遍。由于没有对应的测试条件,因此没作验证。
header('Access-Control-Allow-Origin:*');
CSRF,全称为Cross-Site Request Forgery,跨站请求伪造,是一种网络攻击方式,它能够在用户绝不知情的状况下,以用户的名义伪造请求发送给被攻击站点,从而在未受权的状况下进行权限保护内的操做。
具体来说,能够这样理解CSRF。攻击者借用用户的名义,向某一服务器发送恶意请求,对服务器来说,这一请求是彻底合法的,但攻击者确完成了一个恶意操做,好比以用户的名义发送邮件,盗取帐号,购买商品等等
通常网站防护CSRF攻击的方案:
(1)验证token值。
(2)验证HTTP头的Referer。
(3)在HTTP头中自定义属性并验证
(4)服务器端表单hash认证
在全部的表单里面随机生成一个hash,server在表单处理时去验证这个hash值是否正确,这样工做量比较大
// 第一种 .ovh{ overflow:hidden; } // 第二种 .clear{ clear:both; } // 第三种 .clearfix:after{ content:"";//设置内容为空 height:0;//高度为0 line-height:0;//行高为0 display:block;//将文本转为块级元素 visibility:hidden;//将元素隐藏 clear:both//清除浮动 } .clearfix{ zoom:1;为了兼容IE }
当给父元素设置"overflow:hidden"时,实际上建立了一个超级属性BFC,此超级属性反过来决定了"height:auto"是如何计算的。在“BFC布局规则”中提到:计算BFC的高度时,浮动元素也参与计算。所以,父元素在计算其高度时,加入了浮动元素的高度,“顺便”达成了清除浮动的目标,因此父元素就包裹住了子元素。
BFC(Block Formatting Context),块级格式化上下文,是Web页面中盒模型布局的CSS渲染模式。它的定位体系属于常规文档流。
原理(渲染规则):
<!-- 边距重叠 --> <section id="margin"> <style> #margin{ background: green; overflow: hidden; } #margin p{ background: red; margin: 10px 0; } </style> <p>1</p> <!-- 在增长一个BFC父级就能够消除边距重叠 --> <div style="overflow: hidden;"> <p>2</p> </div> <p>3</p> </section>
<!-- 左右布局 BFC的区域不会与浮动元素的box重叠--> <section id="layout"> <style> #layout .left{ float: left; width: 200px; height: 300px; background: pink; } #layout .right{ height: 500px; background: yellow; overflow: hidden; } </style> <div class="left"></div> <div class="right"></div> </section>
<!-- 计算BFC高度时,浮动元素也会参与计算 清除浮动 --> <section id="float"> <style> #float{ background: red; overflow: auto; } #float .float-left{ float: left; } </style> <div class="float-left">我是浮动元素</div> </section>
怎么建立BFC:
浮动,绝对定位元素,inline-blocks, table-cells, table-captions,和overflow的值不为visible的元素,(除了这个值已经被传到了视口的时候)将建立一个新的块级格式化上下文。
上面的引述几乎总结了一个BFC是怎样造成的。可是让咱们以另外一种方式来从新定义以便能更好的去理解.
参考连接:
理解CSS中BFC
这个直接看 阮一峰:Flex 布局教程
单词sticky的中文意思是“粘性的”,position:sticky表现也符合这个粘性的表现。基本上,能够看出是position:relative和position:fixed的结合体——当元素在屏幕内,表现为relative,就要滚出显示器屏幕的时候,表现为fixed。
详细讲解的仍是看大神的吧,张鑫旭:position:sticky
原型与原型链,做用域及闭包,异步和单线程。
三座大山,真不是一两句能够说清楚的,只有靠你们多看,多用,多理解,放点连接吧。
原型,原型链,call/apply
JavaScript从初级往高级走系列————prototype
JavaScript从初级往高级走系列————异步
JavaScript的预编译过程
内存空间详解
做用域和闭包
JavaScript深刻之词法做用域和动态做用域
JavaScript深刻之做用域链
事件循环机制
参考连接:
什么是闭包?https://mp.weixin.qq.com/s/OthfFRwf-rQmVbMnXAqnCg
做用域与闭包https://yangbo5207.github.io/wutongluo/ji-chu-jin-jie-xi-lie/si-3001-zuo-yong-yu-lian-yu-bi-bao.html
简言之,闭包是由函数引用其周边状态(词法环境)绑在一块儿造成的(封装)组合结构。在 JavaScript 中,闭包在每一个函数被建立时造成。
这是基本原理,但为何咱们关心这些?实际上,因为闭包与它的词法环境绑在一块儿,所以闭包让咱们可以从一个函数内部访问其外部函数的做用域。
要使用闭包,只须要简单地将一个函数定义在另外一个函数内部,并将它暴露出来。要暴露一个函数,能够将它返回或者传给其余函数。
内部函数将可以访问到外部函数做用域中的变量,即便外部函数已经执行完毕。
在 JavaScript 中,闭包是用来实现数据私有的原生机制。当你使用闭包来实现数据私有时,被封装的变量只能在闭包容器函数做用域中使用。你没法绕过对象被受权的方法在外部访问这些数据。在 JavaScript 中,任何定义在闭包做用域下的公开方法才能够访问这些数据。
参考连接:
js引擎执行机制https://segmentfault.com/a/1190000012806637
事件循环机制
// setTimeout中的回调函数才是进入任务队列的任务 setTimeout(function() { console.log('xxxx'); }) // 很是多的同窗对于setTimeout的理解存在误差。因此大概说一下误解: // setTimeout做为一个任务分发器,这个函数会当即执行,而它所要分发的任务,也就是它的第一个参数,才是延迟执行
promise里面的是宏任务,then后面的是微任务。
这个问题本质就是为啥须要异步。若是js不是异步的话,因为js代码自己是自上而下执行的,那么若是上一行代码须要执行好久,下面的代码就会被阻塞,对用户来讲,就是”卡死”,这样的话,会形成不好的用户体验。
你可能知道,Javascript语言的执行环境是"单线程"(single thread)。
所谓"单线程",就是指一次只能完成一件任务。若是有多个任务,就必须排队,前面一个任务完成,再执行后面一个任务,以此类推。
这种模式的好处是实现起来比较简单,执行环境相对单纯;坏处是只要有一个任务耗时很长,后面的任务都必须排队等着,会拖延整个程序的执行。常见的浏览器无响应(假死),每每就是由于某一段Javascript代码长时间运行(好比死循环),致使整个页面卡在这个地方,其余任务没法执行。
为了解决这个问题,Javascript语言将任务的执行模式分红两种:同步(Synchronous)和异步(Asynchronous)。
假定有两个函数f1和f2,后者等待前者的执行结果。
若是f1是一个很耗时的任务,能够考虑改写f1,把f2写成f1的回调函数
function f1(callback){ setTimeout(function () { // f1的任务代码 callback(); }, 1000); }
回调函数的优势是简单、容易理解和部署,缺点是不利于代码的阅读和维护,各个部分之间高度耦合(Coupling),流程会很混乱,并且每一个任务只能指定一个回调函数。
另外一种思路是采用事件驱动模式。任务的执行不取决于代码的顺序,而取决于某个事件是否发生。
f1.on('done', f2);
上面这行代码的意思是,当f1发生done事件,就执行f2。而后,对f1进行改写:
function f1(){ setTimeout(function () { // f1的任务代码 f1.trigger('done'); }, 1000); }
咱们假定,存在一个"信号中心",某个任务执行完成,就向信号中心"发布"(publish)一个信号,其余任务能够向信号中心"订阅"(subscribe)这个信号,从而知道何时本身能够开始执行。这就叫作"发布/订阅模式"(publish-subscribe pattern),又称"观察者模式"(observer pattern)。
jQuery.subscribe("done", f2);
function f1(){ setTimeout(function () { // f1的任务代码 jQuery.publish("done"); }, 1000); }
f1().then(f2).then(f3);
function create() { // 建立一个空的对象 let obj = new Object() // 得到构造函数 let Con = [].shift.call(arguments) // 连接到原型 obj.__proto__ = Con.prototype // 绑定 this,执行构造函数 let result = Con.apply(obj, arguments) // 确保 new 出来的是个对象 return typeof result === 'object' ? result : obj }
JS原型继承和类式继承http://www.cnblogs.com/constantince/p/4754992.html
// 类继承 var father = function() { this.age = 52; this.say = function() { alert('hello i am '+ this.name ' and i am '+this.age + 'years old'); } } var child = function() { this.name = 'bill'; father.call(this); } var man = new child(); man.say();
// 原型继承 var father = function() { } father.prototype.a = function() { } var child = function(){} //开始继承 child.prototype = new father(); var man = new child(); man.a();
和原型对比起来,构造函数(类)式继承有什么不同呢?首先,构造函数继承的方法都会存在父对象之中,每一次实例,都会将funciton保存在内存中,这样的作法毫无觉得会带来性能上的问题。其次类式继承是不可变的。在运行时,没法修改或者添加新的方法,这种方式是一种固步自封的死方法。而原型继承是能够经过改变原型连接而对子类进行修改的。另外就是类式继承不支持多重继承,而对于原型继承来讲,你只须要写好extend对对象进行扩展便可。
==是===类型转换(又称强制),==只须要值相等就会返回true,而===必须值和数据类型都相同才会返回true。
1.每一个函数都包含两个非继承而来的方法:call()方法和apply()方法。
2.相同点:这两个方法的做用是同样的。
都是在特定的做用域中调用函数,等于设置函数体内this对象的值,以扩充函数赖以运行的做用域。
通常来讲,this老是指向调用某个方法的对象,可是使用call()和apply()方法时,就会改变this的指向。
3.不一样点:接收参数的方式不一样。
apply()方法 接收两个参数,一个是函数运行的做用域(this),另外一个是参数数组。
语法:apply([thisObj [,argArray] ]);,调用一个对象的一个方法,2另外一个对象替换当前对象。
说明:若是argArray不是一个有效数组或不是arguments对象,那么将致使一个TypeError,若是没有提供argArray和thisObj任何一个参数,那么Global对象将用做thisObj。
call()方法 第一个参数和apply()方法的同样,可是传递给函数的参数必须列举出来。
语法:call([thisObject[,arg1 [,arg2 [,…,argn]]]]);,应用某一对象的一个方法,用另外一个对象替换当前对象。
说明: call方法能够用来代替另外一个对象调用一个方法,call方法能够将一个函数的对象上下文从初始的上下文改变为thisObj指定的新对象,若是没有提供thisObj参数,那么Global对象被用于thisObj。
bind和call、apply最大的区别就是,call、apply不只改变this的指向,还会直接支持代码,而bind不会。
var cat = { name: '咪咪' } function beatTheMonster(){ console.log(this.name); } beatTheMonster.call(cat); // 1.call 改变了this的指向。改变到了cat上。 // 2.beatTheMonster函数/方法执行了 // 3.bind(),保存了方法,并无直接调用它
<input type="file" name="file" onchange="showPreview(this)" /> <img id="portrait" src="" width="70" height="75"> function showPreview(source) { var file = source.files[0]; if(window.FileReader) { var fr = new FileReader(); fr.onloadend = function(e) { document.getElementById("portrait").src = e.target.result; }; fr.readAsDataURL(file); } }
var result = [] function unfold(arr){ for(var i=0;i< arr.length;i++){ if(typeof arr[i]=="object" && arr[i].length>1) { unfold(arr[i]); } else { result.push(arr[i]); } } } var arr = [1,3,4,5,[6,[0,1,5],9],[2,5,[1,5]],[5]]; unfold(arr)
var c=[1,3,4,5,[6,[0,1,5],9],[2,5,[1,5]],[5]]; var b = c.toString().split(',')
var arr=[1,3,4,5,[6,[0,1,5],9],[2,5,[1,5]],[5]]; const flatten = arr => arr.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []); var result = flatten(arr)
参考连接:
概括总结this的指向问题https://finget.github.io/2018/11/28/this/
ECMAScript规范解读thishttps://github.com/mqyqingfeng/Blog/issues/7
function foo() { console.log(this.a) } var a = 1 foo() var obj = { a: 2, foo: foo } obj.foo() // 以上二者状况 `this` 只依赖于调用函数前的对象,优先级是第二个状况大于第一个状况 // 如下状况是优先级最高的,`this` 只会绑定在 `c` 上,不会被任何方式修改 `this` 指向 var c = new foo() c.a = 3 console.log(c.a) // 还有种就是利用 call,apply,bind 改变 this,这个优先级仅次于 new
箭头函数中的this:
function a() { return () => { return () => { console.log(this) } } } console.log(a()()())
箭头函数实际上是没有 this 的,这个函数中的 this 只取决于他外面的第一个不是箭头函数的函数的 this。在这个例子中,由于调用 a 符合前面代码中的第一个状况,因此 this 是 window。而且 this 一旦绑定了上下文,就不会被任何代码改变。
理解 JavaScript 的 async/awaithttps://segmentfault.com/a/1190000007535316
async function async1() { console.log( 'async1 start') await async2() console.log( 'async1 end') } async function async2() { console.log( 'async2') } async1() console.log( 'script start')
这里注意一点,可能你们都知道await会让出线程,阻塞后面的代码,那么上面例子中, async2
和 script start
谁先打印呢?
是从左向右执行,一旦碰到await直接跳出,阻塞 async2() 的执行?
仍是从右向左,先执行async2后,发现有await关键字,因而让出线程,阻塞代码呢?
实践的结论是,从右向左的。先打印async2,后打印的 script start。
之因此提一嘴,是由于我常常看到这样的说法,「一旦遇到await就马上让出线程,阻塞后面的代码」。
个人理解:callback是解决异步的早期方案,可是会致使‘回调地狱’,而后就出现了Promise,利用.then
优化了回调地狱的问题,而async/await是在promise 进一步封装,利用看似同步的方式解决异步问题。Promise和async/await都是语法糖。就是写起来更简单,阅读性和维护性加强。
Promise 和 async/await在执行时都干了什么,推荐看看:8 张图帮你一步步看清 async/await 和 promise 的执行顺序
直接粘贴大神的代码:
// 三种状态 const PENDING = "pending"; const RESOLVED = "resolved"; const REJECTED = "rejected"; // promise 接收一个函数参数,该函数会当即执行 function MyPromise(fn) { let _this = this; _this.currentState = PENDING; _this.value = undefined; // 用于保存 then 中的回调,只有当 promise // 状态为 pending 时才会缓存,而且每一个实例至多缓存一个 _this.resolvedCallbacks = []; _this.rejectedCallbacks = []; _this.resolve = function (value) { if (value instanceof MyPromise) { // 若是 value 是个 Promise,递归执行 return value.then(_this.resolve, _this.reject) } setTimeout(() => { // 异步执行,保证执行顺序 if (_this.currentState === PENDING) { _this.currentState = RESOLVED; _this.value = value; _this.resolvedCallbacks.forEach(cb => cb()); } }) }; _this.reject = function (reason) { setTimeout(() => { // 异步执行,保证执行顺序 if (_this.currentState === PENDING) { _this.currentState = REJECTED; _this.value = reason; _this.rejectedCallbacks.forEach(cb => cb()); } }) } // 用于解决如下问题 // new Promise(() => throw Error('error)) try { fn(_this.resolve, _this.reject); } catch (e) { _this.reject(e); } } MyPromise.prototype.then = function (onResolved, onRejected) { var self = this; // 规范 2.2.7,then 必须返回一个新的 promise var promise2; // 规范 2.2.onResolved 和 onRejected 都为可选参数 // 若是类型不是函数须要忽略,同时也实现了透传 // Promise.resolve(4).then().then((value) => console.log(value)) onResolved = typeof onResolved === 'function' ? onResolved : v => v; onRejected = typeof onRejected === 'function' ? onRejected : r => throw r; if (self.currentState === RESOLVED) { return (promise2 = new MyPromise(function (resolve, reject) { // 规范 2.2.4,保证 onFulfilled,onRjected 异步执行 // 因此用了 setTimeout 包裹下 setTimeout(function () { try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }); })); } if (self.currentState === REJECTED) { return (promise2 = new MyPromise(function (resolve, reject) { setTimeout(function () { // 异步执行onRejected try { var x = onRejected(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (reason) { reject(reason); } }); })); } if (self.currentState === PENDING) { return (promise2 = new MyPromise(function (resolve, reject) { self.resolvedCallbacks.push(function () { // 考虑到可能会有报错,因此使用 try/catch 包裹 try { var x = onResolved(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (r) { reject(r); } }); self.rejectedCallbacks.push(function () { try { var x = onRejected(self.value); resolutionProcedure(promise2, x, resolve, reject); } catch (r) { reject(r); } }); })); } }; // 规范 2.3 function resolutionProcedure(promise2, x, resolve, reject) { // 规范 2.3.1,x 不能和 promise2 相同,避免循环引用 if (promise2 === x) { return reject(new TypeError("Error")); } // 规范 2.3.2 // 若是 x 为 Promise,状态为 pending 须要继续等待不然执行 if (x instanceof MyPromise) { if (x.currentState === PENDING) { x.then(function (value) { // 再次调用该函数是为了确认 x resolve 的 // 参数是什么类型,若是是基本类型就再次 resolve // 把值传给下个 then resolutionProcedure(promise2, value, resolve, reject); }, reject); } else { x.then(resolve, reject); } return; } // 规范 2.3.3.3.3 // reject 或者 resolve 其中一个执行过得话,忽略其余的 let called = false; // 规范 2.3.3,判断 x 是否为对象或者函数 if (x !== null && (typeof x === "object" || typeof x === "function")) { // 规范 2.3.3.2,若是不能取出 then,就 reject try { // 规范 2.3.3.1 let then = x.then; // 若是 then 是函数,调用 x.then if (typeof then === "function") { // 规范 2.3.3.3 then.call( x, y => { if (called) return; called = true; // 规范 2.3.3.3.1 resolutionProcedure(promise2, y, resolve, reject); }, e => { if (called) return; called = true; reject(e); } ); } else { // 规范 2.3.3.4 resolve(x); } } catch (e) { if (called) return; called = true; reject(e); } } else { // 规范 2.3.4,x 为基本类型 resolve(x); } }
MyPromise.all = (arr) => { if (!Array.isArray(arr)) { throw new TypeError('参数应该是一个数组!'); }; return new MyPromise(function(resolve, reject) { let i = 0, result = []; next(); function next() { //若是不是MyPromise对象,须要转换 MyPromise.resolve(arr[i]).then(res => { result.push(res); i++; if (i === arr.length) { resolve(result); } else { next(); }; }, reject); }; }) };
参考连接:
原生es6封装一个Promise对象
你是否在平常开发中遇到一个问题,在滚动事件中须要作个复杂计算或者实现一个按钮的防二次点击操做。
这些需求均可以经过函数防抖动来实现。尤为是第一个需求,若是在频繁的事件回调中作复杂计算,颇有可能致使页面卡顿,不如将屡次计算合并为一次计算,只在一个精确点作操做。
PS:防抖和节流的做用都是防止函数屡次调用。区别在于,假设一个用户一直触发这个函数,且每次触发函数的间隔小于wait,防抖的状况下只会调用一次,而节流的 状况会每隔必定时间(参数wait)调用函数。
咱们先来看一个袖珍版的防抖理解一下防抖的实现:
// func是用户传入须要防抖的函数 // wait是等待时间 const debounce = (func, wait = 50) => { // 缓存一个定时器id let timer = 0 // 这里返回的函数是每次用户实际调用的防抖函数 // 若是已经设定过定时器了就清空上一次的定时器 // 开始一个新的定时器,延迟执行用户传入的方法 return function(...args) { if (timer) clearTimeout(timer) timer = setTimeout(() => { func.apply(this, args) }, wait) } } // 不难看出若是用户调用该函数的间隔小于wait的状况下,上一次的时间还未到就被清除了,并不会执行函数
这是一个简单版的防抖,可是有缺陷,这个防抖只能在最后调用。通常的防抖会有immediate选项,表示是否当即调用。这二者的区别,举个栗子来讲:
延迟执行
的防抖函数,它老是在一连串(间隔小于wait的)函数触发以后调用。当即执行
的防抖函数,它老是在第一次调用,而且下一次调用必须与前一次调用的时间间隔大于wait才会触发。// 这个是用来获取当前时间戳的 function now() { return +new Date() } /** * 防抖函数,返回函数连续调用时,空闲时间必须大于或等于 wait,func 才会执行 * * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @param {boolean} immediate 设置为ture时,是否当即调用函数 * @return {function} 返回客户调用函数 */ function debounce (func, wait = 50, immediate = true) { let timer, context, args // 延迟执行函数 const later = () => setTimeout(() => { // 延迟函数执行完毕,清空缓存的定时器序号 timer = null // 延迟执行的状况下,函数会在延迟函数中执行 // 使用到以前缓存的参数和上下文 if (!immediate) { func.apply(context, args) context = args = null } }, wait) // 这里返回的函数是每次实际调用的函数 return function(...params) { // 若是没有建立延迟执行函数(later),就建立一个 if (!timer) { timer = later() // 若是是当即执行,调用函数 // 不然缓存参数和调用上下文 if (immediate) { func.apply(this, params) } else { context = this args = params } // 若是已有延迟执行函数(later),调用的时候清除原来的并从新设定一个 // 这样作延迟函数会从新计时 } else { clearTimeout(timer) timer = later() } } }
节流:
/** * underscore 节流函数,返回函数连续调用时,func 执行频率限定为 次 / wait * * @param {function} func 回调函数 * @param {number} wait 表示时间窗口的间隔 * @param {object} options 若是想忽略开始函数的的调用,传入{leading: false}。 * 若是想忽略结尾函数的调用,传入{trailing: false} * 二者不能共存,不然函数不能执行 * @return {function} 返回客户调用函数 */ _.throttle = function(func, wait, options) { var context, args, result; var timeout = null; // 以前的时间戳 var previous = 0; // 若是 options 没传则设为空对象 if (!options) options = {}; // 定时器回调函数 var later = function() { // 若是设置了 leading,就将 previous 设为 0 // 用于下面函数的第一个 if 判断 previous = options.leading === false ? 0 : _.now(); // 置空一是为了防止内存泄漏,二是为了下面的定时器判断 timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { // 得到当前时间戳 var now = _.now(); // 首次进入前者确定为 true // 若是须要第一次不执行函数 // 就将上次时间戳设为当前的 // 这样在接下来计算 remaining 的值时会大于0 if (!previous && options.leading === false) previous = now; // 计算剩余时间 var remaining = wait - (now - previous); context = this; args = arguments; // 若是当前调用已经大于上次调用时间 + wait // 或者用户手动调了时间 // 若是设置了 trailing,只会进入这个条件 // 若是没有设置 leading,那么第一次会进入这个条件 // 还有一点,你可能会以为开启了定时器那么应该不会进入这个 if 条件了 // 其实仍是会进入的,由于定时器的延时 // 并非准确的时间,极可能你设置了2秒 // 可是他须要2.2秒才触发,这时候就会进入这个条件 if (remaining <= 0 || remaining > wait) { // 若是存在定时器就清理掉不然会调用二次回调 if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { // 判断是否设置了定时器和 trailing // 没有的话就开启一个定时器 // 而且不能不能同时设置 leading 和 trailing timeout = setTimeout(later, remaining); } return result; }; };
懒加载也就是延迟加载
原理:
页面中的img元素,若是没有src属性,浏览器就不会发出请求去下载图片,只有经过javascript设置了图片路径,浏览器才会发送请求。
懒加载的原理就是先在页面中把全部的图片统一使用一张占位图进行占位,把正真的路径存在元素的“data-url”(这个名字起个本身认识好记的就行)属性里,要用的时候就取出来,再设置
// 懒加载 function loadImg(src){ let promise = new Promise(function (resolve, reject) { let img = document.createElement('img') img.onload = function () { resolve(img) } img.onerror = function () { reject('图片加载失败') } img.src = src }) return promise }
预加载 提早加载图片,当用户须要查看时可直接从本地缓存中渲染
实现预加载的三种方法:
#preload-01 { background: url(http://domain.tld/image-01.png) no-repeat -9999px -9999px; } #preload-02 { background: url(http://domain.tld/image-02.png) no-repeat -9999px -9999px; } #preload-03 { background: url(http://domain.tld/image-03.png) no-repeat -9999px -9999px; }
将这三个ID选择器应用到(X)HTML元素中,咱们即可经过CSS的background属性将图片预加载到屏幕外的背景上。只要这些图片的路径保持不变,当它们在Web页面的其余地方被调用时,浏览器就会在渲染过程当中使用预加载(缓存)的图片。简单、高效,不须要任何JavaScript。
该方法虽然高效,但仍有改进余地。使用该法加载的图片会同页面的其余内容一块儿加载,增长了页面的总体加载时间。为了解决这个问题,咱们增长了一些JavaScript代码,来推迟预加载的时间,直到页面加载完毕。代码以下:
function preloader() { if (document.getElementById) { document.getElementById("preload-01").style.background = "url(http://domain.tld/image-01.png) no-repeat -9999px -9999px"; document.getElementById("preload-02").style.background = "url(http://domain.tld/image-02.png) no-repeat -9999px -9999px"; document.getElementById("preload-03").style.background = "url(http://domain.tld/image-03.png) no-repeat -9999px -9999px"; } } function addLoadEvent(func) { var oldonload = window.onload; if (typeof window.onload != 'function') { window.onload = func; } else { window.onload = function() { if (oldonload) { oldonload(); } func(); } } } addLoadEvent(preloader);
var images = new Array() function preload() { for (i = 0; i < preload.arguments.length; i++) { images[i] = new Image() images[i].src = preload.arguments[i] } } preload( "http://domain.tld/gallery/image-001.jpg", "http://domain.tld/gallery/image-002.jpg", "http://domain.tld/gallery/image-003.jpg" )
window.onload = function() { setTimeout(function() { // XHR to request a JS and a CSS var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://domain.tld/preload.js'); xhr.send(''); xhr = new XMLHttpRequest(); xhr.open('GET', 'http://domain.tld/preload.css'); xhr.send(''); // preload image new Image().src = "http://domain.tld/preload.png"; }, 1000); };
上面代码预加载了“preload.js”、“preload.css”和“preload.png”。1000毫秒的超时是为了防止脚本挂起,而致使正常页面出现功能问题。
window.onload = function() { setTimeout(function() { // reference to <head> var head = document.getElementsByTagName('head')[0]; // a new CSS var css = document.createElement('link'); css.type = "text/css"; css.rel = "stylesheet"; css.href = "http://domain.tld/preload.css"; // a new JS var js = document.createElement("script"); js.type = "text/javascript"; js.src = "http://domain.tld/preload.js"; // preload JS and CSS head.appendChild(css); head.appendChild(js); // preload image new Image().src = "http://domain.tld/preload.png"; }, 1000); };
这里,咱们经过DOM建立三个元素来实现三个文件的预加载。正如上面提到的那样,使用Ajax,加载文件不会应用到加载页面上。从这点上看,Ajax方法优越于JavaScript。
参考连接:
Javascript图片预加载详解
借用babel工具能够学习一下,es6的class 编译成es5时,长什么样
// ES6 class Person{ constructor(name,age){ this.name = name this.age = age } say() { console.log(this.name) } run() { console.log('run fast') } // 静态方法,类调用 static getGirl(){ console.log('girl friend') } }
// ES5 var _createClass = function() { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; // 枚举 descriptor.enumerable = descriptor.enumerable || false; // 可配置 descriptor.configurable = true; if ("value" in descriptor) // 可写 descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function(Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); // 禁止 直接调用 Person() function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Person = function () { function Person(name, age) { _classCallCheck(this, Person); this.name = name; this.age = age; } _createClass(Person, [{ key: 'say', value: function say() { console.log(this.name); } }, { key: 'run', value: function run() { console.log('run fast'); } }], [{ key: 'getGirl', value: function getGirl() { console.log('girl friend'); } }]); return Person; }();
关于对象的enumerable
、writable
、configurable
,能够看看Javascript properties are enumerable, writable and configurable
默认排序顺序是根据字符串Unicode码点
函数式编程的本质,函数式编程中的函数这个术语不是指计算机中的函数,而是指数学中的函数,即自变量的映射。也就是说一个函数的值仅决定于函数参数的值,不依赖其余状态。好比sqrt(x)函数计算x的平方根,只要x不变,不管何时调用,调用几回,值都是不变的。
函数式的最主要的好处是不可变性带来的。没有可变的状态,函数就是引用透明的没有反作用。函数即不依赖外部的状态也不修改外部的状态,函数调用的结果不依赖调用的时间和位置,这样写的代码容易进行推理,不容易出错。这使得单元测试和调试更容易。
参考连接:
js函数式编程指南
回调地狱、代码的可阅读性和可维护性下降
直接上连接:如何给localStorage设置一个过时时间?
async function test() { console.log('Hello') let res = await sleep(1000) console.log(res) } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)) } test()
参考连接:
JavaScript的sleep实现--Javascript异步编程学习
window._pt_lt = new Date().getTime(); window._pt_sp_2 = []; _pt_sp_2.push('setAccount,2953009d'); var _protocol = (("https:" == document.location.protocol) ? " https://" : " http://"); (function() { var atag = document.createElement('script'); atag.type = 'text/javascript'; atag.async = true; atag.src = _protocol + 'js.ptengine.cn/2953009d.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(atag, s); })();
浅拷贝只是对指针的拷贝,拷贝后两个指针指向同一个内存空间,深拷贝不但对指针进行拷贝,并且对指针指向的内容进行拷贝,经深拷贝后的指针是指向两个不一样地址的指针。
参考连接:
js浅拷贝和深拷贝
特性 | cookie | sessionStorage | localStorage |
---|---|---|---|
数据生命期 | 生成时就会被指定一个maxAge值,这就是cookie的生存周期,在这个周期内cookie有效,默认关闭浏览器失效 | 页面会话期间可用 | 除非数据被清除,不然一直存在 |
存放数据大小 | 4K左右(由于每次http请求都会携带cookie) | 通常5M或更大 | |
与服务器通讯 | 由对服务器的请求来传递,每次都会携带在HTTP头中,若是使用cookie保存过多数据会带来性能问题 | 数据不是由每一个服务器请求传递的,而是只有在请求时使用数据,不参与和服务器的通讯 | |
易用性 | cookie须要本身封装setCookie,getCookie | 能够用源生接口,也可再次封装来对Object和Array有更好的支持 | |
共同点 | 都是保存在浏览器端,和服务器端的session机制不一样 |
时间同样。引用类型的变量都是堆内存。堆内存就像书架同样,只要你知道书名,就能直接找到对应的书。
var a = {b: 1} 存放在哪里?
var a = {b: {c: 1}}存放在哪里?
var a = {name: "前端开发"}; var b = a; a = null, 那么b输出什么?
js变量能够用来保存两种类型的值:基本类型值和引用类型值。在ES6以前共有6种数据类型:Undefined、Null、Boolean、Number,String和Object,其中前5种是基本类型值。
1.执行时间
window.onload必须等到页面内包括图片的全部元素加载完毕后才能执行。
$(document).ready()是DOM结构绘制完毕后就执行,没必要等到加载完毕。
2.编写个数不一样
window.onload不能同时编写多个,若是有多个window.onload方法,只会执行一个
$(document).ready()能够同时编写多个,而且均可以获得执行
3.简化写法
window.onload没有简化写法
$(document).ready(function(){})能够简写成$(function(){});
let arr = [1,1,3,4,3,5,6,8,6,5,8] function get() { let num = 0; arr.forEach(item => { num = num^item // 异或运算 }) console.log(num) } get()
1.beforcreate
2.created
3.beformount
4.mounted
5.beforeUpdate
6.updated
7.actived
8.deatived
9.beforeDestroy
10.destroyed
JavaScript从初级往高级走系列————Virtual Dom
Vue3基于Proxy 的新数据监听系统,全语音特性支持 + 更好的性能
Vue2.x用的是基于ES5的getter/setter,也就是Object.defineProperty这个API。
每一个vue 组件都会代理它所包含的 data、props、computed,这些代理都是经过Object.defineProperty实现的,大量的Object.defineProperty是很大的性能消耗
利用Proxy减小组件实例初始化开销,暴露给用户的这个this,实际上是一个真正的组件实例的一个Proxy
基于Proxy的监听是所谓的Lazy by default,只有当一个数据被用到的时候才会监听
原理简述:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>双向绑定</title> </head> <body> 手写一个简单双向绑定<br/> <input type="text" id="model"><br/> <div id="modelText"></div> </body> <script> var model = document.querySelector("#model"); var modelText = document.querySelector("#modelText"); var defaultName = "defaultName"; var userInfo = {} model.value = defaultName; Object.defineProperty(userInfo, "name", { get: function () { return defaultName; }, set: function (value) { defaultName = value; model.value = value; console.log("-----value"); console.log(value); modelText.textContent = value; } }) userInfo.name = "new value"; var isEnd = true; model.addEventListener("keyup", function () { if (isEnd) { userInfo.name = this.value; } }, false) //加入监听中文输入事件 model.addEventListener("compositionstart", function () { console.log("开始输入中文"); isEnd = false; }) model.addEventListener("compositionend", function () { isEnd = true; console.log("结束输入中文"); }) </script> </html>
参考连接:
前端路由简介以及vue-router实现原理
【源码拾遗】从vue-router看前端路由的两种实现
浅谈vue-router原理
<router-link>
组件支持用户在具备路由功能的应用中 (点击) 导航。 经过 to 属性指定目标地址,默认渲染成带有正确连接的 <a>
标签,能够经过配置 tag 属性生成别的标签.。另外,当目标路由成功激活时,连接元素自动设置一个表示激活的 CSS 类名。
<router-link>
比起写死的 <a href="...">
会好一些,理由以下:
router-link
会守卫点击事件,让浏览器再也不从新加载页面。base
选项以后,全部的 to
属性都不须要写 (基路径) 了。参考连接:
react-router从Link组件和a标签的区别提及
对于非UI控件来讲,不存在双向,只有单向。只有UI控件才有双向的问题。
getDefaultProps
getInitialState
componentWillMount
render
componentDidMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
componentDidUpdate
componentWillUnmount
在代码中调用setState函数以后,React 会将传入的参数对象与组件当前的状态合并,而后触发所谓的调和过程(Reconciliation)。通过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树而且着手从新渲染整个UI界面。在 React 获得元素树以后,React 会自动计算出新的树与老树的节点差别,而后根据差别对界面进行最小化重渲染。在差别计算算法中,React 可以相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是所有从新渲染。
简单而言,React Element 是描述屏幕上所见内容的数据结构,是对于 UI 的对象表述。典型的 React Element 就是利用 JSX 构建的声明式代码片而后被转化为createElement的调用组合。而 React Component 则是能够接收参数输入而且返回某个React Element的函数或者类。更多介绍能够参考React Elements vs React Components。
在组件须要包含内部状态或者使用到生命周期函数的时候使用 Class Component ,不然使用函数式组件。
Refs 是 React 提供给咱们的安全访问 DOM 元素或者某个组件实例的句柄。咱们能够为元素添加ref属性而后在回调函数中接受该元素在 DOM 树中的句柄,该值会做为回调函数的第一个参数返回:
class CustomForm extends Component { handleSubmit = () => { console.log("Input Value: ", this.input.value) } render () { return ( <form onSubmit={this.handleSubmit}> <input type='text' ref={(input) => this.input = input} /> <button type='submit'>Submit</button> </form> ) } }
上述代码中的input域包含了一个ref属性,该属性声明的回调函数会接收input对应的 DOM 元素,咱们将其绑定到this指针以便在其余的类函数中使用。另外值得一提的是,refs 并非类组件的专属,函数式组件一样可以利用闭包暂存其值:
function CustomForm ({handleSubmit}) { let inputElement return ( <form onSubmit={() => handleSubmit(inputElement.value)}> <input type='text' ref={(input) => inputElement = input} /> <button type='submit'>Submit</button> </form> ) }
Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。
render () { return ( <ul> {this.state.todoItems.map(({task, uid}) => { return <li key={uid}>{task}</li> })} </ul> ) }
在开发过程当中,咱们须要保证某个元素的 key 在其同级元素中具备惟一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近建立的仍是被移动而来的元素,从而减小没必要要的元素重渲染。此外,React 还须要借助 Key 值来判断元素与本地状态的关联关系,所以咱们毫不可忽视转换函数中 Key 的重要性。
React 的核心组成之一就是可以维持内部状态的自治组件,不过当咱们引入原生的HTML表单元素时(input,select,textarea 等),咱们是否应该将全部的数据托管到 React 组件中仍是将其仍然保留在 DOM 元素中呢?这个问题的答案就是受控组件与非受控组件的定义分割。受控组件(Controlled Component)代指那些交由 React 控制而且全部的表单数据统一存放的组件。譬以下面这段代码中username变量值并无存放到DOM元素中,而是存放在组件状态数据中。任什么时候候咱们须要改变username变量值时,咱们应当调用setState函数进行修改。
class ControlledForm extends Component { state = { username: '' } updateUsername = (e) => { this.setState({ username: e.target.value, }) } handleSubmit = () => {} render () { return ( <form onSubmit={this.handleSubmit}> <input type='text' value={this.state.username} onChange={this.updateUsername} /> <button type='submit'>Submit</button> </form> ) } }
而非受控组件(Uncontrolled Component)则是由DOM存放表单数据,并不是存放在 React 组件中。咱们能够使用 refs 来操控DOM元素:
class UnControlledForm extends Component { handleSubmit = () => { console.log("Input Value: ", this.input.value) } render () { return ( <form onSubmit={this.handleSubmit}> <input type='text' ref={(input) => this.input = input} /> <button type='submit'>Submit</button> </form> ) } }
居然非受控组件看上去更好实现,咱们能够直接从 DOM 中抓取数据,而不须要添加额外的代码。不过实际开发中咱们并不提倡使用非受控组件,由于实际状况下咱们须要更多的考虑表单验证、选择性的开启或者关闭按钮点击、强制输入格式等功能支持,而此时咱们将数据托管到 React 中有助于咱们更好地以声明式的方式完成这些功能。引入 React 或者其余 MVVM 框架最初的缘由就是为了将咱们从繁重的直接操做 DOM 中解放出来。
咱们应当将AJAX 请求放到 componentDidMount 函数中执行,主要缘由有下:
shouldComponentUpdate容许咱们手动地判断是否要进行组件更新,根据组件的应用场景设置函数的合理返回值可以帮咱们避免没必要要的更新。
一般状况下咱们会使用 Webpack 的 DefinePlugin 方法来将 NODE_ENV 变量值设置为 production。编译版本中 React 会忽略 propType 验证以及其余的告警信息,同时还会下降代码库的大小,React 使用了 Uglify 插件来移除生产环境下没必要要的注释等信息。
props.children并不必定是数组类型,譬以下面这个元素:
<Parent> <h1>Welcome.</h1> </Parent>
为了解决跨浏览器兼容性问题,React 会将浏览器原生事件(Browser Native Event)封装为合成事件(SyntheticEvent)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差别,保证了行为的一致性。另外有意思的是,React 并无直接将事件附着到子元素上,而是以单一事件监听器的方式将全部的事件发送到顶层进行处理。这样 React 在更新 DOM 的时候就不须要考虑如何去处理附着在 DOM 上的事件监听器,最终达到优化性能的目的。
createElement 函数是 JSX 编译以后使用的建立 React Element 的函数,而 cloneElement 则是用于复制某个元素并传入新的 Props。
该函数会在setState函数调用完成而且组件开始重渲染的时候被调用,咱们能够用该函数来监听渲染是否完成:
this.setState( { username: 'tylermcginnis33' }, () => console.log('setState has finished and the component has re-rendered.') )
this.setState((prevState, props) => { return { streak: prevState.streak + props.count } })
这段代码没啥问题,不过只是不太经常使用罢了,详细能够参考React中setState同步更新策略
参考连接:
参考连接:
Express框架详解
深刻理解express框架
express框架的简单实现
阮一峰:JavaScript 运行机制详解:再谈Event Loop
建立了一个前端学习交流群,感兴趣的朋友,一块儿来嗨呀!