記錄一下3月底到4月的前端開發工程師面經

b840c72e59dd5b880ed68bb8d10b6c62.jpeg

文章會持續更新css

1.https 原理(加密 證書)

  1. 客戶端使用https的url訪問web服務器,要求與服務器創建ssl鏈接
  2. web服務器收到客戶端請求後,會將網站的證書(包含公鑰)傳送一份給客戶端
  3. 客戶端收到網站證書後會檢查證書的頒發機構以及過時時間,若是沒有問題就隨機產生一個祕鑰
  4. 客戶端利用公鑰將會話祕鑰加密,並傳送給服務端,服務端利用本身的私鑰解密出會話祕鑰
  5. 以後服務器與客戶端使用祕鑰加密傳輸

2. 網絡安全相關 (XSS攻擊和CSRF攻擊原理及防護措施;token認證;cookie和session)

XSS

  1. XSS: Cross Site Scripting攻擊,全稱跨站腳本攻擊.html

    XSS指惡意攻擊者利用網站沒有對用戶提交數據進行轉義處理或者過濾不足的缺點,進而添加一些 代碼,嵌入到web頁面中去。使別的用戶訪問都會執行相應的嵌入代碼。 從而盜取用戶資料、利用用戶身份進行某種動做或者對訪問者進行病毒侵害的一種攻擊方式。前端

  2. XSS攻擊的危害包括:vue

    一、盜取各種用戶賬號,如機器登陸賬號、用戶網銀賬號、各種管理員賬號html5

    二、控制企業數據,包括讀取、篡改、添加、刪除企業敏感數據的能力java

    三、盜竊企業重要的具備商業價值的資料node

    四、非法轉帳webpack

    五、強制發送電子郵件nginx

    六、網站掛馬git

    七、控制受害者機器向其它網站發起攻擊

  3. 緣由解析

    主要緣由:過於信任客戶端提交的數據!

    解決辦法: 不信任客戶端提交的數據,只要是客戶端提交的數據就應該先進行相應的過濾處理後方可進行下一步的操做.

    進一步分析: 客戶端提交的數據自己就是應用所需的,可是噁心攻擊者利用網站對客戶端提提交數據的信任,在數據中插入一些符號以及JavaScript代碼,那麼這些數據就會成爲應用代碼中給的一部分了,那麼攻擊者就能夠肆無忌憚的展開攻擊

    所以咱們絕對不可信任任何客戶端提交的數據

  4. 類型

    持久性(存儲型)和非持久性(反射型)

    持久型: 持久型也就是攻擊的代碼被寫入數據庫中,這個攻擊危害性很大,若是網站訪問量很大 的話,就會致使大量正常訪問的頁面,就會致使大量正常訪問頁面的用戶都受到攻擊。最典型的 就是留言板的XSS攻擊

    非持久型: 非持久型XSS是指發送請求時,XSS代碼出如今請求的URL中,做爲參數提交到服務 器,服務器解析並響應。響應結果中包含XSS代碼,最後瀏覽器解析並執行,從概念上能夠看出, 反射型XSS代碼首先是出如今URL中,而後須要服務端解析,最後須要瀏覽器以後XSS代碼才能攻 擊

  5. 防護方法

    兩種方式用來防護

    1. 轉義字符

    • 首先,對於用戶的輸入應該是永遠不信任的,最廣泛的作法就是轉義輸入輸出的內容,對於括號,尖括號,斜槓進行轉義

    2. CSP

    • 內容安全策略 (CSP) 是一個額外的安全層,用於檢測並削弱某些特定類型的攻擊,包括跨站腳本 (XSS) 和數據注入攻擊等。不管是數據盜取、網站內容污染仍是散發惡意軟件,這些攻擊都是主要的手段

      CSP 的主要目標是減小和報告 XSS 攻擊 ,XSS 攻擊利用了瀏覽器對於從服務器所獲取的內容的信任。惡意腳本在受害者的瀏覽器中得以運行,由於瀏覽器信任其內容來源,即便有的時候這些腳本並不是來自於它本該來的地方。

      CSP經過指定有效域——即瀏覽器承認的可執行腳本的有效來源——使服務器管理者有能力減小或消除XSS攻擊所依賴的載體。一個CSP兼容的瀏覽器將會僅執行從白名單域獲取到的腳本文件,忽略全部的其餘腳本 (包括內聯腳本和HTML的事件處理屬性)。

      做爲一種終極防禦形式,始終不容許執行腳本的站點能夠選擇全面禁止腳本執行

      CSP本質上就是創建白名單,開發者明確告訴瀏覽器哪些外部資源能夠進行加載和執行,咱們只須要配置規則,如何攔截是由瀏覽器本身實現的,咱們能夠經過這種方式來儘可能減小XSS攻擊

    開啓CSP的方式(若是使用CSP)

      1. 你能夠使用 Content-Security-Policy HTTP頭部 來指定你的策略,像這樣:
    設置HTTP Header中的Content-Security-Policy: policy
    複製代碼
      1. 設置meta標籤的方式
    meta http-equiv=「Content-Security-Policy」 content=「default-src ‘self’; img-src https://*; child-src ‘none’;」>
    複製代碼

    常見用例(設置HTTP Header來舉例)

    • 一個網站管理者想要全部內容均來自站點的同一個源 (不包括其子域名) 只容許加載本站資源
    Content-Security-Policy: default-src ‘self’
    複製代碼
    • 一個網站管理者容許內容來自信任的域名及其子域名 (域名沒必要須與CSP設置所在的域名相同)
    Content-Security-Policy: default-src ‘self’ *.trusted.com
    複製代碼
    • 一個網站管理者容許網頁應用的用戶在他們本身的內容中包含來自任何源的圖片, 可是限制音頻或視頻需從信任的資源提供者(得到),全部腳本必須從特定主機服務器獲取可信的代碼.
    Content-Security-Policy: default-src ‘self’; img-src *; media-src media1.com media2.com; script-src userscripts.example.com
    複製代碼
    • 只容許加載HTTPS協議圖片
    Content-Security-Policy: img-src https://*
    複製代碼
    • 容許加載任何來源框架
    Content-Security-Policy: child-src ‘none’
    複製代碼

    對於這種方式來講,這要開發者配置了正確的規則,那麼即便網站存在漏洞,攻擊者也不能執行它的攻擊代碼,並且CSP的兼容性不錯.

CSRF

1 基本概念

CSRF(Cross-site request forgery)跨站請求僞造,也被稱爲「One Click Attack」或者Session Riding,一般縮寫爲CSRF或者XSRF,是一種對網站的惡意利用。儘管聽起來像跨站腳本(XSS),但它與XSS很是不一樣,XSS利用站點內的信任用戶,而CSRF則經過假裝成受信任用戶的請求來利用受信任的網站。與XSS攻擊相比,CSRF攻擊每每不大流行(所以對其進行防範的資源也至關稀少)和難以防範,因此被認爲比XSS更具危險性。

2 原理

原理就是攻擊者構造出一個後端請求地址,誘導用戶點擊或者經過某些途徑自動發起請求。若是用戶是在登陸狀態下的話,後端就覺得是用戶在操做,從而進行相應的邏輯

也就是完成一次CSRF攻擊,受害者必須依次完成兩個步驟: 1 登陸受信任的網站,並在本地生成Cookie 2 在不登出信任網站的狀況下,訪問危險網站

你也許有疑問:若是我不知足以上兩個條件中的一個,我就不會收到CSRF攻擊。 的確如此,可是你不能保證如下狀況的發生: 1 你不能保證你登陸了一個網站後,再也不打開一個tab頁面並訪問其餘頁面 2 你不能保證你關閉瀏覽器後,你的本地Cookie馬上過時,你的上次會話已經結束.(事實上,關閉瀏覽器不能結束一個會話,)

3 危害(CSRF能夠作什麼)

攻擊者盜用了你身份,以你的名義發送惡意請求,CSRF可以作的事情:以你的名義發送郵件,盜取你的帳號甚至是購買商品,虛擬貨幣的轉帳,我的隱私泄露和財產安全

CSRF攻擊是源於WEB的隱式身份驗證機制!WEB的身份驗證機制雖然能夠保證一個請求是來自於某個用戶的瀏覽器,但卻沒法保證該請求是用戶批准發送的!

4 如何防護

防範CSRF攻擊能夠遵循如下幾種規則:

  • GET請求不對數據進行修改
  • 不讓第三方網站訪問到Cookie
  • 阻止第三方網站請求接口
  • 請求時附帶驗證信息,好比驗證碼或者Token

SameSite

  • 能夠對Cookie設置SameSite屬性,該屬性表示Cookie不隨着跨域請求發送,能夠很大程度上減小CSRF的攻擊,可是該屬性目前並非全部瀏覽器都兼容

驗證 Referer HTTP頭部

  • 對於須要防範CSRF的請求,咱們能夠經過驗證Referer來判斷請求是否爲第三方網站發起的.

Token服務端覈對令牌

  • 服務器下發一個服務端覈對令牌隨機Token,每次發送請求時將Token攜帶上,服務器驗證Token是否有效

驗證碼

  • 這個方案的思路是:每次的用戶提交都須要用戶在表單中填寫一個圖片上的隨機字符串,厄…這個方案能夠徹底解決CSRF,但我的以爲在易用性方面彷佛不是太好,還有聽聞是驗證碼圖片的使用涉及了一個被稱爲MHTML的Bug,可能在某些版本的微軟IE中受影響。

3.緩存方式

這個其實就是強緩存和協商緩存的問題,強緩存會直接去取緩存的文件,而協商緩存會去像服務器發送一次確認文檔是否有效的請求。

詳細: mp.weixin.qq.com/s/G5FIrWOts…

通常js、css等靜態資源 走強緩存, html走協商緩存(沒有hash)

