面试准备

##HTML

DOM(Document Object Model 文档对象模型)

  • DOM 是 W3C(万维网联盟)的标准。
  • DOM 定义了访问 HTML 和 XML 文档的标准。
  • W3C DOM 是中立于平台和语言的接口,它允许程序和脚本动态地访问和更新文档的内容、结构和样式。
    • W3C DOM 标准被分为 3 个不同的部分:
      • 核心 DOM - 针对任何结构化文档的标准模型。
      • XML DOM - 针对 XML 文档的标准模型,定义了所有 XML 元素的对象和属性,以及访问它们的方法。
      • HTML DOM - 针对 HTML 文档的标准模型。

HTML DOM 定义了访问和操作 HTML 文档的标准方法

DOM 将 HTML 文档表达为树结构


Doctype

声明位于文档中的最前面,处于 标签之前。告知浏览器以何种模式来渲染文档。

Doctype文档类型 DTD
HTML 4.01 规定了三种文档类型:Strict(严格)、Transitional (过渡)以及 Frameset(框架)。

严格模式与过渡模式

  • 严格模式的排版和 JS 运作模式是以该浏览器支持的最高标准运行。

  • 在过渡模式中,页面以宽松的向后兼容的方式显示。模拟老式浏览器的行为以防止站点无法工作。

  • DOCTYPE不存在或格式不正确会导致文档以过渡模式呈现。


HTML5

  • 增加了

  • 在存储方面,提供了 sessionStoragelocalStorage 等,方便数据在 客户端的存储 和获取。

  • 在多媒体方面,规定了音频和视频元素

  • 画布(Canvas) API、地理(Geolocation) API。

  • WebSocket 传输协议。

  • web worker。


WebSocket

  • Websocket 是 Web 应用程序的传输协议,基于TCP,是一个HTML5协议。

  • 是一个持久化的协议,服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。

  • WebSocket提供了 双向的,按序到达的数据流服务器和客户端可以彼此相互推送信息,通过在客户端和服务器之间保持双工连接,服务器的更新可以被及时推送给客户端,而不需要客户端以一定时间间隔去轮询。( WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求。)

  • WebSocket允许跨域通信

  • 在 WebSocket API,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。


web worker

  • web worker运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能

(当在 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() 方法可以安全地实现跨源通信,允许来自不同源的脚本采用 异步 方式进行有限的通信,可以实现跨文本档多窗口跨域消息传递。

  • window.postMessage() 方法被调用时,会在所有页面脚本执行完毕之后(e.g. 在该方法之后设置的事件、之前设置的timeout 事件,etc.)向目标窗口派发一个 MessageEvent 消息。

    • 该MessageEvent消息有四个属性需要注意:
      • message 属性表示该message 的类型;
      • data 属性为 window.postMessage 的第一个参数;
      • origin 属性表示调用 window.postMessage() 方法时调用页面的当前状态;
      • source 属性记录调用 window.postMessage() 方法的窗口信息。
  • 语法: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);
  • 用于接收消息的任何事件监听器必须首先使用 originsource 属性来检查消息的发送者的身份。

  • 与任何异步调度的脚本(超时,用户生成的事件)一样,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));
	// 把被拖元素追加到放置元素(目标元素)中
}

  • DataTransfer 对象:拖放对象用来传递的媒介,使用一般为Event.dataTransfer。
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 虽然是存储在浏览器,但是 通常由服务器端进行设置

    • 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是否过期。

    • domainpath

      • namedomainpath 可以标识一个唯一的 Cookie

      • domainpath 两个选项共同决定了 Cookie 何时被浏览器自动添加到请求头部中发送出去。

      • domain 的默认值为设置该 Cookie 的网页所在的域名,path 默认值为设置该 Cookie 的网页所在的目录。

      • path 设置为“/”时允许所有路径使用Cookie。path属性需要使用符号“/”结尾。

      • 注意:修改、删除Cookie时,新建的Cookie除 valuemaxAge 之外的所有属性,例如 namepathdomain 等,都要与原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值
// 函数中的参数为 要获取的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是否存在
// 函数中的参数为,要判断的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)
        }   
    }
}
  • 删除cookie
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。Session机制决定了当前客户只会获取到自己的Session,而不会获取到别人的Session。各客户的Session也彼此独立,互不可见。

  • Session的使用比Cookie方便,但是过多的Session存储在服务器内存中,会对服务器造成压力。

    • 为防止内存溢出,服务器会把长时间内没有活跃的Session从内存删除。这个时间就是Session的超时时间。如果超过了超时时间没访问过服务器,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缓存)

  • 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 |


HTTP缓存机制

缓存的作用:

  • 降低服务器压力减少网络带宽消耗
  • 加快页面打开速度,减少白屏时间 。
  • 在无网络的情况下提供数据。

Web 缓存大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存。

缓存

浏览器缓存主要是 HTTP 协议定义的缓存机制,在 HTTP协议头 和 HTML的meta标签 中定义。但是代理服务器不解析 HTML 内容,一般应用广泛的是用 HTTP 头信息控制缓存。

HTTP 头信息控制缓存大致分为两种:强缓存协商缓存。强缓存如果命中缓存,不需要和服务器端发生交互,而协商缓存不管是否命中都要和服务器端发生交互,强制缓存的优先级高于协商缓存。

面试准备_第1张图片

  • 强缓存

    对于强制缓存来说,响应 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 请求,可能经常变动的文件。

      • Cache-Control: no-cache / max-age=0

参考自: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;	
    
  • 元素水平垂直居中

    • transform: translate(-50%,-50%);
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%,-50%);
    
    // 或直接
    margin: 50vh 0 0 50vh;
    transform: translate(-50%,-50%);
    
    • display: flex;
    display: flex;
    justify-content: center;
    align-items: center;
    
    • display: table-cell;
    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


em & rem

浏览器的默认字体大小是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

  • 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会以大的为准,产生margin的塌陷现象。

      • 可以通过触发BFC来解决。

  • BFC布局规则:

    • 内部的盒子从顶端开始垂直地一个接一个地排列。

    • 两个盒子之间的垂直的间隙是由他们的margin 值所决定的。在一个BFC中,两个相邻的块级盒子的垂直外边距会产生折叠。

    • 每一个盒子的左外边缘(margin-left)会触碰到容器的左边缘(border-left)(对于从右到左的格式来说,则触碰到右边缘)。即使存在浮动也是如此。

    • BFC的区域不会与float box重叠。

    • 计算BFC的高度时,浮动元素也参与计算。


清浮动

  • 在浮动元素下添加

    。(clear属性只能加在块元素)

    .clear{ 
    	height:0px; 
    	font-size:0; 
    	clear:both;
    }
    
    • 缺点:增加无意义标签
  • 给父元素设置 overflow属性: overflow: autooverflow: hidden

  • 浮动元素父级应用after伪元素。

    .clear:after{
    	content:'';
    	display:block;
    	clear:both;
    }
    

    IE6、7还要增加.clear{ zoom:1; }

  • 浮动元素父级设置高度。

  • 浮动元素父级浮动。


box-sizing(盒模型)

  • box-sizing属性主要用来控制元素的盒模型的解析模式。默认值是content-box

    • content-box:让元素维持 W3C 的标准盒模型。元素的宽度/高度由border + padding + content的宽度/高度决定,设置width/height属性指的是content部分的宽/高
    • border-box:让元素维持 IE 传统盒模型(IE6以下版本和IE6~7的怪异模式)。设置width/height属性指的是border + padding + content
  • 标准浏览器下,按照W3C规范对盒模型解析,一旦修改了元素的边框或内距,就会影响元素的盒子尺寸,就不得不重新计算元素的盒子尺寸,从而影响整个页面的布局。


伪类 & 伪元素

  • 伪类的效果可以通过添加实际的类来实现。
    可以叠加使用。
    优先级与类相同。

  • 伪元素的效果可以通过添加实际的元素来实现 。
    伪元素在一个选择器中只能出现一次,并且只能出现在末尾。
    优先级与标签相同

  • 它们的本质区别就是是否抽象创造了新元素

面试准备_第2张图片

面试准备_第3张图片


display:none & visibility:hidden

  • display:none :隐藏对应的元素,在文档布局中不再给它分配空间

  • visibility:hidden :隐藏对应的元素,但是在文档布局中仍保留原来的空间


position:absolute & float

  • 共同点:对内联元素设置float和absolute属性,可以让元素脱离文档流,并且可以设置其宽高

  • 不同点:float仍会占据位置,absolute会覆盖文档流中的其他元素。


position定位

  • 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 比 内联优先级高

不可继承的样式
displayborderpaddingmarginwidthheightbackgroundoverflowpositionz-indexfloatclearvertical-align

所有元素可继承的样式
visibilitycursor

内联元素可继承的样式
fontfont-sizefont-familycolorletter-spacingword-spacingwhite-spaceline-heightfont-weighttext-transform

块状元素可继承的样式
text-indenttext-align

列表元素可继承的样式
list-stylelist-style-typelist-style-positionlist-style-image

**表格元素可继承的样式 **
border-collapse


CSS选择器从右向左解析

  • 从右向左的匹配在第一步就筛选掉了大量的不符合条件的最右节点(叶子节点),避免了许多无效匹配,避免对所有元素进行遍历。

  • 而从左向右的匹配规则的性能都浪费在了失败的查找上面。

  • 写 css 时需要注意:

    • dom深度尽量浅;
    • 不要为 id选择器或类选择器指定标签;
    • 避免后代选择符,尽量使用子选择符,因为子元素匹配符的概率要大于后代元素匹配符;
    • 避免使用通配符。

& @import

  • link 属于HTML标签,而 @import 是CSS提供的;

  • 页面被加载的时,link 会同时被加载,而 @import 被引用的CSS会等到引用它的CSS文件被加载完再加载;

  • @import 只在IE5以上才能识别,而 link 是HTML标签,无兼容问题;

  • link 方式的样式的权重高于 @import 的权重。


绘图

  • 原理:元素的每条 boder 都是一个等腰梯形

    面试准备_第4张图片

    • 当元素宽高皆为0时,元素的每条 boder 都是一个等腰直角三角形

    面试准备_第5张图片

    • IE6以下的兼容性问题

    面试准备_第6张图片

    • 解决方法:line-height:0;
  • 梯形

    面试准备_第7张图片

  • 平行四边形

    面试准备_第8张图片

  • 三角形

    面试准备_第9张图片

    • 等腰直角三角形

    面试准备_第10张图片

  • 椭圆

    面试准备_第11张图片

  • 六角星
    面试准备_第12张图片

  • 对话框

    面试准备_第13张图片

实现思想: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;
}

  • 线性渐变
    面试准备_第14张图片

  • 径向渐变

面试准备_第15张图片


  • 重复线性渐变

面试准备_第16张图片


  • 重复径向渐变

面试准备_第17张图片


  • 图形的平面旋转动画
//过渡
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);}
}

###自适应浏览器宽度的正方形

  • CSS3 vw 单位
div{  
    width:30%;  
    background:red;  
    height:30vw;      
    /*  CSS3:1vw = 1% viewport width
        相对于视窗的宽度。视窗宽度是100vw 
        视窗为浏览器内部的可视区域大小,即 window.innerWidth  / window.innerHeight 大小,不包含任务栏标题栏以及底部工具栏的浏览器区域大小。*/
}  
  • 设置垂直方向的padding撑开容器(容器内无内容)
div{  
    width:30%;  
    height:0;  
    padding-bottom: 30%;  
    background:red;  
}  

margin, padding 的百分比数值是相对父元素的宽度计算的。

  • 利用 after伪元素的 padding-top 撑开容器
div{    
    width:30%;    
    background:red;    
    max-width:200px;  
}    
div:after{    
    content: '';    
    display: block;    
    padding-top:100%;   
}    

SCSS & LESS

  • Sass功能:

    • 完全兼容 CSS3;

    • 在 CSS 基础上增加变量嵌套 (选择器嵌套、属性嵌套)、混合等功能;

    • 通过函数进行颜色值与属性值**运算*;

    • 提供控制指令等高级功能。

  • Sass和Less都属于CSS预处理器。 CSS 预处理器定义了一种新的语言,其基本思想是,用一种专门的编程语言,为 CSS 增加一些编程的特性,将 CSS 作为目标生成文件,然后开发者就只要使用这种语言进行CSS的编码工作。

  • CSS有具体以下几个缺点:

    • 语法不够强大,比如无法嵌套书写,导致模块化开发中需要书写很多重复的选择器;
    • 没有变量和合理的样式复用机制,使得逻辑上相关的属性值必须以字面量的形式重复输出,导致难以维护。

    CSS预处理器,提供 CSS 缺失的样式复用机制减少冗余代码,提高样式代码的可维护性,大大提高了我们的开发效率

为什么选择使用Sass而不是Less?

Less与Sass处理机制不一样,前者是通过客户端处理的,后者是通过服务端处理,相比较之下前者解析会比后者慢一点。

为什么选择使用Sass而不是Less?


精灵图(雪碧图)

  • 优点:减少网络请求次数,提高页面加载速度。

  • 利用background-imagebackground- repeatbackground-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
    

