##HTML
HTML DOM 定义了访问和操作 HTML 文档的标准方法。
DOM 将 HTML 文档表达为树结构。
声明位于文档中的最前面,处于
标签之前。告知浏览器以何种模式来渲染文档。
Doctype文档类型 DTD
HTML 4.01 规定了三种文档类型:Strict(严格)、Transitional (过渡)以及 Frameset(框架)。
严格模式与过渡模式
严格模式的排版和 JS 运作模式是以该浏览器支持的最高标准运行。
在过渡模式中,页面以宽松的向后兼容的方式显示。模拟老式浏览器的行为以防止站点无法工作。
DOCTYPE不存在或格式不正确会导致文档以过渡模式呈现。
增加了
、、
、
等语义化标签。
在存储方面,提供了 sessionStorage 、localStorage 等,方便数据在 客户端的存储 和获取。
在多媒体方面,规定了音频和视频元素 和
。
画布(Canvas) API、地理(Geolocation) API。
WebSocket 传输协议。
web worker。
Websocket 是 Web 应用程序的传输协议,基于TCP,是一个HTML5协议。
是一个持久化的协议,服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。
WebSocket提供了 双向的,按序到达的数据流,服务器和客户端可以彼此相互推送信息,通过在客户端和服务器之间保持双工连接,服务器的更新可以被及时推送给客户端,而不需要客户端以一定时间间隔去轮询。( WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求。)
WebSocket允许跨域通信。
在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
(当在 HTML 页面中执行脚本时,页面的状态是不可响应的,直到脚本已完成,而 web worker 会在后台运行。)
worker主线程
worker = new Worker( url )
加载一个JS文件来创建一个worker,同时返回一个worker实例。worker.postMessage(data)
方法来向worker发送数据。worker.onmessage
方法来接收worker发送过来的数据。worker.terminate()
来终止一个worker的执行。提供了window.postMessage() 方法,可以安全地实现跨源通信。
window.postMessage() 方法可以安全地实现跨源通信,允许来自不同源的脚本采用 异步 方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
window.postMessage() 方法被调用时,会在所有页面脚本执行完毕之后(e.g. 在该方法之后设置的事件、之前设置的timeout 事件,etc.)向目标窗口派发一个 MessageEvent 消息。
语法:Window.postMessage(data, targetOrigin, [transfer]);
data:将要发送到其他 window的数据。
html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点,部分浏览器只能处理字符串参数,所以在传递参数的时候需要使用 JSON.stringify()
方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。
targrtOrigin:字符串参数。通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"
(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配 targetOrigin
提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。
具体实现:
/*
* A窗口的域名是,以下是A窗口的script标签下的代码:
*/
var popup = window.open(...popup details...);
// 如果弹出框没有被阻止且加载完成
// 假设当前页面没有改变location,这条语句会成功添加message到发送队列中去
popup.postMessage("hello there!", "http://example.org");
function receiveMessage(event)
{
if (event.origin !== "http://example.org")
return;
// 这里的 event.source 是我们通过window.open打开的弹出页面 popup
// event.data 是 popup发送给当前页面的消息 "hi there yourself! the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);
/*
* 弹出页 popup 域名是,以下是script标签中的代码:
*/
//当A页面postMessage被调用后,这个function被addEventListenner调用
function receiveMessage(event)
{
if (event.origin !== "http://example.com:8080")
return;
// event.source 就当前弹出页的来源页面
// event.data 是 "hello there!"
// 假设你已经验证了所受到信息的origin , 一个很方便的方式就是把enent.source作为回信的对象,并且把event.origin作为targetOrigin
event.source.postMessage("hi there yourself! the secret response " + "is: rheeeeet!", event.origin);
}
window.addEventListener("message", receiveMessage, false);
用于接收消息的任何事件监听器必须首先使用 origin
和 source
属性来检查消息的发送者的身份。
与任何异步调度的脚本(超时,用户生成的事件)一样,postMessage
的调用者不可能检测到侦听由 postMessage
发送的事件的事件处理程序何时抛出异常。
分派事件的 origin
属性的值不受调用窗口中 document.domain
的当前值的影响。
draggable 属性:标签元素要设置 draggable="true"
,否则不会有效果。
Event.effectAllowed 属性:拖拽的效果。
event.dataTransfer.effectAllowed = "move"
事件
事件 | 作用元素 | 描述 |
---|---|---|
ondragstart | 被拖放的元素 | 当拖放元素开始被拖放的时候触发的事件 |
ondragenter | 目标元素 | 当被拖放元素进入目标元素的时候触发的事件 |
ondragover | 目标元素 | 被拖放元素在目标元素上移动的时候触发的事件 |
ondragleave | 目标元素 | 被拖放的元素离开目标元素的的时候触发的事件 |
ondrop | 目标元素 | 被拖放的元素在目标元素上同时鼠标放开触发的事件 |
ondragend | 被拖放元素 | 当拖拽完成后触发的事件 |
ondragstart = (event) => ev.dataTransfer.setData("Text",ev.target.id);
// dataTransfer.setData() 方法设置被拖数据的数据类型和值
ondragover = (event) => event.preventDefault()
// 默认地,无法将数据/元素放置到其他元素中。如果需要设置允许放置,我们必须阻止对元素的默认处理方式。
ondrop = (event) => {
event.preventDefault();
// 调用 preventDefault() 来避免浏览器对数据的默认处理(drop 事件的默认行为是以链接形式打开)
var data=ev.dataTransfer.getData("Text");
// 通过 dataTransfer.getData("Text") 方法获得被拖的数据。
event.target.appendChild(document.getElementById(data));
// 把被拖元素追加到放置元素(目标元素)中
}
Tables | Are | Cool |
---|---|---|
col 3 is | right-aligned | $1600 |
col 2 is | centered | $12 |
zebra stripes | are neat | $1 |
属性 | 描述 | 参数 |
---|---|---|
dropEffect | 设置或返回拖放目标上允许发生的拖放行为和要显示的光标类型 | copy 复制式被显示 link 链接样式被显示 move 移动样式被显示 none 默认,没有鼠标定义样式被定义 |
effectAllowed | 设置或返回被拖动元素允许发生的拖动行为与该对象的源元素 | copy 选项被复制 link 选项被dataTransfer作为link方式保存 move 当放置时,对象被移动至目标对象 copylink 选项是被复制还是被作为link方式保存关键在于目标对象 linkmove 选项是被作为link方式保存还是被移动关键在于目标对象 all 所有效果都被支持 none 不支持任何效果 uninitialized 默认不能通过这个属性传递任何值 |
types | 存入数据的种类,字符串的伪数组 | |
clearData() | 清除DataTransfer对象中存放的数据,如果省略参数format,则清除全部数据 | |
setData(format,dta) | 将指定格式的数据赋值给dataTransr对象 | 参数format定义数据的格式也就是数据的类型,赋值的数据 |
getData(format,data) | 从dataTransfer对象中获取指定格式的数据 | format代表数据格式,data为数据。 |
setDragImage(Element image,long x,long y) | 用img元素来设置拖放图标(部分浏览器中可以用canvas等其他元素来设置) | element设置自定义图标,x设置图标与鼠标在水平方向上的距离,y设置图标与鼠标在垂直方向上的距离。 |
参考自:HTML5拖放API
####localStorage
localStorage存储的值都是字符串类型(在处理 json 数据时,需要借助 JSON 类。(JSON.parse()
& JSON.stringify()
)实现字符串与 json 转换。)
localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡。
没有时间限制,除非清除浏览器缓存。
//创建和访问 localStorage
if (window.localStorage) {
localStorage.setItem("key", "value"); // 将value存储到key字段
localStorage.getItem("key"); //获取指定key本地存储的值
}
//还可以用点(.)操作符,及[]的方式进行数据存储和读取
window.localStorage['key'] = value
window.localStorage.key = value
localStorage.removeItem(key) 删除指定key本地存储的值
localStorage.clear(); 清除所有的key & value
// 使用key()方法,向其中出入索引即可获取对应的键
localStorage.key(1)
localStorage在浏览器的隐私模式下面是不可读取的。
localStorage 在PC上的兼容性不太好,而且当网络速度快、协商缓存响应快时使用localStorage 的速度比不上HTTP缓存,并且不能缓存css文件。而移动端由于网速慢,使用localStorage 要快于HTTP缓存。
####sessionStorage
针对一个 session 进行数据存储。当用户关闭浏览器窗口后,数据会被删除。
sessionStorage 存储的值也是字符串类型,方法同 localStorage。
存储
定义:
Cookie 就是浏览器储存在用户电脑上的一小段文本文件。
Cookie 是纯文本格式,不包含任何可执行的代码。
Cookie 由键值对构成,由分号和空格隔开。
Cookie 虽然是存储在浏览器,但是 通常由服务器端进行设置。
Cookie 的大小限制在 4kb 左右(4096字节)。
Cookie是绑定在特定域名上的。
属性
expires
/ max-age
控制 Cookie 失效时刻的选项。如果没有设置这两个选项,则默认有效期为 session,即会话 Cookie。这种 Cookie 在浏览器关闭后就没有了。
expires 是 http/1.0 协议中的选项,必须是 GMT 格式 的时间(可以通过 new Date().toGMTString()
或者 new Date().toUTCString()
来获得)
在新的 http/1.1 协议中 expires 已经由 max-age 选项代替。如果同时设置了 max-age 和 expires,以 max-age 的时间为准。
如果max-age为负数,则表示该cookie仅在本浏览器窗口以及本窗口打开的子窗口内有效,关闭窗口后该cookie即失效。max-age为负数的Cookie,为临时性cookie,不会被持久化,不会被写到cookie文件中。cookie信息保存在浏览器内存中,因此关闭浏览器该cookie就消失了。cookie默认的max-age值为-1。
如果max-age为0,则表示删除该cookie。cookie机制没有提供删除cookie的方法,因此通过设置该cookie即时失效实现删除cookie的效果。失效的Cookie会被浏览器从cookie文件或者内存中删除。
Cookie对象的Expires属性设置为MinValue表示永不过期。
注意:从客户端读取Cookie时,包括maxAge在内的其他属性都是不可读的,也不会被提交。浏览器提交Cookie时只会提交name与value属性。maxAge属性只被浏览器用来判断Cookie是否过期。
domain
和 path
name
、domain
和 path
可以标识一个唯一的 Cookie。
domain
和 path
两个选项共同决定了 Cookie 何时被浏览器自动添加到请求头部中发送出去。
domain
的默认值为设置该 Cookie 的网页所在的域名,path
默认值为设置该 Cookie 的网页所在的目录。
path
设置为“/”时允许所有路径使用Cookie。path属性需要使用符号“/”结尾。
注意:修改、删除Cookie时,新建的Cookie除 value
、maxAge
之外的所有属性,例如 name
、path
、domain
等,都要与原Cookie完全一样。否则,浏览器将视为两个不同的Cookie不予覆盖,导致修改、删除失败。
浏览器判断一个网站是否能操作另一个网站 Cookie 的依据是 域名。
secure属性
当设置为 true 时,表示创建的 Cookie 会被以安全的形式向服务器传输,也就是只能在 HTTPS 连接中被浏览器传递到服务器端进行会话验证,如果是 HTTP 连接则不会传递该信息,所以不会被窃取到Cookie 的具体内容。默认为 false,通过一个普通的HTTP连接传输。
目的:防止信息在传递的过程中被监听捕获后信息泄漏。
secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果需要高安全性,需要在程序中对Cookie内容加密、解密,以防泄密。
HttpOnly属性
如果在Cookie中设置了"HttpOnly"属性,那么通过程序(JS脚本、Applet等)将无法读取到Cookie信息,(不能通过document.cookie获取。)这样能有效的防止XSS攻击。默认情况下,cookie不会带 HttpOnly 选项(即为空)。
目的:防止程序获取cookie后进行攻击(XSS)。
在客户端是不能通过 js 代码去设置一个HttpOnly 类型的cookie的,这种类型的cookie只能通过服务端来设置。
注意:HttpOnly 属性和 Secure 属性相互独立:一个 cookie 既可以是 HttpOnly 的也可以有 Secure 属性。
作用:
会话状态管理(如用户登录状态、购物车、游戏分数和其它需要记录的信息);
个性化设置(如用户自定义设置、主题等);
浏览器行为跟踪(如跟踪分析用户行为)。
创建和存储cookie
// 函数中的参数分别为 cookie 的名称、值以及过期天数
function setCookie( c_name, value, expiresday){
var exdate = new Date();
exdate.setDate( exdate.getDate() + expiresday );
document.cookie = c_name + "=" + escape(value) +
(( expiredays == null ) ? "" : ";expires=" + exdate.toGMTString());
}
setCookie('name','hhh',1); // cookie过期时间为1天。
// 如果要设置过期时间以秒为单位
function setCookie(c_name,value,expiressecond){
var exdate=new Date();
exdate.setTime(exdate.getTime()+expiressecond * 1000);
document.cookie = c_name + "=" + escape(value) +
(( expireseconds== null ) ? "" : ";expires=" + exdate.toGMTString());
}
setCookie('name','hhh',3600); //cookie过期时间为一个小时
// 函数中的参数为 要获取的cookie键的名称。
function getCookie(c_name){
if (document.cookie.length>0){
c_start=document.cookie.indexOf(c_name + "=");
if (c_start!=-1){
c_start = c_start + c_name.length + 1;
c_end=document.cookie.indexOf(";",c_start);
if (c_end==-1){
c_end=document.cookie.length;
}
return unescape(document.cookie.substring(c_start,c_end));
}
}
return "";
}
var username= getCookie('name');
console.log(username);
// 函数中的参数为,要判断的cookie名称
function checkCookie(c_name){
username=getCookie(c_name);
if (username!=null && username!=""){
// 如果cookie值存在,执行下面的操作。
alert('Welcome again '+username+'!');
}else{
username=prompt('Please enter your name:',"");
if (username!=null && username!=""){
//如果cookie不存在,执行下面的操作。
setCookie('username',username,365)
}
}
}
function removeCookie(key) {
setCookie(key, '', -1);//这里只需要把Cookie保质期退回一天便可以删除
}
Cookie满足同源策略
网站 images.google.com 与 www.google.com 域名不一样,二者同样不能互相操作彼此的Cookie。
访问完 zhidao.baidu.com 再访问 wenku.baidu.com 还需要重新登陆百度账号。
解决办法:设置 document.domain = ‘baidu.com’;
// 跨子域
参考自:Cookie/Session机制详解
Session是指一个终端用户与交互系统进行通信的时间间隔,通常指从注册进入系统到注销退出系统之间所经过的时间。
Session保存在服务器上。客户端浏览器访问服务器的时候,服务器把客户端信息以某种形式记录在服务器上。客户端浏览器再次访问时只需要从该Session中查找该客户的状态就可以了。
当多个客户端执行程序时,服务器会保存多个客户端的Session。Session机制决定了当前客户只会获取到自己的Session,而不会获取到别人的Session。各客户的Session也彼此独立,互不可见。
Session的使用比Cookie方便,但是过多的Session存储在服务器内存中,会对服务器造成压力。
Session在用户第一次访问服务器的时候自动创建。需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session。
Session保存在服务器,对客户端是透明的,但它的正常运行仍然需要客户端浏览器的支持。这是因为 Session 使用 Cookie 作为识别标志。
HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为 SESSIONID
的Cookie,它的值为该Session的id(也就是HttpSession.getId()
的返回值)。Session依据该Cookie来识别是否为同一用户。
该Cookie为服务器自动生成的,它的maxAge属性一般为–1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。
因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session。但是由浏览器窗口内的链接、脚本等打开的新窗口(也就是说不是双击桌面浏览器图标等打开的窗口)除外,这类子窗口会共享父窗口的Cookie,因此会共享一个Session。
URL地址重写
URL地址重写是对客户端不支持Cookie的解决方案。URL地址重写的原理是将该用户Session的id信息重写到URL地址中。服务器能够解析重写后的URL获取Session的id。这样即使客户端不支持Cookie,也可以使用Session来记录用户状态。
Cache位于 Web服务器和客户端 之间,用于在Http请求期间保存页面或者数据,存储于服务器的内存中,允许您自定义如何缓存项以及将它们缓存多长时间。
Cache允许将频繁访问的服务器资源的副本存储在内存中,当用户发出相同的请求后,服务器不是再次处理而是将Cache中保存的数据直接返回给用户。不发生服务器-客户端数据传输。
当缺乏系统内存时,缓存会自动移除很少使用的或优先级较低的项以释放内存。该技术也称为清理,这是缓存确保过期数据不使用宝贵的服务器资源的方式之一。
Cache节省的是服务器处理时间。
Cache实例是每一个应用程序专有的,其生命周期==该应用程序周期。应用程序重启将重新创建其实例。
Cache是多会话共享的,因此使用它可以提高网站性能,但是可能泄露用户的安全信息,还由于在服务器缺乏内存时可能会自动移除,Cache因此需要在每次获取数据时检测该Cache项是否还存在。
Cache["ID"]="cc"; // 或者Cache.Insert("ID","test");
String ID =Cache["ID"].ToString();
通常使用最频繁的是Session。
Session缓存和Cache缓存的区别:
最大的区别是Cache提供缓存依赖来更新数据,而Session只能依靠定义的缓存时间来判断缓存数据是否有效。
即使应用程序终止,只要Cache.Add方法中定义的缓存时间未过期,下次开启应用程序时,缓存的数据依然存在。而Session缓存只是存在于一次会话中,会话结束后,数据也就失效了。
Session容易丢失,导致数据的不确定性,而Cache不会出现这种情况。
由于Session是每次会话就被加载,所以不适宜存放大量信息,否则会导致服务器的性能降低。而Cache则主要用来保存大容量信息,如数据库中的多个表。
用户停止使用应用程序之后,Session信息仍在内存中存留一段时间
Session 和 Cookie的区别:
Cookie数据存放在客户的浏览器上,session数据放在服务器上。
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用Cookie。
cookie在浏览器与服务器之间来回传递;sessionStorage 和 localStorage不会把数据发给服务器,仅在本地保存。
cookie数据还有路径的概念,可以限制cookie只属于某个路径下。
| 特性 | Cookie | localStorage | sessionStorage | session |
| ------------- | ------------- | ----- |
| 数据有效期 | 一般由服务器生成,可设置失效时间。如果在浏览器生成,默认是关闭浏览器之后失效。 | 除非被清除,否则永久保存。 | 仅在当前会话有效,关闭页面或浏览器后被清除。 | 服务器会把长时间没有活动的Session从服务器内存中清除,此时Session便失效。Tomcat中Session的默认失效时间为20分钟。
| 存储数据大小 | 4KB |一般 5MB |一般 5MB | 20条Cookie |
| 作用域 | 在所有同源窗口共享 | 在所有同源窗口共享 | 不在不同的浏览器窗口中共享 | 20条Cookie |
缓存的作用:
Web 缓存大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存。
浏览器缓存主要是 HTTP 协议定义的缓存机制,在 HTTP协议头 和 HTML的meta
标签 中定义。但是代理服务器不解析 HTML 内容,一般应用广泛的是用 HTTP 头信息控制缓存。
HTTP 头信息控制缓存大致分为两种:强缓存和协商缓存。强缓存如果命中缓存,不需要和服务器端发生交互,而协商缓存不管是否命中都要和服务器端发生交互,强制缓存的优先级高于协商缓存。
强缓存
对于强制缓存来说,响应 header 中会有两个字段来标明失效规则: Expires / Cache-Control
Expires
指缓存过期时间,即下一次请求,请求时间小于缓存过期时间时,直接使用缓存数据。
过期时间是由服务器端生成的,如果客户端时间表示出错或者没有转换到正确的时区都可能导致缓存命中的误差。
Expires 是 HTTP/1.0 的标准,现在更倾向于用 HTTP/1.1 中定义的 Cache-Control。两个同时存在时也是 Cache-Control 的优先级更高。
Cache-Control
Cache-Control 可以由多个字段组合而成,常见的取值有private、public、no-cache、max-age,no-store,默认为private。
描述 |
---|
ivate |
blic |
x-age |
age |
-cache |
-store |
协商缓存
缓存的资源到期了,并不意味着资源内容发生了改变,如果和服务器上的资源没有差异,实际上没有必要再次请求。客户端和服务器端通过某种验证机制验证当前请求资源是否可以使用缓存。
浏览器第一次请求数据之后会将数据和响应头部的缓存标识存储起来。再次请求时会带上存储的头部字段,服务器端验证是否可用。如果返回 304 Not Modified,代表资源没有发生改变可以使用缓存的数据,获取新的过期时间。反之返回 200 就相当于重新请求了一遍资源并替换旧资源。
Last-modified / If-Modified-Since
Last-modified:服务器端资源的最后修改时间,响应头部会带上这个标识。第一次请求之后,浏览器记录这个时间,再次请求时,请求头部带上 If-Modified-Since 即为之前记录下的时间。服务器端收到带 If-Modified-Since 的请求后会去和资源的最后修改时间对比。若修改过就返回最新资源,状态码 200,若没有修改过则返回 304。
注意:如果响应头中有 Last-modified 而没有 Expire 或 Cache-Control 时,浏览器会有自己的算法来推算出一个时间缓存该文件多久,不同浏览器得出的时间不一样,所以 Last-modified 要记得配合 Expires/Cache-Control 使用。
Etag/If-None-Match
由服务器端上生成的一段 hash 字符串,第一次请求时响应头带上 ETag,之后的请求中带上 If-None-Match,即ETag的值 ,服务器检查 ETag,返回 304 或 200。
Last-Modified 和 Etag 区别:
某些服务器不能精确得到资源的最后修改时间,这样就无法通过最后修改时间判断资源是否更新。
如果资源修改非常频繁,在秒以下的时间内进行修改,而Last-modified 只能精确到秒。
一些资源的最后修改时间改变了,但是内容没改变,使用 Last-modified 看不出内容没有改变。
Etag 的精度比 Last-modified 高,属于强验证,要求资源字节级别的一致,优先级高。如果服务器端有提供 ETag 的话,必须先对 ETag 进行 Conditional Request。
注意:实际使用 ETag/Last-modified 要注意保持一致性,做负载均衡和反向代理的话可能会出现不一致的情况。计算 ETag 也是需要占用资源的,如果修改不是过于频繁,看自己的需求用 Cache-Control 是否可以满足。
考虑缓存的内容:不经常改变的文件,比如引入的一些第三方文件、打包出来的带有 hash 后缀 css、js 文件(一般来说文件内容改变了,会更新版本号、hash 值,相当于请求另一个文件。),logo、图标、html文件、可以下载的内容。
给 max-age 设置一个较大的值,标准中规定 max-age 的值最大不超过一年。
不应该被缓存的内容:业务敏感的 GET 请求,可能经常变动的文件。
参考自:HTTP 缓存机制一二三
CSS
静态布局:给页面元素设置固定的宽度和高度,单位用px,当窗口缩小,会出现滚动条,拉动滚动条显示被遮挡内容。针对不同分辨率的手机端,分别写不同的样式文件。
自适应布局:创建多个静态布局,每个静态布局对应一个屏幕分辨率范围,使用@media媒体查询技术。
流式布局:元素的宽高用百分比做单位,元素宽高按屏幕分辨率调整,布局不发生变化。屏幕尺度跨度过大的情况下,页面不能正常显示。
响应式布局:采用自适应布局和流式布局的综合方式,为不同屏幕分辨率范围创建流式布局。
弹性布局:要点在于使用 em 和 rem 单位来定义元素宽度,弹性布局的尺寸主要根据字体大小而变化。
内联元素水平居中
text-align:center;
内联元素垂直居中
line-height: 父元素高度;
定宽元素水平居中
margin: 0 auto;
多个块状元素的水平居中,父元素text-align: center;
,子元素display: inline-block;
定宽定高元素水平垂直居中
position: absolute;
left: 50%;
top: 50%;
margin-left: -(元素宽度的一半);
margin-top: -(元素高度的一半);
position: absolute;
left: 0;
top: 0;
right: 0;
botton: 0;
margin: auto;
元素水平垂直居中
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%);
// 或直接
margin: 50vh 0 0 50vh;
transform: translate(-50%,-50%);
display: flex;
justify-content: center;
align-items: center;
display: table-cell;
vertical-align: middle;
text-align: center;
// wrapper
display: grid;
grid: 1fr 1fr 1fr/50px 50px;
// 等价于
grid-template-rows: 1fr 1fr 1fr;;
grid-template-columns: /50px 50px;
// 自适应
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
grid-template-rows: repeat(2, 100px);
grid-gap: 10px 20px ; //上下间隔10px,左右间隔20px
grid-gap: 10px;//上下左右间隔10px
// items
grid-column: 1 / 4;
// 等价于
grid-column-start: 1;
grid-column-end: 4;
参考自:强大的display:grid
Responsive Layout In an Easy Way
浏览器的默认字体大小是16px。
在CSS中,em 实际上是一个垂直测量。一个 em 等于任何字体中的字母所需要的垂直空间,而和它所占据的水平空间没有任何的关系。
如果元素设置了字体大小,元素的 width、height、line-height、margin、padding、border 等值受元素 font-size 值影响,否则受父元素 font-size 值影响。
rem 单位基于 html 元素的字体大小,可以从浏览器字体设置中继承字体大小。
使用 rem 单位的主要目的应该是确保无论用户如何设置自己的浏览器,我们的布局都能调整到合适大小。媒体查询中使用 rem 单位。
不要在多列布局中使用 em 或 rem ,改用 %。
参考自:CSS中强大的EM
rem与em的使用和区别详解
BFC(Block Formatting Context),块级格式化上下文,指一个独立的渲染区域。
它决定了元素如何对其内容进行定位,以及与其他元素的关系和相互作用。在进行盒子元素布局的时候,BFC提供了一个环境,在这个环境中按照一定规则进行布局不会影响到其它环境中的布局。
也就是说,如果一个元素符合了成为BFC的条件,该元素内部元素的布局和定位就和外部元素互不影响。
形成 BFC 的条件
浮动元素,float的值不为none;( left || right || inherit )
定位元素, position的值为 absolute || fixed;
display的值为 inline-block || table-cell ;
overflow的值不为visible( hidden || auto || scroll || inherit )
BFC常见作用
对子元素设置浮动后,父元素会发生高度塌陷,也就是父元素的高度变为0。解决这个问题,只需要把父元素变成一个BFC就行了。
常见的解决方法:为父元素设置 overflow:hidden
或浮动父元素。
根本原因:创建BFC的元素,子浮动元素也会参与其高度计算,即不会产生高度塌陷问题。
在标准文档流中,块级标签之间竖直方向的margin会以大的为准,产生margin的塌陷现象。
可以通过触发BFC来解决。
BFC布局规则:
内部的盒子从顶端开始垂直地一个接一个地排列。
两个盒子之间的垂直的间隙是由他们的margin 值所决定的。在一个BFC中,两个相邻的块级盒子的垂直外边距会产生折叠。
每一个盒子的左外边缘(margin-left)会触碰到容器的左边缘(border-left)(对于从右到左的格式来说,则触碰到右边缘)。即使存在浮动也是如此。
BFC的区域不会与float box重叠。
计算BFC的高度时,浮动元素也参与计算。
在浮动元素下添加 。(clear属性只能加在块元素)
.clear{
height:0px;
font-size:0;
clear:both;
}
给父元素设置 overflow
属性: overflow: auto
或 overflow: hidden
。
浮动元素父级应用after
伪元素。
.clear:after{
content:'';
display:block;
clear:both;
}
IE6、7还要增加.clear{ zoom:1; }
浮动元素父级设置高度。
浮动元素父级浮动。
box-sizing属性主要用来控制元素的盒模型的解析模式。默认值是content-box
。
标准浏览器下,按照W3C规范对盒模型解析,一旦修改了元素的边框或内距,就会影响元素的盒子尺寸,就不得不重新计算元素的盒子尺寸,从而影响整个页面的布局。
伪类的效果可以通过添加实际的类来实现。
可以叠加使用。
优先级与类相同。
伪元素的效果可以通过添加实际的元素来实现 。
伪元素在一个选择器中只能出现一次,并且只能出现在末尾。
优先级与标签相同
它们的本质区别就是是否抽象创造了新元素
display:none :隐藏对应的元素,在文档布局中不再给它分配空间。
visibility:hidden :隐藏对应的元素,但是在文档布局中仍保留原来的空间。
共同点:对内联元素设置float和absolute属性,可以让元素脱离文档流,并且可以设置其宽高。
不同点:float仍会占据位置,absolute会覆盖文档流中的其他元素。
absolute:生成绝对定位的元素, 相对于最近一级的定位不是 static 的父元素来进行定位。
fixed(老IE不支持):生成绝对定位的元素,通常相对于浏览器窗口或 frame 进行定位。
relative:生成相对定位的元素,相对于其在普通流中的位置进行定位。
static:默认值。没有定位,元素出现在正常的流中
sticky:生成粘性定位的元素,容器的位置根据正常文档流计算得出
id选择器( # myid)
类选择器(.myclassname)
标签选择器(div, h1, p)
相邻选择器(h1 + p)
子选择器(ul > li)
后代选择器(li a)
通配符选择器( * )
属性选择器(a[rel = “external”])
伪类选择器(a: hover, li:nth-child)
优先级
内联样式表(标签内部)> 嵌入样式表(当前文件中)> 外部样式表(外部文件中)。
!important > id > class = 伪类选择器 > tag(标签)
!important 比 内联优先级高
不可继承的样式
display
、border
、padding
、margin
、width
、height
、background
、overflow
、position
、z-index
、float
、clear
、vertical-align
…
所有元素可继承的样式
visibility
、cursor
内联元素可继承的样式
font
、font-size
、font-family
、color
、letter-spacing
、word-spacing
、white-space
、line-height
、font-weight
、text-transform
…
块状元素可继承的样式
text-indent
、text-align
列表元素可继承的样式
list-style
、list-style-type
、list-style-position
、list-style-image
**表格元素可继承的样式 **
border-collapse
从右向左的匹配在第一步就筛选掉了大量的不符合条件的最右节点(叶子节点),避免了许多无效匹配,避免对所有元素进行遍历。
而从左向右的匹配规则的性能都浪费在了失败的查找上面。
写 css 时需要注意:
& @import
link
属于HTML标签,而 @import
是CSS提供的;
页面被加载的时,link
会同时被加载,而 @import
被引用的CSS会等到引用它的CSS文件被加载完再加载;
@import
只在IE5以上才能识别,而 link
是HTML标签,无兼容问题;
link
方式的样式的权重高于 @import
的权重。
原理:元素的每条 boder 都是一个等腰梯形
line-height:0;
梯形
平行四边形
三角形
椭圆
对话框
实现思想:div:before
制作一个与边框颜色相同的向下三角形,div:after
制作一个与背景颜色相同的向下三角形,覆盖 div:before
的三角形,利用定位使div:before
三角形的边界显示出来。
具体实现:
div{
width:200px;
height:100px;
border:1px solid blue;
position:relative;
background:#fff;
}
div:before{
width:0;
height:0;
content:'';
position:absolute;
top:100px;
left:150px;
border-top:10px solid blue;
border-left:10px solid transparent;
border-right:10px solid transparent;
}
div:after{
width:0;
height:0;
content:'';
position:absolute;
top:99px;
left:150px;
border-top:10px solid #fff;
border-left:10px solid transparent;
border-right:10px solid transparent;
}
//过渡
div {
transition:transform 1s linear;
/* transition: property duration timing-function delay; */
/* 属性名称 过渡用时 过渡速度 开始时间 */
}
div:hover{
transform:rotate(90deg);
/* rotate(90deg) 二维旋转90度 */
/* skew(10deg,20deg) 沿x轴方向倾斜10度(y轴变),沿y轴方向倾斜20度(x轴变) */
/* scale(2,2) 二维缩放转换 */
/* translate(10px,20px) 沿x轴平移10像素,沿y轴平移20像素 */
}
//动画:一直旋转的时针
div{
animation:round 2s linear infinite;
transform-origin:0 0 0; //旋转基点
/* animation: name duration timing-function delay iteration-count direction; */
/* keyframe名称 动画用时 动画速度 开始时间 播放速度 是否轮流反向播放 */
}
@keyframes round{
25% {transform:rotate(90deg);}
50% {transform:rotate(180deg);}
75% {transform:rotate(270deg);}
100% {transform:rotate(360deg);}
}
div{
width:200px;
height:200px;
border-radius:100px;
background:black;
animation:round 2s linear infinite;
transform:perspective(600px);
}
@keyframes round{
0% {transform:rotateY(0deg);}
25% {transform:rotateY(45deg);}
50% {transform:rotateY(90deg);}
75% {transform:rotateY(135deg);}
100% {transform:rotateY(180deg);}
}
###自适应浏览器宽度的正方形
div{
width:30%;
background:red;
height:30vw;
/* CSS3:1vw = 1% viewport width
相对于视窗的宽度。视窗宽度是100vw
视窗为浏览器内部的可视区域大小,即 window.innerWidth / window.innerHeight 大小,不包含任务栏标题栏以及底部工具栏的浏览器区域大小。*/
}
div{
width:30%;
height:0;
padding-bottom: 30%;
background:red;
}
margin, padding 的百分比数值是相对父元素的宽度计算的。
padding-top
撑开容器div{
width:30%;
background:red;
max-width:200px;
}
div:after{
content: '';
display: block;
padding-top:100%;
}
Sass功能:
完全兼容 CSS3;
在 CSS 基础上增加变量、嵌套 (选择器嵌套、属性嵌套)、混合等功能;
通过函数进行颜色值与属性值**运算*;
提供控制指令等高级功能。
Sass和Less都属于CSS预处理器。 CSS 预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为 CSS 增加一些编程的特性,将 CSS 作为目标生成文件,然后开发者就只要使用这种语言进行CSS的编码工作。
CSS有具体以下几个缺点:
CSS预处理器,提供 CSS 缺失的样式复用机制、减少冗余代码,提高样式代码的可维护性,大大提高了我们的开发效率。
为什么选择使用Sass而不是Less?
Less与Sass处理机制不一样,前者是通过客户端处理的,后者是通过服务端处理,相比较之下前者解析会比后者慢一点。
为什么选择使用Sass而不是Less?
优点:减少网络请求次数,提高页面加载速度。
利用background-image
,background- repeat
,background-position
进行背景定位。(把 background-attachment 属性设置为 “fixed”,才能保证该属性在 Firefox 和 Opera 中正常工作。)
JS
函数嵌套函数。
函数内部可以引用外部的参数和变量。
参数和变量不会被垃圾回收机制回收。
缺点:常驻内存,会增大内存使用量,使用不当很容易造成内存泄露。
常见陷阱
function createFunctions(){
var result = new Array();
for (var i = 0; i < 10; i++){
result[i] = function(){
return i;
};
// result[0-9] = function(){ return i; };
// 没执行函数,函数内部不变,不能将函数内的i替换!
// i = 10;
}
return result;
}
var funcs = createFunctions();
for (var i = 0; i < funcs.length; i++){
console.log(funcs[i]());
}
// 输出 10个10
解决方法:
let
:for (let i = 0; i < 10; i++)
function createFunctions() {
var result = new Array();
for (var i = 0; i < 10; i++){
result[i] = (function (i) {
// 再创建一个函数,用该函数的参数绑定循环变量当前的值
return function () {
return i;
}
})(i); // 立即执行
}
return result;
}
var funcs = createFunctions();
for (var i = 0; i < funcs.length; i++){
console.log(funcs[i]());
}
应用场景:
//全局变量,test1是全局变量
var test1 = 111;
function outer(){
alert(test1);
}
outer(); //111
alert(test1); //111
//闭包,test2是局部变量,这是闭包的目的
//我们经常在小范围使用全局变量,这个时候就可以使用闭包来代替。
(function(){
var test2=222;
function outer(){
alert(test2);
}
function test(){
alert("测试闭包:" + test2);
}
outer(); // 222
test(); // 测试闭包:222
}
)();
alert(test2); //未定义,这里就访问不到test2
function callLater(paramA, paramB, paramC) {
/*使用函数表达式创建并放回一个匿名内部函数的引用*/
return (function () {
/*
这个内部函数将被setTimeout函数执行,并且当它被执行时,它能够访问并操作外部函数传递过来的参数
*/
paramA[paramB] = paramC;
});
}
/*
调用这个函数将在它的执行上下文中创建,并最终返回内部函数对象的引用
传递过来的参数,内部函数在最终被执行时,将使用外部函数的参数
返回的引用被赋予了一个变量
*/
var funcRef = callLater(elStyle, "display", "none");
/*调用setTimeout函数,传递内部函数的引用作为第一个参数*/
setTimeout(funcRef, 500);
/*
一个给对象实例关联一个事件处理器的普通方法,
返回的内部函数被作为事件的处理器,
对象实例被作为obj参数,对象上将要被调用的方法名称被作为第二个参数
*/
function associateObjWithEvent(obj, methodName) {
/*返回的内部函数被用来作为一个DOM元素的事件处理器*/
return (function (e) {
/*
事件对象在DOM标准的浏览器中将被转换为e参数,
如果没有传递参数给事件处理内部函数,将统一处理成IE的事件对象
*/
e = e || window.event;
/*
事件处理器调用obj对象上的以methodName字符串标识的方法
并传递两个对象:通用的事件对象,事件处理器被订阅的元素的引用
这里this参数能够使用,因为内部函数已经被执行作为事件处理器所在元素的一个方法
*/
return obj[methodName](e, this);
});
}
/*
这个构造器函数,通过将元素的ID作为字符串参数传递进来,
来创建将自身关联到DOM元素上的对象,
对象实例想在对应的元素触发onclick、onmouseover、onmouseout事件时
对应的方法被调用。
*/
function DhtmlObject(elementId) {
/*
调用一个方法来获得一个DOM元素的引用
如果没有找到,则为null
*/
var el = getElementWith(elementId);
/*
因为if语句块,el变量的值在内部进行了类型转换,变成了boolean类型
所以当它指向一个对象,结果就为true,如果为null则为false
*/
if (el) {
/*
为了给元素指定一个事件处理函数,调用了associateObjWithEvent函数,
利用它自己(this关键字)作为被调用方法的对象,并且提供方法名称
*/
el.onclick = associateObjWithEvent(this, "doOnClick");
el.onmouseover = associateObjWithEvent(this, "doOnMouseOver");
el.onmouseout = associateObjWithEvent(this, "doOnMouseOut");
}
}
DhtmlObject.prototype.doOnClick = function (event, element) {
//doOnClick body
}
DhtmlObject.prototype.doMouseOver = function (event, element) {
//doMouseOver body
}
DhtmlObject.prototype.doMouseOut = function (event, element) {
//doMouseOut body
}
JS闭包可被利用的常见场景
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Closures
简单(基本)数据类型:Number、String、Boolean、Undefined、Null
复杂数据类型:Object
引用类型:Object、Array、Date、RegExp、Function等
特性 | 简单数据类型 | 复杂数据类型 |
---|---|---|
存储方式 | 把数据名和值直接存储在栈当中 | 在栈中存储数据名和一个堆的地址,在堆中存储属性及值。访问时先从栈获取地址,再到堆中拿出相应的值 |
函数内部对参数的修改 | 简单数据类型作为参数时,函数内部对参数值的修改不会改变外部变量的值 | 复杂数据类型作为参数时,函数内部对参数值的修改会改变外部变量的值 |
typeof返回值类型:number、boolean、string、undefined、object、function。
###null和undefined的区别
null表示尚未存在的对象,转为数值时为0,常用来表示函数企图返回一个不存在的对象。
undefined是一个表示”无”的原始值,转为数值时为NaN。
原型:所有的函数都有一个prototype属性,这个属性引用了一个对象,即原型对象。
Function.prototype = {
constructor : Function,
__proto__ : parent prototype,
some prototype properties: ...
};
原型链:函数的原型对象的 constructor
默认指向函数本身,原型对象除了有原型属性外,为了实现继承,还有一个原型链指针 __proto__
,该指针指向上一层的原型对象,而上一层的原型对象的结构依然类似,这样利用 __proto__
一直指向Object的原型对象上,而Object的原型对象用 Object.prototype.__proto__ = null
表示原型链的最顶端,形成了javascript的原型链继承,同时也解释了为什么所有的javascript对象都具有Object的基本方法。
获取一个实例的原型链
obj.__proto__
Object.getPrototypeOf()
###获取对象属性和原型属性
Object.keys()
返回该对象上所有可枚举的属性和方法,不包括原型链。
function foo(name, attr) { // 创建一个对象的构造方法
this.name = name;
this.attr = attr;
this.sayHi = function () {
return 'hi everyone!';
}
}
foo.prototype.sayGoodBy = function () {
console.log('Say Good By')
}
var myTester = new foo("age", 18) // new创建一个对象
var attr = Object.keys(myTester); // ["name", "attr", "sayHi"]
Object.getOwnProperyNames()
返回该对象上可枚举和不可枚举的属性,不包括原型链。
var attr = Object.getOwnPropertyNames(myTester) // ["name", "attr", "sayHi"]
obj.hasOwnPropery(string)
返回一个布尔值,判断对象是否包含特定的自身(非继承)属性。
string in obj
返回一个布尔值,,判断对象是否包含该属性,包括原型链上的属性。
for(key in obj)
返回所有可遍历枚举的属性,包括原型链上的属性。
判断对象是否为空对象
JSON.stringify(data) === "{}"
for in 循环判断
for(var key in obj) {
return false;
}
Object.getOwnPropertyNames()
Object.getOwnPropertyNames(data).length === 0
Object.keys()
Object.keys(data).length === 0
全局作用域或者普通函数中 this 指向全局对象 window。(严格模式下,如果没有指定, 为 undefined)
//直接打印
console.log(this) // window
//function声明函数
function bar () {console.log(this)}
bar() // window
//function声明函数赋给变量
var bar = function () {console.log(this)}
bar() // window
//自执行函数
(function () {console.log(this)})(); // window
方法调用中谁调用this指向谁。
//对象方法调用(this只会指向它的上一级对象)
var person = {
run: function () {console.log(this)}
}
person.run() // person
//事件绑定
var btn = document.querySelector("button")
btn.onclick = function () {
console.log(this) // btn
}
//事件监听
var btn = document.querySelector("button")
btn.addEventListener('click', function () {
console.log(this) //btn
})
this永远指向的是最后调用它的对象。如果我们从对象中拆取出某个方法, 那么这个方法就会变成了一个普通的函数,不再绑定到原对象上,函数里的 this 指向 window。
var car = {
brand: "Nissan",
getBrand: function(){
console.log(this.brand);
}
};
var getCarBrand = car.getBrand;
getCarBrand(); // window.brand: undefined
当我们在某个对象上执行一个方法, 虽然这个方法可能是在其他对象里面定义的, 但此时 this 关键字已经不再指向原对象, 而是指向调用此方法的对象。
var car = {
brand: "Nissan",
getBrand: function(){
console.log(this.brand);
}
};
var el = document.getElementById("btn");
el.addEventListener("click", car.getBrand); // el.brand: undefined
如果还想指向原来的那个对象,需要在赋值的时候显式地将函数绑定(bind) 到原对象。
在构造函数或者构造函数原型对象中 this 指向构造函数的实例。
// 不使用new,指向window
function Person (name) {
console.log(this) // window
this.name = name; // window.name = name
}
// 使用new
function Person (name) {
console.log(this) // people
this.name = name // people.name = iwen
self = this;
}
var people = new Person('iwen')
console.log(self === people) //true
// 这里new改变了this指向,将this由window指向Person的实例对象people
// 如果返回值是一个对象,那么this指向的就是那个返回的对象,如果返回值不是一个对象那么this还是指向函数的实例。
// 返回空对象或空函数
function fn(){
this.username = "lalala";
return {}; // 或 return function() {}
}
var obj = new fn;
console.log(obj); // {}
console.log(obj.username); // undefined
// 返回1 或 undefined 或 null
function fn()
{
this.username = 'lalala';
return 1;
// 或 return undefined;
// 或 return null;
}
var a = new fn;
console.log(a); // {username: "lalala"}
console.log(a.username); // lalala
闭包函数(即内部函数)不能访问到外部函数的 this 变量。
var car = {
brand: "Nissan",
getBrand: function(){
var closure = function(){
console.log(this.brand);
};
return closure();
}
};
car.getBrand(); // window.brand: undefined
将 this 绑定到外部函数:
var car = {
brand: "Nissan",
getBrand: function(){
var closure = function(){
console.log(this.brand);
}.bind(this);// 注意这里
return closure();
}
};
car.getBrand(); // car.brand: Nissan
另一种处理闭包的常用方法是先将 this 赋值给另一个变量,来避免不必要的改变:
var car = {
brand: "Nissan",
getBrand: function(){
var self = this;
var closure = function(){
console.log(self.brand);
};
return closure();
}
};
car.getBrand(); // car.brand: Nissan
箭头函数
var car = {
brand: "Nissan",
getBrand: function(){
var closure = () => {
console.log(this.brand);
};
return closure();
}
};
car.getBrand(); // car.brand: Nissan
参考自:掌握JS中的“this” (二)
apply() 、call() 和 bind() 都是为了改变函数内部this的指向,this指向他们的第一个参数,当第一个参数为null、undefined的时候,默认指向window;
apply() 的第二个参数是一个参数数组,call() 的参数要全部列举出来;
apply() 、call()调用后马上执行;
bind() 会创建一个新函数,不会马上执行, 可以传给其他函数,在某个适当的时机再调用。
第二个及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
原型链继承
核心:拿父类实例来充当子类原型对象:Child.prototype = new Parent();
优点:简单,易于实现
缺点:
具体实现:
function Parent() {
this.name = ['super'];
this.reName = function () {
this.name.push('super111');
}
}
function Child() {
}
Child.prototype = new Parent(); //核心
var child1 = new Child();
var child2 = new Child();
child1.reName();
console.log(child1.name, child2.name);
// [ 'super', 'super111' ] [ 'super', 'super111' ], 可以看到子类的实例属性皆来自于父类的一个实例,即子类共享了同一个实例
console.log(child1.reName === child2.reName) // true, 共享了父类的方法
借用构造函数
function Child() {
Parent.call(this);
}
优点:
缺点:父类的方法没有被共享,无法实现函数复用,造成内存浪费。
具体实现:
function Parent() {
this.name = ['super'];
this.reName = function () {
this.name.push('super111');
}
}
function Child() {
Parent.call(this); //核心
}
var child1 = new Child();
var child2 = new Child();
child1.reName();
console.log(child1.name, child2.name)
// [ 'super', 'super111' ] [ 'super1' ], 子实例的属性都是相互独立的
console.log(child1.reName === child2.reName) // false, 实例方法也是独立的,没有共享同一个方法
组合式继承(最常用)
思路:使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又保证每个实例都有它自己的属性。
核心:把实例函数都放在原型对象上,以实现函数复用。同时还要保留借用构造函数方式的优点,通过 Parent.call(this)
,继承父类的基本属性和引用属性并保留能传参的优点;通过 Child.prototype = new Parent()
,继承父类函数,实现函数复用。
优点:
缺点:父类构造函数被调用两次,子类实例的属性存在两份,造成内存浪费。
具体实现:
function Parent() {
this.name = ['super'];
}
Parent.prototype.reName = function() {
this.name.push('super111');
}
function Child() {
Parent.call(this); // 生成子类的实例属性(不包括父对象的方法)
}
Child.prototype = new Parent(); // 继承父类的属性和方法
var child1 = new Child();
var child2 = new Child();
child1.reName();
console.log(child1.name, child2.name);
// [ 'super', 'super111' ] [ 'super' ], 子类实例不会相互影响
console.log(child1.reName === child2.reName); //true, 共享了父类的方法
寄生继承
优点:子类都有各自的实例不会相互影响,且共享了父类的方法。
具体实现:
function Parent() {
this.name = ['super'];
}
Parent.prototype.reName = function() {
this.name.push('super111');
}
function Child() {
Parent.call(this); // 生成子类的实例属性(不包括父对象的方法)
}
Child.prototype = Object.create(Parent.prototype)
// 该方法会使用指定的原型对象及其属性去创建一个新的对象
var child1 = new Child();
var child2 = new Child();
console.log(child1.name, child2.name);
// [ 'super', 'super111' ] [ 'super' ], 子类实例不会相互影响
console.log(child1.reName === child2.reName); //true, 共享了父类的方法
ES6 class
和寄生继承实现的效果一致
具体实现:
class Parent {
constructor() {
this.name = ['super'];
}
reName() {
this.name.push('super111');
}
}
class Child extends Parent {
constructor() {
Parent();
}
}
var child1 = new Child();
var child2 = new Child();
child1.reName();
console.log(child1.name, child2.name);
// [ 'super', 'super111' ] [ 'super' ], 子类实例不会相互影响
console.log(child1.reName === child2.reName); //true, 共享了父类的方法
###关于new操作符(构造函数)
var obj=new Object(); // var obj = {};
obj.__proto__= Func.prototype;
var result =Func.call(obj);
if (typeof(result) == "object"){
func=result;
}
else{
func=obj;;
}
更多内容:https://www.cnblogs.com/wangyingblog/p/5583825.html
内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。
引起内存泄漏的操作
全局变量。
被遗忘的计时器或回调。
setTimeout 的第一个参数使用字符串而非函数。
闭包、控制台日志、循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)。
当页面中元素被移除或替换时,若元素绑定的事件仍没被移除,在IE中不会作出恰当处理,此时要先手工移除事件,不然会存在内存泄露。
浅拷贝:浅拷贝是拷贝引用,拷贝后的引用都是指向同一个对象的实例,彼此之间的操作会互相影响。
浅拷贝分两种情况,拷贝源对象的引用 和 源对象拷贝实例。
var a = {c:1};
var b = a;
console.log(a === b); // 输出true。
a.c = 2;
console.log(b.c); // 输出 2
- 源对象拷贝实例
外层源对象是拷贝实例,如果其属性元素为复杂数据类型(类型为Object,Array的属性)时,内层元素拷贝引用。
对源对象直接操作,不影响另外一个对象,但是对其属性操作时候,会改变两外一个对象的属性的值。
常见方法:Object.assign()
```
/* 数组复杂类型属性浅拷贝 */
var a = [{c:1}, {d:2}];
var b = a.slice();
console.log(a === b); // false
a[0].c = 3;
console.log(b[0].c); // 3
/* 对象浅拷贝 */
var obj = {
a: {
a: "hello",
b: 21
}
};
var initalObj = Object.assign({}, obj);
initalObj.a.a = "changed";
console.log(obj.a.a); // "changed"
/* 对象复杂类型属性浅拷贝 */
function simpleClone(initalObj) {
var obj = {};
for ( var i in initalObj) {
obj[i] = initalObj[i];
}
return obj;
}
```
深拷贝:在堆中重新分配内存,并且把源对象所有属性都进行新建拷贝,以保证深拷贝的对象的引用图不包含任何原有对象或对象图上的任何对象,拷贝后的对象与原来的对象是完全隔离,互不影响,包括其内部的元素互不干扰。
常见方法有JSON.parse()
,JSON.stringify()
,lodash的_.cloneDeep
。
/* 数组的深拷贝 */
/* 方法一:for循环 */
var arr = [1,2,3,4,5];
var arr2 = copyArr(arr);
function copyArr(arr) {
let res = [];
for (let i = 0; i < arr.length; i++) {
res.push(arr[i]);
}
return res;
}
/* 方法二:slice() */
var arr = [1,2,3,4,5]
var arr2 = arr.slice(0);
arr[2] = 5;
console.log(arr); //[1,2,5,4,5]
console.log(arr2); //[1,2,3,4,5]
/* 方法三:concat() */
var arr = [1,2,3,4,5];
var arr2 = arr.concat();
arr[2] = 5;
console.log(arr); //[1,2,5,4,5]
console.log(arr2); //[1,2,3,4,5]
/* 方法四:ES6扩展运算符 */
var arr = [1,2,3,4,5]
var [ ...arr2 ] = arr
arr[2] = 5
console.log(arr) // [1, 2, 5, 4, 5]
console.log(arr2) // [1, 2, 3, 4, 5]
/* 对象的深拷贝 */
/* 方法一:JSON.parse() & JSON.stringify()
这种方法只适用于纯数据json对象的深拷贝,会忽略值为function以及undefied的字段,而且对date类型的支持也不太友好;只能克隆原始对象自身的值,不能克隆它继承的值。
*/
function (obj) {
return JSON.parse(JSON.stringify(obj));
}
/* 方法二 */
function deepClone(obj) {
if (obj === null) return null
var result={};
for (var key in obj) {
if (obj[key].constructor === Date) {
result[key] = new Date(obj[key]);
} else if (obj[key].constructor === RegExp) {
result[key] = new RegExp(obj[key]);
} else{
result[key] = typeof obj[key]==='object' ? deepClone(obj[key]): obj[key];
}
}
return result;
}
/* 方法三:扩展运算符 */
var obj = {
name: 'FungLeo',
sex: 'man',
old: '18'
}
var { ...obj2 } = obj
obj.old = '22'
console.log(obj)
console.log(obj2)
更多内容:http://www.cnblogs.com/wangyulue/articles/7684515.html
同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。
当指定的事情完成时,Event Table会将这个函数移入Event Queue。
主线程内的任务执行完毕为空,会去Event Queue读取对应的函数,进入主线程执行。
js引擎存在monitoring process进程,会持续不断的检查主线程执行栈是否为空,一旦为空,就会去Event Queue那里检查是否有等待被调用的函数。
上述过程会不断重复,也就是常说的Event Loop(事件循环)。事件循环是 js 实现异步的一种方法,也是 js 的执行机制。
例子:
let data = [];
$.ajax({
url:www.javascript.com,
data:data,
success:() => {
console.log('发送成功!');
}
})
console.log('代码执行结束');
// ajax 进入Event Table,注册回调函数 success。
// 执行 console.log('代码执行结束')。
// ajax 事件完成,回调函数 success 进入Event Queue。
// 主线程从Event Queue读取回调函数success并执行。
setTimeout(() => {
task()
},3000)
sleep(10000000)
// task()进入Event Table并注册,计时开始。
// 执行sleep函数,计时仍在继续。
// 3秒到了,计时事件 timeout 完成,task()进入Event Queue,但是sleep还没执行完,只好等着。
// sleep执行完,task() 从 Event Queue 进入了主线程执行。
macro-task & micro-task
macro-task(宏任务):包括整体代码script,setTimeout,setInterval
micro-task(微任务):Promise,process.nextTick
进入整体代码(宏任务)后,开始第一次循环。接着执行所有的微任务。然后再次从宏任务开始,找到其中一个任务队列执行完毕,再执行所有的微任务。
setTimeout(fn,0)
的含义是,指定某个任务在主线程最早可得的空闲时间执行,只要主线程执行栈内的同步任务全部执行完成,栈为空就马上执行。
(即便主线程为空,0毫秒实际上也是达不到的。根据HTML的标准,最低是4毫秒。)
对于执行顺序来说,setInterval
会每隔指定的时间将注册的函数置入Event Queue,如果前面的任务耗时太久,那么同样需要等待。(一旦 setInterval 的回调函数 fn
执行时间超过了延迟时间ms,那么就完全看不出来有时间间隔了。)
参考自:这一次,彻底弄懂 JavaScript 执行机制
作用域链的作用是保证执行环境里有权访问的变量和函数是有序的,作用域链的变量只能向上访问,变量访问到window对象即被终止,作用域链向下访问变量是不被允许的。
创建XMLHttpRequest对象
创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息
设置响应HTTP请求状态变化的函数
发送HTTP请求
获取异步调用返回的数据
var request;
// 新建XMLHttpRequest对象
if (window.XMLHttpRequest) {
//现代主流浏览器
request = new XMLHttpRequest();
} else {
// 针对浏览器,比如IE5或IE6
request = new ActiveXObject("Microsoft.XMLHTTP");
}
request.open(method,url);
// 可以通过request.setRequestHeader()设置请求头
request.onreadystatechange = function () { // 状态发生变化时,函数被回调
if (request.readyState === 4) { // 成功完成
// 判断响应结果:
if (request.status === 200) {
// 成功,通过responseText拿到响应的文本
return success(request.responseText);
} else {
// 失败,根据响应码判断失败原因:
return fail(request.status);
}
} else {
// HTTP请求还在继续...
}
}
// 发送请求:
request.send();
参考自:实现AJAX的基本步骤
DOM事件流:
事件捕捉阶段:事件开始由顶层对象触发,然后逐级向下传播,直到目标的元素;
处于目标阶段:处在绑定事件的元素上;
事件冒泡阶段:事件由具体的元素先接收,然后逐级向上传播,直到不具体的元素;
阻止 冒泡/捕获:
event.stopPropagation()
IE :event.cancelBubble=true
阻止默认事件:event.preventDefault()
return false
:同时阻止事件冒泡和默认事件。
stopImmediatePropagation() :阻止事件冒泡或捕获并且阻止相同事件的其他侦听器被调用。
因为事件具有冒泡机制,因此我们可以利用冒泡的原理,把事件加到父级上,触发执行效果。这样做的好处是提高性能。
最重要的是通过 event.target.nodeName
判断子元素。
###JS事件:target & currentTarget
target
在事件流的 目标阶段。currentTarget
在事件流的 捕获,目标及冒泡阶段。传统方式:在JavaScript代码中绑定,获取DOM元素 。
element.onclick = function(e){
// ...
};
优点:
缺点:
只会在事件冒泡中运行,而非捕获和冒泡。
一个元素一次只能绑定一个事件处理函数。新绑定的事件处理函数会覆盖旧的事件处理函数。
事件对象参数(e)仅非IE浏览器可用
绑定事件监听函数:W3C方式。
element.addEventListener('click', function(e){
// ...
}, false);
优点:
该方法同时支持事件处理的 捕获和冒泡 阶段。事件阶段取决于 addEventListener
最后的参数设置:false (冒泡) 或 true (捕获)。
在事件处理函数内部,this关键字引用当前元素。
事件对象总是可以通过处理函数的第一个参数(e)捕获。
可以为同一个元素绑定你所希望的多个事件,同时并不会覆盖先前绑定的事件。
缺点:
绑定事件监听函数:IE方式。
element.attachEvent('onclick', function(){
// ...
});
优点:
缺点:
IE仅支持事件捕获的 冒泡 阶段。
事件监听函数内的this关键字指向了window对象,而不是当前元素。
事件对象仅存在与window.event参数中
事件必须以ontype的形式命名,比如,onclick而非click
仅IE可用。你必须在非IE浏览器中使用W3C的addEventListener 。
在DOM元素中直接绑定: JavaScript 提供两种相等运算符:(相等运算符)和=(严格相等运算符)。 如果两个值不是同一类型,严格相等运算符(=)直接返回false,而相等运算符()会将它们转化成同一个类型,再用严格相等运算符进行比较。 严格相等运算符的算法如下: PS:注意,对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值。 相等运算符的算法如下: 对象(这里指广义的对象,包括数组和函数)与原始类型的值比较时,对象转化成原始类型的值,再进行比较。 两个复合类型(对象、数组、函数)的数据比较时,不是比较它们的值是否相等,而是比较它们是否指向同一个对象。(同===) 参考自:http://javascript.ruanyifeng.com/grammar/operator.html#toc6 ** Object.is 不会做类型转换。 new关键字必须是以function定义的。 Polyfill: ( 箭头函数 。 新增**模板字符串**(为JavaScript提供了简单的字符串插值功能)。 for-of(用来遍历数据,例如数组中的值。) arguments对象可被不定参数和默认参数完美代替。 增加了 let 和 const 命令,用来声明变量。增加了块级作用域。let命令实际上就增加了块级作用域。ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。 ES6将Promise对象纳入规范,提供了原生的Promise对象。 引入 module模块 的概念。 ####箭头函数 箭头函数没有 没有arguments对象,更不能通过arguments对象访问传入参数。只能使用显式命名或其他ES6新特性来完成。 由于箭头函数没有自己的this,不能用call()、apply()、bind()这些方法去改变this的指向。 箭头函数不能当作 generators 使用,使用 yield 会产生错误。 ####Promise对象 ES6 原生提供了 Promise 对象,用来传递异步操作的消息。它代表了某个未来才会知道结果的事件(通常是一个异步操作),并且这个事件提供统一的 API,各种异步操作都可以用同样的方法进行处理。 特点: 对象的状态不受外界影响。Promise 对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称 Fulfilled)和 Rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。 pending 状态的 Promise 对象最终只能有两种状态,一种是fulfilled,一种是rejected。一旦异步任务操作成功,pending状态立马变成fulfilled。反之操作失败,Promise由pending状态变成rejected。而且这种最终的状态都不会再改变。 语法: executor是一个带有 resolve 和 reject 两个参数的函数 。executor 函数在Promise的创建时就立即执行。而异步任务一旦完成,就调用 Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。 then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。 如果没有使用catch方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。 建议总是使用catch方法,而不使用then方法的第二个参数。 因为 Promise.prototype.then 和 Promise.prototype.catch 方法返回 promise 对象自身, 所以它们可以被链式调用。 p1、p2、p3都是Promise对象的实例,如果不是,就会调用Promise.resolve方法将参数转为Promise。 优点:代码层次清晰,便于理解,更加容易维护。 缺点: 无法取消 Promise,一旦新建它就会立即执行,无法中途取消。 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。 当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。 实现Promise 深入 Promise(一)——Promise 实现详解 参考自:深入 Promise(一)——Promise 实现详解 async 表示这是一个async函数,await只能用在这个函数里面。 await 表示在这里等待promise返回结果了,再继续执行。(不必写 async函数的返回值是 Promise 对象。 await 后面跟着的应该是一个promise对象。(其他非promise对象会自动转换成状态为resolve的Promise并立即执行。) 可以直接用标准的try catch语法捕捉错误。 如果await后面的Promise状态转变成了reject,那么整个 async 函数都会停止执行,并且抛出相应的错误。 当一个 async 函数中有多个 await 命令时,如果不想因为一个出错而导致其与的都无法执行,应将 await 放在 如果希望多个请求并发执行,可以使用 更多内容:async 函数 回调函数是异步的,每一层的回调函数都需要依赖上一层的回调执行完,形成了层层嵌套的关系,不利于阅读和维护。 解决方案: 拆解为单个的 事件发布 / 监听模式。监听某一事件,当事件发生时,进行相应回调操作;另一方面,当某些操作完成后,通过发布事件触发回调。这样就可以将原本捆绑在一起的代码解耦。(需要一个事件发布/监听的库,可使用node原生的 使用 Promise。 使用 async/await。 使用 generator。 定义了一个func的生成器函数,调用之后返回了一个迭代器对象(foo),可以用yield返回多次。 调用 generator函数有一个最大的特点,可以在内部执行的过程中交出程序的控制权,yield相当于起到了一个暂停的作用;而当一定情况下,外部又将控制权再移交回来。 yield和yield* 只能在generator函数内部使用,一般的函数内使用会报错。匿名函数内部不能使用yield关键字。 next()方法可以传参,参数值有注入的功能,可改变上一个yield的返回值。 generator不会自动保存相应变量值,需要手动指定。 参考自:ES6笔记(5)-- Generator生成器函数 ###DOM操作 创建新节点 createDocumentFragment() //创建一个DOM片段 createElement() //创建一个具体的元素 createTextNode() //创建一个文本节点 添加、移除、替换、插入 查找 原理:攻击者往Web页面里插入恶意 JS 代码,当用户浏览该页时,嵌入的 JS 代码会被执行,从而达到恶意攻击用户的目的。 攻击类型: 存储型XSS:持久化,XSS 代码存储在服务器中,用户访问该页面的时候触发代码执行。(经过后端,经过数据库。) 反射型XSS:非持久化,需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面。(经过后端,不经过数据库。) 发出请求时,XSS 代码出现在URL中,作为输入提交到服务器端,服务器端解析后响应,XSS 随响应内容一起返回给浏览器,最后浏览器解析执行 XSS 代码。 解决方法: ####CSRF 跨站请求伪造 攻击者盗用用户身份,以用户的名义发送恶意请求,完成攻击者所期望的操作,对服务器来说这个请求是完全合法的。 过程: 用户打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A; 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A; 用户未退出网站A之前,在同一浏览器中,打开一个网页访问网站B; 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A; 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以用户的权限处理该请求,导致来自网站B的恶意代码被执行。 解决方法: 使用验证码:每一个重要的post提交页面,使用一个验证码,因为第三方网站是无法获得验证码的。 在请求中添加 token 并验证:服务器随机产生tooken,然后以tooken为密钥产生一段密文,把token和密文都随cookie交给前端,前端发起请求时把密文和token交给后端,后端对token和密文进行验证,看token能不能生成同样的密文,这样即使黑客拿到了token也无法拿到密文。 验证 HTTP Referer 字段。Referer记录了请求的来源地址,服务器要做的是验证这个来源地址是否合法。 涉及敏感操作的请求改为POST请求。 XSS与CSRF的区别 SQL注入 点击劫持 DDOS攻击 DNS劫持 标记清除 引用计数 defer 并行加载js文件,会按照页面上script标签的顺序执行。 async 并行加载js文件,下载完成立即执行,不会按照页面上script标签的顺序执行。 js代码在加载完后,是立即执行的。执行时会阻塞页面后续的内容(包括页面的渲染、其它资源的下载)。 减少 JavaScript 对性能的影响的方法: 了解浏览器如何进行加载,可以在引用外部样式文件,外部js时,将他们放到合适的位置,使浏览器以最快的速度将文件加载完毕。 了解浏览器如何进行解析,可以在构建 DOM 结构,组织 css 选择器时,选择最优的写法,提高浏览器的解析速率。 了解浏览器如何进行渲染,在设置元素属性,编写 js 文件时,可以减少“reflow”、“repaint”的消耗。 当用户输入一个URL的时候,浏览器就会发送一个请求,请求URL对应的资源。 浏览器请求到 HTML 代码后,将HTML解析成一个DOM树,HTML中的每个 DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。 在生成DOM的最开始阶段(应该是 Bytes → characters 后),并行发起 css、图片、js的请求,无论他们是否在HEAD里。在构建 DOM 树的时候,遇到 JS 和 CSS 元素,HTML解析器就换将控制权转让给 JS 解析器或者是 CSS 解析器。 CSS文件下载完成,开始构建 CSSOM。浏览器把所有样式(用户定义的CSS和用户代理)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式,而FF会去掉 _ 开头的样式。 DOM 和 CSSOM 都是以 DOM Tree 和样式结构体组合后构建 Render tree , render tree能识别样式,render tree中每个 Node 都有自己的 style,而且 Render tree不包含隐藏的节点 (比如 根据CSS2的标准,render tree中的每个节点都称为Box (Box dimensions),理解页面元素为一个具有填充、边距、边框和位置的盒子。 上述过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的 html 都解析完成之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。 Render tree构建完毕后,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作为Layout,就是计算出每个节点在屏幕中的位置。输出的是一棵layout树。 最后浏览器根据这棵layout树,将页面渲染到屏幕上去。 参考自:前端面试-浏览器渲染机制 当render tree中的一部分(或全部)因为元素的规模尺寸,布局,隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。 注意:回流必将引起重绘,而重绘不一定会引起回流。页面若发生回流则需要付出很高的代价。 回流何时发生: 添加或者删除可见的DOM元素; 元素位置改变; 元素尺寸改变——边距、填充、边框、宽度和高度; 内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变; 页面渲染初始化; 浏览器窗口尺寸改变——resize事件发生时。 如何减少回流、重绘 减少回流、重绘其实就是需要减少对render tree的操作(合并多次多DOM和样式的修改),并减少对一些style信息的请求,尽量利用好浏览器的优化策略。具体方法有: el.className += " className1"; Ajax的本质是使用XMLHttpRequest对象来请求数据。 fetch 是全局量 window 的一个方法,它的主要特点有: 从 fetch()返回的 Promise 将不会拒绝HTTP错误状态, 即使响应是一个 HTTP 404 或 500。相反,它会正常解决 (其中ok状态设置为false), 并且仅在网络故障时或任何阻止请求完成时,它才会拒绝。 默认情况下, fetch在服务端不会发送或接收任何 cookies, 如果站点依赖于维护一个用户会话,则导致未经认证的请求(要发送 cookies,必须发送凭据头)。 fetch采用了Promise的异步处理机制,使用比ajax更加简单。 只要协议、域名、端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作。 解决方法 由于同源策略的限制,XmlHttpRequest 只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过 script 标签 实现跨域请求,然后在服务器端输出 JSON 数据并执行回调函数,从而解决了跨域的数据请求。 原理: 首先在客户端注册一个 callback,然后把 callback 的名字传给服务器。 服务器先生成 json 数据,然后以 javascript 语法的方式,生成一个function , function 名字就是传递上来的参数 jsonp。最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。 客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里。(动态执行回调函数) 优点:兼容性好,简单易用,支持浏览器与服务器双向通信。 缺点: 具体实现: 注意:JavaScript 的链接,必须在 function 的下面。 CORS: 服务器端对于CORS的支持,主要就是通过设置 通过修改 使用 使用HTML5中新引进的 回调的次数通常是每秒60次,但大多数浏览器通常匹配 W3C 所建议的刷新频率。在大多数浏览器里,当运行在后台标签页或者隐藏的 回调函数会被传入一个参数,DOMHighResTimeStamp,指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。 返回值为一个long整数,可以传这个值给 requestAnimationFrame的原理是以递归的形式来实现动画 ,与 setTimeout/setInterval 类似 。 使用 setTimeout/setInterval 制作动画有以下缺点: 相比之下,requestAnimationFrame 有以下优点: 动画更加流畅,经由浏览器优化(页面刷新前执行一次); 窗口未激活时,动画暂停,有效节省CPU开销; 省电,对移动端很友好 参考自:HTML5优化Web动画——requestAnimationFrame 单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。 以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的。 编写测试的原则是,一次只测一种情况,且测试代码要非常简单。 mocha是JavaScript的一种单元测试框架,既可以在浏览器环境下运行,也可以在Node.js环境下运行。 mocha的特点主要有: 既可以测试简单的JavaScript函数,又可以测试异步代码,因为异步是JavaScript的特性之一; 可以自动运行所有测试,也可以只运行特定的测试; 可以支持before、after、beforeEach 和 afterEach 来编写初始化代码。 运行方式 如果要测试异步函数,我们要传入的函数需要带一个参数,通常命名为done。测试异步函数需要在函数内部手动调用done()表示测试成功,done(err)表示测试出错。 更多内容:https://www.cnblogs.com/Leo_wl/p/5734889.html React起源于Facebook的内部项目,为了解决页面需要不断重新加载导致速度很慢的问题,每次更改时不必不断更新编码,而是把它们转存到 DOM中,每次只渲染最近的更改。 Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存,利用 dom diff 算法避免了没有必要的dom操作,从而提高性能。 更多内容:深度剖析:如何实现一个 Virtual DOM 算法 两个树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题,React diff 算法的复杂度就可以达到 O(n)。 React的diff策略: 忽略 Web UI 中 DOM 节点跨层级移动; 拥有相同类型的两个组件产生的DOM结构也是相似的,不同类型的两个组件产生的DOM结构则不近相同; 对于同一层级的一组子节点,通过分配唯一的id进行区分(key值); 在Web UI的场景下,基于以上三点,React对tree diff、component diff、element diff进行优化,将普适diff的复杂度降低到一个数量级,保证了整体UI界面的构建性能。 tree diff 基于策略一,React的做法是把 dom tree 分层级,对于两个 dom tree 只比较同一层次的节点,忽略 dom 中节点跨层级移动操作,只对同一个父节点下的所有的子节点进行比较。如果对比发现该父节点不存在则直接删除该节点下所有子节点,不会做进一步比较,这样只需要对dom tree 进行一次遍历就完成了两个 tree 的比较。 e.g. dom 节点跨层级移动处理 所以,保证稳定 dom 结构有利于提升性能,不建议频繁真正的移除或者添加节点。 component diff 同一类型组件遵从 tree diff 比较 v-dom 树; 不同类型组件,先将该组件归类为 dirty component,替换整个组件下的所有子节点; 同一类型组件 Virtual Dom 没有变化,React允许开发者使用 如上图,当组件D → 组件G时,diff 判断为不同类型的组件,虽然它们的结构相似甚至一样,diff 仍然不会比较二者结构,会直接销毁 D 及其子节点,然后新建一个 G 相关的子 tree,这显然会影响性能,官方虽然认定这种情况极少出现,但是开发中的这种现象造成的影响是非常大的。 所以,对于同一类型组件合理使用 React 通过设置唯一 key 的策略,对 element diff 进行算法优化。 对于同一层级的element节点,diff 提供了以下3种节点操作: INSERT_MARKUP 插入节点:对全新节点执行节点插入操作; MOVE_EXISING 移动节点:组件新集合中有组件旧集合中的类型,且 element 可更新,即组件调用了 receiveComponent,这时可以复用之前的 dom,执行 dom 移动操作; REMOVE_NODE 移除节点:此时有两种情况:组件新集合中有组件旧集合中的类型,但对应的element不可更新;旧组件不在新集合里面,这两种情况需要执行节点删除操作。 所以,在开发过程中,同层级的节点添加唯一key值可以极大提升性能,尽量减少将最后一个节点移动到列表首部的操作,当节点达到一定的数量以后或者操作过于频繁,在一定程度上会影响React的渲染性能。比如大量节点拖拽排序的问题。 参考自:浅谈React中的diff [外链图片转存失败(img-gahBxJKZ-1562896477123)(https://huruji.github.io/FE-Interview/image/react1.png)] element就是React实现界面内容的最小单元,一个 element 是一个普通对象,它描述一个组件实例和它所要求的属性,或者一个DOM节点和它所要求的属性。它仅仅包含以下有关信息 : 组件就是一个方法或者一个类,可以接受一定的输入,之后返回一个React Element。 合并操作 调用 component 的 这里的“合并操作”是指, 在一个事件循环当中, DOM 只会被更新一次。 子树渲染 调用 选择性子树渲染: React事件系统有两类:合成事件和原生事件。 React 为了解决跨平台,兼容性问题,基于 Virtual DOM 实现了一个SyntheticEvent(合成事件)层处理事件,代理了原生的事件。 React将所有的事件都绑定在整个文档的根节点(document)上,(当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象。) 我们所定义的事件处理器会接收到一个 SyntheticEvent 对象的实例,同样支持事件的冒泡机制,我们可以使用 **SyntheticEvent ** 事件注册 - 注册事件回调函数。 组件挂载时,React通过 mountCompoent 内部的 事件存储 - 存储事件回调函数,以便触发时进行回调。 存储的入口是 事件分发 根据事件类型的不同,合成不同的跨浏览器的SyntheticEvent对象的实例。 因为事件回调函数执行后可能导致DOM结构的变化,React先将当前的结构以数组的形式存储起来。 事件处理 尽管整个事件系统由React管理,但是其API和使用方法与原生事件一致。这种机制确保了跨浏览器的一致性:在所有浏览器(IE8及以上)都可以使用符合W3C标准的API,包括stopPropagation(),preventDefault()等等。 可以在React中使用原生事件,在 DOM 事件冒泡到 document 上才会触发 React的合成事件,所以 React 合成事件对象的 每一个方法会被wrapper所包裹,必须用perform调用,在被包裹方法前后分别执行 state必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变,都可以从state的变化中反映出来;同时,state还必须是代表一个组件UI呈现的最小状态集,即state中的所有状态都是用于反映组件UI的变化,没有任何多余的状态,也不需要通过其他状态计算而来的中间状态。 组件中用到的一个变量作为组件state的依据: 如不满足上述条件,这个变量更适合定义为组件的一个普通属性,例如组件中用到的定时器,就应该直接定义为this.timer,而不是this.state.timer。 当存在多个组件共同依赖一个状态时,一般的做法是状态上移,将这个状态放到这几个组件的公共父组件中。 State 与 Props 区别: 不能直接修改State。直接修改state,组件并不会re-render。正确的修改方式是使用setState()。 setState 第一个参数为对象时,在同一个方法中多次 第一个参数为函数时,在同一个方法中多次 setState的第二个参数是一个回调函数,在setState的异步操作结束并且组件已经重新渲染的时候执行。我们可以通过这个回调来拿到更新的state的值。 State 的更新是异步的。 调用setState,组件的state并不会立即改变,setState只是把要修改的状态放入一个队列中,React会优化真正的执行时机,并且React会出于性能原因,可能会将多次setState的状态修改合并成一次状态修改。所以不要依赖当前的State,计算下个State。当真正执行状态修改时,依赖的this.state并不能保证是最新的State,因为React会把多次State的修改合并成一次,这时,this.state将还是这几次State修改前的State。另外需要注意的是,同样不能依赖当前的Props计算下个状态,因为Props一般也是从父组件的State中获取,依然无法确定在组件状态更新时的值。 可以使用另一个接收一个函数作为参数的setState,这个函数有两个参数,第一个是当前最新状态(本次组件状态修改后的状态)的前一个状态preState(本次组件状态修改前的状态),第二个参数是当前最新的属性props。 State 的更新是一个浅合并(Shallow Merge)的过程。 React官方建议把State当作是不可变对象,一方面是因为不可变对象方便管理和调试;另一方面是出于性能考虑,当对象组件状态都是不可变对象时,我们在组件的 当State中的某个状态发生变化,我们应该重新创建这个状态对象,而不是直接修改原来的状态。创建新的状态对象的关键是,避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法。 参考自:深入理解React 组件状态(State) setState 只在合成事件和钩子函数(生命周期函数)中是**“异步”更新的,在原生事件**(如 setState 的“异步”是因为合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。 setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。 React中利用队列机制实现了 setState 的异步更新,避免频繁地重复更新state。队列是数组形式实现的。 判断当前组件对象的state更新队列是否存在,如果存在则将新的state值加入队列;如果不存在,则创建该对象的更新队列,队列是以数组形式存在的。 判断是否正在进行批量更新,如果正在进行批量更新,则先将 Component 存入 dirtyComponent 数组中,否则循环遍历所有的 dirtyComponents,更新组件。若setState方法传入了回调函数则将回调函数存入callbackQueue队列。 合并state。在一个生命周期内,在 如果更新队列为null,那么返回原来的state; 如果更新队列有一个更新,那么返回更新值; 如果更新队列有多个更新,那么通过for循环将它们合并。 不能在 参考自:你真的理解setState吗? React源码解析(三):详解事务与更新队列 作用:减少不必要的渲染次数,提高性能,节省代码。 PureComponent 改变了生命周期方法 shouldComponentUpdate,并且它会自动检查组件是否需要重新渲染。当props或者state改变时,PureComponent 将对 props 和 state 进行浅比较,数组和对象比较引用是否相同,嵌套对象和数组是不会被比较的。只有 PureComponent 检测到 state 或者 props 发生变化时,PureComponent 才会调用 render 方法。 易变数据不能使用一个引用!利用 如果一个 PureComponent 的 state 或 props 引用了一个新对象,那么这个组件就会被重新渲染。 因为 不要在render的函数中绑定值。在render的函数中绑定值会导致每次父组件render方法被调用时,一个新的函数被创建。 不要在render方法里派生数据。在render方法里派生数据,会产生新的引用,造成列表不必要的重新渲染。 纯组件忽略重新渲染时,不仅会影响它本身,还会影响它的所有子元素,所以,使用PureComponent 的最佳情况就是展示组件,它既没有子组件,也没有依赖应用的全局状态。 浅比较 通过 如果key里面是对象的话,有可能出现比较不符合预期的情况,所以浅比较是不适用于嵌套类型的比较的。 参考自: [译]何时使用Component还是PureComponent? 你真的了解浅比较么? React16 以前,对 virtural dom 的更新和渲染是同步的。如果组件层级比较深,相应的堆栈也会很深,长时间占用浏览器主线程,一些类似用户输入、鼠标滚动等操作得不到响应。 React16 用了分片的方式解决上面的问题。把一个任务分成很多小片,当分配给这个小片的时间用尽的时候,就检查任务列表中有没有新的、优先级更高的任务,有就做这个新任务,没有就继续做原来的任务。这种方式被叫做异步渲染(Async Rendering)。维护每一个分片的数据结构,就是Fiber。 Fiber通过对象记录组件上需要做或者已经完成的更新,一个组件可以对应多个Fiber。 Fiber是个链表,有 child 和 sibing 属性,指向第一个子节点和相邻的兄弟节点,从而构成fiber tree。return属性指向其父节点。 每个fiber有一个属性updateQueue指向其对应的更新队列。 每个fiber(当前fiber可以称为current)有一个属性alternate,开始时指向一个自己的clone体,update的变化会先更新到alternate上,当更新完毕,alternate替换current。 在render函数中创建的React Element树在第一次渲染的时候会创建一颗结构一模一样的Fiber节点树。不同的React Element类型对应不同的Fiber节点类型。一个React Element的工作就由它对应的Fiber节点来负责。 一个React Element可以对应不止一个Fiber,因为Fiber在 update 的时候,会从原来的Fiber(我们称为current)clone出一个新的Fiber(我们称为alternate)。两个Fiber diff出的变化(side effect)记录在alternate上。所以一个组件在更新时最多会有两个Fiber与其对应,在更新结束后alternate会取代之前的current的成为新的current节点。 更新任务分成两个阶段,Reconciliation Phase和Commit Phase。Reconciliation Phase的任务干的事情是,找出要做的更新工作(Diff Fiber Tree),就是一个计算阶段,计算结果可以被缓存,也就可以被打断;Commmit Phase 需要提交所有更新并渲染,为了防止页面抖动,被设置为不能被打断。 新的生命周期钩子: 静态 新的组件生命周期: 初始化 更新阶段 卸载阶段 (允许在render函数中返回节点数组和字符串。) 参考自:React-从源码分析React Fiber工作原理 ref允许我们访问DOM元素,我们通过在组件中指定ref属性,属性值为一个回调函数,这个回调函数接受一个DOM元素或者 react组件 作为参数,当我们不得不要直接访问DOM元素的时候才去使用它,如需要在组件加载完成就立即让组件中的表单有焦点,即触发 focus事件。 在react中我们渲染一个列表的时候,我们需要为每一个列表项指定一个唯一的key,当没有指定key时,会收到一个warning, 如果指定的key不唯一,只会渲染第一个指定唯一的key的那个元素,使用 key 可以使得 DOM diff 更加高效,避免不必要的列表项更新。 相似之处:都是javascript框架,有路由、状态管理、构建工具等、组件式开发。都用到了Virtual DOM。 不同之处: ###Webpack 打包原理 webpack是一个打包模块的机制,把依赖的模块转化成可以代表这些包的静态文件。 webpack就是识别你的 入口文件,识别你的模块依赖,来打包你的代码。 webpack做的就是分析代码,转换代码,编译代码,输出代码。 Webpack中每个模块有一个唯一的id,是从0开始递增的。整个打包后的bundle.js是一个匿名函数自执行。参数则为一个数组。数组的每一项都为个function。function的内容则为每个模块的内容,并按照require的顺序排列。 Webpack的两个最核心的原理分别是: 一切皆模块 按需加载 参考自:webpack打包原理 中间件提供第三方插件的模式,自定义拦截 常见的中间件: redux-logger:提供日志输出 redux-thunk:处理异步操作,让Redux的dispatch方法的参数支持函数。 redux-promise:处理异步操作,让Redux的dispatch方法的参数支持promise。 异步函数里面要dispatch两次,分别表示异步请求开始和异步请求完成。 三大原则: 单一数据源 整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。 State 是只读的(唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。) 这样确保了视图和网络请求都不能直接修改 state。所有的修改都被集中化处理,且严格按照一个接一个的顺序执行。增强了可预测性和可靠性,避免产生副作用。 使用纯函数来执行修改 因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。避免数据突变和副作用。 优点: Provider就是把我们用 redux 创建的 store 传递到内部的其他组件。让内部组件可以享有这个 store 并提供对 state 的更新。 Mobx利用getter和setter来收集组件的数据依赖关系,从而在数据发生变化的时候精确知道哪些组件需要重绘,在界面的规模变大的时候,往往会有很多细粒度更新,虽然响应式设计会有额外的开销,在界面规模大的时候,这种开销是远比对每一个组件做脏检查小的,因此在这种情况下Mobx会很容易得到比Redux更好的性能。而在数据全部发生改变时,基于脏检查的实现会比Mobx这类响应式有更好的性能。 【译】Redux 还是 Mobx,让我来解决你的困惑! Flux 的最大特点,就是数据的“单向流动”。 用户访问 View View 发出用户的 Action Dispatcher 收到 Action,要求 Store 进行相应的更新 Store 更新后,发出一个"change"事件 View 收到"change"事件后,更新页面 其他一些 HTTP 请求方法 ###常见HTTP状态码 常用端口号 HTTP的默认端口号为80,HTTPS的默认端口号为443。 HTTP在传输过程中使用的是明文传输,内容可能被窃取,而且无法验证通信方的身份,还有可能遭遇身份伪装;而HTTPS在应用层和传输层之间增加了ssl协议用来加密内容,因此通过证书验证来验证身份,即使数据被窃取也无法解密,数据的传输更加安全。 HTTPS协议的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。 HTTPS缺点: HTTPS 协议握手阶段比较费时,会使页面的加载时间延长近50%,增加10%到20%的耗电; 增加服务器的成本,HTTPS 要额外计算,要频繁地做加密和解密操作,几乎每一个字节都需要做加解密; HTTPS 连接缓存不如 HTTP 高效,会增加数据开销和功耗; 参考自:HTTP与HTTPS的区别 https://blog.csdn.net/chinawangfei/article/details/56494459 对称加密,也叫密钥加密,是指加密和解密使用的是相同的密钥。 特点: 对称加密强度非常高,一般破解不了。 无法安全地生成和保管密钥。 假如客户端和服务器之间每次会话都使用固定的、相同的密钥加密和解密,会存在很大的安全隐患。 如果有人从客户端获取到了对称密钥,整个内容就不存在安全性了,而且管理海量的客户端密钥也是一件很复杂的事情。 非对称加密,也叫公钥加密,就是指加密和解密使用的是不同的密钥。 特点: 客户端和服务器每次新建会话时都使用非对称密钥交换算法协商出对称密钥,使用这些对称密钥完成应用数据的加解密和验证,整个会话过程中的密钥只在内存中生成和保存,而且每个会话的对称密钥都不相同(除非会话复用),中间者无法窃取。 非对称密钥交换很安全,但同时也是 HTTPS 性能和速度严重降低的“罪魁祸首”。 请求头 响应头: HTTP/2 将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式的编码。 在HTTP/2中,数据流以消息的形式发送,而消息由一个或多个帧组成,帧可以在数据流上乱序发送,然后再根据每个帧首部的流标识符重新组装。二进制分帧是HTTP/2的基石,其他优化都是在这一基础上来实现的。 HTTP1.x 以换行符作为纯文本的分隔符。 HTTP/2是多路复用的,而非有序并阻塞的——客户端和服务器之间只需要建立一个 TCP 连接,即可同时收发多个文件,而且,该连接在相当长的时间周期内保持打开(持久化),以便复用,大幅降低了多个请求的开销。 同域名下所有通信都在单个 TCP 连接上完成,消除了因多个TCP连接而带来的延时和内存消耗。 单个连接可以承载任意数量的双向数据流,可以并行交错的请求和响应,之间互不干扰。 HTTP1.x中,如果想并发多个请求,必须使用多个 TCP 连接,且浏览器为了控制资源,还会对单个域名有6-8的个数限制,如域名链接数已超过限制,会被挂起等待。 HTTP/2 引入了“服务端推送(server push)”的概念,它允许服务端在客户端需要数据之前就主动地将数据发送到客户端缓存中,从而提高性能。 应用可以通过额外的http头部,列出需要服务器推送哪些资源。 服务器可以解析请求的html,推测出客户端接下来需要请求的资源,然后提前向客户端推送。 服务器除了响应客户端的请求外,还可以向客户端额外推送资源。 服务器推送的资源有自己独立的URL, 可以被浏览器缓存,可以达到多页面共享。 资源推送遵守同源策略,服务器不可随便推送第三方资源给客户端。 客户端可以拒绝推送过来的资源。 添加了请求优先级。在HTTP/2中,每个请求都可以带一个 31bit 的优先值,0表示最高优先级, 数值越大优先级越低。有了这个优先值,客户端和服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。 HTTP/1.x没有应用资源优先级,导致重要 TCP 连接的糟糕使用。 HTTP/2提供更多的加密支持。 头部压缩,减少冗余数据,降低开销。 HTTP/2 在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送;请求一发送了所有的头部字段,第二个请求则只需要发送差异数据; 首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新; 每个新的首部键值对要么被追加到当前表的末尾,要么替换表中之前的值。 HTTP每一次通信都会携带一组头部,用于描述这次通信的的资源、浏览器属性、cookie等。 在HTTP 1.x中,这些信息都是以纯文本协议发送的,给每个请求增加了不小的负荷。 参考自:HTTP/2协议–特性扫盲篇 https://www.jianshu.com/p/67c541a421f9 域名解析,查找缓存。 浏览器获得对应的ip地址后,浏览器与远程 Web 服务器通过 TCP 三次握手协商来建立一个 TCP/IP 连接。 TCP/IP 连接建立起来后,浏览器就可以向服务器发送HTTP请求。 服务器处理请求,返回资源 浏览器处理(加载,解析,渲染) 绘制网页 一个完整HTTP请求的过程为: 应用层(HTTP、SMTP、DNS):允许访问OSI环境的手段(应用协议数据单元APDU) 表示层(FTP):对数据进行翻译、加密和压缩(表示协议数据单元PPDU) 会话层:建立、管理和终止会话(会话协议数据单元SPDU) 传输层(TCP、UDP):提供端到端的可靠报文传递和错误恢复(段Segment) 网络层(IP):负责数据包从源到宿的传递和网际互连(包PackeT) 数据链路层:将比特组装成帧和点到点的传递(帧Frame) 物理层:通过媒介传输比特,确定机械及电气规范(比特Bit) TCP 是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能建立起来。 UDP 是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去。 正向代理 正向代理 是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。 做用: 反向代理 反向代理(Reverse Proxy)实际运行方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。 作用: 规范。 团队开发规范 => 模块化开发,组件化开发,git工作流,团队协作。 开发流程 => 系统测试,日志统计,上线部署,性能优化,基础框架。 自动化:开发工具。 Model(模型) - 模型表示业务数据和逻辑,并提供数据给视图。 View(视图) - 视图是用户看到并与之交互的界面。 Controller(控制器) - 控制器接受用户的输入并调用模型和视图去完成用户的需求。 通信是单向的。 优点: MVC的处理过程,首先控制器接收用户的请求,并决定应该调用哪个模型来进行处理,然后模型用业务逻辑来处理用户的请求并返回数据,最后控制器用相应的视图格式化模型返回的数据,并通过表示层呈现给用户。 MVC的关键是实现了视图和模型的分离。MVC模式通过建立一个“发布/订阅”(publish-subscribe)的机制来分离视图和模型。 发布-订阅机制的目标是发布者,它发出通知时并不需知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。 MVC用到了Observer(观察者模式),正是观察者模式实现了发布-订阅(publish-subscribe)机制,实现了视图和模型的分离。 观察者模式 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 Model 代表数据模型,可以在Model中 定义数据修改 和 操作业务逻辑; view 代表 UI组件 。负责将数据模型转换成UI展现出来 ViewModel 是一个同步View和Model的对象。 在ViewModel当中会有一个Binder,View和Model之间数据同步操作交由给Binder处理。只需要在View的模版语法当中,指令式地声明View上的显示的内容是和Model的哪一块数据绑定的。当ViewModel对进行Model更新的时候,Binder会自动把数据更新到View上去,当用户对View进行操作(例如表单输入),Binder也会自动把数据更新到Model上去。这种方式称为双向数据绑定。 用户操作view层,view数据变化会同步到Model,Model数据变化会立即反应到view中。viewModel通过 双向数据绑定 把view层和Model层连接起来。 更多内容:界面之下:还原真实的MV*模式 一个组件应该有以下特征: 可组合:一个组件易于和其它组件一起使用,或者嵌套在另一个组件内部。 可重用:每个组件都是具有独立功能的,它可以被使用在多个 UI 场景。 可维护:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护。 可测试:因为每个组件都是独立的,那么对于各个组件分别测试显然要比对于整个 UI 进行测试容易的多。 优点: 模块化方案: CommonJS 用于服务器端,依赖保存在本地硬盘,读取速度快。 同步加载。模块加载的顺序,按照其在代码中出现的顺序。 同步的模块加载方式不适合在浏览器环境中;不能非阻塞的并行加载多个模块。 语法: require 命令第一次执行的时候,会加载并执行整个脚本,然后在内存中生成此脚本返回的 exports 对象。 AMD,又称异步加载模块(Asynchronous Module Definition) 比较适合浏览器环境。 异步加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。避免网页失去响应。 管理模块之间的依赖性,便于代码的编写和维护。 代表库:RequireJS 语法: define(’[./util.js]’, function(util){ CMD 遇到依赖后只会去下载 JS 文件,并不会执行,而是等到所有被依赖的 JS 脚本都下载完以后,才从头开始执行主逻辑。因此被依赖模块的执行顺序和书写顺序完全一致。 代表库: Sea.js }) ES6 Module 编译时就能确定模块的依赖关系。 在浏览器和服务器端都可以用。 ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。 ES6 模块遇到 import 命令时,不会去执行模块,而是生成一个只读引用,等到脚本真正执行的时候,再根据这个只读引用,到被加载的那个模块里取值。因为是动态引用取值,不会缓存运行的结果。 import 命令具有提升效果,会提升到整个模块的头部,首先执行,所以不能把 import 写在表达式里面。这和 ES 6 模块的概念不符合。 用babel 转换 ES6 模块,最后转换成 requie方式。 ES6 模块与 CommonJS 模块的差异 CommonJS 模块输出的是一个值的拷贝(export对象 ),ES6 模块输出的是值的引用。 CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。 ES6 模块是动态引用,并且不会缓存值。 参考自:前端模块化浅析 页面结构清晰。 有利于SEO,和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息:爬虫依赖于标签来确定上下文和各个关键字的权重。 方便其他设备解析(如屏幕阅读器、盲人阅读器、移动设备),以有意义的方式来渲染网页。 便于团队开发和维护,语义化更具可读性,可以减少差异化。 网站结构尽量简单,容易被“蜘蛛”抓取。 网页代码优化。 文本缩进不要使用特殊符号 搜索引擎会过滤掉 重要内容不要用JS输出,因为“蜘蛛”不认识。 尽量少使用iframe框架,因为“蜘蛛”一般不会读取其中的内容。 js代码如果是操作DOM操作,应尽量放在body结束标签之前,html代码之后。 性能衡量指标: 优化方法: 代码方面: 高频事件消抖、节流。使用 样式文件放在顶部,脚本文件放在底部。 CSS Sprites。 文件:引入方式、位置、文件合并、延迟加载。 图片懒加载。 图层隔离:将那些会变动的元素提升至单独的图层,比如:动画、渐变。 网络方面: 减少http请求,合理设置 HTTP缓存。 使用HTTP/2。 持久连接,使用 keep-alive 或者 WebSocket。 使用浏览器缓存。 CDN 加速。CDN(内容分发网络)的本质仍然是一个缓存,而且将数据缓存在离用户最近的地方,使用户以最快速度获取数据,即所谓网络访问第一跳。 反向代理。 传统代理服务器位于浏览器一侧,代理浏览器将 http 请求发送到互联网上,而反向代理服务器位于网站机房一侧,代理网站 web 服务器接收 http 请求。 把内容缓存在反向代理服务器上加速用户访问速度,当这些动态内容有变化时,通过内部通知机制通知反向代理缓存失效,反向代理会重新加载最新的动态内容再次缓存起来。 此外,反向代理也可以实现负载均衡的功能,而通过负载均衡构建的应用集群可以提高系统总体处理能力,进而改善网站高并发情况下的性能。 考虑 service worker。 参考自:前端性能 ###前端容灾 解决方法: 再请求一次; localstorage 缓冲接口数据; 备份一份静态数据到 CDN; 利用 Service worker 的 caches API 做页面接口缓存。 参考自:前端容灾 函数式编程是一种抽象程度很高的编程范式(如何编写程序的方法论),主要思想是把运算过程尽量写成一系列嵌套的函数调用。 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,允许返回一个函数。 使用纯函数,没有副作用。函数保持独立,所有功能就是返回一个新的值,不修改系统变量。任意一个函数,只要输入是确定的,输出就是确定的。 和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。和过程化编程相比,函数式编程里函数的计算可随时调用。 优点: 代码简洁,开发快速。函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。 自由度高,可以写出很接近自然语言的代码,易于理解。 方便代码管理。函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试和除错,以及模块化组合。 易于“并发编程”。 代码的热升级。函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。所以,可以在运行状态下直接升级代码,不需要重启,也不需要停机。 缺点:由于所有的数据都是不可变的,所以所有的变量在程序运行期间都是一直存在的,非常占用运行资源。 面向对象编程 面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。 封装性:对象则指的是类的实例。将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。 继承性:继承性是子类自动共享父类数据和方法的机制。 多态性:对象根据所接收的消息而做出动作。同一消息被不同的对象接受时可产生完全不同的行动。 优点:提高了程序的灵活性和可维护性,设计出高内聚、低耦合的系统结构。 缺点:面向对象编程以数据为核心,所以在多线程并发编程中,多个线程同时操作数据的时候可能会导致数据修改的不确定性。 面向过程编程 面向过程的程序设计把计算机程序视为一系列的命令集合,即一组函数的顺序执行。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。 ##数据结构 ###栈和队列的区别 栈的插入和删除操作都在一端进行,只允许在表尾一端进行插入和删除,先进后出。 队列的操作在两端进行,只允许在表尾一端进行插入,在表头一端进行删除,先进先出。 git fetch 和 git pull的区别 git pull 相当于是从远程获取最新版本并merge到本地。 git fetch:相当于是从远程获取最新版本到本地,不会自动merge。 git merge 和 git rebase的区别 git merge 操作会生成一个新的节点,之前的提交分开显示。 git rebase 操作不会生成新的节点,是将两个分支融合成一个线性的提交。 git 命令
判断相等
1 === "1" // false
true === "true" // false
1 === 0x1 // true
NaN === NaN // false NaN与任何值都不相等(包括自身)
+0 === -0 // true 正0等于负0
{} === {} // false
[] === [] // false
(function (){} === function (){}) // false
//如果两个变量引用同一个对象,则它们相等
var v1 = {};
var v2 = v1;
v1 === v2 // true
new Date() > new Date() // false
new Date() < new Date() // false
new Date() === new Date() // false
undefined === undefined // true
null === null // true
var v1;
var v2;
v1 === v2 // true
'true' == true // false 等同于 NaN === 1
false == 'false' // false 等同于 0 === NaN
false == '0' // true
'' == '0' // false
0 == '' // true
'\n 123 \t' == 123 // true
' \t\r\n ' == 0 // true
// 因为字符串转为数字时,省略前置和后置的空格
[1] == [1] //false
{a:1} == {a:1} //false
false == null // false
false == undefined // false
0 == null // false
0 == undefined // false
undefined == null // true
Object.is(value1, value2)
**判断两个值是否相同。Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
Object.create() 与 new
Object.create(proto, [propertiesObject])
方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
。
Object.create 则 function和object都可以进行构建。if (typeof Object.create !== "function") {
Object.create = function (proto, propertiesObject) {
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object: ' + proto);
} else if (proto === null) {
throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");
}
if (typeof propertiesObject != 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");
function F() {}
F.prototype = proto;
return new F();
};
propertiesObject
可选。如果没有指定为 undefined,则是要添加到新创建对象的可枚举属性(即其自身定义的属性,而不是其原型链上的枚举属性)对象的属性描述符以及相应的属性名称。这些属性对应Object.defineProperties(obj, props)
的第二个参数。)
ES6
var f = () => "hhh";
f(); // "hhh"
typeof f; // function
f instanceof Function; // true
[[Construct]]
属性和 prototype
属性,和 new一起用会抛出错误。(因此也没有 super 和 new.target
属性)
var promise = new Promise(
/* executor */
function(resolve, reject) {
// ... some code
if (/* 异步操作成功 */){
resolve(value);
} else {
reject(error);
}
});
resolve()
来解决promise,并将异步操作的结果,作为参数传递出去。反之操作失败,就调用 reject()
,并同样将异步操作报出的错误,作为参数传递出去。注意的是,在executor函数运行时抛出任何一个错误,都会导致promise状态变为rejected,这也意味着异步操作失败。promise.then(function(value) {
// success
}, function(error) {
// failure
});
/* 例子一 */
var myFirstPromise = new Promise(function(resolve, reject){
// 当异步代码执行成功时,调用resolve(...), 当异步代码失败时就会调用reject(...)
setTimeout(function(){
resolve("成功!"); //代码正常执行
}, 250);
});
myFirstPromise.then(function(successMessage){
//successMessage的值是上面调用resolve(...)方法传入的值"成功!"
console.log("Yay! " + successMessage); // Yay! 成功!
});
Promise.prototype.catch()
是 .then(null, rejection)
的别名,用于指定发生错误时的回调函数。promise.then(function(data) {
// success
})
.catch(function(err) {
// 处理 Promise对象 和 前一个then方法指定的回调函数 运行时发生的错误
});
function promise(){
return new Promise((resolve,reject)=> setTimeout(() => resolve({ obj: 3 }), 3000)))
}
var pro = promise();
pro.then(result => {
console.log("success:", result);
x = x + 1;
}).catch(error => console.log("error: ", error));
//success: Object {obj: 3}
//error: ReferenceError: x is not defined
//上面.then()方法中异步操作失败也可以换成catch方法
pro().then(result => console.log("success", result)).catch(result=>console.log("failed",result))
//failed Object {obj: 3}
Promise.resolve()
将现有对象转为Promise对象。Promise.reject(reason)
返回的一定是一个rejected状态的Promise对象Promise.all()
方法用于将多个Promise实例,包装成一个新的Promise实例。var p = Promise.all([p1, p2, p3]);
p的状态由p1、p2、p3决定,分成两种情况:当p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数;只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。Promise.race()
var p = Promise.race([p1, p2, p3]);
只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。
function promise(){
return new Promise((resolve,reject)=>{
console.log("carry on"); //Promise实例一经创建便立即执行匿名函数
var timer1 = setTimeout(() => resolve({ obj: 3 }), 3000)
//当执行到timer1意味着异步操作成功,使用resolve函数来解决
var timer2 = setTimeout(() => {
console.log("going on");
reject({ obj: 3 });}, 3000)
})
//当执行完timer1,Promise的状态已经是fulfilled,不管后续发生什么,都不会再改变状态,因此,timer2即使执行也改变不了Promise的状态
}
setTimeout(() => console.log("timeout"), 4000);
promise().then(result => console.log("success",result), error => console.log("failed", error));
console.log("origin");
//carry on
//origin
//success Object {obj: 3}
//going on
//timeout
function INTERNAL() {}
var PENDING = 0;
var FULFILLED = 1;
var REJECTED = 2;
function Promise(resolver) {
if (typeof resolver !== 'function') {
throw new TypeError('resolver must be a function');
}
this.state = PENDING;
this.value = void 0;
this.queue = [];
if (resolver !== INTERNAL) {
safelyResolveThen(this, resolver);
}
}
function safelyResolveThen(self, then) {
// called 控制 resolve 或 reject 只执行一次
var called = false;
try {
then(function (value) {
if (called) {
return;
}
called = true;
doResolve(self, value);
}, function (error) {
if (called) {
return;
}
called = true;
doReject(self, error);
});
} catch (error) {
if (called) {
return;
}
called = true;
doReject(self, error);
}
}
function doResolve(self, value) {
try {
var then = getThen(value);
if (then) {
safelyResolveThen(self, then);
} else {
self.state = FULFILLED;
self.value = value;
self.queue.forEach(function (queueItem) {
queueItem.callFulfilled(value);
});
}
return self;
} catch (error) {
return doReject(self, error);
}
}
function getThen(obj) {
var then = obj && obj.then;
if (obj && (isObject(obj) || isFunction(obj)) && isFunction(then)) {
return function appyThen() {
then.apply(obj, arguments);
};
}
}
function doReject(self, error) {
self.state = REJECTED;
self.value = error;
self.queue.forEach(function (queueItem) {
queueItem.callRejected(error);
});
return self;
}
Promise.prototype.then = function() {}
Promise.prototype.catch = function() {}
Promise.resolve = function() {}
Promise.reject = function() {}
Promise.all = function() {}
Promise.race = function() {}
async await
.then()
,直接可以得到返回值。)try...catch
语句中执行(或者 await 后面的 Promise 对象再跟一个 catch 方法,处理前面可能出现的错误。)。async function errorTest () {
throw new Error('this is an error');
}
// 在 then 的回调中捕获错误
errorTest().then(
resolve => console.log(resolve),
error => console.log(error)
)
// 在 Promise 的 catch 方法中捕获
errorTest().catch(
error => console.log(error)
)
// 在 try...catch 语句中捕获
try{
errorTest()
} catch (error) {
console.log(error)
}
Promise.all()
方法。await Promise.all([])
回调地狱
function
。
events
模块。)
Generator
function* func() {
yield 'one';
yield 'two';
yield* func2(); // 自动遍历该对象
return 'three';
}
function* func2() {
yield 1;
yield 2;
}
var foo = func();
foo.next(); // {value: "one", done: false}
foo.next(); // {value: "two", done: false}
foo.next(); // {value: 1, done: true}
foo.next(); // {value: 2, done: true}
foo.next(); // {value: "three", done: true}
foo.next(); // {value: undefined, done: true}
next()
方法后,函数内执行yield
语句,输出当前的相应值value(一般为yield关键字后面的运算结果)和状态done(迭代器是否遍历完成)。
- appendChild()
- removeChild()
- replaceChild()
- insertBefore() //并没有insertAfter()
- getElementsByTagName() //通过标签名称
- getElementsByName() //通过元素的Name属性的值(IE容错能力较强,
- 会得到一个数组,其中包括id等于name值的)
- getElementById() //通过元素Id,唯一性
常见网站漏洞的原理及解决方法
XSS (Cross Site Scripting)跨站脚本攻击
DNS劫持的话是指在用户请求过程的域名解析中,分析请求的域名,返回假的IP地址,使用户访问的是假的网址。
垃圾回收方法
defer & async
因为浏览器需要一个稳定的 DOM 树结构,而 JS 中有可能直接改变了 DOM 树结构,比如使用 document.write
或 appendChild()
,甚至是直接使用location.href
进行跳转,浏览器为了防止出现 J S修改 DOM 树,需要重新构建 DOM树 的情况,所以阻塞其他的下载和呈现。
(1)使用script标签的 defer 属性;
(2)使用动态创建的script元素来下载并执行代码(创建script,插入到DOM中,加载完毕后callBack)。
浏览器的渲染机制(页面呈现过程)
tag
都是DOM树中的一个节点,根节点就是我们常用的 document对象。DOM树里包含了 所有HTML标签,包括display:none
,还有用 JS动态添加的元素等。Bytes 字节 → characters 字符 → tokens 语义块 → nodes 节点 → object model
这样的方式生成最终的数据。display:none
的节点,还有 head 节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到 render tree中。注意 visibility:hidden
隐藏的元素还是会包含到 render tree中的,因为visibility:hidden
会影响布局(layout),会占有空间。
前端必读:浏览器内部工作原理
回流与重绘
``` // 比较好的写法一
- 让要操作的元素进行”离线处理”,处理完后一起更新 - 使用DocumentFragment进行缓存操作,引发一次回流和重绘; ``` //不好的写法 var p, t; p = document.creatElement('p'); t = document.creatTextNode('fist paragraph'); p.appendChild(t); document.body.appendChild(p); //将引起一次回流
// 比较好的写法二
el.style.cssText += ";
left: " + left + "px;
top: " + top + "px;";
```
p = document.creatElement('p');
t = document.creatTextNode('second paragraph');
p.appendChild(t);
document.body.appendChild(p); //将再引起一次回流
//好的写法
var p, t, frag;
frag = document.creatDocumentFragment();
p = document.creatElement('p');
t = document.creatTextNode('fist paragraph');
p.appendChild(t);
farg.appendChild(p);
p = document.creatElement('p');
t = document.creatTextNode('second paragraph');
p.appendChild(t);
farg.appendChild(p);
document.body.appendChild(frag); //相比前面的方法,这里仅仅引起一次回流。
```
- 将需要多次重排的元素,position属性设为absolute或fixed,这样此元素就脱离了文档流,它的变化不会影响到其他元素。
fetch & ajax
// 链式处理,将异步变为类似单线程的写法:
fetch('/some/url').then( function(response) {
return . //... 执行成功, 第1步...
}).then( function(returnedValue) {
// ... 执行成功, 第2步...
}).catch( function(err) {
// 中途任何地方出错...在此处理
});
跨域
Access-Control-Allow-Origin
来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。document.domain
来跨子域。
- 将子域和主域的document.domain
设为同一个主域。
前提条件:这两个域名必须属于同一个基础域名,而且所用的协议,端口都要一致,否则无法利用 document.domain 进行跨域。window.name
来进行跨域。
name
属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个 window.name 的,每个页面对 window.name 都有读写的权限,window.name 是持久存在一个窗口载入过的所有页面中window.postMessage
方法来跨域传送数据。
requestAnimationFrame()
window.requestAnimationFrame()
方法告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。重绘之前调用。
里时,requestAnimationFrame() 会暂停调用以提升性能和电池寿命。window.cancelAnimationFrame()
以取消回调函数。
单元测试
mocha
import { parseUrl } from '../../../utils/common'
import { expect } from 'chai'
// describe可以任意嵌套,以便把相关测试看成一组测试。
// 每个it("name", function() {...})就代表一个测试。
describe('parseUrl 测试集', () => {
it('给定一个 URL,将其中的参数解析出来,并输出为对象', () => {
expect(parseUrl('http://xz.it.test.sankuai.com/api/card/print/list/pending?empId=1094827&topOrgCode=3&startTime=1517500800000&endTime=1518969599000&photoOrigin=WORK_CARD&provinceCode=45&cityCode=00053&officeCode=01503&billCode=WD02090946000000389&cardType=FORMAL&pageNo=1&pageSize=8')).to.eql(
{
empId: "1094827",
topOrgCode: "3",
startTime: "1517500800000",
endTime: "1518969599000",
photoOrigin: "WORK_CARD",
provinceCode: "45",
cityCode: "00053",
officeCode: "01503",
billCode: "WD02090946000000389",
cardType: "FORMAL",
pageNo: "1",
pageSize: "8"
})
})
})
it('#async with done', (done) => {
(async function () {
try {
let r = await hello();
assert.strictEqual(r, 15);
done();
} catch (err) {
done(err);
}
})();
});
// 但是用try...catch太麻烦。还有一种更简单的写法,就是直接把async函数当成同步函数来测试:
it('#async function', async () => {
let r = await hello();
assert.strictEqual(r, 15);
});
React
Virtual DOM
Diff算法
两个tree进行对比,右边的新tree发现A节点已经没有了,则会直接销毁A以及下面的子节点B、C;在D节点上面发现多了一个A节点,则会重新创建一个新的A节点以及相应的子节点。
具体的操作顺序:create A → create B → creact C → delete A。
shouldComponentUpdate()
来判断该组件是否进行 diff,运用得当可以节省 diff 计算时间,提升性能。shouldComponentUpdate()
,应该避免结构相同类型不同的组件。
组件的生命周期
ComponentWillMount
、render
、ComponentDidMount
,这三个函数分别代表着组件将会挂载、组件渲染、组件挂载完毕三个阶段。ComponentWillReceiveProps
函数, 接着进入ComponetShouldUpdate
进行判定是否需要更新,如果是state的改变则直接进入ComponentShouldUpdate
判定,这个默认是true,当判定不需要更新的话,组件继续运行,需要更新则依次进入ComponentWillUpdate
、render
、 ComponentDidUpdate
三个生命周期函数,依次代表着组件将要更新、组件在渲染、组件更新完毕。ComponentWillUnmount
,之后才进行卸载。
React Element
渲染
setState()
方法的时候,React 将其标记为 dirty。
当每一个事件循环结束时,React 检查所有标记 dirty 的 component 重新绘制。
setState()
方法时,component 会重新构建包括子节点的 virtual DOM。
如果你在根节点调用 setState, 整个 React 的应用都会被重新渲染。shouldComponentUpdate(object nextProps, object nextState)
事件系统
捕获事件后内部生成合成事件提高浏览器的兼容度,通过统一的事件监听器处理并分发,执行回调函数后再进行销毁释放内存,从而大大提高网页的响应性能。stopPropagation()
和 preventDefault()
来中断它。
_updateDOMProperties
方法进行事件处理,在这个方法中,执行的是 enqueuePutListener
方法去注册事件。EventPluginHub.putListener
函数。所有的回调函数都以二维数组的形式存储在 listenerBank 中,根据组件对应的key来进行管理。
(extractEvents函数内部主要是通过switch函数区分事件类型并调用不同的插件进行处理从而生成SyntheticEvent实例。)
将合成事件放入队列,批处理队列中的事件。
componentDidMount
方法中进行原生事件的绑定,但是React不会自动管理原生事件,所以需要在卸载组件的时候注销掉原生事件。e.stopPropagation()
,只能阻止 React 合成事件冒泡,并不能阻止真实的 DOM 事件冒泡。
在原生事件中的阻止冒泡行为可以阻止合成事件冒泡,因为 DOM 事件的阻止冒泡使事件不会传播到document上。
事务(Transaction)
initialize
和close
。
state
// 错误
this.state.title = 'React';
// 正确
this.setState({title: 'React'});
setState
会被合并,并且对相同属性的设置只保留最后一次的设置;setState
不会被合并。
this.setState((prevState, props) => {...})
this.setState((preState, props) => {...}
shouldComponentUpdate
方法中,仅需要比较状态的引用就可以判断状态是否真的改变,从而避免不必要的render调用。当我们使用React 提供的PureComponent时,更是要保证组件状态是不可变对象,否则在组件的shouldComponentUpdate
方法中,状态比较就可能出现错误,因为PureComponent执行的是浅比较(比较对象的引用)。
this.setState({
count: 1,
title: 'Redux',
success: true
})
// 方法一:将state先赋值给另外的变量,然后创建新数组
var books = this.state.books;
this.setState({
books: books.concat(['React Guide']);
})
// 方法二:使用preState、concat创建新数组
this.setState(preState => ({
books: preState.books.concat(['React Guide']);
}))
// 方法三:ES6 spread syntax
this.setState(preState => ({
books: [...preState.books, 'React Guide'];
}))
// 方法一:将state先赋值给另外的变量,然后使用Object.assign创建新对象
var owner = this.state.owner;
this.setState({
owner: Object.assign({}, owner, {name: 'Jason'});
// owner: {...owner, name: 'Jason'};
})
// 方法二:使用preState、Object.assign创建新对象
this.setState(preState => ({
owner: Object.assign({}, preState.owner, {name: 'Jason'});
// owner: {...preState.owner, name: 'Jason'};
}))
setState 实现原理(利用事务与更新队列)
addEventListener
)和定时器setTimeout
、setInterval
中都是同步更新的。定时器中的每次setState都会引起新的render,即使是同一个定时器中的多次setState。
shouldComponentUpdate
执行之前,所有的state变化都会被合并,最后统一处理:
合并state后React会将this._pendingStateQueue
设置为null,这样 dirtyComponent 进入下一次批量处理时,已经更新过的组件不会进入重复的流程,保证组件只做一次更新操作。componentWillUpdate
中调用setState
,就是因为setState
会令_pendingStateQueue
为 true,导致再次执行组件更新updateComponent
,而后会再次调用componentWillUpdate
,最终循环调用componentWillUpdate
导致浏览器的崩溃。
PureComponent
concat()
、ES6扩展符。{} !== {} && [] !== []
,设置 defaultValue,避免空数组、空对象导致组件重新渲染,每个子组件重新渲染,即使数据本身没有发生变化 。
在纯组件有子组件的时候,所有基于 this.context 改变的子组件,在 this.context 改变时, 将不会重新渲染,除非在父组件(Parent ParentComponent)中声明 contextTypes。// 用原型链的方法
const hasOwn = Object.prototype.hasOwnProperty
// 这个函数实际上是Object.is()的polyfill
function is(x, y) {
if (x === y) {
return x !== 0 || y !== 0 || 1 / x === 1 / y
} else {
return x !== x && y !== y
}
}
export default function shallowEqual(objA, objB) {
// 首先对基本数据类型的比较
if (is(objA, objB)) return true
// 由于Obejct.is()可以对基本数据类型做一个精确的比较, 所以如果不等
// 只有一种情况是误判的,那就是object,所以在判断两个对象都不是object
// 之后,就可以返回false了
if (typeof objA !== 'object' || objA === null ||
typeof objB !== 'object' || objB === null) {
return false
}
// 过滤掉基本数据类型之后,就是对对象的比较了
// 首先拿出key值,对key的长度进行对比
const keysA = Object.keys(objA)
const keysB = Object.keys(objB)
// 长度不等直接返回false
if (keysA.length !== keysB.length) return false
// key相等的情况下,在去循环比较
for (let i = 0; i < keysA.length; i++) {
// key值相等的时候
// 借用原型链上真正的 hasOwnProperty 方法,判断ObjB里面是否有A的key的key值
// 属性的顺序不影响结果也就是{name:'daisy', age:'24'} 跟{age:'24',name:'daisy' }是一样的
// 最后,对对象的value进行一个基本数据类型的比较,返回结果
if (!hasOwn.call(objB, keysA[i]) ||
!is(objA[keysA[i]], objB[keysA[i]])) {
return false
}
}
return true
}
Object.is()
对基本数据类型进行比较,若为对象,先对属性长度进行比较,通过原型链上的 Object.prototype.hasOwnProperty()
方法判断 key 值是否相等(属性的顺序不影响结果),最后,对对象的 value 进行一个基本数据类型的比较,返回结果。
React Fiber
componentWillMount
、componentWillReceiveProps
、componentWillUpdate
生命周期方法不再安全,由于任务执行过程可以被打断,这几个生命周期可能会执行多次。如果使用以上方法,尽量用纯函数。React团队提供了替换的生命周期方法。(shouldComponentUpdate 也可能被多次调用,只是它只返回true或者false,没有副作用,可以暂时忽略。)
static getDerivedStateFromProps
getDerivedStateFromProps
生命周期在组件实例化以及接收新props后调用。它可以返回一个对象来更新state,或者返回null来表示新的props不需要任何state更新。getSnapshotBeforeUpdate
getSnapshotBeforeUpdate
生命周期在更新之前被调用(例如,在DOM被更新之前)。此生命周期的返回值将作为第三个参数传递给componentDidUpdate
。 (这个生命周期可以用于在恢复期间手动保存滚动位置的情况。)
constructor
static getDerivedStateFromProps()
render()
componentDidMount()
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
componentWillUnmount()
【译】最新版本react组件生命周期详解(v16.3.1)
refs
key
React VS Vue
类别
Vue
React
框架类型
mvvm
mvc
构建工具
vue-cli
flux
编写方式
模板
JSX
状态管理
vuex
redux
移动端
Weex
React Native
这意味着我们可以将事物(业务)分割成更小的易于管理的片段,从而达到重用的目的。
按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。
redux中间件
action -> reducer
的过程。变为 action -> middlewares -> reducer
。这种机制可以让我们改变数据流,实现如异步 action ,action 过滤,日志输出,异常报告等功能。
redux
由于是单一的 state tree ,调试也变得非常容易。
在开发中,你可以把应用的 state 保存在本地,从而加快开发速度。
此外,受益于单一的 state tree ,以前难以实现的如“撤销/重做”这类功能也变得轻而易举。
Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。
纯函数本质上就是模块化的,这使它更容易被测试,使代码更好维护。
react-redux
export default connect(mapStateToProps,mapDispatchToProps)(MainPage);
Redux VS MobX
Flux 思想
计算机网络
get & post
特性
get
post
后退按钮 / 刷新
无害
数据会被重新提交
书签
可收藏为书签
不可收藏为书签
缓存
能被缓存
不能缓存
编码类型
application/x-www-form-urlencoded
application/x-www-form-urlencoded (默认)或 multipart/form-data(使用表单上传文件时)。为二进制数据使用多重编码。
历史
参数保留在浏览器历史中
参数不会保留在浏览器历史中
对数据长度的限制
当发送数据时,GET 方法向 URL 添加数据;URL 的长度是受限制的(URL 的最大长度是 2048 个字符)
理论上无限制
对数据类型的限制
只允许 ASCII 字符
没有限制,也允许二进制数据
安全性
与 POST 相比,GET 的安全性较差,因为所发送的数据是 URL 的一部分。在发送密码或其他敏感信息时不要使用 GET
POST 比 GET 更安全,因为参数不会被保存在浏览器历史或 web 服务器日志中
可见性
数据在 URL 中对所有人都是可见的
数据不会显示在 URL 中
方法
描述
HEAD
与 GET 相同,但只返回 HTTP 报头,不返回文档主体。
PUT
上传指定的 URI 表示。
DELETE
删除指定资源。
OPTIONS
返回服务器支持的 HTTP 方法。
CONNECT
把请求连接转换到透明的 TCP/IP 通道。
类
分类描述
XX
服务器收到请求,需要请求者继续执行操作
XX
操作被成功接收并处理
XX
重定向,需要进一步的操作以完成请求
XX
客户端错误,请求包含语法错误或无法完成请求
XX
服务器错误,服务器在处理请求的过程中发生了错误
状态码
状态码英文描述
中文描述
200
OK
请求成功。一般用于GET与POST请求。
301
Moved Permanently
永久重定向。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替。
302
Found
临时重定向。与301类似,但资源只是临时被移动,客户端应继续使用原有URI。
304
Not Modified
未修改。自从上次请求后,所请求的资源未修改过,服务器返回此状态码时,不会返回任何资源。
400
Bad Request
请求出现语法错误,服务器无法理解。
401
(未授权) 请求要求身份验证。
403
Forbidden
服务器理解请求客户端的请求,但是拒绝执行此请求。
404
Not Found
请求失败,请求所希望得到的资源未被在服务器上发现。
405
(方法禁用)禁用请求中指定的方法。
500
Internal Server Error
服务器内部错误,无法完成请求。
502
(错误网关)服务器作为网关或代理,从上游服务器收到无效响应。
503
(服务不可用) 服务器目前无法使用。 通常这只是暂时状态。
504
(网关超时) 服务器作为网关或代理,但是没有及时从上游服务器收到请求。
端口号
对应的服务
21
FTP 文件传输协议
25
SMTP 简单邮件传输协议,主要用于发送邮件。
53
DNS 域名服务器,主要用于域名解析。
80
HTTP 超文本传送协议 (WWW)
443
HTTPS
1080
Socks代理服务
HTTP & HTTPS
对称加密 & 非对称加密
HTTP报头
关于HTTP/2
- 帧(Frame):HTTP/2通信的最小单位,每个帧包含帧首部,至少也会标识出当前帧所属的流。
- 消息(Message):由一个或多个帧组合而成,例如请求和响应。
- 连接(Connection):与 HTTP/1 相同,都是指对应的 TCP 连接;
- 流(Stream):已建立的连接上的双向字节流。
一个url输入浏览器到页面渲染的过程
DNS Resolving -> TCP handshake -> HTTP Request -> Server -> HTTP Response -> TCP shutdown
网络模型(七层)
TCP和UDP的区别
代理
前端工程化
关于MVC
MVVM
组件
单页面应用
前端模块化
node 的模块遵循 CommonJS 规范。
module.exports = {...}
require(url)
// header.js
module.exports = {
title: '我是柚子'
};
// main.js
var header = require('./header');
之后再调用这个模块,从缓存中取值,返回第一次运行的结果,除非手动清除缓存。
缓存是根据绝对路径识别模块的,如果同一个模块放在不同的路径下,还是会重新加载这个模块。// 删除指定模块的缓存
delete require.cache[moduleName];
// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
delete require.cache[key];
})
define(url, function() {..})
require(url, callback() {...})
// lib.js
function bar() {
util.log(‘it is sunshine’);
};
return {
bar: bar
};
});
// main.js
require([’./lib.js’], function(lib){
console.log(lib.bar());
})```
require 接口用来加载一系列模块,define 接口用来定义并暴露一个模块。可以并行加载多个模块。
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// ...
var b = require('./b')
b.doSomething()
// ...
```
一个模块就是一个文件。与CommonJS和Node.js的 Modules 规范保持了很大的兼容性。
// util.js
export let env = 'qa';
setTimeout(() => env = 'local', 1000);
// main.js
import {env} from './util';
console.log('env:', env); // env:qa
setTimeout(() => console.log('new env:', env), 1500); // new env:local
语义化
SEO技巧
标题,尽量做到每个页面的< title >标题中不要设置相同的内容。 标签:关键词,列举出几个页面的重要关键字即可。
中的标签:尽量让代码语义化,在适当的位置使用适当的标签,用正确的标签做正确的事。比如:h1-h6 是用于标题类的,
标签是用来设置页面主导航的等。
标签:页内链接,要加 “title” 属性加以说明。
(而外部链接,链接到其他网站的,则需要加上 el=“nofollow” 属性, 告诉 “蜘蛛” 不要爬,因为一旦“蜘蛛”爬了外部链接之后,就不会再回来了。)应使用 “alt” 属性加以说明。
、
标签 : 需要强调时使用,
标签强调效果仅次于
标签。(
、
标签: 只是用于显示效果时使用,在SEO中不会起任何效果。)
 
; 应当使用CSS进行设置。display:none
其中的内容。(设置z-index或设置到浏览器显示器之外。)
性能优化
_.debounce()
和_. throttle()
,控制高频事件的操作,如:scroll、onChange。
静态资源优化
前端容灾指的是当后端接口挂了,依然可以保证页面展示信息完整。
函数式编程
栈和堆的区别
Git
git checkout -b branchName // 创建并切换分支
git checkout -b branchName copyBranch // 复制并切换分支