4.跨域方式(proxy代理、CORS策略、jsonp腳本跨域、websoket...)

  1. 具體實現
  2. 分別在什麼場景下使用

1. JSONP跨域

jsonp的原理就是利用<script>標籤沒有跨域限制,經過<script>標籤src屬性,發送帶有callback參數的GET請求,服務端將接口返回數據拼湊到callback函數中,返回給瀏覽器,瀏覽器解析執行,從而前端拿到callback函數返回的數據。

jsonp的缺點:只能發送get一種請求。

二、跨域資源共享(CORS)

CORS是一個W3C標準,全稱是"跨域資源共享"(Cross-origin resource sharing)。 它容許瀏覽器向跨源服務器,發出XMLHttpRequest請求,從而克服了AJAX只能同源使用的限制。 CORS須要瀏覽器和服務器同時支持。目前,全部瀏覽器都支持該功能,IE瀏覽器不能低於IE10。

瀏覽器將CORS跨域請求分爲簡單請求和非簡單請求。

只要同時知足一下兩個條件,就屬於簡單請求

(1)使用下列方法之一:

  • head
  • get
  • post

(2)請求的Heder是

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type: 只限於三個值:application/x-www-form-urlencoded、multipart/form-data、text/plain

不一樣時知足上面的兩個條件,就屬於非簡單請求。瀏覽器對這兩種的處理,是不同的。

簡單請求

  對於簡單請求,瀏覽器直接發出CORS請求。具體來講,就是在頭信息之中,增長一個Origin字段。

GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
複製代碼

上面的頭信息中,Origin字段用來講明,本次請求來自哪一個源(協議 + 域名 + 端口)。服務器根據這個值,決定是否贊成此次請求。

CORS請求設置的響應頭字段,都以 Access-Control-開頭:

1)Access-Control-Allow-Origin:必選

  它的值要麼是請求時Origin字段的值,要麼是一個*,表示接受任意域名的請求。

2)Access-Control-Allow-Credentials:可選

  它的值是一個布爾值,表示是否容許發送Cookie。默認狀況下,Cookie不包括在CORS請求之中。設爲true,即表示服務器明確許可,Cookie能夠包含在請求中,一塊兒發給服務器。這個值也只能設爲true,若是服務器不要瀏覽器發送Cookie,刪除該字段便可。

3)Access-Control-Expose-Headers:可選

  CORS請求時,XMLHttpRequest對象的getResponseHeader()方法只能拿到6個基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。若是想拿到其餘字段,就必須在Access-Control-Expose-Headers裏面指定。上面的例子指定,getResponseHeader(‘FooBar’)能夠返回FooBar字段的值。

非簡單請求

  非簡單請求是那種對服務器有特殊要求的請求,好比請求方法是PUT或DELETE,或者Content-Type字段的類型是application/json。非簡單請求的CORS請求,會在正式通訊以前,增長一次HTTP查詢請求,稱爲"預檢"請求(preflight)。

預檢請求

  預檢"請求用的請求方法是OPTIONS,表示這個請求是用來詢問的。請求頭信息裏面,關鍵字段是Origin,表示請求來自哪一個源。除了Origin字段,"預檢"請求的頭信息包括兩個特殊字段。

OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0..
複製代碼

1)Access-Control-Request-Method:必選

  用來列出瀏覽器的CORS請求會用到哪些HTTP方法,上例是PUT。

2)Access-Control-Request-Headers:可選

  該字段是一個逗號分隔的字符串,指定瀏覽器CORS請求會額外發送的頭信息字段,上例是X-Custom-Header。

預檢請求的迴應   服務器收到"預檢"請求之後,檢查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段之後,確認容許跨源請求,就能夠作出迴應。

  HTTP迴應中,除了關鍵的是Access-Control-Allow-Origin字段,其餘CORS相關字段以下:

1)Access-Control-Allow-Methods:必選

  它的值是逗號分隔的一個字符串,代表服務器支持的全部跨域請求的方法。注意,返回的是全部支持的方法,而不單是瀏覽器請求的那個方法。這是爲了不屢次"預檢"請求。

2)Access-Control-Allow-Headers

  若是瀏覽器請求包括Access-Control-Request-Headers字段,則Access-Control-Allow-Headers字段是必需的。它也是一個逗號分隔的字符串,代表服務器支持的全部頭信息字段,不限於瀏覽器在"預檢"中請求的字段。

3)Access-Control-Allow-Credentials:可選

  該字段與簡單請求時的含義相同。

4)Access-Control-Max-Age:可選

  用來指定本次預檢請求的有效期,單位爲秒。

三、nginx代理跨域

 nginx代理跨域,實質和CORS跨域原理同樣,經過配置文件設置請求響應頭Access-Control-Allow-Origin…等字段。

1)nginx配置解決iconfont跨域

  瀏覽器跨域訪問js、css、img等常規靜態資源被同源策略許可,但iconfont字體文件(eot|otf|ttf|woff|svg)例外,此時可在nginx的靜態資源服務器中加入如下配置。

location / {
  add_header Access-Control-Allow-Origin *;
}
複製代碼

2)nginx反向代理接口跨域

跨域問題:同源策略僅是針對瀏覽器的安全策略。服務器端調用HTTP接口只是使用HTTP協議,不須要同源策略,也就不存在跨域問題。

實現思路:經過Nginx配置一個代理服務器域名與domain1相同,端口不一樣)作跳板機,反向代理訪問domain2接口,而且能夠順便修改cookie中domain信息,方便當前域cookie寫入,實現跨域訪問。

nginx具體配置:

#proxy服務器
server {
    listen       81;
    server_name  www.domain1.com;

    location / {
        proxy_pass   http://www.domain2.com:8080;  #反向代理
        proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie裏域名
        index  index.html index.htm;

        # 當用webpack-dev-server等中間件代理接口訪問nignx時,此時無瀏覽器參與,故沒有同源限制,下面的跨域配置可不啓用
        add_header Access-Control-Allow-Origin http://www.domain1.com;  #當前端只跨域不帶cookie時,可爲*
        add_header Access-Control-Allow-Credentials true;
    }
}
複製代碼

四、nodejs中間件代理跨域

node中間件實現跨域代理,原理大體與nginx相同,都是經過啓一個代理服務器,實現數據的轉發,也能夠經過設置cookieDomainRewrite參數修改響應頭中cookie中域名,實現當前域的cookie寫入,方便接口登陸認證。

1)非vue框架的跨域

  使用node + express + http-proxy-middleware搭建一個proxy服務器。

  • 前端代碼:
var xhr = new XMLHttpRequest();

// 前端開關:瀏覽器是否讀寫cookie
xhr.withCredentials = true;

// 訪問http-proxy-middleware代理服務器
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
xhr.send();
複製代碼
  • 中間件服務器代碼:
var express = require('express');
var proxy = require('http-proxy-middleware');
var app = express();

app.use('/', proxy({
    // 代理跨域目標接口
    target: 'http://www.domain2.com:8080',
    changeOrigin: true,

    // 修改響應頭信息,實現跨域並容許帶cookie
    onProxyRes: function(proxyRes, req, res) {
        res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
        res.header('Access-Control-Allow-Credentials', 'true');
    },

    // 修改響應信息中的cookie域名
    cookieDomainRewrite: 'www.domain1.com'  // 能夠爲false,表示不修改
}));

app.listen(3000);
console.log('Proxy server is listen at port 3000...');
複製代碼

2)vue框架的跨域

  node + vue + webpack + webpack-dev-server搭建的項目,跨域請求接口,直接修改webpack.config.js配置。開發環境下,vue渲染服務和接口代理服務都是webpack-dev-server同一個,因此頁面與代理接口之間再也不跨域。

webpack.config.js部分配置:

module.exports = {
    entry: {},
    module: {},
    ...
    devServer: {
        historyApiFallback: true,
        proxy: [{
            context: '/login',
            target: 'http://www.domain2.com:8080',  // 代理跨域目標接口
            changeOrigin: true,
            secure: false,  // 當代理某些https服務報錯時用
            cookieDomainRewrite: 'www.domain1.com'  // 能夠爲false,表示不修改
        }],
        noInfo: true
    }
}
複製代碼

五、document.domain + iframe跨域

此方案僅限主域相同,子域不一樣的跨域應用場景。實現原理:兩個頁面都經過js強制設置document.domain爲基礎主域,就實現了同域。

六、location.hash + iframe跨域

實現原理: a欲與b跨域相互通訊,經過中間頁c來實現。 三個頁面,不一樣域之間利用iframe的location.hash傳值,相同域之間直接js訪問來通訊。

  具體實現:A域:a.html -> B域:b.html -> A域:c.html,a與b不一樣域只能經過hash值單向通訊,b與c也不一樣域也只能單向通訊,但c與a同域,因此c可經過parent.parent訪問a頁面全部對象。

七、window.name + iframe跨域

window.name屬性的獨特之處:name值在不一樣的頁面(甚至不一樣域名)加載後依舊存在,而且能夠支持很是長的 name 值(2MB)。

八、postMessage跨域

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是爲數很少能夠跨域操做的window屬性之一,它可用於解決如下方面的問題:

  • 頁面和其打開的新窗口的數據傳遞
  • 多窗口之間消息傳遞
  • 頁面與嵌套的iframe消息傳遞
  • 上面三個場景的跨域數據傳遞

用法:postMessage(data,origin)方法接受兩個參數:

  • data: html5規範支持任意基本類型或可複製的對象,但部分瀏覽器只支持字符串,因此傳參時最好用JSON.stringify()序列化。
  • origin: 協議+主機+端口號,也能夠設置爲"*",表示能夠傳遞給任意窗口,若是要指定和當前窗口同源的話設置爲"/"。

九、WebSocket協議跨域