解决方法:

  1. 使用letfor (let i = 0; i < 10; i++)
  2. 使用立即执行函数。
    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
    
    • 在函数执行之前为要执行的函数提供具体参数。如setTimeout函数。
    function callLater(paramA, paramB, paramC) {
    	 /*使用函数表达式创建并放回一个匿名内部函数的引用*/
    	 return (function () {
    	     /*
    	     这个内部函数将被setTimeout函数执行,并且当它被执行时,它能够访问并操作外部函数传递过来的参数
    	     */
    	     paramA[paramB] = paramC;
    	 });
    }
    
    /*
    调用这个函数将在它的执行上下文中创建,并最终返回内部函数对象的引用
    传递过来的参数,内部函数在最终被执行时,将使用外部函数的参数
    返回的引用被赋予了一个变量
    */
    var funcRef = callLater(elStyle, "display", "none");
    /*调用setTimeout函数,传递内部函数的引用作为第一个参数*/
    setTimeout(funcRef, 500);
    
    
    • 需要分配一个函数对象的引用,以便在未来的某个时间执行该函数。如在有元素事件处理的对象实例的关联函数上使用一个简单的闭包。通过传递event对象以及要关联元素的一个引用,为事件处理器分配不同的对象实例方法以供调用。
    
    /*
      一个给对象实例关联一个事件处理器的普通方法,
       返回的内部函数被作为事件的处理器,
       对象实例被作为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

    • 变量被声明了,但没有赋值时,就等于undefined。
    • 调用函数时,应该提供的参数没有提供,该参数等于undefined。
    • 对象没有赋值的属性,该属性的值为undefined。
    • 函数没有返回值时,默认返回undefined。

原型与原型链

  • 原型:所有的函数都有一个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

  • 全局作用域或者普通函数中 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” (二)


call() & apply() & bind()

  • 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;  
  • 让Func中的this指向obj,并执行Func的函数体。
var result =Func.call(obj);  
  • 判断Func的返回值类型:如果是值类型,返回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


执行机制

  • javascript是一门单线程语言。
  • 任务分为 同步任务异步任务
    当我们打开网站时,网页的渲染过程就是一大堆同步任务,比如页面骨架和页面元素的渲染。而像加载图片音乐之类占用资源大耗时久的任务,就是异步任务。

面试准备_第18张图片

  • 同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入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对象即被终止,作用域链向下访问变量是不被允许的。


创建ajax过程

  • 创建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 在事件流的 捕获,目标及冒泡阶段
  • 只有当事件流处在目标阶段的时候,两个的指向才是一样的, 而当处于捕获和冒泡阶段的时候,target指向被单击的对象,currentTarget指向当前事件活动的对象(一般为父级)。

事件绑定

  • 传统方式:在JavaScript代码中绑定,获取DOM元素 。

      element.onclick = function(e){
            // ...
        };
    
    • 优点:

      • 非常简单和稳定,可以确保它在你使用的不同浏览器中运作一致
      • 处理事件时,this关键字引用的是当前元素
    • 缺点:

      • 只会在事件冒泡中运行,而非捕获和冒泡。

      • 一个元素一次只能绑定一个事件处理函数。新绑定的事件处理函数会覆盖旧的事件处理函数。

      • 事件对象参数(e)仅非IE浏览器可用

  • 绑定事件监听函数:W3C方式。

    element.addEventListener('click', function(e){
        // ...
    }, false); 
    
    • 优点:

      • 该方法同时支持事件处理的 捕获和冒泡 阶段。事件阶段取决于 addEventListener 最后的参数设置:false (冒泡) 或 true (捕获)。

      • 在事件处理函数内部,this关键字引用当前元素

      • 事件对象总是可以通过处理函数的第一个参数(e)捕获。

      • 可以为同一个元素绑定你所希望的多个事件,同时并不会覆盖先前绑定的事件。

    • 缺点:

      • IE不支持,你必须使用IE的attachEvent函数替代。
  • 绑定事件监听函数:IE方式。

    element.attachEvent('onclick', function(){
        // ...
    });
    
    • 优点:

      • 可以为同一个元素绑定你所希望的多个事件,同时并不会覆盖先前绑定的事件。
    • 缺点:

      • IE仅支持事件捕获的 冒泡 阶段。

      • 事件监听函数内的this关键字指向了window对象,而不是当前元素。

      • 事件对象仅存在与window.event参数中

      • 事件必须以ontype的形式命名,比如,onclick而非click

      • 仅IE可用。你必须在非IE浏览器中使用W3C的addEventListener 。

  • 在DOM元素中直接绑定:


判断相等

JavaScript 提供两种相等运算符:(相等运算符)和=(严格相等运算符)。

如果两个值不是同一类型,严格相等运算符(=)直接返回false,而相等运算符()会将它们转化成同一个类型,再用严格相等运算符进行比较。

  • 严格相等运算符的算法如下:

    • 如果两个值的类型不同,直接返回false。
    1 === "1" // false
    true === "true" // false
    
    • 同一类型的原始类型的值(数值、字符串、布尔值)比较时,值相同就返回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
    

PS:注意,对于两个对象的比较,严格相等运算符比较的是地址,而大于或小于运算符比较的是值。

new Date() > new Date()		// false
new Date() < new Date()		// false
new Date() === new Date() 	// false
  • undefined和null与自身严格相等。
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
    
    • undefined和null与其他类型的值比较时,结果都为false,它们互相比较时结果为true。
    false == null // false
    false == undefined // false
    
    0 == null // false
    0 == undefined // false
    
    undefined == null // true
    

参考自:http://javascript.ruanyifeng.com/grammar/operator.html#toc6

  • **Object.is(value1, value2)**判断两个值是否相同。

    Object.is 不会做类型转换。

    Object.is(+0, -0)    // false
    Object.is(NaN, NaN)  // true
    

Object.create() 与 new

  • Object.create(proto, [propertiesObject]) 方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__

  • new关键字必须是以function定义的。
    Object.create 则 function和object都可以进行构建。

  • Polyfill:

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

  • 箭头函数

  • 新增**模板字符串**(为JavaScript提供了简单的字符串插值功能)。

  • for-of(用来遍历数据,例如数组中的值。)

  • arguments对象可被不定参数默认参数完美代替。

  • 增加了 letconst 命令,用来声明变量。增加了块级作用域。let命令实际上就增加了块级作用域。ES6规定,var命令和function命令声明的全局变量,属于全局对象的属性;let命令、const命令、class命令声明的全局变量,不属于全局对象的属性

  • ES6将Promise对象纳入规范,提供了原生的Promise对象。

  • 引入 module模块 的概念。

####箭头函数

  • 箭头函数不属于普通的 function,所以没有独立的上下文。箭头函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
var f = () => "hhh";
f();                      // "hhh"
typeof f;                 // function
f instanceof Function;    // true

  • 箭头函数没有[[Construct]] 属性和 prototype 属性,和 new一起用会抛出错误。(因此也没有 super 和 new.target 属性)

  • 没有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。而且这种最终的状态都不会再改变。

  • 语法:

    var promise = new Promise(
    	/* executor */
    	function(resolve, reject) {
    		// ... some code
    
    		if (/* 异步操作成功 */){
    		    resolve(value);
    		} else {
    			reject(error);
    		}
    });
    

    executor是一个带有 resolve 和 reject 两个参数的函数 。executor 函数在Promise的创建时就立即执行。而异步任务一旦完成,就调用 resolve() 来解决promise,并将异步操作的结果,作为参数传递出去。反之操作失败,就调用 reject(),并同样将异步操作报出的错误,作为参数传递出去。注意的是,在executor函数运行时抛出任何一个错误,都会导致promise状态变为rejected,这也意味着异步操作失败。

    Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。

    promise.then(function(value) {
      // success
    }, function(error) {
      // failure
    });
    

    then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

    /* 例子一 */
    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方法指定的回调函数 运行时发生的错误
      });
      

      如果没有使用catch方法指定错误处理的回调函数,Promise对象抛出的错误不会传递到外层代码,即不会有任何反应。

      建议总是使用catch方法,而不使用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.prototype.then 和 Promise.prototype.catch 方法返回 promise 对象自身, 所以它们可以被链式调用

    • Promise.resolve() 将现有对象转为Promise对象。

    • Promise.reject(reason) 返回的一定是一个rejected状态的Promise对象

    • Promise.all() 方法用于将多个Promise实例,包装成一个新的Promise实例。

      var p = Promise.all([p1, p2, p3]);
      

      p1、p2、p3都是Promise对象的实例,如果不是,就会调用Promise.resolve方法将参数转为Promise。
      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的回调函数。
    
  • 优点:代码层次清晰,便于理解,更加容易维护。

  • 缺点:

    • 无法取消 Promise,一旦新建它就会立即执行,无法中途取消

    • 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部。

    • 当处于 Pending 状态时,无法得知目前进展到哪一个阶段(刚刚开始还是即将完成)。

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

实现Promise

深入 Promise(一)——Promise 实现详解

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() {}

参考自:深入 Promise(一)——Promise 实现详解


async await

  • async 表示这是一个async函数,await只能用在这个函数里面。

  • await 表示在这里等待promise返回结果了,再继续执行。(不必写.then(),直接可以得到返回值。)

  • async函数的返回值是 Promise 对象。

  • await 后面跟着的应该是一个promise对象。(其他非promise对象会自动转换成状态为resolve的Promise并立即执行。)

  • 可以直接用标准的try catch语法捕捉错误。

    如果await后面的Promise状态转变成了reject,那么整个 async 函数都会停止执行,并且抛出相应的错误。

    当一个 async 函数中有多个 await 命令时,如果不想因为一个出错而导致其与的都无法执行,应将 await 放在 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([])

更多内容:async 函数


回调地狱

  • 回调函数是异步的,每一层的回调函数都需要依赖上一层的回调执行完,形成了层层嵌套的关系,不利于阅读和维护。

  • 解决方案:

    • 拆解为单个的 function

      • 缺点:不利于维护。
    • 事件发布 / 监听模式。监听某一事件,当事件发生时,进行相应回调操作;另一方面,当某些操作完成后,通过发布事件触发回调。这样就可以将原本捆绑在一起的代码解耦。(需要一个事件发布/监听的库,可使用node原生的events模块。)

      • 缺点:模糊了异步方法之间的流程关系。
    • 使用 Promise。

      • 缺点:过多的then也增加了代码的冗余。
    • 使用 async/await。

    • 使用 generator。


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}

定义了一个func的生成器函数,调用之后返回了一个迭代器对象(foo),可以用yield返回多次。

调用next()方法后,函数内执行yield语句,输出当前的相应值value(一般为yield关键字后面的运算结果)和状态done(迭代器是否遍历完成)。

generator函数有一个最大的特点,可以在内部执行的过程中交出程序的控制权,yield相当于起到了一个暂停的作用;而当一定情况下,外部又将控制权再移交回来。

yield和yield* 只能在generator函数内部使用,一般的函数内使用会报错。匿名函数内部不能使用yield关键字。

next()方法可以传参,参数值有注入的功能,可改变上一个yield的返回值。

generator不会自动保存相应变量值,需要手动指定。

参考自:ES6笔记(5)-- Generator生成器函数


###DOM操作

  • 创建新节点

    • createDocumentFragment() //创建一个DOM片段

    • createElement() //创建一个具体的元素

    • createTextNode() //创建一个文本节点

  • 添加、移除、替换、插入

    - appendChild()
    
    - removeChild()
    
    - replaceChild()
    
    - insertBefore() //并没有insertAfter()
    
  • 查找

    - getElementsByTagName()       //通过标签名称
    
    - getElementsByName()    //通过元素的Name属性的值(IE容错能力较强,
    
    - 会得到一个数组,其中包括id等于name值的)
    
    - getElementById()    //通过元素Id,唯一性
    

常见网站漏洞的原理及解决方法

XSS (Cross Site Scripting)跨站脚本攻击

  • 原理:攻击者往Web页面里插入恶意 JS 代码,当用户浏览该页时,嵌入的 JS 代码会被执行,从而达到恶意攻击用户的目的。

  • 攻击类型:

    • 存储型XSS:持久化,XSS 代码存储在服务器中,用户访问该页面的时候触发代码执行。(经过后端,经过数据库。)

    • 反射型XSS:非持久化,需要欺骗用户自己去点击链接才能触发XSS代码(服务器中没有这样的页面和内容),一般容易出现在搜索页面。(经过后端,不经过数据库。)

      发出请求时,XSS 代码出现在URL中,作为输入提交到服务器端,服务器端解析后响应,XSS 随响应内容一起返回给浏览器,最后浏览器解析执行 XSS 代码。

  • 解决方法:

    • 对用户输入的数据进行HTML Entity编码。
    • 对用户表单输入的数据进行过滤,对javascript代码进行转义,然后再存入数据库。
    • 在信息的展示页面,也要进行转义,防止javascript在页面上执行。
    • 尽量采用POST 而非GET 提交表单。

####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的区别

    • XSS是获取信息,不需要提前知道其他用户页面的代码和数据包。
    • CSRF是代替用户完成指定的动作,需要知道其他用户页面的代码和数据包。
  • SQL注入

    • 原理:通过把SQL命令插入到Web表单递交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。
    • 防护原理:
      • 对用户的输入进行校验,可以通过正则表达式,或限制长度,对单引号和双"-"进行转换等。
      • 永远不要使用动态拼装SQL,可以使用参数化的SQL或者直接使用存储过程进行数据查询存取。
      • 不要把机密信息明文存放,加密或者hash掉密码和敏感的信息。
  • 点击劫持

  • DDOS攻击

  • DNS劫持
    DNS劫持的话是指在用户请求过程的域名解析中,分析请求的域名,返回假的IP地址,使用户访问的是假的网址。


垃圾回收方法

  • 标记清除

    • 这是JavaScript最常见的垃圾回收方式,当变量进入执行环境的时候,比如函数中声明一个变量,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”。
    • 垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了
  • 引用计数

    • 在低版本IE中经常会出现内存泄露,很多时候就是因为其采用引用计数方式进行垃圾回收。引用计数的策略是跟踪记录每个值被使用的次数,当声明了一个变量并将一个引用类型赋值给该变量的时候这个值的引用次数就加1,如果该变量的值变成了另外一个,则这个值得引用次数减1,当这个值的引用次数变为0的时 候,说明没有变量在使用,这个值没法被访问了,因此可以将其占用的空间回收,这样垃圾回收器会在运行的时候清理掉引用次数为0的值占用的空间。
    • 在IE中虽然JavaScript对象通过标记清除的方式进行垃圾回收,但BOM与DOM对象却是通过引用计数回收垃圾的,也就是说只要涉及BOM及DOM就会出现循环引用问题。

defer & async

  • defer 并行加载js文件,会按照页面上script标签的顺序执行。

  • async 并行加载js文件,下载完成立即执行,不会按照页面上script标签的顺序执行。

js代码在加载完后,是立即执行的。执行时会阻塞页面后续的内容(包括页面的渲染、其它资源的下载)。
因为浏览器需要一个稳定的 DOM 树结构,而 JS 中有可能直接改变了 DOM 树结构,比如使用 document.writeappendChild(),甚至是直接使用location.href进行跳转,浏览器为了防止出现 J S修改 DOM 树,需要重新构建 DOM树 的情况,所以阻塞其他的下载和呈现。

减少 JavaScript 对性能的影响的方法:

  • 将所有的script标签放到页面底部,也就是body闭合标签之前,这能确保在脚本执行前页面已经完成了DOM树渲染。
  • 尽可能地合并脚本。页面中的script标签越少,加载也就越快,响应也越迅速。无论是外链脚本还是内嵌脚本都是如此。
  • 采用无阻塞下载 JavaScript 脚本的方法:
    (1)使用script标签的 defer 属性;
    (2)使用动态创建的script元素来下载并执行代码(创建script,插入到DOM中,加载完毕后callBack)。

  • 了解浏览器如何进行加载,可以在引用外部样式文件,外部js时,将他们放到合适的位置,使浏览器以最快的速度将文件加载完毕。

  • 了解浏览器如何进行解析,可以在构建 DOM 结构,组织 css 选择器时,选择最优的写法,提高浏览器的解析速率。

  • 了解浏览器如何进行渲染,在设置元素属性,编写 js 文件时,可以减少“reflow”、“repaint”的消耗。


浏览器的渲染机制(页面呈现过程)

  • 当用户输入一个URL的时候,浏览器就会发送一个请求,请求URL对应的资源。

  • 浏览器请求到 HTML 代码后,将HTML解析成一个DOM树,HTML中的每个tag都是DOM树中的一个节点,根节点就是我们常用的 document对象。DOM树里包含了 所有HTML标签,包括display:none,还有用 JS动态添加的元素等。

    DOM 树的构建过程是一个深度遍历过程:当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点

    在生成DOM的最开始阶段(应该是 Bytes → characters 后),并行发起 css、图片、js的请求,无论他们是否在HEAD里。在构建 DOM 树的时候,遇到 JS 和 CSS 元素,HTML解析器就换将控制权转让给 JS 解析器或者是 CSS 解析器。

  • CSS文件下载完成,开始构建 CSSOM。浏览器把所有样式(用户定义的CSS和用户代理)解析成样式结构体,在解析的过程中会去掉浏览器不能识别的样式,比如IE会去掉-moz开头的样式,而FF会去掉 _ 开头的样式。

    DOM 和 CSSOM 都是以 Bytes 字节 → characters 字符 → tokens 语义块 → nodes 节点 → object model 这样的方式生成最终的数据。

  • DOM Tree 和样式结构体组合后构建 Render tree , render tree能识别样式,render tree中每个 Node 都有自己的 style,而且 Render tree不包含隐藏的节点 (比如display:none的节点,还有 head 节点),因为这些节点不会用于呈现,而且不会影响呈现的,所以就不会包含到 render tree中。注意 visibility:hidden隐藏的元素还是会包含到 render tree中的,因为visibility:hidden 会影响布局(layout),会占有空间。

    根据CSS2的标准,render tree中的每个节点都称为Box (Box dimensions),理解页面元素为一个具有填充、边距、边框和位置的盒子。

    上述过程是逐步完成的,为了更好的用户体验,渲染引擎将会尽可能早的将内容呈现到屏幕上,并不会等到所有的 html 都解析完成之后再去构建和布局 render 树。它是解析完一部分内容就显示一部分内容,同时,可能还在通过网络下载其余内容。

  • Render tree构建完毕后,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作为Layout,就是计算出每个节点在屏幕中的位置。输出的是一棵layout树。

  • 最后浏览器根据这棵layout树,将页面渲染到屏幕上去。

面试准备_第19张图片

参考自:前端面试-浏览器渲染机制
前端必读:浏览器内部工作原理


