1.移動端適配1px的問題
解答:
(1)原因:
css中的1px並不等於移動設備的1px,這些由於不同的手機有不同的像素密度。在window對象中有一個devicePixelRatio屬性,他可以反應css中的像素與設備的像素比。
devicePixelRatio的官方的定義為:設備物理像素和設備獨立像素的比例,也就是 devicePixelRatio = 物理像素 / 獨立像素。
關於devicePixelRatio的詳細介紹可以參考張鑫旭的這篇博文,http://www.zhangxinxu.com/wordpress/2012/08/window-devicepixelratio/
(2)解決方法:
a:0.5px邊框
IOS8下已經支持帶小數的px值, media query 對應 devicePixelRatio 有個查詢值 -webkit-min-device-pixel-ratio, css可以寫成這樣
通過-webkit-min-device-pixel-ratio設置。
.border { border: 1px solid #999 } @media screen and (-webkit-min-device-pixel-ratio: 2) { .border { border: 0.5px solid #999 } } @media screen and (-webkit-min-device-pixel-ratio: 3) { .border { border: 0.333333px solid #999 } }
如果使用less/sass的話只是加了1句mixin
缺點: 安卓與低版本IOS不適用, 這個或許是未來的標准寫法, 現在不做指望
b.viewport + rem 實現
同時通過設置對應viewport的rem基准值,這種方式就可以像以前一樣輕松愉快的寫1px了。
在devicePixelRatio = 2 時,輸出viewport:
<meta name="viewport" content="initial-scale=0.5, maximum-scale=0.5, minimum-scale=0.5, user-scalable=no">
<meta name="viewport" content="initial-scale=0.3333333333333333, maximum-scale=0.3333333333333333, minimum-scale=0.3333333333333333, user-scalable=no">
c.使用border-image實現
准備一張符合你要求的border-image:
.border-bottom-1px { border-width: 0 0 1px 0; -webkit-border-image: url(linenew.png) 0 0 2 0 stretch; border-image: url(linenew.png) 0 0 2 0 stretch; }
上文是把border設置在邊框的底部,所以使用的圖片是2px高,上部的1px顏色為透明,下部的1px使用視覺規定的border的顏色。如果邊框底部和頂部同時需要border,可以使用下面的border-image:
.border-image-1px { border-width: 1px 0; -webkit-border-image: url(linenew.png) 2 0 stretch; border-image: url(linenew.png) 2 0 stretch; }
d.使用background-image實現
background-image 跟border-image的方法一樣,你要先准備一張符合你要求的圖片。然后將邊框模擬在背景上。
樣式設置:
.background-image-1px { background: url(../img/line.png) repeat-x left bottom; -webkit-background-size: 100% 1px; background-size: 100% 1px; }
e.使用box-shadow模擬邊框
利用css 對陰影處理的方式實現0.5px的效果
.box-shadow-1px { box-shadow: inset 0px -1px 1px -1px #c8c7cc; }
f.偽類 + transform 實現
對於老項目,有沒有什么辦法能兼容1px的尷尬問題了,個人認為偽類+transform是比較完美的方法了。
原理是把原先元素的 border 去掉,然后利用 :before 或者 :after 重做 border ,並 transform 的 scale 縮小一半,原先的元素相對定位,新做的 border 絕對定位。
單條border樣式設置:
.scale-1px{ position: relative; border:none; } .scale-1px:after{ content: ''; position: absolute; bottom: 0; background: #000; width: 100%; height: 1px; -webkit-transform: scaleY(0.5); transform: scaleY(0.5); -webkit-transform-origin: 0 0; transform-origin: 0 0; }
四條boder樣式設置:
.scale-1px{ position: relative; margin-bottom: 20px; border:none; } .scale-1px:after{ content: ''; position: absolute; top: 0; left: 0; border: 1px solid #000; -webkit-box-sizing: border-box; box-sizing: border-box; width: 200%; height: 200%; -webkit-transform: scale(0.5); transform: scale(0.5); -webkit-transform-origin: left top; transform-origin: left top; }
最好在使用前也判斷一下,結合 JS 代碼,判斷是否 Retina 屏:
if(window.devicePixelRatio && devicePixelRatio >= 2){ document.querySelector('ul').className = 'scale-1px'; }
2.如何解決跨域的問題
1. JSONP
利用 <script> 標簽沒有跨域限制的漏洞,網頁可以得到從其他來源動態產生的 JSON 數據。JSONP 請求一定需要對方的服務器做支持才可以。
JSONP 優點是簡單兼容性好,可用於解決主流瀏覽器的跨域數據訪問的問題。缺點是僅支持 get 方法具有局限性,不安全可能會遭受 XSS 攻擊。
2.cors
CORS 需要瀏覽器和后端同時支持。IE 8 和 9 需要通過 XDomainRequest 來實現。
瀏覽器會自動進行 CORS 通信,實現 CORS 通信的關鍵是后端。只要后端實現了 CORS,就實現了跨域。
服務端設置 Access-Control-Allow-Origin 就可以開啟 CORS。 該屬性表示哪些域名可以訪問資源,如果設置通配符則表示所有網站都可以訪問資源。
雖然設置 CORS 和前端沒什么關系,但是通過這種方式解決跨域問題的話,會在發送請求時出現兩種情況,分別為簡單請求和復雜請求。
3.postMessage
postMessage 是 HTML5 XMLHttpRequest Level 2 中的 API,且是為數不多可以跨域操作的 window 屬性之一,它可用於解決以下方面的問題:
- 頁面和其打開的新窗口的數據傳遞
- 多窗口之間消息傳遞
- 頁面與嵌套的 iframe 消息傳遞
- 上面三個場景的跨域數據傳遞
postMessage()方法允許來自不同源的腳本采用異步方式進行有限的通信,可以實現跨文本檔、多窗口、跨域消息傳遞。
otherWindow.postMessage(message, targetOrigin, [transfer]);
- message: 將要發送到其他 window 的數據。
- targetOrigin:通過窗口的 origin 屬性來指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示無限制)或者一個 URI。在發送消息的時候,如果目標窗口的協議、主機地址或端口這三者的任意一項不匹配 targetOrigin 提供的值,那么消息就不會被發送;只有三者完全匹配,消息才會被發送。
- transfer(可選):是一串和 message 同時傳遞的 Transferable 對象. 這些對象的所有權將被轉移給消息的接收方,而發送一方將不再保有所有權。
4.Websocket
Websocket 是 HTML5 的一個持久化的協議,它實現了瀏覽器與服務器的全雙工通信,同時也是跨域的一種解決方案。WebSocket 和 HTTP 都是應用層協議,都基於 TCP 協議。但是 WebSocket 是一種雙向通信協議,在建立連接之后,WebSocket 的 server 與 client 都能主動向對方發送或接收數據。同時,WebSocket 在建立連接時需要借助 HTTP 協議,連接建立好了之后 client 與 server 之間的雙向通信就與 HTTP 無關了。
原生 WebSocket API 使用起來不太方便,我們使用Socket.io,它很好地封裝了 WebSocket 接口,提供了更簡單、靈活的接口,也對不支持 WebSocket 的瀏覽器提供了向下兼容。
5. Node 中間件代理(兩次跨域)
實現原理:同源策略是瀏覽器需要遵循的標准,而如果是服務器向服務器請求就無需遵循同源策略。
代理服務器,需要做以下幾個步驟:
- 接受客戶端請求 。
- 將請求轉發給服務器。
- 拿到服務器響應數據。
- 將響應轉發給客戶端。
6.nginx 反向代理
實現原理類似於 Node 中間件代理,需要你搭建一個中轉 nginx 服務器,用於轉發請求。
使用 nginx 反向代理實現跨域,是最簡單的跨域方式。只需要修改 nginx 的配置即可解決跨域問題,支持所有瀏覽器,支持 session,不需要修改任何代碼,並且不會影響服務器性能。
實現思路:通過 nginx 配置一個代理服務器(域名與 domain1 相同,端口不同)做跳板機,反向代理訪問 domain2 接口,並且可以順便修改 cookie 中 domain 信息,方便當前域 cookie 寫入,實現跨域登錄。
7.window.name + iframe
window.name屬性的獨特之處:name 值在不同的頁面(甚至不同域名)加載后依舊存在,並且可以支持非常長的 name 值(2MB)。
通過 iframe 的 src 屬性由外域轉向本地域,跨域數據即由 iframe 的 window.name從外域傳遞到本地域。這個就巧妙地繞過了瀏覽器的跨域訪問限制,但同時它又是安全操作。
8.location.hash + iframe
實現原理: a.html 欲與 c.html 跨域相互通信,通過中間頁 b.html 來實現。 三個頁面,不同域之間利用 iframe 的 location.hash 傳值,相同域之間直接 js 訪問來通信。
具體實現步驟:一開始 a.html 給 c.html 傳一個 hash 值,然后 c.html 收到 hash 值后,再把 hash 值傳遞給 b.html,最后 b.html 將結果放到 a.html 的 hash 值中。
9.document.domain + iframe
該方式只能用於二級域名相同的情況下,比如 a.test.com 和 b.test.com 適用於該方式。
只需要給頁面添加 document.domain ='test.com' 表示二級域名都相同就可以實現跨域。
實現原理:兩個頁面都通過 js 強制設置 document.domain 為基礎主域,就實現了同域。
3.路由的動態加載
在Vue項目中,一般使用vue-cli構建項目后,我們會在Router文件夾下面的index.js里面引入相關的路由組件,如:
import Hello from '@/components/Hello' import Boy from '@/components/Boy' import Girl from '@/components/Girl'
這樣做的結果就是webpack在npm run build的時候會打包成一個整個的js文件,如果頁面一多,會導致這個文件非常大,加載緩慢,為了解決這個問題,需要將他分成多個小文件,而且還要實現異步按需加載,即用到了再加載,而不用一股腦全部加載。
1.webpack提供的require.ensure(),這樣可以實現按需加載,並且你可以將多個相同類的組件打包成一個文件,只要給他們指定相同的chunkName即可,如示例中的demo將會打包成一個文件。
{
path:
'/promisedemo'
,
name:
'PromiseDemo'
,
component: r => require.ensure([], () => r(require(
'../components/PromiseDemo'
)),
'demo'
)
},
{
path:
'/hello'
,
name:
'Hello'
,
// component: Hello
component: r => require.ensure([], () => r(require(
'../components/Hello'
)),
'demo'
)
}
|
2.Vue的異步組件技術,這種方法可以實現按需加載,並且一個組件會打包成一個js文件
{
path:
'/promisedemo'
,
name:
'PromiseDemo'
,
component: resolve => require([
'../components/PromiseDemo'
], resolve)
}
|
3.es提案的import(),也是我推薦的方法
首先,可以將異步組件定義為返回一個 Promise 的工廠函數 (該函數返回的 Promise 應該 resolve 組件本身):
const Foo = () => Promise.resolve({
/* 組件定義對象 */
})
|
第二,在 Webpack 2 中,我們可以使用動態 import語法來定義代碼分塊點 (split point):
import(
'./Foo.vue'
)
// 返回 Promise
|
注意:如果您使用的是 Babel,你將需要添加 syntax-dynamic-import 插件,才能使 Babel 可以正確地解析語法。
結合這兩者,這就是如何定義一個能夠被 Webpack 自動代碼分割的異步組件。
const Foo = () => import(
'./Foo.vue'
)
|
const Foo = () => import(
/* webpackChunkName: "group-foo" */
'./Foo.vue'
)
|
4.怎么實現對象的深拷貝
/*
this的使用:在預編譯的過程this指向是window
在全局作用域里this指向是window
call/apply 改變this的指向
obj.function();function()里面的this指向的是obj
*/
var
obj = {
a:
function
() {
console.log(
this
.name)
},
name:
'123'
}
obj.a();
//誰調用這個方法this指向誰,沒用調用就是預編譯
var
foo = 123;
function
print() {
this
.foo = 234;
console.log(foo);
}
/*print();*/
//234
new
print();
//123
/*
arguments.callee:指向函數的引用,也就是它本身
*/
var
num = (
function
(n) {
if
(n == 1) {
return
1;
}
else
{
return
n * arguments.callee(n - 1);
}
}(10))
/*深層拷貝*/
/*
遍歷對象 for (var prop in obj)
1:判斷是不是原始值 typeof()
2:判斷是數組還是對象
3:判斷相應的數組和對象
*/
var
obj = {
name:
'tom'
,
age: 23,
son: {},
wife: [
'sss'
,
'ddd'
]
}
function
deepClone(origin, target) {
//容錯
var
target = target || {},
toStr = Object.prototype.toString(),
arrStr =
"[Object Array]"
;
for
(
var
prop
in
origin){
if
(origin.hasOwnProperty(prop)){
if
(origin[prop] !==
'null'
&&
typeof
(origin[prop]) ==
'object'
){
if
( toStr.call(origin[prop]) == arrStr){
target[prop]= [];
}
else
{
target[prop] ={};
}
deepClone(origin[prop],target[prop]);
}
else
{
target[prop] == origin[prop];
}
}
}
}
|
5.什么叫優雅降級和漸進增強
優雅降級:
Web站點在所有新式瀏覽器中都能正常工作,如果用戶使用的是老式瀏覽器,則代碼會檢查以確認它們是否能正常工作。由於IE獨特的盒模型布局問題,針對不同版本的IE的hack實踐過優雅降級了,為那些無法支持功能的瀏覽器增加候選方案,使之在舊式瀏覽器上以某種形式降級體驗卻不至於完全失效。
漸進增強:
從被所有瀏覽器支持的基本功能開始,逐步地添加那些只有新式瀏覽器才支持的功能,向頁面增加無害於基礎瀏覽器的額外樣式和功能的。當瀏覽器支持時,它們會自動地呈現出來並發揮作用。
.transition {
/*漸進增強寫法*/
-webkit-transition: all .5s;
-moz-transition: all .5s;
-o-transition: all .5s;
transition: all .5s;
}
.transition {
/*優雅降級寫法*/
transition: all .5s;
-o-transition: all .5s;
-moz-transition: all .5s;
-webkit-transition: all .5s;
}
|
前綴CSS3(-webkit- / -moz- / -o-*)和正常的 CSS3 在瀏覽器中的支持情況是這樣的:
- 很久以前:瀏覽器前綴CSS3和正常的CSS3都不支持。
- 不久以前:瀏覽器只支持前綴CSS3,不支持正常CSS3。
- 現在:瀏覽器既支持前綴CSS3,又支持CSS3。
- 未來:瀏覽器不支持前綴CSS3,僅支持正常CSS3。
注意: css中需要知道的是 - 如果屬性不可用,則不發揮任何作用,無影響;如果屬性是相同的作用,則后者會覆蓋前者。
漸進增強的寫法,優先考慮老版本瀏覽器的可用性,最后才考慮新版本瀏覽器的可用性。
而優雅降級的寫法,優先考慮新版本瀏覽器的可用性,最后才考慮瀏覽器的可用性。
就CSS3來說,我們更加推薦漸進增強的寫法。
6.promise、async有什么區別(對async、await的理解,內部原理、介紹下Promise,內部實現)
7.介紹AST(Abstract Syntax Tree)抽象語法樹
8.什么是防抖和節流?有什么區別?如何實現?
防抖
觸發高頻事件后n秒內函數只會執行一次,如果n秒內高頻事件再次被觸發,則重新計算時間
思路:
每次觸發事件時都取消之前的延時調用方法
function
debounce(fn) {
let timeout =
null
;
// 創建一個標記用來存放定時器的返回值
return
function
() {
clearTimeout(timeout);
// 每當用戶輸入的時候把前一個 setTimeout clear 掉
timeout = setTimeout(() => {
// 然后又創建一個新的 setTimeout, 這樣就能保證輸入字符后的 interval 間隔內如果還有字符輸入的話,就不會執行 fn 函數
fn.apply(
this
, arguments);
}, 500);
};
}
function
sayHi() {
console.log(
'防抖成功'
);
}
var
inp = document.getElementById(
'inp'
);
inp.addEventListener(
'input'
, debounce(sayHi));
// 防抖
|
節流
高頻事件觸發,但在n秒內只會執行一次,所以節流會稀釋函數的執行頻率
思路:
每次觸發事件時都判斷當前是否有等待執行的延時函數
function
throttle(fn) {
let canRun =
true
;
// 通過閉包保存一個標記
return
function
() {
if
(!canRun)
return
;
// 在函數開頭判斷標記是否為true,不為true則return
canRun =
false
;
// 立即設置為false
setTimeout(() => {
// 將外部傳入的函數的執行放在setTimeout中
fn.apply(
this
, arguments);
// 最后在setTimeout執行完畢后再把標記設置為true(關鍵)表示可以執行下一次循環了。當定時器沒有執行的時候標記永遠是false,在開頭被return掉
canRun =
true
;
}, 500);
};
}
function
sayHi(e) {
console.log(e.target.innerWidth, e.target.innerHeight);
}
window.addEventListener(
'resize'
, throttle(sayHi));
|
9.vue-router有哪幾種導航鈎子
- 全局導航鈎子
- router.beforeEach(to, from, next),
- router.beforeResolve(to, from, next),
- router.afterEach(to, from ,next)
- 組件內鈎子
- beforeRouteEnter,
- beforeRouteUpdate,
- beforeRouteLeave
- 單獨路由獨享組件
- beforeEnter
10.Vue的雙向數據綁定原理是什么?
vue.js 是采用數據劫持結合發布者-訂閱者模式的方式,通過 Object.defineProperty()來劫持各個屬性的 setter,getter,在數據變動時發布消息給訂閱者,觸發相應的監聽回調。
具體步驟:
第一步:需要 observe 的數據對象進行遞歸遍歷,包括子屬性對象的屬性,都加上 setter 和 getter 這樣的話,給這個對象的某個值賦值,就會觸發 setter,那么就能監聽到了數據變化
第二步:compile 解析模板指令,將模板中的變量替換成數據,然后初始化渲染頁面視圖,並將每個指令對應的節點綁定更新函數,添加監聽數據的訂閱者,一旦數據有變動,收到通知,更新視圖
第三步:Watcher 訂閱者是 Observer 和 Compile 之間通信的橋梁,主要做的事情是:
- 在自身實例化時往屬性訂閱器(dep)里面添加自己
- 自身必須有一個 update()方法
- 待屬性變動 dep.notice()通知時,能調用自身的 update() 方法,並觸發 Compile 中綁定的回調,則功成身退。
第四步:MVVM 作為數據綁定的入口,整合 Observer、Compile 和 Watcher 三者,通過 Observer 來監聽自己的 model 數據變化,通過 Compile 來解析編譯模板指令,最終利用 Watcher 搭起 Observer 和 Compile 之間的通信橋梁,達到數據變化 -> 視圖更新;視圖交互變化(input) -> 數據 model 變更的雙向綁定效果。
11.請詳細說下你對vue生命周期的理解?
總共分為 8 個階段創建前/后,載入前/后,更新前/后,銷毀前/后。
- 創建前/后: 在 beforeCreate 階段,vue 實例的掛載元素 el 還沒有。
- 載入前/后:在 beforeMount 階段,vue 實例的$el 和 data 都初始化了,但還是掛載之前為虛擬的 dom 節點,data.message 還未替換。在 mounted 階段,vue 實例掛載完成,data.message 成功渲染。
- 更新前/后:當 data 變化時,會觸發 beforeUpdate 和 updated 方法。
- 銷毀前/后:在執行 destroy 方法后,對 data 的改變不會再觸發周期函數,說明此時 vue 實例已經解除了事件監聽以及和 dom 的綁定,但是 dom 結構依然存在
12.客戶端存儲
1.常用的客戶端存儲方法有哪些?
平時前端開發中用到最多的是cookie、sessionStorage、localStorage,也有少量的業務場景會使用indexedDB。
2.cookie、sessionStorage 和 localStorage的區別
存儲時效來說:
- cookie可以手動設置失效期,默認為會話級
- sessionStorage的存儲時長是會話級
- localStorage的存儲時長是永久,除非用戶手動利用瀏覽器的工具刪除
訪問的局限性:
- cookie可以設置路徑path,所有他要比另外兩個多了一層訪問限制
- localStorage和sessionStorage的訪問限制是文檔源級別,即協議、主機名和端口
- 還要注意的是,cookie可以通過設置domain屬性值,可以不同二級域名下共享cookie,而Storage不可以,比如http://image.baidu.com的cookie http://map.baidu.com是可以訪問的,前提是Cookie的domain設置為".http://baidu.com",而Storage是不可以的(這個很容易實驗,就不細說了)
存儲大小限制:
- cookie適合存儲少量數據,他的大小限制是個數進行限制,每個瀏覽器的限制數量不同
- Storage的可以存儲數據的量較大,此外他是通過占用空間大小來做限制的,每個瀏覽器的實現也是不同的,大家可以看這篇文章來進一進步了解Web Storage Support Test
操作方法:
- cookie是作為document的屬性存在,並沒有提供標准的方法來直接操作cookie
- Storage提供了setItem()和getItem()還有removeItem()方法,操作方便不易出錯
其他:
- cookie在發送http請求時,會將本地的cookie作為http頭部信息傳遞給服務器
- cookie可以由服務器通過http來設定
3.cookie由哪些部分組成?
除了基礎的鍵值對外,cookie還有下面的屬性:
- Expires :cookie最長有效期
- Max-Age:在 cookie 失效之前需要經過的秒數。(當Expires和Max-Age同時存在時,文檔中給出的是已Max-Age為准,可是我自己用Chrome實驗的結果是取二者中最長有效期的值)
- Domain:指定 cookie 可以送達的主機名。
- Path:指定一個 URL 路徑,這個路徑必須出現在要請求的資源的路徑中才可以發送 Cookie 首部
- Secure:一個帶有安全屬性的 cookie 只有在請求使用SSL和HTTPS協議的時候才會被發送到服務器。
- HttpOnly:設置了 HttpOnly 屬性的 cookie 不能使用 JavaScript 經由
Document.cookie屬性、XMLHttpRequest和RequestAPIs 進行訪問,以防范跨站腳本攻擊(XSS)。
4.如何用原生JS方法來操作cookie
上面已經說過了,在瀏覽器中cookie做為document的一個屬性存在,並沒有提供原生的操作方法,並且所有形式都以字符串拼接的形式存儲,需要開發利用字符串操作的方法來操作document.cookie,從而達到操作客戶端cookie的目的。
想操作cookie,必須知道document.cookie中存儲的字符串是怎樣的結構。
document.cookie返回的結構大概如下面的樣子:
name1=value1; name2=value2; name1=value3
即:document.cookie返回當前頁面可用的(根據cookie的域、路徑、失效時間和安全設置)所有cookie的字符串,一系列由分號隔開的名值對兒。
當想設置cookie時,可以直接對document.cookie賦值,對document.cookie賦值並不會覆蓋掉cookie,除非設置的cookie已經存在。設置cookie的格式如下,和Set-Cookie頭中的使用的格式是一樣的。
name=value; expires=expiration_time; path=domain_path; domain=domain_name; secure
上面這些參數中,只有cookie中的名字和值是必須的。下面是一個簡單的例子:
document.cookie = 'name=Roy';
此外,需要注意的是設置值時需要對於屬性和值都用encodeURIComponent()來保證它不包含任何逗號、分號或空格(cookie值中禁止使用這些值).
5.在Hybrid環境下(混合應用),使用客戶端存儲應該注意哪些?
在混合應用中,主要是注意會話級存儲——sessionStorage。
因為混合應用中的webview從一個頁面跳轉的另一個頁面時,會話並沒有像瀏覽器中那樣是繼承延續的,也就是說,當在A頁面中設置的了sessionStorage值后跳轉的下一個頁面時,這是sessionStorage是全新的,根本獲取不到A頁面中設置的任何sessionStorage。
所以如果你們的app開發者還沒有解決這個問題的話,建議這時使用session級別的cookie來代替sessionStorage,因為cookie是可以跨標簽訪問的,不要會話連續。
6.sessionStorage和localStorage存儲的數據類型是什么?
sessionStorage和localStorage只能存儲字符串類型的數據,如果setItem()方法傳入的數據不是字符串的話,會自動轉換為字符串類型再進行存儲。所以在存儲之前應該使用JSON.stringfy()方法先進行一步安全轉換字符串,取值時再用JSON.parse()方法再轉換一次。
7.session級存儲中,session cookie和sessionStorage有哪些區別?
詳細的分析可以看我之前的文章——同樣是客戶端會話級存儲,sessionStorage和session cookie有什么?
大體的概括就是說:
- sessionStorage的會話基於標簽,即標簽關閉則會話終止,而cookie基於瀏覽器進程。
- sessionStorage的訪問必須基於會話繼承和延續,即只有在當前標簽下或當前標簽打開的標簽下可以訪問sessionStorage中的數據,而cookie是可以跨標簽進行訪問的。
13.前端路由的模式有幾種
1.history 模式
HTML5規范提供了history.pushState和history.replaceState來進行路由控制。通過這兩個方法可以改變url且不向服務器發送請求。同時不會像hash有一個#,更加的美觀。但是history路由需要服務器的支持,並且需將所有的路由重定向倒根頁面。
已經有 hash 模式了,而且 hash 能兼容到IE8, history 只能兼容到 IE10,為什么還要搞個 history 呢?
首先,hash 本來是拿來做頁面定位的,如果拿來做路由的話,原來的錨點功能就不能用了。其次,hash 的傳參是基於 url 的,如果要傳遞復雜的數據,會有體積的限制,而 history 模式不僅可以在url里放參數,還可以將數據存放在一個特定的對象中。
vue-router默認hash模式,使用 URL 的 hash 來模擬一個完整的 URL,於是當 URL 改變時,頁面不會重新加載。
不過這種模式要玩好,還需要后台配置支持。因為我們的應用是個單頁客戶端應用,如果后台沒有正確的配置,當用戶在瀏覽器直接訪問 http://oursite.com/user/id 就會返回 404,這就不好看了。
所以呢,你要在服務端增加一個覆蓋所有情況的候選資源:如果 URL 匹配不到任何靜態資源,則應該返回同一個index.html 頁面,這個頁面就是你 app 依賴的頁面。
相關API:
window.history.pushState(state, title, url) // state:需要保存的數據,這個數據在觸發popstate事件時,可以在event.state里獲取 // title:標題,基本沒用,一般傳 null // url:設定新的歷史記錄的 url。新的 url 與當前 url 的 origin 必須是一樣的,否則會拋出錯誤。url可以是絕對路徑,也可以是相對路徑。 //如 當前url是 https://www.baidu.com/a/,執行history.pushState(null, null, './qq/'),則變成 https://www.baidu.com/a/qq/, //執行history.pushState(null, null, '/qq/'),則變成 https://www.baidu.com/qq/ window.history.replaceState(state, title, url) // 與 pushState 基本相同,但她是修改當前歷史記錄,而 pushState 是創建新的歷史記錄 window.addEventListener("popstate", function() { // 監聽瀏覽器前進后退事件,pushState 與 replaceState 方法不會觸發 }); window.history.back() // 后退 window.history.forward() // 前進 window.history.go(1) // 前進一步,-2為后退兩步,window.history.lengthk可以查看當前歷史堆棧中頁面的數量
2.hash 模式
這里的hash是指url尾巴后的#號及后面的字符。這里的#和css里的#是一個意思。hash也稱作錨點,本身是用來做頁面定位的,她可以使對應id的元素顯示在可是區域內。由於hash值變化不會導致瀏覽器向服務器發出請求,而且hash改變會觸發hashchange事件,瀏覽器的進后退也能對其進行控制,所以人們在 html5 的 history 出現前,基本都是使用 hash 來實現前端路由的。
改變#不觸發網頁加載
http://www.example.com/index.html#location1 // 改成 http://www.example.com/index.html#location
瀏覽器不會重新向服務器請求index.html
使用到的api:
window.location.hash = 'qq' // 設置 url 的 hash,會在當前url后加上 '#qq' var hash = window.location.hash // '#qq' window.addEventListener('hashchange', function(){ // 監聽hash變化,點擊瀏覽器的前進后退會觸發 })
14.flex布局
參考鏈接: https://blog.csdn.net/weixin_34206899/article/details/87992902
參考鏈接: https://www.jianshu.com/p/b591eee50568
15.原型鏈
1.什么是構造函數
構造函數的本質是一個普通函數,他的特點是需要通過new關鍵字來調用,用來創建對象的實例。所有的引用類型,如[],{},function等都是由構造函數實例化而來。一般首字母大寫。
解析:首字母大寫只是約定俗成的規范。首字母小寫的函數也可以用作構造函數。
2.什么是原型和原型鏈
原型模式是JS實現繼承的一種方式。所有的函數都有一個prototype屬性,通過new生成一個對象時,prototype會被實例化為對象的屬性。所有的引用類型都有一個__proto__指向其構造函數的prototype。原型鏈的話,指的就是當訪問一個引用類型時,如果本身沒有這個屬性或方法,就會通過__proto__屬性在父級的原型中找,一級一級往上,直到最頂層為止。
解析:原型鏈最頂層Object的prototype的__proto__指向為null。
3.如何理解 constructor 屬性
答:所有函數的原型對象都有一個constructor屬性指向函數本身。
解析:實例化的對象可以通過[].__proto__.constructor獲取到其構造函數。
4.描述new 操作符的執行過程
- 創建一個空對象。
- 將這個空對象的
__proto__指向構造函數的prototype。 - 將構造函數的
this指向這個對象。 - 執行構造函數中的代碼。
5.如何判斷一個變量是數組類型
答: 使用instanceof關鍵字 或者constructor屬性。
解析:instanceof的原理是判斷操作符左邊對象的原型鏈上是否有右邊構造函數的prototype屬性。
關於構造函數和原型
構造函數:相當於java中“類”的存在,如原生JS中的Array, Function, String, Date等等,都是構造函數。例如new Date()通過new操作符進行調用,用來創建一個Date對象的實例。
一個便於理解的栗子,描述js通過原型模式實現繼承的過程
function Animal (name) { // 構造函數 this.name = name } Animal.prototype.type = 'animal' // 原型上的屬性和方法可以被繼承 Animal.prototype.eat = function () { console.log('eat') } let dog = new Animal('忠犬八公') // 通過new 調用構造函數創建Animal的實例dog console.log(dog.name) // 輸出:忠犬八公 console.log(dog.type) // 輸出:animal dog.eat() // 輸出:eat console.log(dog.__proto__) // 輸出:{ type:'animal', eat: f, __proto__: ...} // dog.__proto__ 指向其構造函數Animal的prototype對象
一個關於原型的實用型例子
function Elem(id) { this.elem = document.getElementById(id) } Elem.prototype.html = function (val) { var elem = this.elem if (val) { elem.innerHTML = val return this // 鏈式編程 }else{ return elem.innerHTML } } Elem.prototype.on = function (type, fn) { var elem = this.elem elem.addEventListener(type, fn) } var div1 = new Elem('div1') div1.html('灶門碳治郎').on('click', (e) => { alert('灶門碳治郎') })
這個栗子,使用原型將對dom節點的操作封裝起來,只要創建一個Elem實例就輕松插入dom和添加事件監聽。
原型鏈圖

所有的引用類型會有一個__proto__屬性指向其構造函數的prototype,當訪問這個引用類型的變量和方法時,會通過__proto__屬性一層層往上找。如[]不止有構造函數Array原型上的方法,還有可以通過原型鏈找到Object原型上的方法。
關於instanceof 和 constructor
instanceof:判斷操作符右邊的參數是否在左邊的原型鏈上。所以[] instanceof Object也為true
let obj = {} let arr = [] console.log(typeof(obj)) // object console.log(typeof(arr)) // object console.log(obj instanceof Array) // false console.log(arr instanceof Array) // true console.log(obj.constructor === Array) // false console.log(arr.constructor === Array) // true
通過以上代碼可以學習通過instanceof關鍵字和constructor 屬性進行數據類型判斷的使用方式。
關於鏈式編程
上述“一個關於原型的實用例子”中,提到了鏈式編程,在此做簡單介紹
function Dog(){ this.run = function(){ alert('dog is run...') return this // 鏈式編程的關鍵 } this.eat = function(){ alert('dog is eat...') return this } this.sleep = function(){ alert('dog is sleep...') return this } } var d1 = new Dog() d1.run().eat().sleep()
通過以上代碼可以看出
- 鏈式編程的設計模式就是,調用的函數的時候,可以基於其返回值繼續調用其他方法。
- 關鍵在於方法執行結束后需要有一個供繼續調用的返回值,如
this等。
16.ES6數組操作:some、every、find、filter、map、forEach有什么區別

some
- 不創建新數組
- 不改變原數組
- 輸出的是判斷為true則馬上跳出循環並return成true
- 回調函數參數,item(數組元素)、index(序列)、arr(數組本身)
- 使用return操作輸出,會循環數組每一項,並在回調函數中操作
//計算對象數組中每個電腦的操作系統是否可用, //大於16位操作系統表示可用,否則不可用 var computers = [ { name: "Apple", ram: 8 }, { name: "IBM", ram: 4 }, { name: "Acer", ram: 32 }, ]; var some = computers.some(function (computer) { return computer.ram > 16; }); console.log(some);//true console.log(computers);//[{ name: "Apple", ram: 8 },{ name: "IBM", ram: 4 },{ name: "Acer", ram: 32 }]
every(與some相反)
- 不創建新數組
- 不改變原數組
- 輸出的是判斷為false則馬上跳出循環並return成false
- 回調函數參數,item(數組元素)、index(序列)、arr(數組本身)
- 使用return操作輸出,會循環數組每一項,並在回調函數中操作
var computers = [ { name: "Apple", ram: 8 }, { name: "IBM", ram: 4 }, { name: "Acer", ram: 32 }, ]; var every = computers.every(function (computer) { return computer.ram > 16; }); console.log(every);//false
find
- 不創建新數組
- 不改變原數組
- 輸出的是一旦判斷為true則跳出循環輸出符合條件的數組元素
- 回調函數參數,item(數組元素)、index(序列)、arr(數組本身)
- 使用return操作輸出,會循環數組每一項,並在回調函數中操作
//假定有一個對象數組,找到符合條件的對象 var users = [ { name: 'Jill' }, { name: 'Alex', id: 1 }, { name: 'Bill' }, { name: 'Alex' }, ]; var user = users.find(function (user) { return user.name === 'Alex'; }); console.log(user);//[{ name: 'Alex', id: 1 }] //假定有一個對象數組(A),根據指定對象的條件找到數組中符合條件的對象 var posts = [ { id: 1, title: "Node.js" }, { id: 2, title: "React.js" }, ]; var comment = { postId: 1, content: 'hello' }; function postForComment(posts, comment) { return posts.find(function (post) { return post.id === comment.postId }) }; console.log(postForComment(posts,comment));//{ id: 1, title: "Node.js" }
filter
- 創建新數組
- 不改變原數組
- 輸出的是判斷為true的數組元素形成的新數組
- 回調函數參數,item(數組元素)、index(序列)、arr(數組本身)
- 使用return操作輸出,會循環數組每一項,並在回調函數中操作
//假定有一個對象數組(A),獲取數組中指定類型的對象放到B數組中 var products = [ { name: "cucumber", type: "vegetable" }, { name: "banana", type: "fruit" }, { name: "celery", type: "vegetable" }, { name: "orange", type: "fruit" }, ]; var filtered = products.filter(function (product) { return product.type === "vegetable" }); console.log(filtered);//[{ name: "celery", type: "vegetable" }, { name: "celery", type: "vegetable" }] //假定有一個對象數組(A),過濾掉不滿足以下條件的對象 //條件:蔬菜 數量大於0 價格小於10 var products = [ { name: "cucumber", type: "vegetable", quantity: 0, price: 1 }, { name: "banana", type: "fruit", quantity: 10, price: 16 }, { name: "celery", type: "vegetable", quantity: 30, price: 8 }, { name: "orange", type: "fruit", quantity: 3, price: 6 }, ]; products = products.filter(function (product) { return product.type === 'vegetable' && product.quantity > 0 && product.price < 10 }); console.log(products);//[{ name: "celery", type: "vegetable", quantity: 30, price: 8 }] console.log(products)//[{ name: "cucumber", type: "vegetable" },{ name: "banana", type: "fruit" },{ name: "celery", type: "vegetable" },{ name: "orange", type: "fruit" }]
map
- 創建新數組
- 不改變原數組
- 輸出的是return什么就輸出什么新數組
- 回調函數參數,item(數組元素)、index(序列)、arr(數組本身)
- 使用return操作輸出,會循環數組每一項,並在回調函數中操作
//假定有一個數值數組(A),將A數組中的值以雙倍的形式放到B數組 var numbers = [1, 2, 3, 4, 5]; var doubled = numbers.map(function (number) { return number * 2; }) console.log(doubled);//[2,4,6,8,10] //假定有一個對象數組(A),將A數組中的對象某個屬性的值存儲到B數組中 var cars = [ { model: 'Buick', price: 'cheap' }, { model: 'BMW', price: 'expensive' }, ]; var prices = cars.map(function (car) { return car.price; }); console.log(prices)//(2) ["cheap", "expensive"]
reduce
- 創建新數組
- 不改變原數組
- 輸出的是return疊加什么就輸出什么 新數組
- 使用return操作輸出,會循環數組每一項,並在回調函數中操作
- 回調函數參數
-
- pre(第一次為數組第一項,之后為上一操作的結果)
- next(數組的下一項)
- index(next項的序列)
- arr(數組本身)
- 回調函數后的改變第一項參數。(不影響原數組)
//計算數組中所有值的總共 var numbers = [10, 20, 30]; var sum = 0; //reduce第二個參數是sum的值,與上面的sum無關 var sumValue = numbers.reduce(function (sum, number) { return sum += number; }, 0); console.log(sumValue);//60 //使用reduce能同時實現map和filter,reduce能遍歷數組兩遍 const numberss=[10,20,30,40]; const doubleedOver50=numberss.reduce((finalList,num)=>{ num=num*2; if(num>50){ finalList.push(num); } return finalList; },[]); console.log(doubleedOver50);//[60,80] //將數組中對象的某個屬性抽離到另外一個數組中 var primaryColors = [ { color: 'red' }, { color: 'yellow' }, { color: 'blue' }, ]; var color = primaryColors.reduce(function (previous, primaryColor) { previous.push(primaryColor.color); return previous; }, []); console.log(color);//["red", "yellow", "blue"] // 判斷字符串括號是否對稱,遇到(時加1,遇到)時減1 function balancedParens(string) { return !string.split("").reduce(function (previous, char) { if (previous < 0) { return previous };//若")"比"("先出現 if (char == "(") { return ++previous }; if (char == ")") { return --previous }; return previous; }, 0); }; console.log(balancedParens(")((()))"));//false
forEach
遍歷數組全部元素,利用回調函數對數組進行操作,自動遍歷數組.length次數,且無法break中途跳出循環
不支持return操作輸出,return只用於控制循環是否跳出當前循環
因此難操作成新數組,新值
var colors = ['red', 'blue', 'green'] colors.forEach(function (color) { console.log(color);//red blue green })
17.數組去重(ES5/ES6)
方法一
- 利用es6中set容器的特點:set容器是無序不可重復的多個value的集合體,將數組作為參數傳入會自動去重
- 循環遍歷 for(let value of target){},可遍歷數組,set容器,map容器,字符串,偽數組
let arr = [1,2,3,3,4,4,5,6,6,7] let arr1 = arr; arr = []; let set2 = new Set(arr1); for(let i of set2){ arr.push(i); } console.log(arr);// [1, 2, 3, 4, 5, 6, 7]
方法二
Array.from(v) : 將偽數組對象或可遍歷對象轉換為真數組
let arr = [3,3,4,5,6,1,3,5,5,6,1] arr = Array.from(new Set(arr)); console.log(arr);// [3, 4, 5, 6, 1]
方法三
三點運算符內部使用for...of循環
let arr = [3,3,4,5,6,1,3,5,5,6,1] let arr1 = [...new Set(arr)]; console.log(arr1); //[3, 4, 5, 6, 1]
方法四
reduce函數的特點
let arr = [1,2,3,4,4,1] let arr1 = arr.reduce((pre,cur)=>{ if(!pre.includes(cur)){ return pre.concat(cur) }else{ return pre } },[]) console.log(arr1);// [1, 2, 3, 4]
方法五
使用的數組方法:
- arrayObject.slice(start,end:可不傳) ,返回一個新的數組,包含從 start 到 end (不包括該元素)的 arrayObject 中的元素,該方法並不會修改數組,而是返回一個子數組。。
- arrayObject.splice(index,howmany,item1,.....,itemX),方法向/從數組中添加/刪除項目,然后返回被刪除的項目,該方法會改變原數組。
let arr = [3,3,4,5,6,1,3,5,5,6,1] for(let i = 0;i<arr.length-1;i++){ let item = arr[i], args = arr.slice(i+1) if(args.includes(item)){ //① arr[i] = null arr.splice(i,1) i-- } } //① arr = arr.filter(item => item!=null) console.log(arr)// [4, 3, 5, 6, 1]
方法六
該方法是方法五的修改版,思路是取每一項和剩下的作對比,如果剩下的數組存在此項,則將數組最后一項與他替換
let arr = [3, 3, 4, 5, 6, 1, 3, 5, 5, 6, 1] for (let i = 0; i < arr.length; i++) { let item = arr[i], args = arr.slice(i + 1) if (args.includes(item)) { arr[i] = arr[arr.length - 1] arr.length-- i-- } } console.log(arr)
方法七
就是簡單的循環套循環遍歷對比
function way1(arr){ var arr1 = [arr[0]]; for(var i = 1;i<arr.length;i++){ var repeat = false; for(var j = 0;j<arr1.length;j++){ if(arr[i] === arr1[j]){ repeat = true; break; } } if(!repeat){ arr1.push(arr[i]) } } return arr1; }
方法八
先排序,取出新的值與相鄰的值進行對比
function way2(arr){ var arr2 = arr.sort(); var arr3 = [arr2[0]]; for(var i=1; i<arr2.length; i++){ if(arr2[i] !== arr3[arr3.length-1]){ arr3.push(arr2[i]); } } return arr3; }
方法九
利用json對象的特點
function way3(arr){ var obj = {}, arr1 = []; for(var i = 0;i < arr.length;i++){ if(!obj[arr[i]]){ obj[arr[i]] = 1; arr1.push(arr[i]); } } return arr1; }
方法十
利用數組的indexOf方法,調用時,若參數在該數組中存在,則返回該參數在數組中的下標位置。
注:此處參數與數組中的元素對比,嚴格采用“===”進行對比,即不做隱式的數據類型轉換
function way4(arr){ var arr1 = []; for(var i=0; i<arr.length; i++){ if(arr1.indexOf(arr[i]) == -1){ arr1.push(arr[i]); } } return arr1; }
18.項目如何管理模塊
在一個項目內,當有多個開發者一起協作開發時,或者功能越來越多、項目越來越龐大時,保證項目井然有序的進行是相當重要的。
一般會從下面幾點來考證一個項目是否管理得很好:
- 可擴展性:能夠很方便、清晰的擴展一個頁面、組件、模塊
- 組件化:多個頁面之間共用的大塊代碼可以獨立成組件,多個頁面、組件之間共用的小塊代碼可以獨立成公共模塊
- 可閱讀性:閱讀性良好(包括目錄文件結構、代碼結構),能夠很快捷的找到某個頁面、組件的文件,也能快捷的看出項目有哪些頁面、組件
- 可移植性:能夠輕松的對項目架構進行升級,或移植某些頁面、組件、模塊到其他項目
- 可重構性:對某個頁面、組件、模塊進行重構時,能夠保證在重構之后功能不會改變、不會產生新 bug
- 開發友好:開發者在開發某一個功能時,能夠有比較好的體驗(不好的體驗比如:多個文件相隔很遠)
- 協作性:多人協作時,很少產生代碼沖突、文件覆蓋等問題
- 可交接性:當有人要離開項目時,交接給其他人是很方便的
參考鏈接: https://blog.csdn.net/weixin_34185512/article/details/88570900