WebSocket protocol是HTML5一種新的協議。它實現了瀏覽器與服務器全雙工通訊,同時容許跨域通信,是server push技術的一種很好的實現。 原生WebSocket API使用起來不太方便,咱們使用Socket.io,它很好地封裝了webSocket接口,提供了更簡單、靈活的接口,也對不支持webSocket的瀏覽器提供了向下兼容。

小結

以上就是9種常見的跨域解決方案,jsonp(只支持get請求,支持老的IE瀏覽器)適合加載不一樣域名的js、css,img等靜態資源;CORS(支持全部類型的HTTP請求,但瀏覽器IE10如下不支持)適合作ajax各類跨域請求;Nginx代理跨域和nodejs中間件跨域原理都類似,都是搭建一個服務器,直接在服務器端請求HTTP接口,這適合先後端分離的前端項目調後端接口。document.domain+iframe適合主域名相同,子域名不一樣的跨域請求。postMessage、websocket都是HTML5新特性,兼容性不是很好,只適用於主流瀏覽器和IE10+。

5. TCP三次握手和四次揮手的理解

1、三次握手講解

  • 客戶端發送位碼爲syn=1,隨機產生seq number=1234567的數據包到服務器,服務器由SYN=1知道客戶端要求創建聯機(客戶端:我要鏈接你)
  • 服務器收到請求後要確認聯機信息,向A發送ack number=(客戶端的seq+1),syn=1,ack=1,隨機產生seq=7654321的包(服務器:好的,你來連吧)
  • 客戶端收到後檢查ack number是否正確,即第一次發送的seq number+1,以及位碼ack是否爲1,若正確,客戶端會再發送ack number=(服務器的seq+1),ack=1,服務器收到後確認seq值與ack=1則鏈接創建成功。(客戶端:好的,我來了)

2、爲何http創建鏈接須要三次握手,不是兩次或四次?

答:三次是最少的安全次數,兩次不安全,四次浪費資源;

3、TCP關閉鏈接過程

  1. Client向Server發送FIN包,表示Client主動要關閉鏈接,而後進入FIN_WAIT_1狀態,等待Server返回ACK包。此後Client不能再向Server發送數據,但能讀取數據。

  2. Server收到FIN包後向Client發送ACK包,而後進入CLOSE_WAIT狀態,此後Server不能再讀取數據,但能夠繼續向Client發送數據。

  3. Client收到Server返回的ACK包後進入FIN_WAIT_2狀態,等待Server發送FIN包。

  4. Server完成數據的發送後,將FIN包發送給Client,而後進入LAST_ACK狀態,等待Client返回ACK包,此後Server既不能讀取數據,也不能發送數據。

  5. Client收到FIN包後向Server發送ACK包,而後進入TIME_WAIT狀態,接着等待足夠長的時間(2MSL)以確保Server接收到ACK包,最後回到CLOSED狀態,釋放網絡資源。

  6. Server收到Client返回的ACK包後便回到CLOSED狀態,釋放網絡資源

4、爲何要四次揮手?

TCP是全雙工信道,何爲全雙工就是客戶端與服務端創建兩條通道,通道1:客戶端的輸出鏈接服務端的輸入;通道2:客戶端的輸入鏈接服務端的輸出。兩個通道能夠同時工做:客戶端向服務端發送信號的同時服務端也能夠向客戶端發送信號。因此關閉雙通道的時候就是這樣:

客戶端:我要關閉輸入通道了。

服務端:好的,你關閉吧,我這邊也關閉這個通道。

服務端:我也要關閉輸入通道了。

客戶端:好的你關閉吧,我也把這個通道關閉。

6. 斐波那契數列

1.遞歸

function f(n) {
	if (n === 1 || n === 2) {
    	return 1;
    } else {
    	return f(n-1) + f(n-2);
    }
}
複製代碼

時間複雜度:O(2^N)

空間複雜度:O(N)

時間複雜度是指數階,屬於爆炸增量函數,在程序設計中咱們應該避免這樣的複雜度。

用遞歸法實現斐波那契數列代碼實現比較簡潔,但在n比較大時會引發棧溢出,沒法實現所需功能。

  1. 尾調用
function f(n, ac1=1, ac2=1) {
	if (n<=2) {
    	return ac2;
    } 
    return f(n-1, ac2, ac1+ac2);
}
複製代碼
  1. 迭代器
function* f(){
	let [prev, curr] = [0, 1];
    // for(;;)至關於死循環 等於while(1)
    for(;;) {
    	yield curr;
        [prev, curr] = [curr, prev + curr];
    }
}
for(let n of f()) {
	if (n > 1000) break;
    console.log(n);
}
複製代碼
  1. 帶有緩存的遞歸
function memozi(fn) {
	var r = {};
    return function(n) {
    	if (r[n] == null) {
        	r[n] = fn(n);
            return r[n];
        } else {
        	return r[n];
        }
    }
}

var fibfn = memozi(function(n) {
	if (n==0) {
    	return 0;
    } else if (n==1) {
    	return 1;
    } else {
    	return fibfn(n-1) + fibfn(n-2)
    }
})
複製代碼

這裏用到閉包相關知識,因此問了「閉包」相關知識(詳情見第15題)。

7. 編程題

給定一個只包括 '(',')','{','}','[',']' 的字符串,判斷字符串是否有效。 有效字符串需知足: 左括號必須用相同類型的右括號閉合。 左括號必須以正確的順序閉合。 注意空字符串可被認爲是有效字符串

var isValid = function(s) {
	const n = s.length;
    if (n % 2 === 1) {
    	return false;
    }
    const pairs = new Map([
    	[')','('],
        [']','['],
        ['}','{']
    ]);
    const stk = [];
    for(let i=0; i<s.length; i++) {
    	if (pairs.has(s[i])) {
        	if (!stk.length || stk[stk.length-1] !== pairs.get(s[i])) {
            	return false;
            }
            stk.pop();
        } else {
        	stk.push(s[i]);
        }
    }
    return !stk.length;
}
複製代碼

8. 提交數據兩種數據結構

URL傳值和form表單提交的區別和原理

區別:

一、url傳值就是get ,from表單就是post ,get是從服務器獲取數據,post是向服務器傳送數據

二、 對於get方式,服務器端用Request.QueryString獲取變量的值,對於post方式,服務器端用Request.Form獲取提交的數據。

三、get傳送的數據量較小,不能大於2KB。post傳送的數據量較大,通常被默認爲不受限制。

四、get安全性很是低,post安全性較高

9.性能優化

    1. 減小 HTTP 請求
    1. 使用 HTTP2
    1. 使用服務端渲染
    1. 靜態資源使用 CDN
    1. 將 CSS 放在文件頭部,JavaScript 文件放在底部
    1. 使用字體圖標 iconfont 代替圖片圖標
    1. 善用緩存,不重複加載相同的資源
    1. 壓縮文件
    1. 圖片優化
    1. 經過 webpack 按需加載代碼,提取第三庫代碼,減小 ES6 轉爲 ES5 的冗餘代碼
    1. 減小重繪重排
    1. 使用事件委託
    1. 注意程序的局部性
    1. if-else 對比 switch
    1. 查找表
    1. 避免頁面卡頓
    1. 使用 requestAnimationFrame 來實現視覺變化
    1. 使用 Web Workers
    1. 使用位操做
    1. 不要覆蓋原生方法
    1. 下降 CSS 選擇器的複雜性
    1. 使用 flexbox 而不是較早的佈局模型
    1. 使用 transform 和 opacity 屬性更改來實現動畫
    1. 合理使用規則,避免過分優化

10.瀏覽器從輸入到頁面呈現內容的過程 及 優化

  • 1.DNS解析
  • 2.TCP階段
  • 3.HTTP階段
  • 4.解析 / 渲染階段
  • 5.佈局layout / 渲染頁面

優化:

1.經過 DNS 預解析來告訴瀏覽器將來咱們可能從某個特定的 URL 獲取資源,當瀏覽器真正使用到該域中的某個資源時就能夠儘快地完成 DNS 解析。

第一步:打開或關閉DNS預解析

你能夠經過在服務器端發送 X-DNS-Prefetch-Control 報頭。或是在文檔中使用值爲 http-equiv 的meta標籤:

<meta http-equiv="x-dns-prefetch-control" content="on">
複製代碼

須要說明的是,在一些高級瀏覽器中,頁面中全部的超連接(<a>標籤),默認打開了DNS預解析。可是,若是頁面中採用的https協議,不少瀏覽器是默認關閉了超連接的DNS預解析。若是加了上面這行代碼,則代表強制打開瀏覽器的預解析。(若是你能在面試中把這句話說出來,則必定是你出彩的地方)

第二步:對指定的域名進行DNS預解析

若是咱們未來可能從 smyhvae.com 獲取圖片或音頻資源,那麼能夠在文檔頂部的 標籤中加入如下內容:

<link rel="dns-prefetch" href="http://www.smyhvae.com/">
複製代碼

當咱們從該 URL 請求一個資源時,就再也不須要等待 DNS 解析的過程。該技術對使用第三方資源特別有用。

11.防抖與節流函數

1.防抖 (debounce)

function debounce (f, wait) {
  let timer
  return (...args) => {
    clearTimeout(timer)
    timer = setTimeout(() => {
      f(...args)
    }, wait)
  }
}
複製代碼

使用場景

  • 1.登陸、發短信等按鈕避免用戶點擊太快,以至於發送了屢次請求,須要防抖
  • 2.調整瀏覽器窗口大小時,resize 次數過於頻繁,形成計算過多,此時須要一次到位,就用到了防抖
  • 3.文本編輯器實時保存,當無任何更改操做一秒後進行保存

2.節流

function throttle (f, wait) {
  let timer
  return (...args) => {
    if (timer) { return }
    timer = setTimeout(() => {
      f(...args)
      timer = null
    }, wait)
  }
}
複製代碼

