前端面試題-基礎


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

 

在devicePixelRatio = 3 時,輸出viewport:
<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' )
這樣做的結果是每個組件都會打包成一個js文件,有時候我們想把某個路由下的所有組件都打包在同個異步塊 (chunk) 中。只需要使用 命名 chunk,一個特殊的注釋語法來提供 chunk name
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 在瀏覽器中的支持情況是這樣的:

  1. 很久以前:瀏覽器前綴CSS3和正常的CSS3都不支持。
  2. 不久以前:瀏覽器只支持前綴CSS3,不支持正常CSS3。
  3. 現在:瀏覽器既支持前綴CSS3,又支持CSS3。
  4. 未來:瀏覽器不支持前綴CSS3,僅支持正常CSS3。

 注意: css中需要知道的是 - 如果屬性不可用,則不發揮任何作用,無影響;如果屬性是相同的作用,則后者會覆蓋前者。

      漸進增強的寫法,優先考慮老版本瀏覽器的可用性,最后才考慮新版本瀏覽器的可用性。

    而優雅降級的寫法,優先考慮新版本瀏覽器的可用性,最后才考慮瀏覽器的可用性。 

   就CSS3來說,我們更加推薦漸進增強的寫法。  

6.promise、async有什么區別(對async、await的理解,內部原理、介紹下Promise,內部實現)

理解 async/await

7.介紹AST(Abstract Syntax Tree)抽象語法樹

AST抽象語法樹

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不可以,比如的cookie 是可以訪問的,前提是Cookie的domain設置為".",而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 和 Request APIs 進行訪問,以防范跨站腳本攻擊(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 操作符的執行過程

  1. 創建一個空對象。
  2. 將這個空對象的__proto__指向構造函數的prototype
  3. 將構造函數的this指向這個對象。
  4. 執行構造函數中的代碼。

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

通過以上代碼可以看出

  1. 鏈式編程的設計模式就是,調用的函數的時候,可以基於其返回值繼續調用其他方法
  2. 關鍵在於方法執行結束后需要有一個供繼續調用的返回值,如this等。

 

16.ES6數組操作:some、every、find、filter、map、forEach有什么區別

some

  1. 不創建新數組
  2. 不改變原數組
  3. 輸出的是判斷為true則馬上跳出循環並return成true
  4. 回調函數參數,item(數組元素)、index(序列)、arr(數組本身)
  5. 使用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相反)

  1. 不創建新數組
  2. 不改變原數組
  3. 輸出的是判斷為false則馬上跳出循環並return成false
  4. 回調函數參數,item(數組元素)、index(序列)、arr(數組本身)
  5. 使用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

  1. 不創建新數組
  2. 不改變原數組
  3. 輸出的是一旦判斷為true則跳出循環輸出符合條件的數組元素
  4. 回調函數參數,item(數組元素)、index(序列)、arr(數組本身)
  5. 使用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

  1. 創建新數組
  2. 不改變原數組
  3. 輸出的是判斷為true的數組元素形成的新數組
  4. 回調函數參數,item(數組元素)、index(序列)、arr(數組本身)
  5. 使用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

  1. 創建新數組
  2. 不改變原數組
  3. 輸出的是return什么就輸出什么新數組
  4. 回調函數參數,item(數組元素)、index(序列)、arr(數組本身)
  5. 使用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

  1. 創建新數組
  2. 不改變原數組
  3. 輸出的是return疊加什么就輸出什么 新數組
  4. 使用return操作輸出,會循環數組每一項,並在回調函數中操作
  5. 回調函數參數
    • 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)

方法一

  1. 利用es6中set容器的特點:set容器是無序不可重復的多個value的集合體,將數組作為參數傳入會自動去重
  2. 循環遍歷 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]

方法五

該方法取每一項和剩下的作對比,如果剩下的數組存在此項,則刪除該項(或變成null再刪除數組中值為null的項 [代碼內注釋①]
使用的數組方法:
  1. arrayObject.slice(start,end:可不傳) ,返回一個新的數組,包含從 start 到 end (不包括該元素)的 arrayObject 中的元素,該方法並不會修改數組,而是返回一個子數組。。
  2. 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM