Javascript是前端面試的重點,本文重點梳理下 Javascript 中的常考基礎知識點,而後就一些容易出現的題目進行解析。限於文章的篇幅,沒法將知識點講解的面面俱到,本文只羅列了一些重難點,若是想要了解更多內容歡迎點擊https://github.com/ljianshu/Blog。javascript
根據 JavaScript 中的變量類型傳遞方式,分爲基本數據類型和引用數據類型。其中基本數據類型包括Undefined、Null、Boolean、Number、String、Symbol (ES6新增,表示獨一無二的值),而引用數據類型統稱爲Object對象,主要包括對象、數組和函數。html
在參數傳遞方式上,有所不一樣:前端
函數的參數若是是簡單類型,會將一個值類型的數值副本傳到函數內部,函數內部不影響函數外部傳遞的參數變量java
若是是一個參數是引用類型,會將引用類型的地址值複製給傳入函數的參數,函數內部修改會影響傳遞參數的引用對象。node
題目:基本類型和引用類型的區別jquery
基本類型和引用類型存儲於內存的位置不一樣,基本類型直接存儲在棧中,而引用類型的對象存儲在堆中,與此同時,在棧中存儲了指針,而這個指針指向正是堆中實體的起始位置。下面經過一個小題目,來看下二者的主要區別:nginx
// 基本類型 var a = 10 var b = a b = 20 console.log(a) // 10 console.log(b) // 20
上述代碼中,a b都是值類型,二者分別修改賦值,相互之間沒有任何影響。再看引用類型的例子:git
// 引用類型 var a = {x: 10, y: 20} var b = a b.x = 100 b.y = 200 console.log(a) // {x: 100, y: 200} console.log(b) // {x: 100, y: 200}
上述代碼中,a b都是引用類型。在執行了b = a以後,修改b的屬性值,a的也跟着變化。由於a和b都是引用類型,指向了同一個內存地址,即二者引用的是同一個值,所以b修改屬性時,a的值隨之改動github
typeof返回一個表示數據類型的字符串,返回結果包括:number、boolean、string、symbol、object、undefined、function等7種數據類型,但不能判斷null、array等面試
typeof Symbol(); // symbol 有效 typeof ; // string 有效 typeof 1; // number 有效 typeof true; //boolean 有效 typeof undefined; //undefined 有效 typeof new Function(); // function 有效 typeof null; //object 無效 typeof [] ; //object 無效 typeof new Date(); //object 無效 typeof new RegExp(); //object 無效
instanceof 是用來判斷A是否爲B的實例,表達式爲:A instanceof B,若是A是B的實例,則返回true,不然返回false。instanceof 運算符用來測試一個對象在其原型鏈中是否存在一個構造函數的 prototype 屬性,但它不能檢測null 和 undefined
[] instanceof Array; //true {} instanceof Object;//true new Date() instanceof Date;//true new RegExp() instanceof RegExp//true null instanceof Null//報錯 undefined instanceof undefined//報錯
constructor做用和instanceof很是類似。但constructor檢測 Object與instanceof不同,還能夠處理基本數據類型的檢測。不過函數的 constructor 是不穩定的,這個主要體如今把類的原型進行重寫,在重寫的過程當中頗有可能出現把以前的constructor給覆蓋了,這樣檢測出來的結果就是不許確的。
Object.prototype.toString.call() 是最準確最經常使用的方式。
Object.prototype.toString.call( ) ; // [object String] Object.prototype.toString.call(1) ; // [object Number] Object.prototype.toString.call(true) ; // [object Boolean] Object.prototype.toString.call(undefined) ; // [object Undefined] Object.prototype.toString.call(null) ; // [object Null] Object.prototype.toString.call(new Function()) ; // [object Function] Object.prototype.toString.call(new Date()) ; // [object Date] Object.prototype.toString.call([]) ; // [object Array] Object.prototype.toString.call(new RegExp()) ; // [object RegExp] Object.prototype.toString.call(new Error()) ; // [object Error]
淺拷貝只複製指向某個對象的指針,而不復制對象自己,新舊對象仍是共享同一塊內存。
淺拷貝的實現方式(詳見https://github.com/ljianshu/Blog/issues/5):
Object.assign():需注意的是目標對象只有一層的時候,是深拷貝
Array.prototype.concat()
Array.prototype.slice()
深拷貝就是在拷貝數據的時候,將數據的全部引用結構都拷貝一份。簡單的說就是,在內存中存在兩個數據結構徹底相同又相互獨立的數據,將引用型類型進行復制,而不是隻複製其引用關係。
深拷貝的實現方式:
熱門的函數庫lodash,也有提供_.cloneDeep用來作深拷貝
jquery 提供一個$.extend能夠用來作深拷貝
JSON.parse(JSON.stringify())
手寫遞歸方法
遞歸實現深拷貝的原理:要拷貝一個數據,咱們確定要去遍歷它的屬性,若是這個對象的屬性還是對象,繼續使用這個方法,如此往復。
//定義檢測數據類型的功能函數 function checkedType(target) { return Object.prototype.toString.call(target).slice(8, -1) } //實現深度克隆---對象/數組 function clone(target) { //判斷拷貝的數據類型 //初始化變量result 成爲最終克隆的數據 let result, targetType = checkedType(target) if (targetType === Object ) { result = {} } else if (targetType === Array ) { result = [] } else { return target } //遍歷目標數據 for (let i in target) { //獲取遍歷數據結構的每一項值。 let value = target[i] //判斷目標結構裏的每一值是否存在對象/數組 if (checkedType(value) === Object || checkedType(value) === Array ) { //對象/數組裏嵌套了對象/數組 //繼續遍歷獲取到value值 result[i] = clone(value) } else { //獲取到value值是基本的數據類型或者是函數。 result[i] = value } } return result }
執行上下文就是當前 JavaScript 代碼被解析和執行時所在環境的抽象概念, JavaScript 中運行任何的代碼都是在執行上下文中運行。執行上下文的生命週期包括三個階段:建立階段→執行階段→回收階段,咱們重點介紹建立階段。
建立階段(當函數被調用,但未執行任何其內部代碼以前)會作如下三件事:
建立變量對象:首先初始化函數的參數arguments,提高函數聲明和變量聲明。
建立做用域鏈:下文會介紹
肯定this指向:下文會介紹
function test(arg){ // 1. 形參 arg 是 "hi" // 2. 由於函數聲明比變量聲明優先級高,因此此時 arg 是 function console.log(arg); var arg = hello ; // 3.var arg 變量聲明被忽略, arg = hello 被執行 function arg(){ console.log( hello world ) } console.log(arg); } test( hi ); /* 輸出: function arg() { console.log( hello world ); } hello */
這是由於當函數執行的時候,首先會造成一個新的私有的做用域,而後依次按照以下的步驟執行:
若是有形參,先給形參賦值
進行私有做用域中的預解釋,函數聲明優先級比變量聲明高,最後後者會被前者所覆蓋,可是能夠從新賦值
私有做用域中的代碼從上到下執行
函數多了,就有多個函數執行上下文,每次調用函數建立一個新的執行上下文,那如何管理建立的那麼多執行上下文呢?
JavaScript 引擎建立了執行棧來管理執行上下文。能夠把執行棧認爲是一個存儲函數調用的棧結構,遵循先進後出的原則。
從上面的流程圖,咱們須要記住幾個關鍵點:
JavaScript執行在單線程上,全部的代碼都是排隊執行。
一開始瀏覽器執行全局的代碼時,首先建立全局的執行上下文,壓入執行棧的頂部。
每當進入一個函數的執行就會建立函數的執行上下文,而且把它壓入執行棧的頂部。當前函數執行完成後,當前函數的執行上下文出棧,並等待垃圾回收。
瀏覽器的JS執行引擎老是訪問棧頂的執行上下文。
全局上下文只有惟一的一個,它在瀏覽器關閉時出棧。
ES6 到來JavaScript 有全局做用域、函數做用域和塊級做用域(ES6新增)。咱們能夠這樣理解:做用域就是一個獨立的地盤,讓變量不會外泄、暴露出去。也就是說做用域最大的用處就是隔離變量,不一樣做用域下同名變量不會有衝突。在介紹做用域鏈以前,先要了解下自由變量,以下代碼中,console.log(a)要獲得a變量,可是在當前的做用域中沒有定義a(可對比一下b)。當前做用域沒有定義的變量,這成爲 自由變量。
var a = 100 function fn() { var b = 200 console.log(a) // 這裏的a在這裏就是一個自由變量 console.log(b) } fn()
自由變量的值如何獲得 —— 向父級做用域(建立該函數的那個父級做用域)尋找。若是父級也沒呢?再一層一層向上尋找,直到找到全局做用域仍是沒找到,就宣佈放棄。這種一層一層的關係,就是做用域鏈 。
function F1() { var a = 100 return function () { console.log(a) } } function F2(f1) { var a = 200 console.log(f1()) } var f1 = F1() F2(f1) // 100
上述代碼中,自由變量a的值,從函數F1中查找而不是F2,這是由於當自由變量從做用域鏈中去尋找,依據的是函數定義時的做用域鏈,而不是函數執行時。
閉包這個概念也是JavaScript中比較抽象的概念,我我的理解,閉包是就是函數中的函數(其餘語言不能這樣),裏面的函數能夠訪問外面函數的變量,外面的變量的是這個內部函數的一部分。
閉包的做用:
使用閉包能夠訪問函數中的變量。
可使變量長期保存在內存中,生命週期比較長。
閉包不能濫用,不然會致使內存泄露,影響網頁的性能。閉包使用完了後,要當即釋放資源,將引用變量指向null。
閉包主要有兩個應用場景:
函數做爲參數傳遞(見做用域部分例子)
函數做爲返回值(以下例)
function outer() { var num = 0 //內部變量 return function add() { //經過return返回add函數,就能夠在outer函數外訪問了。 num++ //內部函數有引用,做爲add函數的一部分了 console.log(num) } } var func1 = outer() // func1() //其實是調用add函數, 輸出1 func1() //輸出2 var func2 = outer() func2() // 輸出1 func2() // 輸出2
先搞明白一個很重要的概念 —— this的值是在執行的時候才能確認,定義的時候不能確認! 爲何呢 —— 由於this是執行上下文環境的一部分,而執行上下文須要在代碼執行以前肯定,而不是定義的時候。看以下例子:
// 狀況1 function foo() { console.log(this.a) //1 } var a = 1 foo() // 狀況2 function fn(){ console.log(this); } var obj={fn:fn}; obj.fn(); //this->obj // 狀況3 function CreateJsPerson(name,age){ //this是當前類的一個實例p1 this.name=name; //=>p1.name=name this.age=age; //=>p1.age=age } var p1=new CreateJsPerson("尹華芝",48); // 狀況4 function add(c, d){ return this.a + this.b + c + d; } var o = {a:1, b:3}; add.call(o, 5, 7); // 1 + 3 + 5 + 7 = 16 add.apply(o, [10, 20]); // 1 + 3 + 10 + 20 = 34 // 狀況5 <button id="btn1">箭頭函數this</button> <script type="text/javascript"> let btn1 = document.getElementById( btn1 ); let obj = { name: kobe , age: 39, getName: function () { btn1.onclick = () => { console.log(this);//obj }; } }; obj.getName(); </script>
接下來咱們逐一解釋上面幾種狀況
對於直接調用 foo 來講,無論 foo 函數被放在了什麼地方,this 必定是 window
對於 obj.foo() 來講,咱們只須要記住,誰調用了函數,誰就是 this,因此在這個場景下 foo 函數中的 this 就是 obj 對象
在構造函數模式中,類中(函數體中)出現的this.xxx=xxx中的this是當前類的一個實例
call、apply和bind:this 是第一個參數
箭頭函數this指向:箭頭函數沒有本身的this,看其外層的是否有函數,若是有,外層函數的this就是內部箭頭函數的this,若是沒有,則this是window。
同步,個人理解是一種線性執行的方式,執行的流程不能跨越。好比說話後在吃飯,吃完飯後在看手機,必須等待上一件事完了,才執行後面的事情。
異步,是一種並行處理的方式,沒必要等待一個程序執行完,能夠執行其它的任務。比方說一我的邊吃飯,邊看手機,邊說話,就是異步處理的方式。在程序中異步處理的結果一般使用回調函數來處理結果。
// 同步 console.log(100) alert(200); console.log(300) //100 200 300
// 異步 console.log(100) setTimeout(function(){ console.log(200) }) console.log(300) //100 300 200
JS 須要異步的根本緣由是 JS 是單線程運行的,即在同一時間只能作一件事,不能「一心二用」。爲了利用多核CPU的計算能力,HTML5提出Web Worker標準,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質。
一個 Ajax 請求因爲網絡比較慢,請求須要 5 秒鐘。若是是同步,這 5 秒鐘頁面就卡死在這裏啥也幹不了了。異步的話,就好不少了,5 秒等待就等待了,其餘事情不耽誤作,至於那 5 秒鐘等待是網速太慢,不是由於 JS 的緣由。
前端使用異步的場景
定時任務:setTimeout,setInterval
網絡請求:ajax請求,動態加載
事件綁定
一個完整的 Event Loop 過程,能夠歸納爲如下階段:
一開始執行棧空,咱們能夠把執行棧認爲是一個存儲函數調用的棧結構,遵循先進後出的原則。micro 隊列空,macro 隊列裏有且只有一個 script 腳本(總體代碼)。
全局上下文(script 標籤)被推入執行棧,同步代碼執行。在執行的過程當中,會判斷是同步任務仍是異步任務,經過對一些接口的調用,能夠產生新的 macro-task 與 micro-task,它們會分別被推入各自的任務隊列裏。同步代碼執行完了,script 腳本會被移出 macro 隊列,這個過程本質上是隊列的 macro-task 的執行和出隊的過程。
上一步咱們出隊的是一個 macro-task,這一步咱們處理的是 micro-task。但須要注意的是:當 macro-task 出隊時,任務是一個一個執行的;而 micro-task 出隊時,任務是一隊一隊執行的。所以,咱們處理 micro 隊列這一步,會逐個執行隊列中的任務並把它出隊,直到隊列被清空。
執行渲染操做,更新界面
檢查是否存在 Web worker 任務,若是有,則對其進行處理
上述過程循環往復,直到兩個隊列都清空
接下來咱們看道例子來介紹上面流程:
Promise.resolve().then(()=>{ console.log( Promise1 ) setTimeout(()=>{ console.log( setTimeout2 ) },0) }) setTimeout(()=>{ console.log( setTimeout1 ) Promise.resolve().then(()=>{ console.log( Promise2 ) }) },0)
最後輸出結果是Promise1,setTimeout1,Promise2,setTimeout2
一開始執行棧的同步任務(這屬於宏任務)執行完畢,會去查看是否有微任務隊列,上題中存在(有且只有一個),而後執行微任務隊列中的全部任務輸出Promise1,同時會生成一個宏任務 setTimeout2
而後去查看宏任務隊列,宏任務 setTimeout1 在 setTimeout2 以前,先執行宏任務 setTimeout1,輸出 setTimeout1
在執行宏任務setTimeout1時會生成微任務Promise2 ,放入微任務隊列中,接着先去清空微任務隊列中的全部任務,輸出 Promise2
清空完微任務隊列中的全部任務後,就又會去宏任務隊列取一個,這回執行的是 setTimeout2
原型:在JavaScript中原型是一個prototype對象,用於表示類型之間的關係。
原型鏈:JavaScript萬物都是對象,對象和對象之間也有關係,並非孤立存在的。對象之間的繼承關係,在JavaScript中是經過prototype對象指向父類對象,直到指向Object對象爲止,這樣就造成了一個原型指向的鏈條,專業術語稱之爲原型鏈。
var Person = function() { this.age = 18 this.name = 匿名 } var Student = function() {} //建立繼承關係,父類實例做爲子類原型 Student.prototype = new Person() var s1 = new Student() console.log(s1)
原型關係圖:
當試圖獲得一個對象的某個屬性時,若是這個對象自己沒有這個屬性,那麼會去它的 __proto__
(即它的構造函數的prototype)中尋找。若是一直找到最上層都沒有找到,那麼就宣告失敗,返回undefined。最上層是什麼 —— Object.prototype.__proto__===null
介紹幾種常見繼承方式(如需瞭解更多,請點擊JavaScript常見的六種繼承方式):
原型鏈+借用構造函數的組合繼承
function Parent(value) { this.val = value } Parent.prototype.getValue = function() { console.log(this.val) } function Child(value) { Parent.call(this, value) } Child.prototype = new Parent() const child = new Child(1) child.getValue() // 1 child instanceof Parent // true
以上繼承的方式核心是在子類的構造函數中經過 Parent.call(this)
繼承父類的屬性,而後改變子類的原型爲 newParent()
來繼承父類的函數。
這種繼承方式優勢在於構造函數能夠傳參,不會與父類引用屬性共享,能夠複用父類的函數,可是也存在一個缺點就是在繼承父類函數的時候調用了父類構造函數,致使子類的原型上多了不須要的父類屬性,存在內存上的浪費。
寄生組合繼承:這種繼承方式對上一種組合繼承進行了優化
function Parent(value) { this.val = value } Parent.prototype.getValue = function() { console.log(this.val) } function Child(value) { Parent.call(this, value) } Child.prototype = Object.create(Parent.prototype, { constructor: { value: Child, enumerable: false, writable: true, configurable: true } }) const child = new Child(1) child.getValue() // 1 child instanceof Parent // true
以上繼承實現的核心就是將父類的原型賦值給了子類,而且將構造函數設置爲子類,這樣既解決了無用的父類屬性問題,還能正確的找到子類的構造函數。
ES6中class 的繼承
ES6中引入了class關鍵字,class能夠經過extends關鍵字實現繼承,還能夠經過static關鍵字定義類的靜態方法,這比 ES5 的經過修改原型鏈實現繼承,要清晰和方便不少。須要注意的是,class關鍵字只是原型的語法糖,JavaScript繼承仍然是基於原型實現的。
class Parent { constructor(value) { this.val = value } getValue() { console.log(this.val) } } class Child extends Parent { constructor(value) { super(value) this.val = value } } let child = new Child(1) child.getValue() // 1 child instanceof Parent // true
class 實現繼承的核心在於使用 extends 代表繼承自哪一個父類,而且在子類構造函數中必須調用 super,由於這段代碼能夠當作 Parent.call(this,value)
。
當網頁被加載時,瀏覽器會建立頁面的文檔對象模型(DOM),咱們能夠認爲 DOM 就是 JS 能識別的 HTML 結構,一個普通的 JS 對象或者數組。接下來咱們介紹常見DOM操做:
新增節點和移動節點
var div1 = document.getElementById( div1 ) // 添加新節點 var p1 = document.createElement( p ) p1.innerHTML = this is p1 div1.appendChild(p1) // 添加新建立的元素 // 移動已有節點。注意,這裏是「移動」,並非拷貝 var p2 = document.getElementById( p2 ) div1.appendChild(p2)
獲取父元素
var div1 = document.getElementById( div1 ) var parent = div1.parentElement
獲取子元素
var div1 = document.getElementById( div1 ) var child = div1.childNodes
刪除節點
var div1 = document.getElementById( div1 ) var child = div1.childNodes div1.removeChild(child[0])
DOM事件模型分爲捕獲和冒泡。一個事件發生後,會在子元素和父元素之間傳播(propagation)。這種傳播分紅三個階段。
(1)捕獲階段:事件從window對象自上而下向目標節點傳播的階段;(2)目標階段:真正的目標節點正在處理事件的階段;(3)冒泡階段:事件從目標節點自下而上向window對象傳播的階段。
DOM事件捕獲的具體流程
捕獲是從上到下,事件先從window對象,而後再到document(對象),而後是html標籤(經過document.documentElement獲取html標籤),而後是body標籤(經過document.body獲取body標籤),而後按照普通的html結構一層一層往下傳,最後到達目標元素。
接下來咱們看個事件冒泡的例子:
// 事件冒泡 <div id="outer"> <div id="inner"></div> </div> ...... window.onclick = function() { console.log( window ); }; document.onclick = function() { console.log( document ); }; document.documentElement.onclick = function() { console.log( html ); }; document.body.onclick = function() { console.log( body ); } outer.onclick = function(ev) { console.log( outer ); }; inner.onclick = function(ev) { console.log( inner ); };
如何阻止冒泡?
經過 event.stopPropagation()
方法阻止事件冒泡到父元素,阻止任何父事件處理程序被執行。咱們能夠在上例中inner元素的click事件上,添加 event.stopPropagation()
這句話後,就阻止了父事件的執行,最後只打印了 inner 。
inner.onclick = function(ev) { console.log( inner ) ev.stopPropagation() }
因爲事件會在冒泡階段向上傳播到父節點,所以能夠把子節點的監聽函數定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件。這種方法叫作事件的代理。
咱們設定一種場景,以下代碼,一個 <div>
中包含了若干個 <a>
,並且還能繼續增長。那如何快捷方便地爲全部 <a>
綁定事件呢?
<div id="div1"> <a href="#">a1</a> <a href="#">a2</a> <a href="#">a3</a> <a href="#">a4</a> </div> <button>點擊增長一個 a 標籤</button>
若是給每一個 <a>
標籤一一都綁定一個事件,那對於內存消耗是很是大的。藉助事件代理,咱們只須要給父容器div綁定方法便可,這樣無論點擊的是哪個後代元素,都會根據冒泡傳播的傳遞機制,把父容器的click行爲觸發,而後把對應的方法執行,根據事件源,咱們能夠知道點擊的是誰,從而完成不一樣的事。
var div1 = document.getElementById( div1 ) div1.addEventListener( click , function (e) { // e.target 能夠監聽到觸發點擊事件的元素是哪個 var target = e.target if (e.nodeName === A ) { // 點擊的是 <a> 元素 alert(target.innerHTML) } })
最後,使用代理的優勢以下:
使代碼簡潔
減小瀏覽器的內存佔用
BOM(瀏覽器對象模型)是瀏覽器自己的一些信息的設置和獲取,例如獲取瀏覽器的寬度、高度,設置讓瀏覽器跳轉到哪一個地址。
window.screen對象:包含有關用戶屏幕的信息
window.location對象:用於得到當前頁面的地址(URL),並把瀏覽器重定向到新的頁面
window.history對象:瀏覽歷史的前進後退等
window.navigator對象:經常用來獲取瀏覽器信息、是否移動端訪問等等
獲取屏幕的寬度和高度
console.log(screen.width) console.log(screen.height)
獲取網址、協議、path、參數、hash 等
// 例如當前網址是 https://juejin.im/timeline/frontend?a=10&b=10#some console.log(location.href) // https://juejin.im/timeline/frontend?a=10&b=10#some console.log(location.protocol) // https: console.log(location.pathname) // /timeline/frontend console.log(location.search) // ?a=10&b=10 console.log(location.hash) // #some
另外,還有調用瀏覽器的前進、後退功能等
history.back() history.forward()
獲取瀏覽器特性(即俗稱的UA)而後識別客戶端,例如判斷是否是 Chrome 瀏覽器
var ua = navigator.userAgent var isChrome = ua.indexOf( Chrome ) console.log(isChrome)
Ajax 是一種異步請求數據的一種技術,對於改善用戶的體驗和程序的性能頗有幫助。簡單地說,在不須要從新刷新頁面的狀況下,Ajax 經過異步請求加載後臺數據,並在網頁上呈現出來。常見運用場景有表單驗證是否登入成功、百度搜索下拉框提示和快遞單號查詢等等。Ajax的目的是提升用戶體驗,較少網絡數據的傳輸量。
如何手寫 XMLHttpRequest 不借助任何庫
var xhr = new XMLHttpRequest() xhr.onreadystatechange = function () { // 這裏的函數異步執行 if (xhr.readyState == 4) { if (xhr.status == 200) { alert(xhr.responseText) } } } xhr.open("GET", "/api", false) xhr.send(null)
由於瀏覽器出於安全考慮,有同源策略。也就是說,若是協議、域名或者端口有一個不一樣就是跨域,Ajax 請求會失敗。
那麼是出於什麼安全考慮纔會引入這種機制呢?其實主要是用來防止 CSRF 攻擊的。簡單點說,CSRF 攻擊是利用用戶的登陸態發起惡意請求。
而後咱們來考慮一個問題,請求跨域了,那麼請求到底發出去沒有?請求必然是發出去了,可是瀏覽器攔截了響應。
常見的幾種跨域解決方案(https://github.com/ljianshu/Blog/issues/55):
JSONP:利用同源策略對 <script>
標籤不受限制,不過只支持GET請求
CORS:實現 CORS 通訊的關鍵是後端,服務端設置 Access-Control-Allow-Origin
就能夠開啓,備受推崇的跨域解決方案,比 JSONP 簡單許多
Node中間件代理或nginx反向代理:主要是經過同源策略對服務器不加限制
sessionStorage 、localStorage 和 cookie 之間的區別
共同點:都是保存在瀏覽器端,且都遵循同源策略。
不一樣點:在於生命週期與做用域的不一樣
做用域:localStorage只要在相同的協議、相同的主機名、相同的端口下,就能讀取/修改到同一份localStorage數據。sessionStorage比localStorage更嚴苛一點,除了協議、主機名、端口外,還要求在同一窗口(也就是瀏覽器的標籤頁)下
生命週期:localStorage 是持久化的本地存儲,存儲在其中的數據是永遠不會過時的,使其消失的惟一辦法是手動刪除;而 sessionStorage 是臨時性的本地存儲,它是會話級別的存儲,當會話結束(頁面被關閉)時,存儲內容也隨之被釋放。
幾種常見模塊化規範的簡介,詳情請點擊: https://github.com/ljianshu/Blog/issues/48
CommonJS規範主要用於服務端編程,加載模塊是同步的,這並不適合在瀏覽器環境,由於同步意味着阻塞加載,瀏覽器資源是異步加載的,所以有了AMD CMD解決方案
AMD規範在瀏覽器環境中異步加載模塊,並且能夠並行加載多個模塊。不過,AMD規範開發成本高,代碼的閱讀和書寫比較困難,模塊定義方式的語義不暢。
CMD規範與AMD規範很類似,都用於瀏覽器編程,依賴就近,延遲執行,能夠很容易在Node.js中運行。不過,依賴SPM 打包,模塊的加載邏輯偏重
ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案
參考資料
浪裏行舟博客
前端面試之道
Web前端面試指導
前端入門4--DOM和BOM
Web 前端面試指南與高頻考題解析
回覆「加羣」與大佬們一塊兒交流學習~