使用場景

  • 1.scroll 事件,每隔一秒計算一次位置信息等
  • 2.瀏覽器播放事件,每一個一秒計算一次進度信息等
  • 3.input 框實時搜索併發送請求展現下拉列表,每隔一秒發送一次請求 (也可作防抖)

總結 (簡要答案)

  • 防抖:防止抖動,單位時間內事件觸發會被重置,避免事件被誤傷觸發屢次。代碼實現重在清零 clearTimeout。防抖能夠比做等電梯,只要有一我的進來,就須要再等一下子。業務場景有避免登陸按鈕屢次點擊的重複提交。
  • 節流:控制流量,單位時間內事件只能觸發一次,與服務器端的限流 (Rate Limit) 相似。代碼實現重在開鎖關鎖 timer=timeout; timer=null。節流能夠比做過紅綠燈,每等一個紅燈時間就能夠過一批。

12. ES6 promise的原理,越細緻越好,與Generator的區別

手寫Promise(簡易版)

function myPromise(fn) {
    this.cbs = [];
    const resolve = (value) => {
        setTimeout(() => {
            this.data = value;
            this.cbs.forEach((cb) => cb(value));
        })
    }
    fn(resolve);
}
myPromise.prototype.then = function (onResolved) {
    return new myPromise((resolve) => {        
        this.cbs.push(() => {
            const res = onResolved(this.data);
            if (res instanceof myPromise) {
                res.then(resolve);
            } else {
                resolve(res);
            }
        });
    });
}
export default myPromise;

複製代碼

promise詳細知識點 juejin.cn/post/690555…

13. 怎麼禁止js訪問cookie

Set-Cookie: name=value; HttpOnly

14. 實現左側固定,右側自適應兩欄佈局的方法

HTML佈局:

<div class="outer">
   <div class="sidebar">固定寬度區(sideBar)</div>
    <div class="content">自適應區(content)</div>
</div>
<div class="footer">footer</div>
複製代碼

方法:

一、將左側div浮動,右側div設置margin-left