回流与重绘

  • 当render tree中的一部分(或全部)因为元素的规模尺寸布局隐藏等改变而需要重新构建。这就称为回流(reflow)。每个页面至少需要一次回流,就是在页面第一次加载的时候。在回流的时候,浏览器会使渲染树中受到影响的部分失效,并重新构造这部分渲染树,完成回流后,浏览器会重新绘制受影响的部分到屏幕中,该过程成为重绘。

  • 当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color。则就叫称为重绘。

    注意:回流必将引起重绘,而重绘不一定会引起回流。页面若发生回流则需要付出很高的代价。

  • 回流何时发生:

    • 添加或者删除可见的DOM元素;

    • 元素位置改变;

    • 元素尺寸改变——边距、填充、边框、宽度和高度;

    • 内容改变——比如文本改变或者图片大小改变而引起的计算值宽度和高度改变;

    • 页面渲染初始化;

    • 浏览器窗口尺寸改变——resize事件发生时。

如何减少回流、重绘

  • 减少回流、重绘其实就是需要减少对render tree的操作(合并多次多DOM和样式的修改),并减少对一些style信息的请求,尽量利用好浏览器的优化策略。具体方法有:

    • 直接改变className,如果动态改变样式,则使用cssText
    ``` // 比较好的写法一

el.className += " className1";

// 比较好的写法二
el.style.cssText += ";
left: " + left + "px;
top: " + top + "px;";
```
- 让要操作的元素进行”离线处理”,处理完后一起更新 - 使用DocumentFragment进行缓存操作,引发一次回流和重绘; ``` //不好的写法 var p, t; p = document.creatElement('p'); t = document.creatTextNode('fist paragraph'); p.appendChild(t); document.body.appendChild(p); //将引起一次回流
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

  • Ajax的本质是使用XMLHttpRequest对象来请求数据。

  • fetch 是全局量 window 的一个方法,它的主要特点有:

    • 第一个参数是URL;
    • 第二个是可选参数,可以控制不同配置的 init 对象;
    • 使用了 Promises 来处理结果/回调。
    // 链式处理,将异步变为类似单线程的写法: 
    fetch('/some/url').then( function(response) {
        return . //... 执行成功, 第1步...
    }).then( function(returnedValue) {
        // ... 执行成功, 第2步...
    }).catch( function(err) {
        // 中途任何地方出错...在此处理 
    });
    
  • 从 fetch()返回的 Promise 将不会拒绝HTTP错误状态, 即使响应是一个 HTTP 404 或 500。相反,它会正常解决 (其中ok状态设置为false), 并且仅在网络故障时或任何阻止请求完成时,它才会拒绝。

  • 默认情况下, fetch在服务端不会发送或接收任何 cookies, 如果站点依赖于维护一个用户会话,则导致未经认证的请求(要发送 cookies,必须发送凭据头)。

  • fetch采用了Promise的异步处理机制,使用比ajax更加简单。


跨域

只要协议域名端口有任何一个不同,都被当作是不同的域,之间的请求就是跨域操作。

解决方法

  • JSONP
    • 由于同源策略的限制,XmlHttpRequest 只允许请求当前源(域名、协议、端口)的资源,为了实现跨域请求,可以通过 script 标签 实现跨域请求,然后在服务器端输出 JSON 数据并执行回调函数,从而解决了跨域的数据请求。

    • 原理:

      • 首先在客户端注册一个 callback,然后把 callback 的名字传给服务器。

      • 服务器先生成 json 数据,然后以 javascript 语法的方式,生成一个function , function 名字就是传递上来的参数 jsonp。最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。

      • 客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里。(动态执行回调函数)

    • 优点:兼容性好,简单易用,支持浏览器与服务器双向通信。

    • 缺点:

      • 只支持GET请求。
      • 没有关于 JSONP 调用的错误处理。
    • 具体实现:

      
      
      
    

注意:JavaScript 的链接,必须在 function 的下面。

  • CORS: 服务器端对于CORS的支持,主要就是通过设置Access-Control-Allow-Origin来进行的。如果浏览器检测到相应的设置,就可以允许Ajax进行跨域的访问。

  • 通过修改document.domain来跨子域。
    - 将子域和主域的document.domain设为同一个主域。
    前提条件:这两个域名必须属于同一个基础域名,而且所用的协议,端口都要一致,否则无法利用 document.domain 进行跨域。

  • 使用window.name来进行跨域。

    • window对象有个name属性,该属性有个特征:即在一个窗口(window)的生命周期内,窗口载入的所有的页面都是共享一个 window.name 的,每个页面对 window.name 都有读写的权限,window.name 是持久存在一个窗口载入过的所有页面中
  • 使用HTML5中新引进的 window.postMessage 方法来跨域传送数据。


requestAnimationFrame()

window.requestAnimationFrame() 方法告诉浏览器您希望执行动画并请求浏览器在下一次重绘之前调用指定的函数来更新动画。该方法使用一个回调函数作为参数,这个回调函数会在浏览器重绘之前调用。重绘之前调用。

回调的次数通常是每秒60次,但大多数浏览器通常匹配 W3C 所建议的刷新频率。在大多数浏览器里,当运行在后台标签页或者隐藏的 里时,requestAnimationFrame() 会暂停调用以提升性能和电池寿命。

回调函数会被传入一个参数,DOMHighResTimeStamp,指示当前被 requestAnimationFrame() 排序的回调函数被触发的时间。

返回值为一个long整数,可以传这个值给 window.cancelAnimationFrame() 以取消回调函数。

  • requestAnimationFrame的原理是以递归的形式来实现动画 ,与 setTimeout/setInterval 类似 。

  • 使用 setTimeout/setInterval 制作动画有以下缺点:

    • 不能保证ms的准确性(JavaScript单线程,可能造成阻塞);
    • 没有优化调用动画的循环机制;
    • 没有考虑到绘制动画的最佳时机(只是简单的按一定时间调用循环)。
  • 相比之下,requestAnimationFrame 有以下优点:

    • 动画更加流畅,经由浏览器优化(页面刷新前执行一次);

    • 窗口未激活时,动画暂停,有效节省CPU开销;

  • 省电,对移动端很友好

参考自:HTML5优化Web动画——requestAnimationFrame


单元测试

  • 单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。

  • 以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的。

  • 编写测试的原则是,一次只测一种情况,且测试代码要非常简单。

mocha

  • mocha是JavaScript的一种单元测试框架,既可以在浏览器环境下运行,也可以在Node.js环境下运行。

  • mocha的特点主要有:

    • 既可以测试简单的JavaScript函数,又可以测试异步代码,因为异步是JavaScript的特性之一;

    • 可以自动运行所有测试,也可以只运行特定的测试;

    • 可以支持before、after、beforeEach 和 afterEach 来编写初始化代码。

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"
			})
	})
})
  • 运行方式

    • 终端: C:…\hello-test> node_modules\mocha\bin\mocha
    • 在package.json中添加npm命令:npm test
    • 在VS Code中创建配置文件.vscode/launch.json
  • 如果要测试异步函数,我们要传入的函数需要带一个参数,通常命名为done。测试异步函数需要在函数内部手动调用done()表示测试成功,done(err)表示测试出错。

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);
});
  • 不建议在mocha测试框架中使用箭头函数。箭头函数语法中对this的绑定让会用例函数没办法访问Mocha框架上下文中定义的一些函数。

更多内容:https://www.cnblogs.com/Leo_wl/p/5734889.html


React

React起源于Facebook的内部项目,为了解决页面需要不断重新加载导致速度很慢的问题,每次更改时不必不断更新编码,而是把它们转存到 DOM中,每次只渲染最近的更改。

Virtual DOM

  1. 用 JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档中;
  2. 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异;
  3. 把2所记录的差异应用到步骤1所构建的真正的 DOM 树上,视图就更新了。

Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存,利用 dom diff 算法避免了没有必要的dom操作,从而提高性能。

更多内容:深度剖析:如何实现一个 Virtual DOM 算法


Diff算法

两个树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题,React diff 算法的复杂度就可以达到 O(n)。

React的diff策略:

  1. 忽略 Web UI 中 DOM 节点跨层级移动

  2. 拥有相同类型的两个组件产生的DOM结构也是相似的,不同类型的两个组件产生的DOM结构则不近相同;

  3. 对于同一层级的一组子节点,通过分配唯一的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 节点跨层级移动处理
    面试准备_第20张图片
    两个tree进行对比,右边的新tree发现A节点已经没有了,则会直接销毁A以及下面的子节点B、C;在D节点上面发现多了一个A节点,则会重新创建一个新的A节点以及相应的子节点。
    具体的操作顺序:create A → create B → creact C → delete A。

    所以,保证稳定 dom 结构有利于提升性能,不建议频繁真正的移除或者添加节点。

  • component diff

  1. 同一类型组件遵从 tree diff 比较 v-dom 树;

  2. 不同类型组件,先将该组件归类为 dirty component,替换整个组件下的所有子节点;

  3. 同一类型组件 Virtual Dom 没有变化,React允许开发者使用 shouldComponentUpdate() 来判断该组件是否进行 diff,运用得当可以节省 diff 计算时间,提升性能。

    e.g.不同类型组件
    面试准备_第21张图片

    如上图,当组件D → 组件G时,diff 判断为不同类型的组件,虽然它们的结构相似甚至一样,diff 仍然不会比较二者结构,会直接销毁 D 及其子节点,然后新建一个 G 相关的子 tree,这显然会影响性能,官方虽然认定这种情况极少出现,但是开发中的这种现象造成的影响是非常大的。

    所以,对于同一类型组件合理使用shouldComponentUpdate(),应该避免结构相同类型不同的组件。

  • element diff

React 通过设置唯一 key 的策略,对 element diff 进行算法优化。

对于同一层级的element节点,diff 提供了以下3种节点操作:

  1. INSERT_MARKUP 插入节点:对全新节点执行节点插入操作;

  2. MOVE_EXISING 移动节点:组件新集合中有组件旧集合中的类型,且 element 可更新,即组件调用了 receiveComponent,这时可以复用之前的 dom,执行 dom 移动操作;

  3. REMOVE_NODE 移除节点:此时有两种情况:组件新集合中有组件旧集合中的类型,但对应的element不可更新;旧组件不在新集合里面,这两种情况需要执行节点删除操作。

所以,在开发过程中,同层级的节点添加唯一key值可以极大提升性能,尽量减少将最后一个节点移动到列表首部的操作,当节点达到一定的数量以后或者操作过于频繁,在一定程度上会影响React的渲染性能。比如大量节点拖拽排序的问题。

参考自:浅谈React中的diff


组件的生命周期

  • React的组件在第一次挂载的时候会首先获得父组件传递的props,接着获取初始的state值;
  • 接着经历挂载 阶段的三个生命周期函数也就是ComponentWillMountrenderComponentDidMount,这三个函数分别代表着组件将会挂载、组件渲染、组件挂载完毕三个阶段。
  • 在组件挂载完成之后,组件的props和state的任一 改变都会导致组件进入更新状态,在组件更新阶段如果是props改变,则进入ComponentWillReceiveProps函数, 接着进入ComponetShouldUpdate进行判定是否需要更新,如果是state的改变则直接进入ComponentShouldUpdate 判定,这个默认是true,当判定不需要更新的话,组件继续运行,需要更新则依次进入ComponentWillUpdaterenderComponentDidUpdate三个生命周期函数,依次代表着组件将要更新、组件在渲染、组件更新完毕。
  • 当组件卸载时,会首先 进入生命周期函数ComponentWillUnmount,之后才进行卸载。

[外链图片转存失败(img-gahBxJKZ-1562896477123)(https://huruji.github.io/FE-Interview/image/react1.png)]


React Element

element就是React实现界面内容的最小单元,一个 element 是一个普通对象,它描述一个组件实例和它所要求的属性,或者一个DOM节点和它所要求的属性。它仅仅包含以下有关信息 :

  • 组件类型 (‘div’);
  • 属性 (‘color’:);
  • 它所包含的若干个子元素(children)。

组件就是一个方法或者一个类,可以接受一定的输入,之后返回一个React Element。


渲染

  • 合并操作

    调用 component 的 setState() 方法的时候,React 将其标记为 dirty。
    当每一个事件循环结束时,React 检查所有标记 dirty 的 component 重新绘制。

这里的“合并操作”是指, 在一个事件循环当中, DOM 只会被更新一次

  • 子树渲染

    调用 setState() 方法时,component 会重新构建包括子节点的 virtual DOM。
    如果你在根节点调用 setState, 整个 React 的应用都会被重新渲染。

选择性子树渲染:shouldComponentUpdate(object nextProps, object nextState)


事件系统

React事件系统有两类:合成事件和原生事件。

React 为了解决跨平台,兼容性问题,基于 Virtual DOM 实现了一个SyntheticEvent(合成事件)层处理事件,代理了原生的事件。

React将所有的事件都绑定在整个文档的根节点(document)上,(当组件挂载或卸载时,只是在这个统一的事件监听器上插入或删除一些对象。)
捕获事件后内部生成合成事件提高浏览器的兼容度,通过统一的事件监听器处理并分发,执行回调函数后再进行销毁释放内存,从而大大提高网页的响应性能。

我们所定义的事件处理器会接收到一个 SyntheticEvent 对象的实例,同样支持事件的冒泡机制,我们可以使用stopPropagation()preventDefault() 来中断它。

**SyntheticEvent **

  • 事件注册 - 注册事件回调函数。

    组件挂载时,React通过 mountCompoent 内部的 _updateDOMProperties 方法进行事件处理,在这个方法中,执行的是 enqueuePutListener 方法去注册事件。

  • 事件存储 - 存储事件回调函数,以便触发时进行回调。

    存储的入口是EventPluginHub.putListener函数。所有的回调函数都以二维数组的形式存储在 listenerBank 中,根据组件对应的key来进行管理。

  • 事件分发

    根据事件类型的不同,合成不同的跨浏览器的SyntheticEvent对象的实例。
    (extractEvents函数内部主要是通过switch函数区分事件类型并调用不同的插件进行处理从而生成SyntheticEvent实例。)

    因为事件回调函数执行后可能导致DOM结构的变化,React先将当前的结构以数组的形式存储起来。

  • 事件处理
    将合成事件放入队列,批处理队列中的事件。

尽管整个事件系统由React管理,但是其API和使用方法与原生事件一致。这种机制确保了跨浏览器的一致性:在所有浏览器(IE8及以上)都可以使用符合W3C标准的API,包括stopPropagation(),preventDefault()等等。


  • 可以在React中使用原生事件,在componentDidMount方法中进行原生事件的绑定,但是React不会自动管理原生事件,所以需要在卸载组件的时候注销掉原生事件。

  • DOM 事件冒泡到 document 上才会触发 React的合成事件,所以 React 合成事件对象的e.stopPropagation(),只能阻止 React 合成事件冒泡,并不能阻止真实的 DOM 事件冒泡。
    在原生事件中的阻止冒泡行为可以阻止合成事件冒泡,因为 DOM 事件的阻止冒泡使事件不会传播到document上。


事务(Transaction)

每一个方法会被wrapper所包裹,必须用perform调用,在被包裹方法前后分别执行initializeclose


state

  • state必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变,都可以从state的变化中反映出来;同时,state还必须是代表一个组件UI呈现的最小状态集,即state中的所有状态都是用于反映组件UI的变化,没有任何多余的状态,也不需要通过其他状态计算而来的中间状态。

  • 组件中用到的一个变量作为组件state的依据:

    • state不能通过Props从父组件中获取。
    • state不能在组件的整个生命周期中都保持不变。
    • state不能通过其他状态(State)或者属性(Props)计算得到。
    • state必须在组件的render方法中使用。

    如不满足上述条件,这个变量更适合定义为组件的一个普通属性,例如组件中用到的定时器,就应该直接定义为this.timer,而不是this.state.timer。

当存在多个组件共同依赖一个状态时,一般的做法是状态上移,将这个状态放到这几个组件的公共父组件中。

  • State 与 Props 区别:

    • State是可变的,是组件内部维护的一组用于反映组件UI变化的状态集合;
    • 而Props对于使用它的组件来说,是只读的,要想修改Props,只能通过该组件的父组件修改。
  • 不能直接修改State。直接修改state,组件并不会re-render。正确的修改方式是使用setState()。

    // 错误
    this.state.title = 'React';
    
    // 正确
    this.setState({title: 'React'});
    
  • setState

    • 第一个参数为对象时,在同一个方法中多次setState会被合并,并且对相同属性的设置只保留最后一次的设置;

      第一个参数为函数时,在同一个方法中多次setState 不会被合并
      this.setState((prevState, props) => {...})

    • 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。

    this.setState((preState, props) => {...}
    
  • State 的更新是一个浅合并(Shallow Merge)的过程。

  • React官方建议把State当作是不可变对象,一方面是因为不可变对象方便管理和调试;另一方面是出于性能考虑,当对象组件状态都是不可变对象时,我们在组件的shouldComponentUpdate方法中,仅需要比较状态的引用就可以判断状态是否真的改变,从而避免不必要的render调用。当我们使用React 提供的PureComponent时,更是要保证组件状态是不可变对象,否则在组件的shouldComponentUpdate方法中,状态比较就可能出现错误,因为PureComponent执行的是浅比较(比较对象的引用)。

    当State中的某个状态发生变化,我们应该重新创建这个状态对象,而不是直接修改原来的状态。创建新的状态对象的关键是,避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法。

    • 状态的类型是不可变类型(数字,字符串,布尔值,null, undefined),直接给要修改的状态赋一个新值即可。
    this.setState({
      count: 1,
      title: 'Redux',
      success: true
    })
    
    • 状态的类型是数组,将state先赋值给另外的变量,然后使用数组的concat方法、slice方法、filter方法或ES6的数组扩展语法(spread syntax)。注意不要使用push、pop、shift、unshift、splice等方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改,而concat、slice、filter会返回一个新的数组。
    // 方法一:将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先赋值给另外的变量,然后使用ES6 的Object.assgin方法或使用对象扩展语法。
    // 方法一:将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'};
    }))
    

参考自:深入理解React 组件状态(State)


setState 实现原理(利用事务与更新队列)

  • setState 只在合成事件钩子函数(生命周期函数)中是**“异步”更新的,在原生事件**(如 addEventListener)和定时器setTimeoutsetInterval中都是同步更新的。定时器中的每次setState都会引起新的render,即使是同一个定时器中的多次setState。

  • setState 的“异步”是因为合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

  • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

React中利用队列机制实现了 setState 的异步更新,避免频繁地重复更新state。队列是数组形式实现的。

  • 判断当前组件对象的state更新队列是否存在,如果存在则将新的state值加入队列;如果不存在,则创建该对象的更新队列,队列是以数组形式存在的。

  • 判断是否正在进行批量更新,如果正在进行批量更新,则先将 Component 存入 dirtyComponent 数组中,否则循环遍历所有的 dirtyComponents,更新组件。若setState方法传入了回调函数则将回调函数存入callbackQueue队列。

  • 合并state。在一个生命周期内,在shouldComponentUpdate执行之前,所有的state变化都会被合并,最后统一处理:

    • 如果更新队列为null,那么返回原来的state;

    • 如果更新队列有一个更新,那么返回更新值;

    • 如果更新队列有多个更新,那么通过for循环将它们合并。
      合并state后React会将this._pendingStateQueue设置为null,这样 dirtyComponent 进入下一次批量处理时,已经更新过的组件不会进入重复的流程,保证组件只做一次更新操作

      不能在componentWillUpdate中调用setState,就是因为setState会令_pendingStateQueue为 true,导致再次执行组件更新updateComponent,而后会再次调用componentWillUpdate,最终循环调用componentWillUpdate导致浏览器的崩溃。

面试准备_第22张图片

参考自:你真的理解setState吗?

React源码解析(三):详解事务与更新队列


PureComponent

作用:减少不必要的渲染次数,提高性能,节省代码。

PureComponent 改变了生命周期方法 shouldComponentUpdate,并且它会自动检查组件是否需要重新渲染。当props或者state改变时,PureComponent 将对 props 和 state 进行浅比较,数组和对象比较引用是否相同,嵌套对象和数组是不会被比较的。只有 PureComponent 检测到 state 或者 props 发生变化时,PureComponent 才会调用 render 方法。

易变数据不能使用一个引用!利用concat()、ES6扩展符。

如果一个 PureComponent 的 state 或 props 引用了一个新对象,那么这个组件就会被重新渲染。

因为{} !== {} && [] !== [],设置 defaultValue,避免空数组、空对象导致组件重新渲染,每个子组件重新渲染,即使数据本身没有发生变化 。

不要在render的函数中绑定值。在render的函数中绑定值会导致每次父组件render方法被调用时,一个新的函数被创建。

不要在render方法里派生数据。在render方法里派生数据,会产生新的引用,造成列表不必要的重新渲染。

纯组件忽略重新渲染时,不仅会影响它本身,还会影响它的所有子元素,所以,使用PureComponent 的最佳情况就是展示组件,它既没有子组件,也没有依赖应用的全局状态。
在纯组件有子组件的时候,所有基于 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 进行一个基本数据类型的比较,返回结果。

如果key里面是对象的话,有可能出现比较不符合预期的情况,所以浅比较是不适用于嵌套类型的比较的。

参考自:

[译]何时使用Component还是PureComponent?

你真的了解浅比较么?


React Fiber

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 需要提交所有更新并渲染,为了防止页面抖动,被设置为不能被打断。

componentWillMountcomponentWillReceivePropscomponentWillUpdate生命周期方法不再安全,由于任务执行过程可以被打断,这几个生命周期可能会执行多次。如果使用以上方法,尽量用纯函数。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()

(允许在render函数中返回节点数组和字符串。)

参考自:React-从源码分析React Fiber工作原理
【译】最新版本react组件生命周期详解(v16.3.1)


refs

ref允许我们访问DOM元素,我们通过在组件中指定ref属性,属性值为一个回调函数,这个回调函数接受一个DOM元素或者 react组件 作为参数,当我们不得不要直接访问DOM元素的时候才去使用它,如需要在组件加载完成就立即让组件中的表单有焦点,即触发 focus事件。


key

在react中我们渲染一个列表的时候,我们需要为每一个列表项指定一个唯一的key,当没有指定key时,会收到一个warning, 如果指定的key不唯一,只会渲染第一个指定唯一的key的那个元素,使用 key 可以使得 DOM diff 更加高效,避免不必要的列表项更新


React VS Vue

  • 相似之处:都是javascript框架,有路由、状态管理、构建工具等、组件式开发。都用到了Virtual DOM。

  • 不同之处:

类别 Vue React
框架类型 mvvm mvc
构建工具 vue-cli flux
编写方式 模板 JSX
状态管理 vuex redux
移动端 Weex React Native

###Webpack 打包原理

webpack是一个打包模块的机制,把依赖的模块转化成可以代表这些包的静态文件。

webpack就是识别你的 入口文件,识别你的模块依赖,来打包你的代码。

webpack做的就是分析代码,转换代码,编译代码,输出代码。

Webpack中每个模块有一个唯一的id,是从0开始递增的。整个打包后的bundle.js是一个匿名函数自执行。参数则为一个数组。数组的每一项都为个function。function的内容则为每个模块的内容,并按照require的顺序排列。

Webpack的两个最核心的原理分别是:

  1. 一切皆模块
    这意味着我们可以将事物(业务)分割成更小的易于管理的片段,从而达到重用的目的。

  2. 按需加载
    按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度减轻了它的总体体积,因为某些代码块可能永远不会被加载。

参考自:webpack打包原理


redux中间件

中间件提供第三方插件的模式,自定义拦截 action -> reducer 的过程。变为 action -> middlewares -> reducer 。这种机制可以让我们改变数据流,实现如异步 action ,action 过滤,日志输出,异常报告等功能。

常见的中间件:

redux-logger:提供日志输出

redux-thunk:处理异步操作,让Redux的dispatch方法的参数支持函数。

redux-promise:处理异步操作,让Redux的dispatch方法的参数支持promise。

异步函数里面要dispatch两次,分别表示异步请求开始和异步请求完成。


redux

  • 三大原则:

    • 单一数据源

      整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
      由于是单一的 state tree ,调试也变得非常容易。
      在开发中,你可以把应用的 state 保存在本地,从而加快开发速度
      此外,受益于单一的 state tree ,以前难以实现的如“撤销/重做”这类功能也变得轻而易举。

    • State 是只读的(唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。)

      这样确保了视图网络请求不能直接修改 state。所有的修改都被集中化处理,且严格按照一个接一个的顺序执行。增强了可预测性可靠性,避免产生副作用。
      Action 就是普通对象而已,因此它们可以被日志打印、序列化、储存、后期调试或测试时回放出来。

    • 使用纯函数来执行修改

      因为 reducer 只是函数,你可以控制它们被调用的顺序,传入附加数据,甚至编写可复用的 reducer 来处理一些通用任务,如分页器。避免数据突变和副作用。
      纯函数本质上就是模块化的,这使它更容易被测试,使代码更好维护

  • 优点:

    • 能够在应用的任何位置快速获取该数据。对于深度嵌套的组件结构,数据必须途经所有中间组件才能向下传递到目标对象。

react-redux

  • Provider就是把我们用 redux 创建的 store 传递到内部的其他组件。让内部组件可以享有这个 store 并提供对 state 的更新。

  • export default connect(mapStateToProps,mapDispatchToProps)(MainPage);

    • mapStateToProps:就是把状态绑定到组件的属性当中。。
    • mapDispatchToProps:redux用store.dispatch(action)来发出操作,那么我们同样可以把这个方法封装起来,即绑定到我们的方法中。

Redux VS MobX

Mobx利用getter和setter来收集组件的数据依赖关系,从而在数据发生变化的时候精确知道哪些组件需要重绘,在界面的规模变大的时候,往往会有很多细粒度更新,虽然响应式设计会有额外的开销,在界面规模大的时候,这种开销是远比对每一个组件做脏检查小的,因此在这种情况下Mobx会很容易得到比Redux更好的性能。而在数据全部发生改变时,基于脏检查的实现会比Mobx这类响应式有更好的性能。

【译】Redux 还是 Mobx,让我来解决你的困惑!


Flux 思想

Flux 的最大特点,就是数据的“单向流动”。

  1. 用户访问 View

  2. View 发出用户的 Action

  3. Dispatcher 收到 Action,要求 Store 进行相应的更新

  4. Store 更新后,发出一个"change"事件

  5. View 收到"change"事件后,更新页面


计算机网络

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 中

其他一些 HTTP 请求方法

方法 描述
HEAD 与 GET 相同,但只返回 HTTP 报头,不返回文档主体。
PUT 上传指定的 URI 表示。
DELETE 删除指定资源。
OPTIONS 返回服务器支持的 HTTP 方法。
CONNECT 把请求连接转换到透明的 TCP/IP 通道。

  • HTTP状态码分类
    分类描述
    XX 服务器收到请求,需要请求者继续执行操作
    XX 操作被成功接收并处理
    XX 重定向,需要进一步的操作以完成请求
    XX 客户端错误,请求包含语法错误或无法完成请求
    XX 服务器错误,服务器在处理请求的过程中发生了错误

###常见HTTP状态码

状态码 状态码英文描述 中文描述
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的默认端口号为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报头

  • 请求头

    • Accept
    • Accept-Encoding
    • Accept-Language
    • Cache-control
    • Host
    • User-agent
    • Accept-Language
    • Referer
    • Connection
  • 响应头:

    • Cache-Control
    • Content-Type
    • Content-Encoding
    • Date
    • Expires
    • Last-Modified:标记此文件在服务期端最后被修改的时间
    • Access-Control-Allow-Origin
    • ETag
    • Connection
    • Keep-Alive
    • Sever

关于HTTP/2

- 帧(Frame):HTTP/2通信的最小单位,每个帧包含帧首部,至少也会标识出当前帧所属的流。
- 消息(Message):由一个或多个帧组合而成,例如请求和响应。
- 连接(Connection):与 HTTP/1 相同,都是指对应的 TCP 连接;
- 流(Stream):已建立的连接上的双向字节流。
  • 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


一个url输入浏览器到页面渲染的过程

  • 域名解析,查找缓存

    • 查找浏览器缓存(DNS缓存)
    • 查找操作系统缓存(如果浏览器缓存没有,浏览器会从hosts文件查找是否有DNS信息)
    • 查找路由器缓存
    • 查找ISP缓存
  • 浏览器获得对应的ip地址后,浏览器与远程 Web 服务器通过 TCP 三次握手协商来建立一个 TCP/IP 连接

  • TCP/IP 连接建立起来后,浏览器就可以向服务器发送HTTP请求

  • 服务器处理请求,返回资源

  • 浏览器处理(加载,解析,渲染)

    • HTML页面加载顺序从上而下。
    • 解析文档为有意义的结构,DOM树;解析css文件为样式表对象。
    • 渲染。将DOM树进行可视化表示。
  • 绘制网页

    • 浏览器根据HTML和CSS计算得到渲染数,最终绘制到屏幕上。

    一个完整HTTP请求的过程为:
    DNS Resolving -> TCP handshake -> HTTP Request -> Server -> HTTP Response -> TCP shutdown


网络模型(七层)

  • 应用层(HTTP、SMTP、DNS):允许访问OSI环境的手段(应用协议数据单元APDU)

  • 表示层(FTP):对数据进行翻译、加密和压缩(表示协议数据单元PPDU)

  • 会话层:建立、管理和终止会话(会话协议数据单元SPDU)

  • 传输层(TCP、UDP):提供端到端的可靠报文传递和错误恢复(段Segment)

  • 网络层(IP):负责数据包从源到宿的传递和网际互连(包PackeT)

  • 数据链路层:将比特组装成帧和点到点的传递(帧Frame)

  • 物理层:通过媒介传输比特,确定机械及电气规范(比特Bit)


TCP和UDP的区别

  • TCP 是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能建立起来。

  • UDP 是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去。


代理

  • 正向代理

    正向代理 是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。

    面试准备_第23张图片

    做用:

    • 访问原来无法访问的资源,如google;
    • 可以做缓存,加速访问资源;
    • 对客户端访问授权,上网进行认证
    • 代理可以记录用户访问记录(上网行为管理),对外隐藏用户信息。
  • 反向代理

    反向代理(Reverse Proxy)实际运行方式是指以代理服务器来接受internet上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。

    作用:

    • 保证内网的安全,可以使用反向代理提供WAF功能,阻止web攻击。
      大型网站,通常将反向代理作为公网访问地址,Web服务器是内网。
      面试准备_第24张图片

    • 负载均衡,通过反向代理服务器来优化网站的负载。
      面试准备_第25张图片


前端工程化

  • 规范。

    • 团队开发规范 => 模块化开发,组件化开发,git工作流,团队协作。

    • 开发流程 => 系统测试,日志统计,上线部署,性能优化,基础框架。

  • 自动化:开发工具。

关于MVC

  • Model(模型) - 模型表示业务数据和逻辑,并提供数据给视图。

  • View(视图) - 视图是用户看到并与之交互的界面。

  • Controller(控制器) - 控制器接受用户的输入并调用模型和视图去完成用户的需求。

  • 通信是单向的。

  • 优点:

    • 把业务逻辑和展示逻辑分离,模块化程度高。且当应用逻辑需要变更的时候,不需要变更业务逻辑和展示逻辑,只需要Controller换成另外一个Controller就行了。
    • 观察者模式可以做到多视图同时更新。

MVC的处理过程,首先控制器接收用户的请求,并决定应该调用哪个模型来进行处理,然后模型用业务逻辑来处理用户的请求并返回数据,最后控制器用相应的视图格式化模型返回的数据,并通过表示层呈现给用户。

MVC的关键是实现了视图和模型的分离。MVC模式通过建立一个“发布/订阅”(publish-subscribe)的机制来分离视图和模型。

发布-订阅机制的目标是发布者,它发出通知时并不需知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。

MVC用到了Observer(观察者模式),正是观察者模式实现了发布-订阅(publish-subscribe)机制,实现了视图和模型的分离。

观察者模式

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。


MVVM

  • 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 进行测试容易的多。


单页面应用

  • 优点
    • 用户体验好,速度快,内容的改变不需要重新加载整个页面,SPA相对服务器压力小。
    • 没有页面切换,就没有白屏阻塞 。
  • 缺点:
    • 不利于SEO
    • 初次加载耗时增多

前端模块化

优点:

  • 分离: 代码需要分离成小块,增强代码可读性。
  • 解决命名冲突、文件依赖问题。
  • 可重用,降低代码维护成本。

模块化方案:

  • CommonJS
    node 的模块遵循 CommonJS 规范。

    • 用于服务器端,依赖保存在本地硬盘,读取速度快

    • 同步加载。模块加载的顺序,按照其在代码中出现的顺序。

    • 同步的模块加载方式不适合在浏览器环境中;不能非阻塞的并行加载多个模块。

    • 语法:

      module.exports = {...}
      
      require(url)
      
    // header.js
    module.exports = {
        title: '我是柚子'
    };
    
    // main.js
    var header = require('./header');
    

    require 命令第一次执行的时候,会加载并执行整个脚本,然后在内存中生成此脚本返回的 exports 对象。
    之后再调用这个模块,从缓存中取值,返回第一次运行的结果,除非手动清除缓存。
    缓存是根据绝对路径识别模块的,如果同一个模块放在不同的路径下,还是会重新加载这个模块。

    // 删除指定模块的缓存
    delete require.cache[moduleName];
    
    // 删除所有模块的缓存
    Object.keys(require.cache).forEach(function(key) {
      delete require.cache[key];
    })
    
  • AMD,又称异步加载模块(Asynchronous Module Definition)

    • 比较适合浏览器环境。

    • 异步加载模块,模块的加载不影响它后面语句的运行。所有依赖这个模块的语句,都定义在一个回调函数中,等到加载完成之后,这个回调函数才会运行。避免网页失去响应。

    • 管理模块之间的依赖性,便于代码的编写和维护。

    • 代表库:RequireJS

    • 语法:

      define(url, function() {..})
      
      require(url, callback() {...})
      
     // lib.js
    

define(’[./util.js]’, function(util){
   function bar() {
   util.log(‘it is sunshine’);
   };
   return {
   bar: bar
   };
  });
  
// main.js
require([’./lib.js’], function(lib){
console.log(lib.bar());
})

```	

require 接口用来加载一系列模块,define 接口用来定义并暴露一个模块。可以并行加载多个模块。
  • CMD

    • 遇到依赖后只会去下载 JS 文件,并不会执行,而是等到所有被依赖的 JS 脚本都下载完以后,才从头开始执行主逻辑。因此被依赖模块的执行顺序和书写顺序完全一致。

    • 代表库: Sea.js

    define(function(require, exports, module) {
    var a = require('./a')
    a.doSomething()
    // ...
    var b = require('./b') 
    b.doSomething()
    // ...
    

})
```
一个模块就是一个文件。与CommonJS和Node.js的 Modules 规范保持了很大的兼容性。

  • ES6 Module

    • 编译时就能确定模块的依赖关系。

    • 在浏览器和服务器端都可以用。

    ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。

    ES6 模块遇到 import 命令时,不会去执行模块,而是生成一个只读引用,等到脚本真正执行的时候,再根据这个只读引用,到被加载的那个模块里取值。因为是动态引用取值,不会缓存运行的结果

    import 命令具有提升效果,会提升到整个模块的头部,首先执行,所以不能把 import 写在表达式里面。这和 ES 6 模块的概念不符合。

    // 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
    

    用babel 转换 ES6 模块,最后转换成 requie方式。

  • ES6 模块与 CommonJS 模块的差异

  • CommonJS 模块输出的是一个值的拷贝(export对象 ),ES6 模块输出的是值的引用

  • CommonJS 模块是运行时加载,ES6 模块是编译时输出接口

  • ES6 模块是动态引用,并且不会缓存值。

参考自:前端模块化浅析


语义化

  • 页面结构清晰。

  • 有利于SEO,和搜索引擎建立良好沟通,有助于爬虫抓取更多的有效信息:爬虫依赖于标签来确定上下文和各个关键字的权重。

  • 方便其他设备解析(如屏幕阅读器、盲人阅读器、移动设备),以有意义的方式来渲染网页。

  • 便于团队开发和维护,语义化更具可读性,可以减少差异化。


SEO技巧

  • 网站结构尽量简单,容易被“蜘蛛”抓取。

    • 控制首页链接数量。
    • 扁平化的目录层次,尽量让“蜘蛛”只要跳转3次,就能到达网站内的任何一个内页。
    • 控制页面的大小,减少http请求,提高网站的加载速度。
  • 网页代码优化。

    • </code> 标题,尽量做到每个页面的< title >标题中不要设置相同的内容。</p> </li> <li> <p><code><meta keywords></code> 标签:关键词,列举出几个页面的重要关键字即可。</p> </li> <li> <p><code><body></code>中的标签:尽量让代码<strong>语义化</strong>,在适当的位置使用适当的标签,用正确的标签做正确的事。比如:h1-h6 是用于标题类的,<code><nav></code>标签是用来设置页面主导航的等。</p> </li> <li> <p><code><a></code>标签:页内链接,要加 “title” 属性加以说明。<br> (而外部链接,链接到其他网站的,则需要加上 el=“nofollow” 属性, 告诉 “蜘蛛” 不要爬,因为一旦“蜘蛛”爬了外部链接之后,就不会再回来了。)</p> </li> <li> <p><code><img></code>应使用 “alt” 属性加以说明。</p> </li> <li> <p><code><strong></code>、<code><em></code>标签 : 需要强调时使用,<code><em></code>标签强调效果仅次于<code><strong></code>标签。(<code><b></code>、<code><i></code>标签: 只是用于显示效果时使用,在SEO中不会起任何效果。)</p> </li> <li> <p>文本缩进不要使用特殊符号 <code> </code>; 应当使用CSS进行设置。</p> </li> <li> <p>搜索引擎会过滤掉<code>display:none</code>其中的内容。(设置z-index或设置到浏览器显示器之外。)</p> </li> <li> <p>重要内容不要用JS输出,因为“蜘蛛”不认识。</p> </li> <li> <p>尽量少使用iframe框架,因为“蜘蛛”一般不会读取其中的内容。</p> </li> <li> <p>js代码如果是操作DOM操作,应尽量放在body结束标签之前,html代码之后。</p> </li> </ul> </li> </ul> <hr> <h3>性能优化</h3> <p>性能衡量指标:</p> <ul> <li>白屏时间</li> <li>首屏时间</li> <li>用户可交互时间</li> <li>完全加载时间</li> <li>首字节时间</li> <li>DNS 解析时间</li> <li>TCP 连接时间</li> <li>HTTP 请求时间</li> <li>HTTP 响应时间</li> </ul> <p>优化方法:</p> <ul> <li> <p>代码方面:</p> <ul> <li> <p>高频事件消抖、节流。使用<code>_.debounce()</code>和<code>_. throttle()</code>,控制高频事件的操作,如:scroll、onChange。</p> </li> <li> <p>样式文件放在顶部,脚本文件放在底部。</p> </li> <li> <p>CSS Sprites。</p> </li> <li> <p>文件:引入方式、位置、文件合并、延迟加载。</p> </li> <li> <p>图片懒加载。</p> </li> <li> <p>图层隔离:将那些会变动的元素提升至单独的图层,比如:动画、渐变。</p> </li> </ul> </li> <li> <p>网络方面:</p> <ul> <li> <p>减少http请求,合理设置 HTTP缓存。</p> </li> <li> <p>使用HTTP/2。</p> </li> <li> <p>持久连接,使用 keep-alive 或者 WebSocket。</p> </li> <li> <p>使用浏览器缓存。</p> </li> <li> <p>CDN 加速。CDN(内容分发网络)的本质仍然是一个缓存,而且将数据缓存在离用户最近的地方,使用户以最快速度获取数据,即所谓网络访问第一跳。</p> </li> <li> <p>反向代理。</p> <p>传统代理服务器位于浏览器一侧,代理浏览器将 http 请求发送到互联网上,而反向代理服务器位于网站机房一侧,代理网站 web 服务器接收 http 请求。</p> <p>把内容缓存在反向代理服务器上加速用户访问速度,当这些动态内容有变化时,通过内部通知机制通知反向代理缓存失效,反向代理会重新加载最新的动态内容再次缓存起来。</p> <p>此外,反向代理也可以实现<strong>负载均衡</strong>的功能,而通过负载均衡构建的应用集群可以提高系统总体处理能力,进而改善网站高并发情况下的性能。</p> </li> <li> <p>考虑 service worker。</p> </li> </ul> </li> </ul> <p>参考自:前端性能<br> </p> <hr> <h3>静态资源优化</h3> <ul> <li>配置超长时间的本地缓存 —— 节省带宽,提高性能</li> <li>采用内容摘要作为缓存更新依据 —— 精确的缓存控制</li> <li>静态资源CDN部署 —— 优化网络请求</li> <li>更资源发布路径实现非覆盖式发布 —— 平滑升级</li> </ul> <hr> <p>###前端容灾<br> 前端容灾指的是当后端接口挂了,依然可以保证页面展示信息完整。</p> <p>解决方法:</p> <ul> <li> <p>再请求一次;</p> </li> <li> <p>localstorage 缓冲接口数据;</p> </li> <li> <p>备份一份静态数据到 CDN;</p> </li> <li> <p>利用 Service worker 的 caches API 做页面接口缓存。</p> </li> </ul> <p>参考自:前端容灾</p> <hr> <h3>函数式编程</h3> <ul> <li> <p>函数式编程是一种抽象程度很高的编程范式(如何编写程序的方法论),主要思想是把运算过程尽量写成<strong>一系列嵌套的函数调用</strong>。</p> </li> <li> <p>函数式编程的一个特点就是,允许把函数本身作为<strong>参数</strong>传入另一个函数,允许<strong>返回</strong>一个函数。</p> </li> <li> <p>使用<strong>纯函数</strong>,没有副作用。函数保持独立,所有功能就是返回一个新的值,不修改系统变量。任意一个函数,只要输入是确定的,输出就是确定的。</p> </li> <li> <p>和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。和过程化编程相比,函数式编程里函数的计算可随时调用。</p> </li> <li> <p>优点:</p> <ul> <li> <p>代码简洁,开发快速。函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。</p> </li> <li> <p>自由度高,可以写出很接近自然语言的代码,易于理解。</p> </li> <li> <p>方便代码管理。函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行<u>单元测试</u>和除错,以及<u>模块化</u>组合。</p> </li> <li> <p>易于“并发编程”。</p> </li> <li> <p>代码的热升级。函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。所以,可以在运行状态下直接升级代码,不需要重启,也不需要停机。</p> </li> </ul> </li> <li> <p>缺点:由于所有的数据都是不可变的,所以所有的变量在程序运行期间都是一直存在的,非常占用运行资源。</p> </li> </ul> <p><strong>面向对象编程</strong></p> <p>面向对象的程序设计把计算机程序视为一组<strong>对象</strong>的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是<u>一系列消息在各个对象之间传递</u>。</p> <ul> <li> <p>封装性:对象则指的是类的实例。<u>将对象作为程序的基本单元,将程序和数据封装其中</u>,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。</p> </li> <li> <p>继承性:继承性是子类自动<u>共享父类数据和方法</u>的机制。</p> </li> <li> <p>多态性:对象根据所接收的消息而做出动作。<u>同一消息被不同的对象接受时可产生完全不同的行动</u>。</p> </li> <li> <p>优点:提高了程序的灵活性和可维护性,设计出高内聚、低耦合的系统结构。</p> </li> <li> <p>缺点:面向对象编程以数据为核心,所以在多线程并发编程中,多个线程同时操作数据的时候可能会导致<strong>数据修改的不确定性</strong>。</p> </li> </ul> <p><strong>面向过程编程</strong></p> <p>面向过程的程序设计把计算机程序视为一系列的<strong>命令</strong>集合,即<u>一组函数的顺序执行</u>。为了简化程序设计,面向过程把函数继续切分为子函数,即把大块函数通过切割成小块函数来降低系统的复杂度。</p> <blockquote> <p>##数据结构</p> </blockquote> <p>###栈和队列的区别</p> <ul> <li> <p>栈的插入和删除操作都在一端进行,只允许在表尾一端进行插入和删除,先进后出。</p> </li> <li> <p>队列的操作在两端进行,只允许在表尾一端进行插入,在表头一端进行删除,先进先出。</p> </li> </ul> <hr> <h3>栈和堆的区别</h3> <ul> <li>栈区(stack)—— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。</li> <li>堆区(heap)—— 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收。</li> <li>堆(数据结构):堆可以被看成是一棵树,如:堆排序。</li> <li>栈(数据结构):一种先进后出的数据结构。</li> </ul> <blockquote> <h2>Git</h2> </blockquote> <ul> <li> <p>git fetch 和 git pull的区别</p> <ul> <li> <p>git pull 相当于是从远程获取最新版本并merge到本地。</p> </li> <li> <p>git fetch:相当于是从远程获取最新版本到本地,不会自动merge。</p> </li> </ul> </li> <li> <p>git merge 和 git rebase的区别</p> <ul> <li> <p>git merge 操作会生成一个<strong>新的节点</strong>,之前的提交分开显示。</p> </li> <li> <p>git rebase 操作不会生成新的节点,是将两个分支融合成一个线性的提交。</p> </li> </ul> </li> <li> <p>git 命令</p> </li> </ul> <pre><code>git checkout -b branchName // 创建并切换分支 git checkout -b branchName copyBranch // 复制并切换分支 </code></pre> </div> </div> </div> </div> </div> <!--PC和WAP自适应版--> <div id="SOHUCS" sid="1283348096960446464"></div> <script type="text/javascript" src="/views/front/js/chanyan.js"></script> <!-- 文章页-底部 动态广告位 --> <div class="youdao-fixed-ad" id="detail_ad_bottom"></div> </div> <div class="col-md-3"> <div class="row" id="ad"> <!-- 文章页-右侧1 动态广告位 --> <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_1"> </div> </div> <!-- 文章页-右侧2 动态广告位 --> <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_2"></div> </div> <!-- 文章页-右侧3 动态广告位 --> <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_3"></div> </div> </div> </div> </div> </div> </div> <div class="container"> <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(前端)</h4> <div id="paradigm-article-related"> <div class="recommend-post mb30"> <ul class="widget-links"> <li><a href="/article/1835509897106649088.htm" title="Long类型前后端数据不一致" target="_blank">Long类型前后端数据不一致</a> <span class="text-muted">igotyback</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a> <div>响应给前端的数据浏览器控制台中response中看到的Long类型的数据是正常的到前端数据不一致前后端数据类型不匹配是一个常见问题,尤其是当后端使用Java的Long类型(64位)与前端JavaScript的Number类型(最大安全整数为2^53-1,即16位)进行数据交互时,很容易出现精度丢失的问题。这是因为JavaScript中的Number类型无法安全地表示超过16位的整数。为了解决这个问</div> </li> <li><a href="/article/1835498925755297792.htm" title="DIV+CSS+JavaScript技术制作网页(旅游主题网页设计与制作)云南大理" target="_blank">DIV+CSS+JavaScript技术制作网页(旅游主题网页设计与制作)云南大理</a> <span class="text-muted">STU学生网页设计</span> <a class="tag" taget="_blank" href="/search/%E7%BD%91%E9%A1%B5%E8%AE%BE%E8%AE%A1/1.htm">网页设计</a><a class="tag" taget="_blank" href="/search/%E6%9C%9F%E6%9C%AB%E7%BD%91%E9%A1%B5%E4%BD%9C%E4%B8%9A/1.htm">期末网页作业</a><a class="tag" taget="_blank" href="/search/html%E9%9D%99%E6%80%81%E7%BD%91%E9%A1%B5/1.htm">html静态网页</a><a class="tag" taget="_blank" href="/search/html5%E6%9C%9F%E6%9C%AB%E5%A4%A7%E4%BD%9C%E4%B8%9A/1.htm">html5期末大作业</a><a class="tag" taget="_blank" href="/search/%E7%BD%91%E9%A1%B5%E8%AE%BE%E8%AE%A1/1.htm">网页设计</a><a class="tag" taget="_blank" href="/search/web%E5%A4%A7%E4%BD%9C%E4%B8%9A/1.htm">web大作业</a> <div>️精彩专栏推荐作者主页:【进入主页—获取更多源码】web前端期末大作业:【HTML5网页期末作业(1000套)】程序员有趣的告白方式:【HTML七夕情人节表白网页制作(110套)】文章目录二、网站介绍三、网站效果▶️1.视频演示2.图片演示四、网站代码HTML结构代码CSS样式代码五、更多源码二、网站介绍网站布局方面:计划采用目前主流的、能兼容各大主流浏览器、显示效果稳定的浮动网页布局结构。网站程</div> </li> <li><a href="/article/1835497792265613312.htm" title="【加密社】Solidity 中的事件机制及其应用" target="_blank">【加密社】Solidity 中的事件机制及其应用</a> <span class="text-muted">加密社</span> <a class="tag" taget="_blank" href="/search/%E9%97%B2%E4%BE%83/1.htm">闲侃</a><a class="tag" taget="_blank" href="/search/%E5%8C%BA%E5%9D%97%E9%93%BE/1.htm">区块链</a><a class="tag" taget="_blank" href="/search/%E6%99%BA%E8%83%BD%E5%90%88%E7%BA%A6/1.htm">智能合约</a><a class="tag" taget="_blank" href="/search/%E5%8C%BA%E5%9D%97%E9%93%BE/1.htm">区块链</a> <div>加密社引言在Solidity合约开发过程中,事件(Events)是一种非常重要的机制。它们不仅能够让开发者记录智能合约的重要状态变更,还能够让外部系统(如前端应用)监听这些状态的变化。本文将详细介绍Solidity中的事件机制以及如何利用不同的手段来触发、监听和获取这些事件。事件存储的地方当我们在Solidity合约中使用emit关键字触发事件时,该事件会被记录在区块链的交易收据中。具体而言,事件</div> </li> <li><a href="/article/1835496149843275776.htm" title="关于城市旅游的HTML网页设计——(旅游风景云南 5页)HTML+CSS+JavaScript" target="_blank">关于城市旅游的HTML网页设计——(旅游风景云南 5页)HTML+CSS+JavaScript</a> <span class="text-muted">二挡起步</span> <a class="tag" taget="_blank" href="/search/web%E5%89%8D%E7%AB%AF%E6%9C%9F%E6%9C%AB%E5%A4%A7%E4%BD%9C%E4%B8%9A/1.htm">web前端期末大作业</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a><a class="tag" taget="_blank" href="/search/%E6%97%85%E6%B8%B8/1.htm">旅游</a><a class="tag" taget="_blank" href="/search/%E9%A3%8E%E6%99%AF/1.htm">风景</a> <div>⛵源码获取文末联系✈Web前端开发技术描述网页设计题材,DIV+CSS布局制作,HTML+CSS网页设计期末课程大作业|游景点介绍|旅游风景区|家乡介绍|等网站的设计与制作|HTML期末大学生网页设计作业,Web大学生网页HTML:结构CSS:样式在操作方面上运用了html5和css3,采用了div+css结构、表单、超链接、浮动、绝对定位、相对定位、字体样式、引用视频等基础知识JavaScrip</div> </li> <li><a href="/article/1835496148601761792.htm" title="HTML网页设计制作大作业(div+css) 云南我的家乡旅游景点 带文字滚动" target="_blank">HTML网页设计制作大作业(div+css) 云南我的家乡旅游景点 带文字滚动</a> <span class="text-muted">二挡起步</span> <a class="tag" taget="_blank" href="/search/web%E5%89%8D%E7%AB%AF%E6%9C%9F%E6%9C%AB%E5%A4%A7%E4%BD%9C%E4%B8%9A/1.htm">web前端期末大作业</a><a class="tag" taget="_blank" href="/search/web%E8%AE%BE%E8%AE%A1%E7%BD%91%E9%A1%B5%E8%A7%84%E5%88%92%E4%B8%8E%E8%AE%BE%E8%AE%A1/1.htm">web设计网页规划与设计</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/dreamweaver/1.htm">dreamweaver</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a> <div>Web前端开发技术描述网页设计题材,DIV+CSS布局制作,HTML+CSS网页设计期末课程大作业游景点介绍|旅游风景区|家乡介绍|等网站的设计与制作HTML期末大学生网页设计作业HTML:结构CSS:样式在操作方面上运用了html5和css3,采用了div+css结构、表单、超链接、浮动、绝对定位、相对定位、字体样式、引用视频等基础知识JavaScript:做与用户的交互行为文章目录前端学习路线</div> </li> <li><a href="/article/1835448238103162880.htm" title="springboot+vue项目实战一-创建SpringBoot简单项目" target="_blank">springboot+vue项目实战一-创建SpringBoot简单项目</a> <span class="text-muted">苹果酱0567</span> <a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95%E9%A2%98%E6%B1%87%E6%80%BB%E4%B8%8E%E8%A7%A3%E6%9E%90/1.htm">面试题汇总与解析</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E4%B8%AD%E9%97%B4%E4%BB%B6/1.htm">中间件</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>这段时间抽空给女朋友搭建一个个人博客,想着记录一下建站的过程,就当做笔记吧。虽然复制zjblog只要一个小时就可以搞定一个网站,或者用cms系统,三四个小时就可以做出一个前后台都有的网站,而且想做成啥样也都行。但是就是要从新做,自己做的意义不一样,更何况,俺就是专门干这个的,嘿嘿嘿要做一个网站,而且从零开始,首先呢就是技术选型了,经过一番思量决定选择-SpringBoot做后端,前端使用Vue做一</div> </li> <li><a href="/article/1835437775344726016.htm" title="博客网站制作教程" target="_blank">博客网站制作教程</a> <span class="text-muted">2401_85194651</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/maven/1.htm">maven</a> <div>首先就是技术框架:后端:Java+SpringBoot数据库:MySQL前端:Vue.js数据库连接:JPA(JavaPersistenceAPI)1.项目结构blog-app/├──backend/│├──src/main/java/com/example/blogapp/││├──BlogApplication.java││├──config/│││└──DatabaseConfig.java</div> </li> <li><a href="/article/1835428317084348416.htm" title="最简单将静态网页挂载到服务器上(不用nginx)" target="_blank">最简单将静态网页挂载到服务器上(不用nginx)</a> <span class="text-muted">全能全知者</span> <a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a><a class="tag" taget="_blank" href="/search/nginx/1.htm">nginx</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a> <div>最简单将静态网页挂载到服务器上(不用nginx)如果随便弄个静态网页挂在服务器都要用nignx就太麻烦了,所以直接使用Apache来搭建一些简单前端静态网页会相对方便很多检查Web服务器服务状态:sudosystemctlstatushttpd#ApacheWeb服务器如果发现没有安装web服务器:安装Apache:sudoyuminstallhttpd启动Apache:sudosystemctl</div> </li> <li><a href="/article/1835427057752961024.htm" title="补充元象二面" target="_blank">补充元象二面</a> <span class="text-muted">Redstone Monstrosity</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a> <div>1.请尽可能详细地说明,防抖和节流的区别,应用场景?你的回答中不要写出示例代码。防抖(Debounce)和节流(Throttle)是两种常用的前端性能优化技术,它们的主要区别在于如何处理高频事件的触发。以下是防抖和节流的区别和应用场景的详细说明:防抖和节流的定义防抖:在一段时间内,多次执行变为只执行最后一次。防抖的原理是,当事件被触发后,设置一个延迟定时器。如果在这个延迟时间内事件再次被触发,则重</div> </li> <li><a href="/article/1835420753252675584.htm" title="微信小程序开发注意事项" target="_blank">微信小程序开发注意事项</a> <span class="text-muted">jun778895</span> <a class="tag" taget="_blank" href="/search/%E5%BE%AE%E4%BF%A1%E5%B0%8F%E7%A8%8B%E5%BA%8F/1.htm">微信小程序</a><a class="tag" taget="_blank" href="/search/%E5%B0%8F%E7%A8%8B%E5%BA%8F/1.htm">小程序</a> <div>微信小程序开发是一个融合了前端开发、用户体验设计、后端服务(可选)以及微信小程序平台特性的综合性项目。这里,我将详细介绍一个典型的小程序开发项目的全过程,包括项目规划、设计、开发、测试及部署上线等各个环节,并尽量使内容达到或超过2000字的要求。一、项目规划1.1项目背景与目标假设我们要开发一个名为“智慧校园助手”的微信小程序,旨在为学生提供一站式校园生活服务,包括课程表查询、图书馆座位预约、食堂</div> </li> <li><a href="/article/1835411044768509952.htm" title="字节二面" target="_blank">字节二面</a> <span class="text-muted">Redstone Monstrosity</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a> <div>1.假设你是正在面试前端开发工程师的候选人,面试官让你详细说出你上一段实习过程的收获和感悟。在上一段实习过程中,我获得了宝贵的实践经验和深刻的行业洞察,以下是我的主要收获和感悟:一、专业技能提升框架应用熟练度:通过实际项目,我深入掌握了React、Vue等前端框架的使用,不仅提升了编码效率,还学会了如何根据项目需求选择合适的框架。问题解决能力:在实习期间,我遇到了许多预料之外的技术难题。通过查阅文</div> </li> <li><a href="/article/1835398064727224320.htm" title="前端代码上传文件" target="_blank">前端代码上传文件</a> <span class="text-muted">余生逆风飞翔</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>点击上传文件import{ElNotification}from'element-plus'import{API_CONFIG}from'../config/index.js'import{UploadFilled}from'@element-plus/icons-vue'import{reactive}from'vue'import{BASE_URL}from'../config/index'i</div> </li> <li><a href="/article/1835385458356482048.htm" title="uniapp实现动态标记效果详细步骤【前端开发】" target="_blank">uniapp实现动态标记效果详细步骤【前端开发】</a> <span class="text-muted">2401_85123349</span> <a class="tag" taget="_blank" href="/search/uni-app/1.htm">uni-app</a> <div>第二个点在于实现将已经被用户标记的内容在下一次获取后刷新它的状态为已标记。这是什么意思呢?比如说上面gif图中的这些人物对象,有一些已被该用户添加为关心,那么当用户下一次进入该页面时,这些已经被添加关心的对象需要以“红心”状态显现出来。这个点的难度还不算大,只需要在每一次获取后端的内容后对标记对象进行状态更新即可。II.动态标记效果实现思路和步骤首先,整体的思路是利用动态类名对不同的元素进行选择。</div> </li> <li><a href="/article/1835373236217540608.htm" title="360前端星计划-动画可以这么玩" target="_blank">360前端星计划-动画可以这么玩</a> <span class="text-muted">马小蜗</span> <div>动画的基本原理定时器改变对象的属性根据新的属性重新渲染动画functionupdate(context){//更新属性}constticker=newTicker();ticker.tick(update,context);动画的种类1、JavaScript动画操作DOMCanvas2、CSS动画transitionanimation3、SVG动画SMILJS动画的优缺点优点:灵活度、可控性、性能</div> </li> <li><a href="/article/1835368019430305792.htm" title="Vue + Express实现一个表单提交" target="_blank">Vue + Express实现一个表单提交</a> <span class="text-muted">九旬大爷的梦</span> <div>最近在折腾一个cms系统,用的vue+express,但是就一个表单提交就弄了好久,记录一下。环境:Node10+前端:Vue服务端:Express依赖包:vueexpressaxiosexpress-formidableelement-ui(可选)前言:axiosget请求参数是:paramsaxiospost请求参数是:dataexpressget接受参数是req.queryexpresspo</div> </li> <li><a href="/article/1835354447627251712.htm" title="前端知识点" target="_blank">前端知识点</a> <span class="text-muted">ZhangTao_zata</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a> <div>下面是一个最基本的html代码body{font-family:Arial,sans-serif;margin:20px;}//JavaScriptfunctionthatdisplaysanalertwhencalledfunctionshowMessage(){alert("Hello!Youclickedthebutton.");}MyFirstHTMLPageWelcometoMyPage</div> </li> <li><a href="/article/1835352325032603648.htm" title="第三十一节:Vue路由:前端路由vs后端路由的了解" target="_blank">第三十一节:Vue路由:前端路由vs后端路由的了解</a> <span class="text-muted">曹老师</span> <div>1.认识前端路由和后端路由前端路由相对于后端路由而言的,在理解前端路由之前先对于路由有一个基本的了解路由:简而言之,就是把信息从原地址传输到目的地的活动对于我们来说路由就是:根据不同的url地址展示不同的页面内容1.1后端路由以前咱们接触比较多的后端路由,当改变url地址时,浏览器会向服务器发送请求,服务器根据这个url,返回不同的资源内容后端路由的特点就是前端每次跳转到不同url地址,都会重新访</div> </li> <li><a href="/article/1835350917352878080.htm" title="华雁智科前端面试题" target="_blank">华雁智科前端面试题</a> <span class="text-muted">因为奋斗超太帅啦</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E7%AC%94%E8%AF%95%E9%9D%A2%E8%AF%95%E9%97%AE%E9%A2%98%E6%95%B4%E7%90%86/1.htm">前端笔试面试问题整理</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/ecmascript/1.htm">ecmascript</a> <div>1.var变量的提升题目:vara=1functionfun(){console.log(b)varb=2}fun()console.log(a)正确输出结果:undefined、1答错了,给一个大嘴巴子,错误答案输出结果为:2,1此题主要考察var定义的变量,作用域提升的问题,相当于varaa=1functionfun(){varbconsole.log(b)b=2}fun()console.l</div> </li> <li><a href="/article/1835350535818014720.htm" title="如何建设数据中台(五)——数据汇集—打破企业数据孤岛" target="_blank">如何建设数据中台(五)——数据汇集—打破企业数据孤岛</a> <span class="text-muted">weixin_47088026</span> <a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95%E5%92%8C%E6%80%BB%E7%BB%93/1.htm">学习记录和总结</a><a class="tag" taget="_blank" href="/search/%E4%B8%AD%E5%8F%B0/1.htm">中台</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E4%B8%AD%E5%8F%B0/1.htm">数据中台</a><a class="tag" taget="_blank" href="/search/%E7%A8%8B%E5%BA%8F%E4%BA%BA%E7%94%9F/1.htm">程序人生</a><a class="tag" taget="_blank" href="/search/%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/1.htm">经验分享</a> <div>数据汇集——打破企业数据孤岛要构建企业级数据中台,第一步就是将企业内部各个业务系统的数据实现互通互联,打破数据孤岛,主要通过数据汇聚和交换来实现。企业采集的数据可以是线上采集、线下数据采集、互联网数据采集、内部数据采集等。线上数据采集主要载体分为互联网和移动互联网两种,对应有系统平台、网页、H5、小程序、App等,可以采用前端或后端埋点方式采集数据。线下数据采集主要是通过硬件来采集,例如:WiFi</div> </li> <li><a href="/article/1835343473629294592.htm" title="分布式锁和spring事务管理" target="_blank">分布式锁和spring事务管理</a> <span class="text-muted">暴躁的鱼</span> <a class="tag" taget="_blank" href="/search/%E9%94%81%E5%8F%8A%E4%BA%8B%E5%8A%A1/1.htm">锁及事务</a><a class="tag" taget="_blank" href="/search/%E5%88%86%E5%B8%83%E5%BC%8F/1.htm">分布式</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>最近开发一个小程序遇到一个需求需要实现分布式事务管理业务需求用户在使用小程序的过程中可以查看景点,对景点地区或者城市标记是否想去,那么需要统计一个地点被标记的人数,以及记录某个用户对某个地点是否标记为想去,用两个表存储数据,一个地点表记录改地点被标记的次数,一个用户意向表记录某个用户对某个地点是否标记为想去。由于可能有多个用户同时标记一个地点,每个用户在前端点击想去按钮之后,后台接收到请求,从数据</div> </li> <li><a href="/article/1835340577596600320.htm" title="前端CSS面试常见题" target="_blank">前端CSS面试常见题</a> <span class="text-muted">剑亦未配妥</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E9%9D%A2%E8%AF%95/1.htm">前端面试</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a><a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95/1.htm">面试</a> <div>边界塌陷盒模型有两种:W3C盒模型和IE盒模型,区别在于宽度是否包含边框定义:同时给兄弟/父子盒模型设置上下边距,理论上边距值是两者之和,实际上不是注意:浮动和定位不会产生边界塌陷;只有块级元素垂直方向才会产生margin合并margin计算方案margin同为正负:取绝对值大的值一正一负:求和父子元素边界塌陷解决父元素可以通过调整padding处理;设置overflowhidden,触发BFC子</div> </li> <li><a href="/article/1835331376895848448.htm" title="【JS】前端文件读取FileReader操作总结" target="_blank">【JS】前端文件读取FileReader操作总结</a> <span class="text-muted">程序员-张师傅</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a> <div>前端文件读取FileReader操作总结FileReader是JavaScript中的一个WebAPI,它允许web应用程序异步读取用户计算机上的文件(或原始数据缓冲区)的内容,例如读取文件以获取其内容,并在不将文件发送到服务器的情况下在客户端使用它。这对于处理图片、文本文件等非常有用,尤其是当你想要在用户界面中即时显示文件内容或进行文件预览时。创建FileReader对象首先,你需要创建一个Fi</div> </li> <li><a href="/article/1835331375377510400.htm" title="【前端】vue 报错:The template root requires exactly one element" target="_blank">【前端】vue 报错:The template root requires exactly one element</a> <span class="text-muted">程序员-张师傅</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a> <div>【前端】vue报错:Thetemplaterootrequiresexactlyoneelement在Vue.js中,当你遇到错误“Thetemplaterootrequiresexactlyoneelement”时,这通常意味着你的Vue组件的模板(template)根节点不是单一的元素。Vue要求每个组件的模板必须有一个根元素来包裹所有的子元素。这个错误通常出现在以下几种情况:模板中有多个并行</div> </li> <li><a href="/article/1835302949362954240.htm" title="从单体到微服务:FastAPI ‘挂载’子应用程序的转变" target="_blank">从单体到微服务:FastAPI ‘挂载’子应用程序的转变</a> <span class="text-muted">黑金IT</span> <a class="tag" taget="_blank" href="/search/fastapi/1.htm">fastapi</a><a class="tag" taget="_blank" href="/search/%E5%BE%AE%E6%9C%8D%E5%8A%A1/1.htm">微服务</a><a class="tag" taget="_blank" href="/search/fastapi/1.htm">fastapi</a><a class="tag" taget="_blank" href="/search/%E6%9E%B6%E6%9E%84/1.htm">架构</a> <div>在现代Web应用开发中,模块化架构是一种常见的设计模式,它有助于将大型应用程序分解为更小、更易于管理的部分。FastAPI,作为一个高性能的PythonWeb框架,提供了强大的支持来实现这种模块化设计。通过“挂载”子应用程序,我们可以为不同的功能区域(如前端接口、管理员接口和用户中心)创建独立的应用程序,并将它们整合到一个主应用程序中。本文将详细介绍如何在FastAPI中使用“挂载”子应用程序的方</div> </li> <li><a href="/article/1835296397365178368.htm" title="创建一个完整的购物商城系统是一个复杂的项目,涉及前端(用户界面)、后端(服务器逻辑)、数据库等多个部分。由于篇幅限制,我无法在这里提供一个完整的系统代码,但我可以分别给出一些关键部分的示例代码,涵盖几" target="_blank">创建一个完整的购物商城系统是一个复杂的项目,涉及前端(用户界面)、后端(服务器逻辑)、数据库等多个部分。由于篇幅限制,我无法在这里提供一个完整的系统代码,但我可以分别给出一些关键部分的示例代码,涵盖几</a> <span class="text-muted">uthRaman</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/ui/1.htm">ui</a><a class="tag" taget="_blank" href="/search/%E6%9C%8D%E5%8A%A1%E5%99%A8/1.htm">服务器</a> <div>前端(HTML/CSS/JavaScript)grsyzp.cnHTML页面结构(index.html)html购物商城欢迎来到购物商城JavaScript(Ajax请求商品数据,app.js)javascriptdocument.addEventListener('DOMContentLoaded',function(){fetch('/api/products').then(response=</div> </li> <li><a href="/article/1835293121953492992.htm" title="了解 UNPKG:前端开发者的包管理利器" target="_blank">了解 UNPKG:前端开发者的包管理利器</a> <span class="text-muted">小于负无穷</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/typescript/1.htm">typescript</a><a class="tag" taget="_blank" href="/search/css/1.htm">css</a><a class="tag" taget="_blank" href="/search/html5/1.htm">html5</a><a class="tag" taget="_blank" href="/search/node.js/1.htm">node.js</a> <div>在现代前端开发中,JavaScript包管理和模块化是至关重要的,而npm则是最流行的JavaScript包管理器之一。不过,随着前端项目复杂性的增加,有时候我们希望快速引入外部依赖,而无需本地安装和构建。此时,CDN(内容分发网络)成为了一种方便快捷的解决方案,而UNPKG就是这种方式中的佼佼者。什么是UNPKG?UNPKG是一个基于npm的内容分发网络(CDN),它允许开发者直接通过URL从n</div> </li> <li><a href="/article/1835291483406692352.htm" title="前端three.js的Sprite模拟下雪动画效果" target="_blank">前端three.js的Sprite模拟下雪动画效果</a> <span class="text-muted">qq_35430208</span> <a class="tag" taget="_blank" href="/search/three.js/1.htm">three.js</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/javascript/1.htm">javascript</a><a class="tag" taget="_blank" href="/search/%E4%B8%89%E7%BB%B4%E5%9C%BA%E6%99%AF%E4%B8%AD%E4%B8%8B%E9%9B%AA%E6%95%88%E6%9E%9C/1.htm">三维场景中下雪效果</a><a class="tag" taget="_blank" href="/search/threejs%E5%AE%9E%E7%8E%B0%E4%B8%8B%E9%9B%AA%E6%95%88%E6%9E%9C/1.htm">threejs实现下雪效果</a> <div>一、效果如图所示:二、原理同下雨一样三、完整代码:index.jsimport*asTHREEfrom'three';import{OrbitControls}from'three/addons/controls/OrbitControls.js';importmodelfrom'./model.js';//模型对象//场景constscene=newTHREE.Scene();scene.add</div> </li> <li><a href="/article/1835243206963458048.htm" title="系列3:【深入】qiankun动态与按需加载子应用—像电影一样控制出现时机" target="_blank">系列3:【深入】qiankun动态与按需加载子应用—像电影一样控制出现时机</a> <span class="text-muted">rabbit_it</span> <a class="tag" taget="_blank" href="/search/qiankun%E5%AD%A6%E4%B9%A0/1.htm">qiankun学习</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/1.htm">前端框架</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/%E9%98%BF%E9%87%8C%E4%BA%91/1.htm">阿里云</a> <div>一、引言:为何需要动态加载在现代前端开发中,性能优化始终是一个关键问题。对于微前端架构而言,管理多个子应用带来了前所未有的灵活性,但也对资源的加载和使用效率提出了更高要求。假设你的微前端项目就像一场电影,而子应用是场景或演员。在不同的情节中,我们只需要特定的场景和演员出现,而不需要所有场景和演员一开始就站在舞台上等待。这时,动态加载和按需加载就成为了关键工具——让需要的内容在正确的时机上场,节省性</div> </li> <li><a href="/article/1835239047803531264.htm" title="ODOO不同版本与平台选择" target="_blank">ODOO不同版本与平台选择</a> <span class="text-muted">chouchengyin2080</span> <a class="tag" taget="_blank" href="/search/c%23/1.htm">c#</a><a class="tag" taget="_blank" href="/search/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/1.htm">操作系统</a><a class="tag" taget="_blank" href="/search/%E8%BF%90%E7%BB%B4/1.htm">运维</a> <div>1.10.0vs11.0vs8.0截至2017年底,最新的ODOO发布版为ODOO11.0,但功能上有一定精简(去除财务模块,去除工作流支持),技术上变动较大(代码逐步迁移至Python3,前端框架改写得抽象)。所以如果是从生产使用的角度来讲,ODOO10.0是当前最好选择,因为其更稳定,第三方模块也更多更全面。而如果是ODOO技术爱好从业者,则逐步迁移至ODOO11.0也有必要,因为其底层技术架</div> </li> <li><a href="/article/1835221149026447360.htm" title="Nuxt Kit 自动导入功能:高效管理你的模块和组合式函数" target="_blank">Nuxt Kit 自动导入功能:高效管理你的模块和组合式函数</a> <span class="text-muted">qcidyu</span> <a class="tag" taget="_blank" href="/search/%E5%A5%BD%E7%94%A8%E7%9A%84%E5%B7%A5%E5%85%B7%E9%9B%86%E5%90%88/1.htm">好用的工具集合</a><a class="tag" taget="_blank" href="/search/%E4%BB%A3%E7%A0%81%E6%95%88%E7%8E%87/1.htm">代码效率</a><a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF%E6%8A%80%E5%B7%A7/1.htm">前端技巧</a><a class="tag" taget="_blank" href="/search/Vue%E5%BC%80%E5%8F%91/1.htm">Vue开发</a><a class="tag" taget="_blank" href="/search/%E7%BB%84%E5%90%88%E5%BC%8F%E5%87%BD%E6%95%B0/1.htm">组合式函数</a><a class="tag" taget="_blank" href="/search/%E6%A8%A1%E5%9D%97%E7%AE%A1%E7%90%86/1.htm">模块管理</a><a class="tag" taget="_blank" href="/search/%E8%87%AA%E5%8A%A8%E5%AF%BC%E5%85%A5/1.htm">自动导入</a><a class="tag" taget="_blank" href="/search/Nuxt/1.htm">Nuxt</a><a class="tag" taget="_blank" href="/search/Kit/1.htm">Kit</a> <div>title:NuxtKit自动导入功能:高效管理你的模块和组合式函数date:2024/9/14updated:2024/9/14author:cmdragonexcerpt:通过使用NuxtKit的自动导入功能,您可以更高效地管理和使用公共函数、组合式函数和VueAPI。无论是单个导入、目录导入还是从第三方模块导入,您都可以通过简单的API调用轻松实现。categories:前端开发tags:N</div> </li> <li><a href="/article/102.htm" title="xml解析" target="_blank">xml解析</a> <span class="text-muted">小猪猪08</span> <a class="tag" taget="_blank" href="/search/xml/1.htm">xml</a> <div>1、DOM解析的步奏 准备工作:    1.创建DocumentBuilderFactory的对象    2.创建DocumentBuilder对象    3.通过DocumentBuilder对象的parse(String fileName)方法解析xml文件    4.通过Document的getElem</div> </li> <li><a href="/article/229.htm" title="每个开发人员都需要了解的一个SQL技巧" target="_blank">每个开发人员都需要了解的一个SQL技巧</a> <span class="text-muted">brotherlamp</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a><a class="tag" taget="_blank" href="/search/linux%E8%A7%86%E9%A2%91/1.htm">linux视频</a><a class="tag" taget="_blank" href="/search/linux%E6%95%99%E7%A8%8B/1.htm">linux教程</a><a class="tag" taget="_blank" href="/search/linux%E8%87%AA%E5%AD%A6/1.htm">linux自学</a><a class="tag" taget="_blank" href="/search/linux%E8%B5%84%E6%96%99/1.htm">linux资料</a> <div>  对于数据过滤而言CHECK约束已经算是相当不错了。然而它仍存在一些缺陷,比如说它们是应用到表上面的,但有的时候你可能希望指定一条约束,而它只在特定条件下才生效。 使用SQL标准的WITH CHECK OPTION子句就能完成这点,至少Oracle和SQL Server都实现了这个功能。下面是实现方式: CREATE TABLE books (   id &</div> </li> <li><a href="/article/356.htm" title="Quartz——CronTrigger触发器" target="_blank">Quartz——CronTrigger触发器</a> <span class="text-muted">eksliang</span> <a class="tag" taget="_blank" href="/search/quartz/1.htm">quartz</a><a class="tag" taget="_blank" href="/search/CronTrigger/1.htm">CronTrigger</a> <div>转载请出自出处:http://eksliang.iteye.com/blog/2208295 一.概述 CronTrigger 能够提供比 SimpleTrigger 更有具体实际意义的调度方案,调度规则基于 Cron 表达式,CronTrigger 支持日历相关的重复时间间隔(比如每月第一个周一执行),而不是简单的周期时间间隔。 二.Cron表达式介绍 1)Cron表达式规则表 Quartz</div> </li> <li><a href="/article/483.htm" title="Informatica基础" target="_blank">Informatica基础</a> <span class="text-muted">18289753290</span> <a class="tag" taget="_blank" href="/search/Informatica/1.htm">Informatica</a><a class="tag" taget="_blank" href="/search/Monitor/1.htm">Monitor</a><a class="tag" taget="_blank" href="/search/manager/1.htm">manager</a><a class="tag" taget="_blank" href="/search/workflow/1.htm">workflow</a><a class="tag" taget="_blank" href="/search/Designer/1.htm">Designer</a> <div>1. 1)PowerCenter Designer:设计开发环境,定义源及目标数据结构;设计转换规则,生成ETL映射。 2)Workflow  Manager:合理地实现复杂的ETL工作流,基于时间,事件的作业调度 3)Workflow  Monitor:监控Workflow和Session运行情况,生成日志和报告 4)Repository  Manager:</div> </li> <li><a href="/article/610.htm" title="linux下为程序创建启动和关闭的的sh文件,scrapyd为例" target="_blank">linux下为程序创建启动和关闭的的sh文件,scrapyd为例</a> <span class="text-muted">酷的飞上天空</span> <a class="tag" taget="_blank" href="/search/scrapy/1.htm">scrapy</a> <div>对于一些未提供service管理的程序  每次启动和关闭都要加上全部路径,想到可以做一个简单的启动和关闭控制的文件   下面以scrapy启动server为例,文件名为run.sh:   #端口号,根据此端口号确定PID PORT=6800 #启动命令所在目录 HOME='/home/jmscra/scrapy/' #查询出监听了PORT端口</div> </li> <li><a href="/article/737.htm" title="人--自私与无私" target="_blank">人--自私与无私</a> <span class="text-muted">永夜-极光</span> <div>            今天上毛概课,老师提出一个问题--人是自私的还是无私的,根源是什么?               从客观的角度来看,人有自私的行为,也有无私的</div> </li> <li><a href="/article/864.htm" title="Ubuntu安装NS-3 环境脚本" target="_blank">Ubuntu安装NS-3 环境脚本</a> <span class="text-muted">随便小屋</span> <a class="tag" taget="_blank" href="/search/ubuntu/1.htm">ubuntu</a> <div>  将附件下载下来之后解压,将解压后的文件ns3environment.sh复制到下载目录下(其实放在哪里都可以,就是为了和我下面的命令相统一)。输入命令:   sudo ./ns3environment.sh >>result   这样系统就自动安装ns3的环境,运行的结果在result文件中,如果提示     com</div> </li> <li><a href="/article/991.htm" title="创业的简单感受" target="_blank">创业的简单感受</a> <span class="text-muted">aijuans</span> <a class="tag" taget="_blank" href="/search/%E5%88%9B%E4%B8%9A%E7%9A%84%E7%AE%80%E5%8D%95%E6%84%9F%E5%8F%97/1.htm">创业的简单感受</a> <div>        2009年11月9日我进入a公司实习,2012年4月26日,我离开a公司,开始自己的创业之旅。      今天是2012年5月30日,我忽然很想谈谈自己创业一个月的感受。 当初离开边锋时,我就对自己说:“自己选择的路,就是跪着也要把他走完”,我也做好了心理准备,准备迎接一次次的困难。我这次走出来,不管成败</div> </li> <li><a href="/article/1118.htm" title="如何经营自己的独立人脉" target="_blank">如何经营自己的独立人脉</a> <span class="text-muted">aoyouzi</span> <a class="tag" taget="_blank" href="/search/%E5%A6%82%E4%BD%95%E7%BB%8F%E8%90%A5%E8%87%AA%E5%B7%B1%E7%9A%84%E7%8B%AC%E7%AB%8B%E4%BA%BA%E8%84%89/1.htm">如何经营自己的独立人脉</a> <div>独立人脉不是父母、亲戚的人脉,而是自己主动投入构造的人脉圈。“放长线,钓大鱼”,先行投入才能产生后续产出。   现在几乎做所有的事情都需要人脉。以银行柜员为例,需要拉储户,而其本质就是社会人脉,就是社交!很多人都说,人脉我不行,因为我爸不行、我妈不行、我姨不行、我舅不行……我谁谁谁都不行,怎么能建立人脉?我这里说的人脉,是你的独立人脉。   以一个普通的银行柜员</div> </li> <li><a href="/article/1245.htm" title="JSP基础" target="_blank">JSP基础</a> <span class="text-muted">百合不是茶</span> <a class="tag" taget="_blank" href="/search/jsp/1.htm">jsp</a><a class="tag" taget="_blank" href="/search/%E6%B3%A8%E9%87%8A/1.htm">注释</a><a class="tag" taget="_blank" href="/search/%E9%9A%90%E5%BC%8F%E5%AF%B9%E8%B1%A1/1.htm">隐式对象</a> <div>  1,JSP语句的声明 <%! 声明 %>    声明:这个就是提供java代码声明变量、方法等的场所。 表达式 <%= 表达式 %>    这个相当于赋值,可以在页面上显示表达式的结果, 程序代码段/小型指令 <% 程序代码片段 %>   2,JSP的注释   <!-- --> </div> </li> <li><a href="/article/1372.htm" title="web.xml之session-config、mime-mapping" target="_blank">web.xml之session-config、mime-mapping</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/web.xml/1.htm">web.xml</a><a class="tag" taget="_blank" href="/search/servlet/1.htm">servlet</a><a class="tag" taget="_blank" href="/search/session-config/1.htm">session-config</a><a class="tag" taget="_blank" href="/search/mime-mapping/1.htm">mime-mapping</a> <div>session-config 1.定义: <session-config> <session-timeout>20</session-timeout> </session-config> 2.作用:用于定义整个WEB站点session的有效期限,单位是分钟。   mime-mapping 1.定义: <mime-m</div> </li> <li><a href="/article/1499.htm" title="互联网开放平台(1)" target="_blank">互联网开放平台(1)</a> <span class="text-muted">Bill_chen</span> <a class="tag" taget="_blank" href="/search/%E4%BA%92%E8%81%94%E7%BD%91/1.htm">互联网</a><a class="tag" taget="_blank" href="/search/qq/1.htm">qq</a><a class="tag" taget="_blank" href="/search/%E6%96%B0%E6%B5%AA%E5%BE%AE%E5%8D%9A/1.htm">新浪微博</a><a class="tag" taget="_blank" href="/search/%E7%99%BE%E5%BA%A6/1.htm">百度</a><a class="tag" taget="_blank" href="/search/%E8%85%BE%E8%AE%AF/1.htm">腾讯</a> <div>现在各互联网公司都推出了自己的开放平台供用户创造自己的应用,互联网的开放技术欣欣向荣,自己总结如下: 1.淘宝开放平台(TOP) 网址:http://open.taobao.com/ 依赖淘宝强大的电子商务数据,将淘宝内部业务数据作为API开放出去,同时将外部ISV的应用引入进来。 目前TOP的三条主线: TOP访问网站:open.taobao.com ISV后台:my.open.ta</div> </li> <li><a href="/article/1626.htm" title="【MongoDB学习笔记九】MongoDB索引" target="_blank">【MongoDB学习笔记九】MongoDB索引</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/mongodb/1.htm">mongodb</a> <div>索引 可以在任意列上建立索引 索引的构造和使用与传统关系型数据库几乎一样,适用于Oracle的索引优化技巧也适用于Mongodb 使用索引可以加快查询,但同时会降低修改,插入等的性能 内嵌文档照样可以建立使用索引 测试数据     var p1 = { "name":"Jack", "age&q</div> </li> <li><a href="/article/1753.htm" title="JDBC常用API之外的总结" target="_blank">JDBC常用API之外的总结</a> <span class="text-muted">白糖_</span> <a class="tag" taget="_blank" href="/search/jdbc/1.htm">jdbc</a> <div> 做JAVA的人玩JDBC肯定已经很熟练了,像DriverManager、Connection、ResultSet、Statement这些基本类大家肯定很常用啦,我不赘述那些诸如注册JDBC驱动、创建连接、获取数据集的API了,在这我介绍一些写框架时常用的API,大家共同学习吧。     ResultSetMetaData获取ResultSet对象的元数据信息 </div> </li> <li><a href="/article/1880.htm" title="apache VelocityEngine使用记录" target="_blank">apache VelocityEngine使用记录</a> <span class="text-muted">bozch</span> <a class="tag" taget="_blank" href="/search/VelocityEngine/1.htm">VelocityEngine</a> <div>VelocityEngine是一个模板引擎,能够基于模板生成指定的文件代码。   使用方法如下:     VelocityEngine engine = new VelocityEngine();// 定义模板引擎     Properties properties = new Properties();// 模板引擎属</div> </li> <li><a href="/article/2007.htm" title="编程之美-快速找出故障机器" target="_blank">编程之美-快速找出故障机器</a> <span class="text-muted">bylijinnan</span> <a class="tag" taget="_blank" href="/search/%E7%BC%96%E7%A8%8B%E4%B9%8B%E7%BE%8E/1.htm">编程之美</a> <div> package beautyOfCoding; import java.util.Arrays; public class TheLostID { /*编程之美 假设一个机器仅存储一个标号为ID的记录,假设机器总量在10亿以下且ID是小于10亿的整数,假设每份数据保存两个备份,这样就有两个机器存储了同样的数据。 1.假设在某个时间得到一个数据文件ID的列表,是</div> </li> <li><a href="/article/2134.htm" title="关于Java中redirect与forward的区别" target="_blank">关于Java中redirect与forward的区别</a> <span class="text-muted">chenbowen00</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/servlet/1.htm">servlet</a> <div>在Servlet中两种实现: forward方式:request.getRequestDispatcher(“/somePage.jsp”).forward(request, response); redirect方式:response.sendRedirect(“/somePage.jsp”); forward是服务器内部重定向,程序收到请求后重新定向到另一个程序,客户机并不知</div> </li> <li><a href="/article/2261.htm" title="[信号与系统]人体最关键的两个信号节点" target="_blank">[信号与系统]人体最关键的两个信号节点</a> <span class="text-muted">comsci</span> <a class="tag" taget="_blank" href="/search/%E7%B3%BB%E7%BB%9F/1.htm">系统</a> <div>         如果把人体看做是一个带生物磁场的导体,那么这个导体有两个很重要的节点,第一个在头部,中医的名称叫做 百汇穴, 另外一个节点在腰部,中医的名称叫做 命门         如果要保护自己的脑部磁场不受到外界有害信号的攻击,最简单的</div> </li> <li><a href="/article/2388.htm" title="oracle 存储过程执行权限" target="_blank">oracle 存储过程执行权限</a> <span class="text-muted">daizj</span> <a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/%E5%AD%98%E5%82%A8%E8%BF%87%E7%A8%8B/1.htm">存储过程</a><a class="tag" taget="_blank" href="/search/%E6%9D%83%E9%99%90/1.htm">权限</a><a class="tag" taget="_blank" href="/search/%E6%89%A7%E8%A1%8C%E8%80%85/1.htm">执行者</a><a class="tag" taget="_blank" href="/search/%E8%B0%83%E7%94%A8%E8%80%85/1.htm">调用者</a> <div>在数据库系统中存储过程是必不可少的利器,存储过程是预先编译好的为实现一个复杂功能的一段Sql语句集合。它的优点我就不多说了,说一下我碰到的问题吧。我在项目开发的过程中需要用存储过程来实现一个功能,其中涉及到判断一张表是否已经建立,没有建立就由存储过程来建立这张表。 CREATE OR REPLACE PROCEDURE TestProc  IS    fla</div> </li> <li><a href="/article/2515.htm" title="为mysql数据库建立索引" target="_blank">为mysql数据库建立索引</a> <span class="text-muted">dengkane</span> <a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/%E6%80%A7%E8%83%BD/1.htm">性能</a><a class="tag" taget="_blank" href="/search/%E7%B4%A2%E5%BC%95/1.htm">索引</a> <div>前些时候,一位颇高级的程序员居然问我什么叫做索引,令我感到十分的惊奇,我想这绝不会是沧海一粟,因为有成千上万的开发者(可能大部分是使用MySQL的)都没有受过有关数据库的正规培训,尽管他们都为客户做过一些开发,但却对如何为数据库建立适当的索引所知较少,因此我起了写一篇相关文章的念头。  最普通的情况,是为出现在where子句的字段建一个索引。为方便讲述,我们先建立一个如下的表。</div> </li> <li><a href="/article/2642.htm" title="学习C语言常见误区 如何看懂一个程序 如何掌握一个程序以及几个小题目示例" target="_blank">学习C语言常见误区 如何看懂一个程序 如何掌握一个程序以及几个小题目示例</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/c/1.htm">c</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a> <div>如果看懂一个程序,分三步   1、流程   2、每个语句的功能   3、试数   如何学习一些小算法的程序 尝试自己去编程解决它,大部分人都自己无法解决 如果解决不了就看答案 关键是把答案看懂,这个是要花很大的精力,也是我们学习的重点 看懂之后尝试自己去修改程序,并且知道修改之后程序的不同输出结果的含义 照着答案去敲 调试错误 </div> </li> <li><a href="/article/2769.htm" title="centos6.3安装php5.4报错" target="_blank">centos6.3安装php5.4报错</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/centos6/1.htm">centos6</a> <div>报错内容如下: Resolving Dependencies --> Running transaction check ---> Package php54w.x86_64 0:5.4.38-1.w6 will be installed --> Processing Dependency: php54w-common(x86-64) = 5.4.38-1.w6 for </div> </li> <li><a href="/article/2896.htm" title="JSONP请求" target="_blank">JSONP请求</a> <span class="text-muted">flyer0126</span> <a class="tag" taget="_blank" href="/search/jsonp/1.htm">jsonp</a> <div>      使用jsonp不能发起POST请求。 It is not possible to make a JSONP POST request. JSONP works by creating a <script> tag that executes Javascript from a different domain; it is not pos</div> </li> <li><a href="/article/3023.htm" title="Spring Security(03)——核心类简介" target="_blank">Spring Security(03)——核心类简介</a> <span class="text-muted">234390216</span> <a class="tag" taget="_blank" href="/search/Authentication/1.htm">Authentication</a> <div>核心类简介 目录 1.1     Authentication 1.2     SecurityContextHolder 1.3     AuthenticationManager和AuthenticationProvider 1.3.1  &nb</div> </li> <li><a href="/article/3150.htm" title="在CentOS上部署JAVA服务" target="_blank">在CentOS上部署JAVA服务</a> <span class="text-muted">java--hhf</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/jdk/1.htm">jdk</a><a class="tag" taget="_blank" href="/search/centos/1.htm">centos</a><a class="tag" taget="_blank" href="/search/Java%E6%9C%8D%E5%8A%A1/1.htm">Java服务</a> <div>    本文将介绍如何在CentOS上运行Java Web服务,其中将包括如何搭建JAVA运行环境、如何开启端口号、如何使得服务在命令执行窗口关闭后依旧运行     第一步:卸载旧Linux自带的JDK ①查看本机JDK版本 java -version    结果如下 java version "1.6.0"</div> </li> <li><a href="/article/3277.htm" title="oracle、sqlserver、mysql常用函数对比[to_char、to_number、to_date]" target="_blank">oracle、sqlserver、mysql常用函数对比[to_char、to_number、to_date]</a> <span class="text-muted">ldzyz007</span> <a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/SQL+Server/1.htm">SQL Server</a> <div>oracle                                &n</div> </li> <li><a href="/article/3404.htm" title="记Protocol Oriented Programming in Swift of WWDC 2015" target="_blank">记Protocol Oriented Programming in Swift of WWDC 2015</a> <span class="text-muted">ningandjin</span> <a class="tag" taget="_blank" href="/search/protocol/1.htm">protocol</a><a class="tag" taget="_blank" href="/search/WWDC+2015/1.htm">WWDC 2015</a><a class="tag" taget="_blank" href="/search/Swift2.0/1.htm">Swift2.0</a> <div>其实最先朋友让我就这个题目写篇文章的时候,我是拒绝的,因为觉得苹果就是在炒冷饭, 把已经流行了数十年的OOP中的“面向接口编程”还拿来讲,看完整个Session之后呢,虽然还是觉得在炒冷饭,但是毕竟还是加了蛋的,有些东西还是值得说说的。 通常谈到面向接口编程,其主要作用是把系统设计和具体实现分离开,让系统的每个部分都可以在不影响别的部分的情况下,改变自身的具体实现。接口的设计就反映了系统</div> </li> <li><a href="/article/3531.htm" title="搭建 CentOS 6 服务器(15) - Keepalived、HAProxy、LVS" target="_blank">搭建 CentOS 6 服务器(15) - Keepalived、HAProxy、LVS</a> <span class="text-muted">rensanning</span> <a class="tag" taget="_blank" href="/search/keepalived/1.htm">keepalived</a> <div>(一)Keepalived (1)安装 # cd /usr/local/src # wget http://www.keepalived.org/software/keepalived-1.2.15.tar.gz # tar zxvf keepalived-1.2.15.tar.gz # cd keepalived-1.2.15 # ./configure # make &a</div> </li> <li><a href="/article/3658.htm" title="ORACLE数据库SCN和时间的互相转换" target="_blank">ORACLE数据库SCN和时间的互相转换</a> <span class="text-muted">tomcat_oracle</span> <a class="tag" taget="_blank" href="/search/oracle/1.htm">oracle</a><a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a> <div>SCN(System Change Number 简称 SCN)是当Oracle数据库更新后,由DBMS自动维护去累积递增的一个数字,可以理解成ORACLE数据库的时间戳,从ORACLE 10G开始,提供了函数可以实现SCN和时间进行相互转换;    用途:在进行数据库的还原和利用数据库的闪回功能时,进行SCN和时间的转换就变的非常必要了;    操作方法:   1、通过dbms_f</div> </li> <li><a href="/article/3785.htm" title="Spring MVC 方法注解拦截器" target="_blank">Spring MVC 方法注解拦截器</a> <span class="text-muted">xp9802</span> <a class="tag" taget="_blank" href="/search/spring+mvc/1.htm">spring mvc</a> <div>应用场景,在方法级别对本次调用进行鉴权,如api接口中有个用户唯一标示accessToken,对于有accessToken的每次请求可以在方法加一个拦截器,获得本次请求的用户,存放到request或者session域。 python中,之前在python flask中可以使用装饰器来对方法进行预处理,进行权限处理 先看一个实例,使用@access_required拦截: ? </div> </li> </ul> </div> </div> </div> <div> <div class="container"> <div class="indexes"> <strong>按字母分类:</strong> <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a> </div> </div> </div> <footer id="footer" class="mb30 mt30"> <div class="container"> <div class="footBglm"> <a target="_blank" href="/">首页</a> - <a target="_blank" href="/custom/about.htm">关于我们</a> - <a target="_blank" href="/search/Java/1.htm">站内搜索</a> - <a target="_blank" href="/sitemap.txt">Sitemap</a> - <a target="_blank" href="/custom/delete.htm">侵权投诉</a> </div> <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved. <!-- <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>--> </div> </div> </footer> <!-- 代码高亮 --> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script> <link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/> <script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script> </body> </html>