/*方法1*/
.outer{overflow: hidden; border: 1px solid red;}
.sidebar{float: left;width:200px;height: 150px; background: #BCE8F1;}
.content{margin-left:200px;height:100px;background: #F0AD4E;}
複製代碼
  1. flex
/*方法2*/
.outer7{display: flex; border: 1px solid red;}
.sidebar7{flex:0 0 200px;height:150px;background: #BCE8F1;}
.content7{flex: 1;height:100px;background: #F0AD4E;}
複製代碼

flex能夠說是最好的方案了,代碼少,使用簡單。但存在兼容性,有朝一日,你們都改用現代瀏覽器,就能夠使用了。

須要注意的是,flex容器的一個默認屬性值:align-items: stretch;。這個屬性致使了列等高的效果。 爲了讓兩個盒子高度自動,須要設置: align-items: flex-start;

  1. float + BFC方法
/*方法3*/
.outer6{overflow: auto; border: 1px solid red;}
.sidebar6{float: left;height:150px;background: #BCE8F1;}
.content6{overflow:auto;height:100px;background: #F0AD4E;}
複製代碼

這個方案一樣是利用了左側浮動,可是右側盒子經過overflow: auto;造成了BFC,所以右側盒子不會與浮動的元素重疊。

延伸問題:由於代碼中用了flex佈局, 問了flex佈局相關知識

Flex是Flexible Box的縮寫,意爲」彈性佈局」,用來爲盒狀模型提供最大的靈活性。 任何一個容器均可以指定爲Flex佈局。容器分爲兩種,塊flex和行內flex.

.box{
	display: flex; /*webkit須要加前綴*/
    /*display:inline-flex;*/
}
複製代碼

Flex佈局有兩層,採用flex佈局的元素稱爲flex容器,其子元素則自動成flex item,即項目. 注:flex不一樣於block,flex容器的子元素的float,clear,vertical-align屬性將失效.

Flex佈局:

  1. flex容器有兩根軸:水平主軸就是x軸(main axis)和豎直軸也是y軸(cross axis),兩軸相關位置標識以下:

  2. flex容器屬性:

  • flex-direction:決定項目的排列方向。

  • flex-wrap:即一條軸線排不下時如何換行。

  • flex-flow:是flex-direction屬性和flex-wrap屬性的簡寫形式,默認值爲row nowrap。

  • justify-content:定義了項目在主軸上的對齊方式。(justify)

  • align-items:定義項目在交叉軸上如何對齊。

  • align-content:定義了多根軸線的對齊方式。若是項目只有一根軸線,該屬性不起做用。(換行會產生多軸)

Flex item屬性:

  • order:定義項目的排列順序。數值越小,排列越靠前,默認爲0。

  • flex-grow:定義項目的放大比例,若是全部項目的flex-grow屬性都爲1,則它們將等分剩餘空間(若是有的話)。若是一個項目的flex-grow屬性爲2,其餘項目都爲1,則前者佔據的剩餘空間將比其餘項多一倍。

  • flex-shrink:定義了項目的縮小比例,默認爲1,若是全部項目的flex-shrink屬性都爲1,當空間不足時,都將等比例縮小。若是一個項目的flex-shrink屬性爲0,其餘項目都爲1,則空間不足時,前者不縮小。

  • flex-basis:定義了在分配多餘空間以前,項目佔據的主軸空間(main size)。

  • flex:是flex-grow, flex-shrink 和 flex-basis的簡寫,默認值爲0 1 auto。後兩個屬性可選。

  • align-self:容許單個項目有與其餘項目不同的對齊方式,可覆蓋align-items屬性。默認值爲auto,表示繼承父元素的align-items屬性,若是沒有父元素,則等同於stretch。

15. 閉包

什麼是「閉包」。

「函數」和「函數內部能訪問到的變量」(也叫環境)的總和,就是一個閉包。
複製代碼

「閉包」的做用是什麼。

閉包經常用來「間接訪問一個變量」。換句話說,「隱藏一個變量」。
複製代碼

關於閉包的謠言

閉包會形成內存泄露?

錯。

說這話的人根本不知道什麼是內存泄露。內存泄露是指你用不到(訪問不到)的變量,依然佔居着內存空間,不能被再次利用起來。

閉包裏面的變量明明就是咱們須要的變量(lives),憑什麼說是內存泄露?

這個謠言是如何來的?

由於 IE。IE 有 bug,IE 在咱們使用完閉包以後,依然回收不了閉包裏面引用的變量。

這是 IE 的問題,不是閉包的問題。

詳細講解: zhuanlan.zhihu.com/p/22486908

16.VUE雙向數據綁定原理

1.vue 雙向數據綁定是經過 數據劫持 結合 發佈訂閱模式的方式來實現的, 也就是說數據和視圖同步,數據發生變化,視圖跟着變化,視圖變化,數據也隨之發生改變;

2.核心:關於VUE雙向數據綁定,其核心是 Object.defineProperty()方法;

3.介紹一下Object.defineProperty()方法

(1)Object.defineProperty(obj, prop, descriptor) ,這個語法內有三個參數,分別爲 obj (要定義其上屬性的對象) prop (要定義或修改的屬性) descriptor (具體的改變方法)

(2)簡單地說,就是用這個方法來定義一個值。當調用時咱們使用了它裏面的get方法,當咱們給這個屬性賦值時,又用到了它裏面的set方法;

17. webpack打包過程

webpack打包流程歸納

webpack的運行流程是一個串行的過程,從啓動到結束會依次執行如下流程:

  • 1.初始化參
  • 2.開始編譯 用上一步獲得的參數初始Compiler對象,加載全部配置的插件,通 過執行對象的run方法開始執行編譯
  • 3.肯定入口 根據配置中的 Entry 找出全部入口文件
  • 4.編譯模塊 從入口文件出發,調用全部配置的 Loader 對模塊進行編譯,再找出該模塊依賴的模塊,再遞歸本步驟直到全部入口依賴的文件都通過了本步驟的處理
  • 5.完成模塊編譯 在通過第4步使用 Loader 翻譯完全部模塊後, 獲得了每一個模塊被編譯後的最終內容及它們之間的依賴關係
  • 6.輸出資源:根據入口和模塊之間的依賴關係,組裝成一個個包含多個模塊的 Chunk,再將每一個 Chunk 轉換成一個單獨的文件加入輸出列表中,這是能夠修改輸出內容的最後機會
  • 7.輸出完成:在肯定好輸出內容後,根據配置肯定輸出的路徑和文件名,將文件的內容寫入文件系統中。

在以上過程當中, Webpack 會在特定的時間點廣播特定的事件,插件在監聽到感興趣的事件後會執行特定的邏輯,井且插件能夠調用 Webpack 提供的 API 改變 Webpack 的運行結果。其實以上7個步驟,能夠簡單概括爲初始化、編譯、輸出,三個過程,而這個過程其實就是前面說的基本模型的擴展。

18. Map 和 Object 的區別

  • 在 Object 中, key 必須是簡單數據類型(整數,字符串或者是 symbol),而在 Map 中則能夠是 JavaScript 支持的全部數據類型,也就是說能夠用一個 Object 來當作一個Map元素的 key。

  • Map 元素的順序遵循插入的順序,而 Object 的則沒有這一特性。

  • Map 繼承自 Object 對象。

  • 新建實例

    • Object 支持如下幾種方法來建立新的實例:
    var obj = {...};
    
    var obj = new Object();
    
    var obj = Object.create(null);
    複製代碼
    • Map 僅支持下面這一種構建方法:
    var map = new Map([[1, 2], [2, 3]]); // map = {1 => 2, 2 => 3}
    複製代碼
  • 數據訪問

    • Map 想要訪問元素,能夠使用 Map 自己的原生方法:
    map.get(1) // 2
    複製代碼
    • Object 能夠經過 . 和 [ ] 來訪問
    obj.id;
    obj['id'];
    複製代碼
    • 判斷某個元素是否在 Map 中能夠使用
    map.has(1);
    複製代碼
    • 判斷某個元素是否是在 Object 中須要如下操做:
    obj.id === undefined;
    // 或者
    'id' in obj;
    複製代碼

    另外須要注意的一點是,Object 能夠使用 Object.prototype.hasOwnProperty() 來判斷某個key是不是這個對象自己的屬性,從原型鏈繼承的屬性不包括在內。

  • 新增一個數據

    • Map 能夠使用 set() 操做:
    map.set(key, value)       // 當傳入的 key 已經存在的時候,Map 會覆蓋以前的值
    複製代碼
    • Object 新增一個屬性能夠使用:
    obj['key'] = value;
    obj.key = value;
    // object也會覆蓋
    複製代碼
  • 刪除數據

    • 在 Object 中沒有原生的刪除方法,咱們能夠使用以下方式:
    delete obj.id;
    // 下面這種作法效率更高
    obj.id = undefined
    複製代碼

    須要注意的是,使用 delete 會真正的將屬性從對象中刪除,而使用賦值 undefined 的方式,僅僅是值變成了 undefined。屬性仍然在對象上,也就意味着 在使用 for … in… 去遍歷的時候,仍然會訪問到該屬性。

    • Map 有原生的 delete 方法來刪除元素:
    var isDeleteSucceeded = map.delete(1);
    console.log(isDeleteSucceeded ); // true
    // 所有刪除
    map.clear();
    複製代碼
  • 獲取size

    • Map 自身有 size 屬性,能夠本身維持 size 的變化。

    • Object 則須要藉助 Object.keys() 來計算

    console.log(Object.keys(obj).length); 
    複製代碼
  • Iterating

    Map 自身支持迭代,Object 不支持。

    如何肯定一個類型是否是支持迭代呢? 能夠使用如下方法:

    console.log(typeof obj[Symbol.iterator]); // undefined
    console.log(typeof map[Symbol.iterator]); // function
    複製代碼

什麼時候使用 Map ,什麼時候使用 Object?

  • 當所要存儲的是簡單數據類型,而且 key 都爲字符串或者整數或者 Symbol 的時候,優先使用 Object ,由於Object能夠使用 字符變量 的方式建立,更加高效。

  • 當須要在單獨的邏輯中訪問屬性或者元素的時候,應該使用 Object

  • JSON 直接支持 Object,但不支持 Map

  • Map 是純粹的 hash, 而 Object 還存在一些其餘內在邏輯,因此在執行 delete 的時候會有性能問題。因此寫入刪除密集的狀況應該使用 Map。

  • Map 會按照插入順序保持元素的順序,而Object作不到。

  • Map 在存儲大量元素的時候性能表現更好,特別是在代碼執行時不能肯定 key 的類型的狀況

19. new 一個函數發生了什麼

構造調用:

  • 創造一個全新的對象
  • 這個對象會被執行 [[Prototype]] 鏈接,將這個新對象的 [[Prototype]] 連接到這個構造函數.prototype 所指向的對象
  • 這個新對象會綁定到函數調用的 this
  • 若是函數沒有返回其餘對象,那麼 new 表達式中的函數調用會自動返回這個新對象

若是函數返回一個對象,那麼new 這個函數調用返回這個函數的返回對象,不然返回 new 建立的新對象

20.數組去重

Array.from(new Set([1, 1, 2, 2]))
複製代碼

21.數組扁平化

function flatten(arr) {
  let result = [];

  for (let i = 0; i < arr.length; i++) {
    if (Array.isArray(arr[i])) {
      result = result.concat(flatten(arr[i]));
    } else {
      result = result.concat(arr[i]);
    }
  }

  return result;
}

const a = [1, [2, [3, 4]]];
console.log(flatten(a));
複製代碼

22.事件循環機制 (Event Loop)

事件循環機制從總體上告訴了咱們 JavaScript 代碼的執行順序 Event Loop即事件循環,是指瀏覽器或Node的一種解決javaScript單線程運行時不會阻塞的一種機制,也就是咱們常用異步的原理。 先執行宏任務隊列,而後執行微任務隊列,而後開始下一輪事件循環,繼續先執行宏任務隊列,再執行微任務隊列。

  • 宏任務:script/setTimeout/setInterval/setImmediate/ I/O / UI Rendering
  • 微任務:process.nextTick()/Promise  

上訴的 setTimeout 和 setInterval 等都是任務源,真正進入任務隊列的是他們分發的任務。

優先級

  • setTimeout = setInterval 一個隊列
  • setTimeout > setImmediate 
  • process.nextTick > Promise

執行微任務過程當中產生的新的微任務並不會推遲到下一個循環中執行,而是在當前的循環中繼續執行

23 .函數中的arguments是數組嗎?類數組轉數組的方法瞭解一下?

是類數組,是屬於鴨子類型的範疇,長得像數組,

  • ... 運算符
  • Array.from
  • Array.prototype.slice.apply(arguments)

24. 編寫自定義webpack插件

一個典型的Webpack插件代碼以下:

class MyWebpackPlugin {
  constructor(options) {
  }
  
  apply(compiler) {
    // 插入鉤子函數
    compiler.hooks.emit.tap('MyWebpackPlugin', (compilation) => {});
  }
}

module.exports = MyWebpackPlugin;
複製代碼

接下來須要在webpack.config.js中引入這個插件。

module.exports = {
  plugins:[
    // 傳入插件實例
    new MyWebpackPlugin({
      param:'paramValue'
    }),
  ]
};
複製代碼

Webpack在啓動時會實例化插件對象,在初始化compiler對象以後會調用插件實例的apply方法,傳入compiler對象,插件實例在apply方法中會註冊感興趣的鉤子,Webpack在執行過程當中會根據構建階段回調相應的鉤子。

詳細知識點請google

25.實現一個 EventEmitter

function EventEmitter() {
  this.__events = {};
}
EventEmitter.VERSION = '1.0.0';

EventEmitter.prototype.on = function(eventName, listener) {
  if (!eventName || !listener) return;
  // 判斷回調的 listener(或listener.listener) 是否爲函數
  if (!isValidListener(listener)) {
    throw new TypeError('listener must be a function');
  }
  var events = this.__events;
  var listeners = events[eventName] = events[eventName] || [];
  var listenerIsWrapped = typeof listener === 'object';
  // 不重複添加事件, 判斷是否有同樣的
  if (indexOf(listeners, listener) === -1) {
    listeners.push(listenerIsWrapped ? listener : {
      listener: listener,
      once: false
    })
  }
  return this;
}

EventEmitter.prototype.emit = function(eventName, args) {
  // 經過內部對象獲取對應自定義事件的回調函數
  var listeners = this.__events[eventName];
  if (!listeners) return;
  // 考慮多個 listener 的狀況
  for (var i = 0; i < listeners.length; i++) {
    var listener = listeners[i];
    if (listener) {
      listener.listener.apply(this, args || []);
      // listener 中 once 爲true 的進行特殊處理
      if (listener.once) {
        this.off(eventName, listener.listener)
      }
    }
  }
  return this;
}

EventEmitter.prototype.off = function(eventName, listener) {
  var listeners = this.__events[eventName];
  if (!listeners) return;
  var index;
  for (var i = 0, len = listeners.length; i < len; i++) {
    if (listeners[i] && listeners[i].listener === listener) {
      index = i;
      break;
    }
  }
  if (typeof index !== 'undefined') {
    listeners.splice(index, 1, null);
  }
  return this;
}

EventEmitter.prototype.once = function(eventName, listener) {
  // 調用 on 方法, once 參數傳入 true,待執行以後進行 once 處理
  return this.on(eventName, {
    listener: listener,
    once: true
  })
}

EventEmitter.prototype.alloOff = function(eventName) {
  // 若是該 eventName 存在,則將其對應的 listeners 的數組直接清空
  if (eventName && this.__events[eventName]) {
    this.__events[eventName] = [];
  } else {
    this.__events = {};
  }
}

// 判斷是不是合法的 listener
function isValidListener(listener) {
  if (typeof listener === 'function') {
    return true;
  } else if (listener && typeof listener === 'object') {
    return isValidListener(listener.listener);
  } else {
    return false;
  }
}

// 判斷新增自定義事件是否存在
function indexOf(array, item) {
  var result = -1;
  item = typeof item === 'object' ? item.listener : item;
  for(var i = 0, len = array.length; i<len; i++) {
    if (array[i].listener === item) {
      result = i;
      break;
    }
  }
  return result;
}
複製代碼

在 Vue 框架中不一樣組件之間的通信裏,有一種解決方案叫 EventBus。和 EventEmitter的思路相似,它的基本用途是將 EventBus 做爲組件傳遞數據的橋樑,全部組件共用相同的事件中心,能夠向該中心註冊發送事件或接收事件,全部組件均可以收到通知,使用起來很是便利,其核心其實就是發佈-訂閱模式的落地實現

26. 垃圾回收:釋放內存,提高瀏覽器頁面性能

1. JavaScript 的內存管理

棧內存中的基本類型,能夠經過操做系統直接處理;而堆內存中的引用類型,正是因爲能夠常常變化,大小不固定,所以須要 JavaScript 的引擎經過垃圾回收機制來處理

2.Chrome 內存回收機制

  1. 新生代內存回收: Scavenge 算法

  2. 老生代內存回收: Mark-Sweep(標記清除) 和 Mark-Compact(標記整理)

老生代內存的管理方式和新生代的內存管理方式區別仍是比較大的。Scavenge 算法比較適合內存較小的狀況處理;而對於老生代內存較大、變量較多的時候,仍是須要採用「標記-清除」結合「標記-整理」這樣的方式處理內存問題,並儘可能避免內存碎片的產生

3.內存泄漏與優化

場景

  • 1.過多的緩存未釋放;

  • 2.閉包太多未釋放;

  • 3.定時器或者回調太多未釋放;

  • 4.太多無效的 DOM 未釋放;

  • 5.全局變量太多未被發現。

27. 淺談前端模塊化規範

CommonJs和Es Module的區別

1.CommonJs

  • CommonJs能夠動態加載語句,代碼發生在運行時

  • CommonJs混合導出,仍是一種語法,只不過不用聲明前面對象而已,當我導出引用對象時以前的導出就被覆蓋了

  • CommonJs導出值是拷貝,能夠修改導出的值,這在代碼出錯時,很差排查引發變量污染

2.Es Module

  • Es Module是靜態的,不能夠動態加載語句,只能聲明在該文件的最頂部,代碼發生在編譯時
  • Es Module混合導出,單個導出,默認導出,徹底互不影響
  • Es Module導出是引用值以前都存在映射關係,而且值都是可讀的,不能修改

28. css div 垂直水平居中,且 div 高度永遠是寬度的一半(寬度能夠不指定)

html,
      body {
        width: 100%;
        height: 100%;
      }

      .outer {
        width: 400px;
        height: 100%;
        background: blue;
        margin: 0 auto;

        display: flex;
        align-items: center;
      }

      .inner {
        position: relative;
        width: 100%;
        height: 0;
        padding-bottom: 50%;
        background: red;
      }

      .box {
        position: absolute;
        width: 100%;
        height: 100%;
        display: flex;
        justify-content: center;
        align-items: center;
      }

    <div class="outer">
      <div class="inner">
        <div class="box">hello</div>
      </div>
    </div>
複製代碼

padding-bottom 值爲%時,是基於父元素寬度的百分比下內邊距.

29. visibility 和 display 的差異(還有opacity)

  • visibility 設置 hidden 會隱藏元素,可是其位置還存在與頁面文檔流中,不會被刪除,因此會觸發瀏覽器渲染引擎的重繪
  • display 設置了 none 屬性會隱藏元素,且其位置也不會被保留下來,因此會觸發瀏覽器渲染引擎的迴流和重繪。
  • opacity 會將元素設置爲透明,可是其位置也在頁面文檔流中,不會被刪除,因此會觸發瀏覽器渲染引擎的重繪

30.js腳本加載問題,async、defer問題

  • 若是依賴其餘腳本和 DOM 結果,使用 defer
  • 若是與 DOM 和其餘腳本依賴不強時,使用 async

31.深拷貝

const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {
  if (obj.constructor === Date) 
  return new Date(obj)       // 日期對象直接返回一個新的日期對象
  if (obj.constructor === RegExp)
  return new RegExp(obj)     //正則對象直接返回一個新的正則對象
  //若是循環引用了就用 weakMap 來解決
  if (hash.has(obj)) return hash.get(obj)
  let allDesc = Object.getOwnPropertyDescriptors(obj)
  //遍歷傳入參數全部鍵的特性
  let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
  //繼承原型鏈
  hash.set(obj, cloneObj)
  for (let key of Reflect.ownKeys(obj)) { 
    cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
  }
  return cloneObj
}
複製代碼

32.Vue 的父組件和子組件生命週期鉤子執行順序是什麼

  • 1.加載渲染過程

父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

  • 2.子組件更新過程

父beforeUpdate->子beforeUpdate->子updated->父updated

  • 3.父組件更新過程

父beforeUpdate->父updated

  • 4.銷燬過程

父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

總結:從外到內,再從內到外

33. 下面代碼中 a 在什麼狀況下會打印 1?

var a = ?;
if(a == 1 && a == 2 && a == 3){
 	conso.log(1);
}
複製代碼

答案解析 由於==會進行隱式類型轉換 因此咱們重寫toString方法就能夠了

var a = {
  i: 1,
  toString() {
    return a.i++;
  }
}

if( a == 1 && a == 2 && a == 3 ) {
  console.log(1);
}
複製代碼

34.考察做用域

下面代碼輸出什麼

var a = 10;
(function () {
    console.log(a)
    a = 5
    console.log(window.a)
    var a = 20;
    console.log(a)
})()
複製代碼

依次輸出:undefined -> 10 -> 20

解析:

在當即執行函數中,var a = 20; 語句定義了一個局部變量 a,因爲js的變量聲明提高機制,局部變量a的聲明會被提高至當即執行函數的函數體最上方,且因爲這樣的提高並不包括賦值,所以第一條打印語句會打印undefined,最後一條語句會打印20。

因爲變量聲明提高,a = 5; 這條語句執行時,局部的變量a已經聲明,所以它產生的效果是對局部的變量a賦值,此時window.a 依舊是最開始賦值的10,

35.在 Vue 中,子組件爲什麼不能夠修改父組件傳遞的 Prop,若是修改了,Vue 是如何監控到屬性的修改並給出警告的

1.子組件爲什麼不能夠修改父組件傳遞的 Prop

單向數據流,易於監測數據的流動,出現了錯誤能夠更加迅速的定位到錯誤發生的位置。

2.若是修改了,Vue 是如何監控到屬性的修改並給出警告的

if (process.env.NODE_ENV !== 'production') {
      var hyphenatedKey = hyphenate(key);
      if (isReservedAttribute(hyphenatedKey) ||
          config.isReservedAttr(hyphenatedKey)) {
        warn(
          ("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
          vm
        );
      }
      defineReactive$$1(props, key, value, function () {
        if (!isRoot && !isUpdatingChildComponent) {
          warn(
            "Avoid mutating a prop directly since the value will be " +
            "overwritten whenever the parent component re-renders. " +
            "Instead, use a data or computed property based on the prop's " +
            "value. Prop being mutated: \"" + key + "\"",
            vm
          );
        }
      });
    }
複製代碼

在initProps的時候,在defineReactive時經過判斷是否在開發環境,若是是開發環境,會在觸發set的時候判斷是否此key是否處於updatingChildren中被修改,若是不是,說明此修改來自子組件,觸發warning提示。

須要特別注意的是,當你從子組件修改的prop屬於基礎類型時會觸發提示。 這種狀況下,你是沒法修改父組件的數據源的, 由於基礎類型賦值時是值拷貝。你直接將另外一個非基礎類型(Object, array)賦值到此key時也會觸發提示(但實際上不會影響父組件的數據源), 當你修改object的屬性時不會觸發提示,而且會修改父組件數據源的數據。

36.cookie 和 token 都存放在 header 中,爲何不會劫持 token?

一、首先token不是防止XSS的,而是爲了防止CSRF的;

二、CSRF攻擊的緣由是瀏覽器會自動帶上cookie,而瀏覽器不會自動帶上token

37.下面的代碼打印什麼內容,爲何

var b = 10;
(function b(){
    b = 20;
    console.log(b); 
})();
複製代碼

1打印結果內容以下:

ƒ b() {
b = 20;
console.log(b)
}
複製代碼

緣由:

做用域:執行上下文中包含做用於鏈: 在理解做用域鏈以前,先介紹一下做用域,做用域能夠理解爲執行上下文中申明的變量和做用的範圍;包括塊級做用域/函數做用域;

特性:聲明提早:一個聲明在函數體內都是可見的,函數聲明優先於變量聲明; 在非匿名自執行函數中,函數變量爲只讀狀態沒法修改;

38.實現 Promise.race()

Promise._race = promises => new Promise((resolve, reject) => {
	promises.forEach(promise => {
		promise.then(resolve, reject)
	})
})
複製代碼

39. 輸出如下代碼執行的結果並解釋爲何

var obj = {
    '2': 3,
    '3': 4,
    'length': 2,
    'splice': Array.prototype.splice,
    'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)
複製代碼

結果:

截屏2021-03-14 上午11.37.43.png

  • push() 方法將一個或多個元素添加到數組的末尾,並返回該數組的新長度。
  • 根據MDN的說法理解,push方法應該是根據數組的length來根據參數給數組建立一個下標爲length的屬性,
  • push方法影響了數組的length屬性和對應下標的值

在對象中加入splice屬性方法,和length屬性後。這個對象變成一個類數組。

題目的解釋應該是:

  • 1.使用第一次push,obj對象的push方法設置 obj[2]=1;obj.length+=1
  • 2.使用第二次push,obj對象的push方法設置 obj[3]=2;obj.length+=1
  • 3.使用console.log輸出的時候,由於obj具備 length 屬性和 splice 方法,故將其做爲數組進行打印
  • 4.打印時由於數組未設置下標爲 0 1 處的值,故打印爲empty,主動 obj[0] 獲取爲 undefined

第一第二步還能夠具體解釋爲:由於每次push只傳入了一個參數,因此 obj.length 的長度只增長了 1。push方法自己還能夠增長更多參數

40.for in 和 for of的區別, for of怎麼遍歷對象

  • for in適合遍歷對象
  • for of適合遍歷數組

for-of循環不支持普通對象,但若是你想迭代一個對象的屬性,如下方法能夠實現

方法1:
for (var key of Object.keys(someObject)) {
  console.log(key + ": " + someObject[key]);
}

方法2:
var obj = {
    a:1,
    b:2,
    c:3
};
obj[Symbol.iterator] = function*(){
    var keys = Object.keys(obj);
    for(var k of keys){
        yield [k,obj[k]]
    }
};

for(var [k,v] of obj){
    console.log(k,v);
}
複製代碼

全部擁有Symbol.iterator的對象被稱爲可迭代的

41. webpack 打包構建流程,用過哪些類

  • 1.Webpack CLI 啓動打包流程;
  • 2.載入 Webpack 核心模塊,建立 Compiler 對象;
  • 3.使用 Compiler 對象開始編譯整個項目;
  • 4.從入口文件開始,解析模塊依賴,造成依賴關係樹;
  • 5.遞歸依賴樹,將每一個模塊交給對應的 Loader 處理;
  • 6.合併 Loader 處理完的結果,將打包結果輸出到 dist 目錄。

42. 原型鏈,繼承

43.vue響應式原理 ?基本都會問

連接: www.bilibili.com/video/BV1G5…

44.TS相關知識

45.vue性能優化

  • 1.函數式組件(Functional components)

    優化前的組件代碼以下:

    <template>
      <div class="cell">
        <div v-if="value" class="on"></div>
        <section v-else class="off"></section>
      </div>
    </template>
    
    <script>
    export default {
      props: ['value'],
    }
    </script>
    
    複製代碼

    優化後的組件代碼以下:

    <template functional>
      <div class="cell">
        <div v-if="props.value" class="on"></div>
        <section v-else class="off"></section>
      </div>
    </template>
    
    複製代碼

    函數式組件和普通的對象類型的組件不一樣,它不會被看做成一個真正的組件,咱們知道在 patch 過程當中,若是遇到一個節點是組件 vnode,會遞歸執行子組件的初始化過程;而函數式組件的 render 生成的是普通的 vnode,不會有遞歸子組件的過程,所以渲染開銷會低不少。所以,函數式組件也不會有狀態,不會有響應式數據,生命週期鉤子函數這些東西。你能夠把它當成把普通組件模板中的一部分 DOM 剝離出來,經過函數的方式渲染出來,是一種在 DOM 層面的複用。

    1. Child component splitting

46.數組扁平化(depth幾成扁平)

47. spa單頁應用, 怎麼避免內存泄露

簡介

若是你在用 Vue 開發應用,那麼就要小心內存泄漏的問題。這個問題在單頁應用 (SPA) 中尤其重要,由於在 SPA 的設計中,用戶使用它時是不須要刷新瀏覽器的,因此 JavaScript 應用須要自行清理組件來確保垃圾回收以預期的方式生效。

內存泄漏在 Vue 應用中一般不是來自 Vue 自身的,更多地發生於把其它庫集成到應用中的時候。

一個更常見的實際的場景是使用 Vue Router 在一個單頁應用中路由到不一樣的組件。當一個用戶在你的應用中導航時,Vue Router 從虛擬 DOM 中移除了元素,並替換爲了新的元素。Vue 的 beforeDestroy() 生命週期鉤子是一個解決基於 Vue Router 的應用中的這類問題的好地方。 咱們能夠將清理工做放入 beforeDestroy() 鉤子,像這樣:

beforeDestroy: function () {
  this.choicesSelect.destroy()
}
複製代碼

總結

Vue 讓開發很是棒的響應式的 JavaScript 應用程序變得很是簡單,可是你仍然須要警戒內存泄漏。這些內存泄漏每每會發生在使用 Vue 以外的其它進行 DOM 操做的三方庫時。請確保測試應用的內存泄漏問題並在適當的時機作必要的組件清理。

48. vue中 solt和slot-scope的原理

如下講解基於Vue 2.6

  1. slot 和 solt-scope在組件內部被統一整合成了函數

  2. 他們的渲染做用域都是 子組件

  3. 而且都能經過 this.$scopedSlots去訪問

父組件通過初始化時的一系列處理,每一個插槽會轉換成一個key(插槽名,未命名時是default)對應的函數(有做用域參數的話,會傳做用越參數). 子組件的實例 this.$scopedSlots 就能夠訪問到 父組件裏的 ‘插槽函數’。 若是是 普通插槽, 就直接調用函數生成 vnode, 若是是 做用域插槽, 就帶着 props 去調用函數生成 vnode.

總結: Vue 2.6 版本後對 slot 和 slot-scope 作了一次統一的整合,讓它們所有都變爲函數的形式,全部的插槽均可以在 this.$scopedSlots 上直接訪問,這讓咱們在開發高級組件的時候變得更加方便。在優化上,Vue 2.6 也儘量的讓 slot 的更新不觸發父組件的渲染,經過一系列巧妙的判斷和算法去儘量避免沒必要要的渲染。在 2.5 的版本中,因爲生成 slot 的做用域是在父組件中,因此明明是子組件的插槽 slot 的更新是會帶着父組件一塊兒更新的)

具體文章連接: juejin.cn/post/684490…

49.路由鉤子在Vue生命週期的體現

1.完整的路由導航解析流程(不包括其餘生命週期)

    1. 導航被觸發
    1. 在失活的組件裏調用 beforeRouteLeave 守衛
    1. 調用全局的 beforeEach 守衛
    1. 在重用的組件裏調用 beforeRouteUpdate 守衛(2.2+)
    1. 在路由配置裏調用 beforeEnter
    1. 解析異步路由組件
    1. 在被激活的組件裏調用 beforeRouterEnter
    1. 調用全局的 beforeResolve 守衛(2.5+)
    1. 導航被確認
    1. 調用全局的 afterEach 鉤子
    1. 觸發 DOM 更新
    1. 調用 beforeRouteEnter 守衛中傳給 next 的回調函數,建立好的組件實例會做爲回調函數的參數傳入

2.觸發鉤子的完整順序

路由導航、keep-alive、和組件生命週期鉤子結合起來的, 觸發順序, 假設是從a組件離開, 第一次進入b組件:

  • beforeRouteLeave:路由組件的組件離開路由前鉤子, 可取消路由離開
  • beforeEach: 路由全局前置守衛, 可用於登陸驗證、全局路由loadding等
  • beforeEnter: 路由獨享守衛
  • beforeRouteEnter: 路由組件的組件進入路由前鉤子
  • beforeResolve: 路由全局解析守衛
  • afterEach: 路由全局後置鉤子
  • beforeCreate: 組件生命週期, 不能訪問this
  • created: 組件生命週期, 能夠訪問this, 不能訪問dom
  • beforeMount: 組件生命週期
  • deactivated: 離開緩存組件a, 或觸發a的beforeDestroy和destroyed組件銷燬鉤子
  • mounted: 訪問/操做dom
  • activated: 進入緩存組件,進入a的嵌套子組件(若是有的話)
  • 執行beforeRouterEnte回調函數next

50. 生命週期?那個生命週期能夠獲取到真實DOM?修改data裏面的數據,會觸發什麼生命週期?

  1. 總共分爲8個階段建立前/後,載入前/後,更新前/後,銷燬前/後。
  • 建立期間的生命週期函數
    • beforeCreate:實例剛在內存中被建立出來,此時,尚未初始化好 data 和 methods 屬性

    • created:實例已經在內存中建立OK,此時 data 和 methods 已經建立OK,此時尚未開始 編譯模板

    • beforeMount:此時已經完成了模板的編譯,可是尚未掛載到頁面中

    • mounted:此時,已經將編譯好的模板,掛載到了頁面指定的容器中顯示

  • 運行期間的生命週期函數:
    • beforeUpdate:狀態更新以前執行此函數, 此時 data 中的狀態值是最新的,可是界面上顯示的 數據仍是舊的,由於此時尚未開始從新渲染DOM節點

    • updated:實例更新完畢以後調用此函數,此時 data 中的狀態值 和 界面上顯示的數據,都已經完成了更新,界面已經被從新渲染好了!

  • 銷燬期間的生命週期函數:
    • beforeDestroy:實例銷燬以前調用。在這一步,實例仍然徹底可用。
    • destroyed:Vue 實例銷燬後調用。調用後,Vue 實例指示的全部東西都會解綁定,全部的事件監聽器會被移除,全部的子實例也會被銷燬。
  1. mounted生命週期能夠獲取到真實DOM

  2. 修改data裏面的數據,會觸發 beforeUpdate、updated生命週期

51.Vue組件data爲何是一個函數

data是一個函數的時候,每個實例的data屬性都是獨立的,不會相互影響

52. vue 組件通訊?通常說了vuex,就會問vuex用法?action和mutations區別?實現原理等?

  1. props/$emit

  2. e m i t / emit/ on

  3. vuex

      1. Vue Components: Vue組件。HTML頁面上, 負責接收用戶操做等交互行爲, 執行dispatch方法觸發對應的action進行迴應
      1. dispatch: 操做行爲觸發方法,是惟一能執行action的方法
      1. actions: 操做行爲處理模塊,有組件中的$store.dispatch('action 名稱', datal) 來觸發。 而後由commit()來觸發mutation的調用,間接更新 state. 負責處理Vue Components接收到的全部交互行爲。包含同步/異步操做, 支持多個同名方法, 按照註冊的順序依次觸發。 向後臺API 請求的操做就在這個模塊中進行, 包括觸發其餘action以及提交mutation的操做。該模塊提供了Promise的封裝,以支持action的鏈式觸發。
      1. commit: 狀態改變提交操做方法。對mutation進行提交, 是惟一能執行mutation的方法
      1. mutations: 狀態改變操做方法, 有actions中的commit('mutation 名稱')來觸發。是Vuex修改state的惟一推薦方法。該方法只能進行同步操做,且方法名只能全局惟一。操做之中會有一些hook暴露出來, 以進行state的監控等。
      1. state: 頁面狀態管理容器對象。集中存儲Vue components中data對象的零散數據,全局惟一,以進行統一的狀態管理。頁面顯示所需的數據從該對象中進行讀取,利用Vue的細粒度數據響應機制來進行高效的狀態更新。
      1. state對象讀取方法
  4. $attrs/$listeners

    • $attrs:包含了父做用域中不被prop所識別(且獲取)的特性綁定(class 和 style除外)。當一個組件沒有聲明任何 prop 時,這裏會包含全部父做用域的綁定 (class 和 style 除外),而且能夠經過 v-bind="$attrs" 傳入內部組件。一般配合 interitAttrs 選項一塊兒使用。
    • $listeners:包含了父做用域中的 (不含 .native 修飾器的) v-on 事件監聽器。它能夠經過 v-on="$listeners" 傳入內部組件

簡單來講:$attrs與$listeners 是兩個對象, a t t r s 裏存放的是父組件中綁定的非 P r o p s 屬性, attrs 裏存放的是父組件中綁定的非 Props 屬性, listeners裏存放的是父組件中綁定的非原生事件。

  1. provide/inject

    Vue2.2.0新增API,這對選項須要一塊兒使用,以容許一個祖先組件向其全部子孫後代注入一個依賴,不論組件層次有多深,並在起上下游關係成立的時間裏始終生效。一言而蔽之:祖先組件中經過provider來提供變量,而後在子孫組件中經過inject來注入變量。 provide / inject API 主要解決了跨級組件間的通訊問題,不過它的使用場景,主要是子組件獲取上級組件的狀態,跨級組件間創建了一種主動提供與依賴注入的關係。

  2. p a r e n t / parent / children與 ref

53.$nextTick 做用?實現原理?微任務向宏任務的降級處理,常常被問到說出幾種宏任務,微任務。

$nextTick 做用:在下次 DOM 更新循環結束以後執行延遲迴調。在修改數據以後當即使用這個方法,獲取更新後的 DOM。

實現原理:

1.能力檢測

這一塊其實很簡單,衆所周知,Event Loop分爲宏任務(macro task)以及微任務( micro task),無論執行宏任務仍是微任務,完成後都會進入下一個tick,並在兩個tick之間執行UI渲染。 可是,宏任務耗費的時間是大於微任務的,因此在瀏覽器支持的狀況下,優先使用微任務。若是瀏覽器不支持微任務,使用宏任務;可是,各類宏任務之間也有效率的不一樣,須要根據瀏覽器的支持狀況,使用不一樣的宏任務。

2.根據能力檢測以不一樣方式執行回調隊列

降級處理: Promise -> MutationObserver -> setImmediate -> setTimeout

  • 宏任務:
    • I/O,事件隊列中的每個事件都是一個macrotask

    • setTimeout / setInterval

    • MessageChannel是通訊渠道API,ie11以上和其它瀏覽器支持。

    • setImmediate 目前只有IE10以上實現了該方法其它瀏覽器不支持.做用回調功能,node支持。

    • requestAnimationFrame 也算宏任務 node不支持。

  • 微任務
    • Promise.then catch finally
    • MutationObserver 瀏覽器支持 IE11以上 node不支持,它會在指定的DOM發生變化時被調用
    • process.nextTick 瀏覽器不支持 node支持
實現一個簡易的nextTick

let callbacks = []
let pending = false

function nextTick (cb) {
    callbacks.push(cb)

    if (!pending) {
        pending = true
        setTimeout(flushCallback, 0)
    }
}

function flushCallback () {
    pending = false
    let copies = callbacks.slice()
    callbacks.length = 0
    copies.forEach(copy => {
        copy()
    })
}
複製代碼

54.vue scoped屬性做用?實現原理?

當style標籤具備該scoped屬性時,其CSS將僅應用於當前組件的元素

<style scoped>
.example {
 color: red;
}
</style>
<template>
 <div class="example">hi</div>
</template>

<style>
.example[data-v-5558831a] {
 color: red;
}
</style>
<template>
 <div class="example" data-v-5558831a>hi</div>
</template>
複製代碼

實現原理: PostCSS給一個組件中的全部dom添加了一個獨一無二的動態屬性,而後,給CSS選擇器額外添加一個對應的屬性選擇器來選擇該組件中dom,這種作法使得樣式只做用於含有該屬性的dom——組件內部dom

55.vue router有幾種模式?實現方式?

  1. hash模式

hash模式的工做原理是hashchange事件,能夠在window監聽hash的變化。咱們在url後面隨便添加一個#xx觸發這個事件。

HashHistory的push和replace()

window.onhashchange = function(event){
    console.log(event);
  }
複製代碼
  1. history模式(對應HTML5History)

HTML5History.pushState()和HTML5History.replaceState()

在HTML5History中添加對修改瀏覽器地址欄URL的監聽是直接在構造函數中執行的,對HTML5History的popstate 事件進行監聽:

constructor (router: Router, base: ?string) {
  
 window.addEventListener('popstate', e => {
 const current = this.current
 this.transitionTo(getLocation(this.base), route => {
 if (expectScroll) {
 handleScroll(router, route, current, true)
 }
 })
 })
}
複製代碼

56. key的做用?沒有key的狀況,vue會怎麼作?會引出diff的問題

  1. key的做用主要是爲了高效的更新虛擬DOM,其原理是vue在patch過程當中經過key能夠精準判斷兩個節點是不是同一個,從而避免頻繁更新不一樣元素, 使得整個patch過程更加高效, 減小DOM操做, 提升性能。

  2. 另外, 若不設置key還可能在列表更新時引起一些隱藏的bug

  3. vue中在使用相同標籤名元素的過分切換時,也會使用到key屬性, 其目的也是爲了讓vue能夠區分它們, 不然vue只會替換其內部屬性而不會觸發過分效果。

57. vue diff過程

58.vue 2.x defineProperty缺陷?業務代碼裏面怎麼處理?$set原理?vue是怎麼重寫數組方法的?考察你是否是真的看過源碼

1.vue 2.x defineProperty缺陷

  • Object.defineProperty沒法監控到數組下標的變化,致使經過數組下標添加元素,不能實時響應;

  • Object.defineProperty只能劫持對象的屬性,從而須要對每一個對象,每一個屬性進行遍歷,若是,屬性值是對象,還須要深度遍歷。Proxy能夠劫持整個對象,並返回一個新的對象。

  • 不能監聽動態length的變化.

    例如,arr = [1],直接更改arr[10] = 20,這樣就監聽不到了,由於length在規範不容許重寫,而arr[0]直接更改是能夠監聽到的。

  • 數組方法使用不能監聽到數組的變動,例如push

    這也是爲何vue重寫這些方法的緣由

  • 數據的變化是經過getter/setter來追蹤的。由於這種追蹤方式,有些語法中,即使是數據發生了變化,vue也檢查不到。好比 向Object添加屬性/ 刪除Object的屬性。

  • 檢測數組的變化,由於只是攔截了unshift shift push pop splice sort reverse 這幾個方法,因此像

    list[0] = 4
    list.length = 0
    複製代碼

    檢測不到

業務代碼處理:this.$set(this.data,」key」,value’)

Object.defineProperty自己有必定的監控到數組下標變化的能力:Object.defineProperty自己是能夠監控到數組下標的變化的,可是在 Vue 中,從性能/體驗的性價比考慮,尤大大就棄用了這個特性。

2.$set原理

在 set 方法中,對 target 是數組和對象作了分別的處理, target 是數組時,會調用重寫過的 splice 方法進行手動 Observe 。

對於對象,若是 key 原本就是對象的屬性,則直接修改值觸發更新,不然調用 defineReactive 方法從新定義響應式對象。

3. vue是怎麼重寫數組方法的

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function (method) {  
  // cache original method
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if (inserted) ob.observeArray(inserted)
    // notify change
    ob.dep.notify()
    return result
  })
})
複製代碼

重寫了數組中的那些方法,首先獲取到這個數組的__ob__,也就是它的Observer對象,若是有新的值,就調用observeArray繼續對新的值觀察變化,而後手動調用notify,通知渲染watcher,執行update

59.vue 3.0 proxy優缺點?怎麼處理vue3不支持IE?

  • Proxy不只能夠代理對象,還能夠代理數組。還能夠代理動態增長的屬性。

60.computed 和 watch 的區別和運用的場景?除了基本的,看你能不能說出三種watcher的區別

computed:計算屬性

計算屬性是由data中的已知值,獲得的一個新值。 這個新值只會根據已知值的變化而變化,其餘不相關的數據的變化不會影響該新值。 計算屬性不在data中,計算屬性新值的相關已知值在data中。 別人變化影響我本身。 watch:監聽數據的變化

監聽data中數據的變化 監聽的數據就是data中的已知值 個人變化影響別人

1.watch擅長處理的場景:一個數據影響多個數據

2.computed擅長處理的場景:一個數據受多個數據影響

61. 如何實現圖片的懶加載

思路: 如何判斷圖片出如今了當前視口 (即如何判斷咱們可以看到圖片)

如何控制圖片的加載

方案一: 位置計算 + 滾動事件 (Scroll) + DataSet API

  • 1.位置計算: clientTop,offsetTop,clientHeight 以及 scrollTop 各類關於圖片的高度做比對

  • 2.監聽 window.scroll 事件

  • 3.DataSet API: <img data-src="solo.jpg">

    • 首先設置一個臨時 Data 屬性 data-src,控制加載時使用 src 代替 data-src,可利用 DataSet API 實現

    • img.src = img.datset.src

方案二: getBoundingClientRect API + Scroll + DataSet API

    1. Element.getBoundingClientRect() 方法返回元素的大小及其相對於視口的位置
      // clientHeight 表明當前視口的高度
       img.getBoundingClientRect().top < document.documentElement.clientHeight
      複製代碼
  • 2.監聽 window.scroll
    1. 同上

方案三: IntersectionObserver API + DataSet API

const observer = new IntersectionObserver((changes) => {
  // changes: 目標元素集合
  changes.forEach((change) => {
    // intersectionRatio
    if (change.isIntersecting) {
      const img = change.target
      img.src = img.dataset.src
      observer.unobserve(img)
    }
  })
})

observer.observe(img)
複製代碼

ie不支持

方案四: LazyLoading屬性

<img src="shanyue.jpg" loading="lazy">
複製代碼

除chrome幾乎都不支持

62. form表單設計

考察 element-ui 源碼錶單設計, 代碼倉庫地址: github.com/glihui/guo-…

63.最後:分享下本身整理的部分知識點文章連接

相關文章
相關標籤/搜索