一年前寫了一篇JavaScript八張思維導圖,主要是對前端JavaScript知識點的一個系統的整理和總結。本篇文章用了近一個月時間,蒐集整理了網上各種面試筆試題及本人針對前端的一些理解以及各路大神針對前端難點部分的詳細介紹,可以作為以後面試或者考察面試人員的參考。
相信通過這兩篇文章的學習,一定會讓你對前端有一個更深的認識。
如果覺得對你有所幫助,希望能夠動動手指給個Star(O(∩_∩)O)。
後續會持續更新......
JavaScript基礎
介紹JS的基本資料型別
string number boolean undefined null
介紹JS有哪些內建物件
- 資料封裝類物件: String Number Boolean Array Object
- 其他物件: Function Math Date Error Arguments RegExp
宿主物件和原生物件的區別
- 原生物件(native object)是由ECMAScript規範定義的JavaScript內建物件,如 String Number Boolean Array Object Function Math Date Error Arguments RegExp等。
- 宿主物件(host object)是由執行時的環境(瀏覽器或node)決定的,如window、XMLHTTPRequest等。
null、undefined及未宣告變數之間的區別。如何區分?
- 未宣告變數預設值為undefined
typeof null === 'object' // true
typeof undefined === 'undefined' // true
==和===的區別
==比較之前會先進行型別轉換,即不會對型別進行比較。例如:
12 == '12' // true true == 1 // true false == '0' // true
===會比較數值和型別。例如:
12 == '12' // false 12 == 12 // true true == 1 // false false == '0' // false
JS隱式轉換及應用場景
JS在使用運算子號或者對比符時,會自帶隱式轉換,規則如下:
- -、*、/、% :一律轉換成數值後計算
+:
- 數字 + 字串 = 字串, 運算順序是從左到右
- 數字 + 物件, 優先呼叫物件的valueOf -> toString
- 數字 + boolean/null -> 數字
- 數字 + undefined -> NaN
- [1].toString() === '1'
- {}.toString() === '[object object]'
NaN !== NaN 、+undefined 為 NaN
"Attribute"和"Property"的區別
"Attribute"是在HTML中定義的,而"property"是在DOM上定義的。為了說明區別,假設我們在HTML中有一個文字框:
<input type="text" value="Hello">
const input = document.querySelector('input');
console.log(input.getAttribute('value')); // Hello
console.log(input.value); // Hello
但是在文字框中鍵入“ World!”後:
console.log(input.getAttribute('value')); // Hello
console.log(input.value); // Hello World!
NaN是什麼?如何判斷是否是NaN型別
- 定義: 全域性屬性 NaN 的值表示不是一個數字(Not-A-Number)
如何判斷一個值是否是NaN: 等號運算子(== 和 ===) 不能被用來判斷一個值是否是 NaN。必須使用 Number.isNaN() 或 isNaN() 函式。
NaN === NaN; // false Number.NaN === NaN; // false isNaN(NaN); // true isNaN(Number.NaN); // true
如何判斷兩個物件相等
需要考慮三個問題:
- 如果物件的屬性值之一本身就是一個物件
- 如果屬性值中的一個是NaN(在JavaScript中,是不是等於自己唯一的價值?)
- 如果一個屬性的值為undefined,而另一個物件沒有這個屬性(因而計算結果為不確定?)
檢查物件的“值相等”的一個強大的方法,最好是依靠完善的測試庫,涵蓋了各種邊界情況。Underscore和Lo-Dash有一個名為_.isEqual()方法,用來比較好的處理深度物件的比較。您可以使用它們像這樣:
// Outputs: true
console.log(_.isEqual(obj1, obj2));
什麼是'user strict',使用它有什麼優缺點?
'use strict' 是用於對整個指令碼或單個函式啟用嚴格模式的語句。嚴格模式是可選擇的一個限制 JavaScript 的變體一種方式 。
優點:
- 無法再意外建立全域性變數。
- 會使引起靜默失敗(silently fail,即:不報錯也沒有任何效果)的賦值操丟擲異常。
- 試圖刪除不可刪除的屬性時會丟擲異常(之前這種操作不會產生任何效果)。
- 要求函式的引數名唯一。
- 全域性作用域下,this的值為undefined。
- 捕獲了一些常見的編碼錯誤,並丟擲異常。
- 禁用令人困惑或欠佳的功能。
缺點:
- 缺失許多開發人員已經習慣的功能。
- 無法訪問function.caller和function.arguments。
- 以不同嚴格模式編寫的指令碼合併後可能導致問題。
總的來說,我認為利大於弊,我從來不使用嚴格模式禁用的功能,因此我推薦使用嚴格模式。
call,apply和bind的作用是什麼?兩者區別是什麼?
.call和.apply都用於呼叫函式,第一個引數將用作函式內 this 的值。然而,.call接受逗號分隔的引數作為後面的引數,而.apply接受一個引數陣列作為後面的引數。一個簡單的記憶方法是,從call中的 C 聯想到逗號分隔(comma-separated),從apply中的 A 聯想到陣列(array)。
function add(a, b) {
return a + b;
}
console.log(add.call(null, 1, 2)); // 3
console.log(add.apply(null, [1, 2])); // 3
.call和.apply是立即執行的, .bind 返回函式的副本,但帶有繫結上下文!它不是立即執行的。
const person = { name: 'Lydia' }
function sayHi(age) {
console.log(`${this.name} is ${age}`)
}
sayHi.call(person, 21)
sayHi.bind(person, 21)
結果: Lydia is 21 function
請說明Function.prototype.bind的用法
摘自MDN:
bind()方法建立一個新的函式, 當被呼叫時,將其 this 關鍵字設定為提供的值,在呼叫新函式時,在任何提供之前提供一個給定的引數序列。
根據我的經驗,將this的值繫結到想要傳遞給其他函式的類的方法中是非常有用的。在 React 元件中經常這樣做。
如何判斷是否為空陣列
var arr = [];
if (Array.isArray(arr) && arr.length === 0) {
console.log('是空陣列');
}
// Array.isArray是ES5提供的,如果不支援。用下面的方案。
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
陣列方法
- map: 遍歷陣列,返回回撥返回值組成的新陣列
- forEach: 無法break,可以用try/catch中throw new Error來停止
- filter: 過濾
- some: 有一項返回true,則整體為true
- every: 有一項返回false,則整體為false
- join: 通過指定連線符生成字串
- sort(fn) / reverse: 排序與反轉,改變原陣列
- concat: 連線陣列,不影響原陣列, 淺拷貝
- slice(start, end): 返回截斷後的新陣列,不改變原陣列
- splice(start, number, value...): 返回刪除元素組成的陣列,value 為插入項,改變原陣列
- indexOf / lastIndexOf(value, fromIndex): 查詢陣列項,返回對應的下標
- reduce / reduceRight(fn(prev, cur), defaultPrev): 兩兩執行,prev 為上次化簡函式的return值,cur 為當前值(從第二項開始)
陣列亂序:
var arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
arr.sort(function () {
return Math.random() - 0.5;
});
陣列拆解:
// flat: [1,[2,3]] --> [1, 2, 3]
Array.prototype.flat = function() {
return this.toString().split(',').map(item => +item )
}
push、pop、shift、unshift功能及返回值
- push:新增新元素到陣列末尾,返回陣列長度
- pop:移除陣列最後一個元素,返回元素本身
- unshift:新增新元素到陣列開頭,返回陣列長度
- shift:移除陣列首個元素,返回元素本身
以上4種操作均會改變陣列本身
.forEach和.map()迴圈的主要區別,使用場景舉例
map用法:
let array = [1, 2, 3, 4, 5];
let newArray = array.map((item, i, arr) => {
return item * 2;
});
console.log("array:", array); // [1, 2, 3, 4, 5]
console.log("newArray:", newArray); // [2, 4, 6, 8, 10]
// 此處的array接受map方法運算之後的返回值
// 但是map方法並不能改變原來的陣列
forEach用法:
let array = [1, 2, 3, 4, 5];
let newArray = array.forEach((item, i, arr) => {
console.log('item:' + item + ', index:' + i);
// array[i] = item * 2; // 可以用這種方式改變原始陣列的值
});
console.log("array:", array); // [1, 2, 3, 4, 5]
console.log("newArray:", newArray); // undefined
// forEach方法沒有返回值
- 能用forEach()做到的,map()同樣可以。反過來也是如此。
- map()會分配記憶體空間儲存新陣列並返回,forEach()不會返回資料。
- forEach()允許callback更改原始陣列的元素。map()返回新的陣列。
JS執行物件查詢時,永遠不會去查詢原型的函式是哪個?
Object.prototype.hasOwnProperty()
如何將arguments轉為陣列
- Array.prototype.slice.call(arguments)
- [].slice.call(arguments)
物件的遍歷方法
for迴圈
for (let property in obj) { console.log(property); }
但是,這還會遍歷到它的繼承屬性,在使用之前,你需要加入obj.hasOwnProperty(property)檢查。
Object.keys()
Object.keys(obj).forEach((property) => { ... })
Object.keys()方法會返回一個由一個給定物件的自身可列舉屬性組成的陣列。
Object.getOwnPropertyNames()
Object.getOwnPropertyNames(obj).forEach((property) => { ... })
Object.getOwnPropertyNames()方法返回一個由指定物件的所有自身屬性的屬性名(包括不可列舉屬性但不包括 Symbol 值作為名稱的屬性)組成的陣列。
陣列的遍歷方法
for loops
for (let i = 0; i < arr.length; i++) { ... }
forEach
arr.forEach((item, index, arr) { ... })
map
arr.map((item, index, arr) => { ... })
匿名函式的典型應用場景
匿名函式可以在 IIFE 中使用,來封裝區域性作用域內的程式碼,以便其宣告的變數不會暴露到全域性作用域。
(function() {
// 一些程式碼。
})();
匿名函式可以作為只用一次,不需要在其他地方使用的回撥函式。當處理函式在呼叫它們的程式內部被定義時,程式碼具有更好地自閉性和可讀性,可以省去尋找該處理函式的函式體位置的麻煩。
setTimeout(function() {
console.log('Hello world!');
}, 1000);
匿名函式可以用於函數語言程式設計或 Lodash(類似於回撥函式)。
const arr = [1, 2, 3];
const double = arr.map(function(el) {
return el * 2;
});
console.log(double); // [2, 4, 6]
IIFE(立即執行函式)的用法
IIFE( 立即呼叫函式表示式)是一個在定義時就會立即執行的 JavaScript 函式。
(function () {
statements
})();
這是一個被稱為 自執行匿名函式 的設計模式,主要包含兩部分。第一部分是包圍在 圓括號運算子 () 裡的一個匿名函式,這個匿名函式擁有獨立的詞法作用域。這不僅避免了外界訪問此 IIFE 中的變數,而且又不會汙染全域性作用域。
第二部分再一次使用 () 建立了一個立即執行函式表示式,JavaScript 引擎到此將直接執行函式。
document的load事件和DOMContentLoaded事件之間的區別
當初始的 HTML 文件被完全載入和解析完成之後,DOMContentLoaded事件被觸發,而無需等待樣式表、影象和子框架的完成載入。
window的load事件僅在 DOM 和所有相關資源全部完成載入後才會觸發。
資料型別判斷方式有幾種
下面將對如下資料進行判斷它們的型別
var bool = true
var num = 1
var str = 'abc'
var und = undefined
var nul = null
var arr = [1, 2, 3]
var obj = {a: 'aa', b: 'bb'}
var fun = function() {console.log('I am a function')}
typeof: 適合基本的資料型別和函式
console.log(typeof bool); // boolean console.log(typeof num); // number console.log(typeof str); // string console.log(typeof und); // undefined console.log(typeof nul); // object console.log(typeof arr); // object console.log(typeof obj); // object console.log(typeof fun); // function
由結果可知typeof可以測試出number、string、boolean、undefined及function,而對於null及陣列、物件,typeof均檢測出為object,不能進一步判斷它們的型別。
instanceof: 判斷物件型別,基於原型鏈去判斷。
obj instanceof Object: 左運算元是一個物件,右運算元是一個函式構造器或者函式物件,判斷左邊的運算元的原型鏈_proto_屬性是否有右邊這個函式物件的proptotype屬性。console.log(bool instanceof Boolean);// false console.log(num instanceof Number); // false console.log(str instanceof String); // false console.log(und instanceof Object); // false console.log(arr instanceof Array); // true console.log(nul instanceof Object); // false console.log(obj instanceof Object); // true console.log(fun instanceof Function);// true var bool2 = new Boolean() console.log(bool2 instanceof Boolean);// true var num2 = new Number() console.log(num2 instanceof Number);// true var str2 = new String() console.log(str2 instanceof String);// true function Animation(){} var ani = new Animation() console.log(ani instanceof Animation);// true function Dog(){} Dog.prototype = new Animation() var dog = new Dog() console.log(dog instanceof Dog); // true console.log(dog instanceof Animation);// true console.log(dog instanceof Object); // true
從結果中看出instanceof不能區別undefined和null,而且對於基本型別如果不是用new宣告的則也測試不出來,對於是使用new宣告的型別,它還可以檢測出多層繼承關係。
constructor: 返回對建立此物件的函式的引用
console.log(bool.constructor === Boolean); // true console.log(num.constructor === Number); // true console.log(str.constructor === String); // true console.log(arr.constructor === Array); // true console.log(obj.constructor === Object); // true console.log(fun.constructor === Function); // true console.log(ani.constructor === Animation); // true console.log(dog.constructor === Dog); // false console.log(dog.constructor === Animation);// true
null 和 undefined 是無效的物件,因此是不會有 constructor 存在的,這兩種型別的資料需要通過其他方式來判斷。
函式的 constructor 是不穩定的,這個主要體現在自定義物件上,當開發者重寫 prototype 後,原有的 constructor 引用會丟失。所以dog.constructor === Animation 而不是 Dog
Object.prototype.toString.call
console.log(Object.prototype.toString.call(bool)); //[object Boolean] console.log(Object.prototype.toString.call(num)); //[object Number] console.log(Object.prototype.toString.call(str)); //[object String] console.log(Object.prototype.toString.call(und)); //[object Undefined] console.log(Object.prototype.toString.call(nul)); //[object Null] console.log(Object.prototype.toString.call(arr)); //[object Array] console.log(Object.prototype.toString.call(obj)); //[object Object] console.log(Object.prototype.toString.call(fun)); //[object Function] console.log(Object.prototype.toString.call(dog)); //[object Object]
原理(摘自高階程式設計3):在任何值上呼叫 Object 原生的 toString() 方法,都會返回一個 [object NativeConstructorName] 格式的字串。每個類在內部都有一個 [[Class]] 屬性,這個屬性中就指定了上述字串中的建構函式名。
但是它不能檢測非原生建構函式的建構函式名。使用jquery中的$.type
console.log($.type(bool)); //boolean console.log($.type(num)); //number console.log($.type(str)); //string console.log($.type(und)); //undefined console.log($.type(nul)); //null console.log($.type(arr)); //array console.log($.type(obj)); //object console.log($.type(fun)); //function console.log($.type(dog)); //object
$.type()內部原理就是用的Object.prototype.toString.call()
DOM操作(增刪改查)
新增操作
let element = document.createElement("div"); // 建立元素 body.appendChild(element); // 將一個節點新增到指定父節點的子節點列表末尾
刪除操作
var oldChild = node.removeChild(child); // 刪除子元素 ChildNode.remove() // 刪除元素
修改操作
Node.innerText // 修改元素文字內容 Element.innerHTML // 設定或獲取描述元素後代的HTML語句
查詢操作
Document.getElementById() // 返回對擁有指定 id 的第一個物件的引用 Document.querySelector() // 返回文件中匹配指定的CSS選擇器的第一元素 Document.querySelectorAll() // 返回與指定的選擇器組匹配的文件中的元素列表
https://developer.mozilla.org/zh-CN/docs/Web/API/Document_Object_Model
非同步載入JS 的方式有哪些
瀏覽器下載除JS外的資源時,會並行下載,以提高效能。但下載JS指令碼時,會禁止並行下載(稱為指令碼阻塞Scripts Block Downloads)。瀏覽器遇到JS時,必須等JS下載,解析,執行完後,才能繼續並行下載下一個資源。原因是JS可能會改變頁面或改變JS間的依賴關係,例如A.js中用document.write改變頁面,B.js依賴於A.js。因此要嚴格保證順序,不能並行下載。
由於瀏覽器在遇到<body>標籤前是不會渲染頁面的,為了避免白屏,通常的建議是將JS放到
標籤底下,可以有最佳的使用者體驗。按推薦度排序:
動態建立<script>標籤(Dynamic Script Element)
var script = document.createElement('script'); // 建立script標籤 script.type = "text/javascript"; script.src = "A.js"; document.getElementsByTagName('head')[0].appendChild(script); // 塞進頁面
先用document.createElement(‘script’)生成一個script標籤,再設定它的src屬性,最後將其插入到<head>中。
script標籤被插入到頁面的DOM樹後,就會開始下載src屬性指定的指令碼。而且通過動態指令碼元素下載指令碼是非同步的,不會阻塞頁面的其他下載和處理過程,因此script標籤插入<head>中也沒問題。
Script async
<script type="text/javascript" src="A.js" async></script>
瀏覽器解析到HTML裡的該行script標籤,發現指定為async,會非同步下載解析執行指令碼。
async 是HTML5裡為script標籤新增的屬性,對於低版本瀏覽器會存在相容性問題。
它會在下載完成後立刻執行,而不是會等到DOM載入完成之後再執行,所以還是有可能會造成阻塞。
這種方式只適用於引用外部js檔案的<script>標籤。
新增async屬性的js檔案不應該使用document.write方法。
對多個帶有async的js檔案,它不能保證按順序執行,它是哪個js檔案先下載完就先執行哪個。
Script defer
<script type="text/javascript" src="A.js" defer></script>
瀏覽器解析到HTML裡的該行script標籤,發現指定為defer,會暫緩下載解析執行指令碼。而是等到頁面載入完畢後,才載入指令碼(更精確地說,是在DOM樹構建完成後,在window.onload觸發前,載入defer的指令碼)。
defer也是隻適用於外部js檔案,也不能在js中使用document.write方法。
可以保證多個js檔案的執行順序就是它們在頁面中出現的順序。
document.write和innerHTML有何區別
- document.write會重繪整個頁面,如果不指定元素的話,它會覆蓋掉整個頁面內容。
- innerHTML只會重繪頁面的一部分。
jQuery.extend和jQuery.fn.extend的區別
針對jQuery效能的優化方法
如何判斷當前指令碼執行在瀏覽器還是node環境中
通過判斷Global物件是否為window,如果不為window,則當前指令碼執行在node.js環境中。
this === window ? 'browser' : 'node';
Canvas和SVG的比較
Ajax的原理
- http://www.cnblogs.com/lidgblogs/p/7403828.html
- https://www.cnblogs.com/ygj0930/p/6126542.html
- http://www.cnblogs.com/jackson0714/p/AJAX.html#_label8
JS事件委託、事件冒泡
IE和Firefox的事件機制有何區別,如何阻止冒泡?
- IE只支援事件冒泡,火狐同時支援事件捕獲和事件冒泡兩種。
- 阻止事件冒泡的方式不同
IE: e.cancelBubble = true
W3C: e.stopPropagation()
JS記憶體空間的管理
基礎資料型別與棧記憶體
JS中的基礎資料型別,這些值都有固定的大小,往往都儲存在棧記憶體中,由系統自動分配儲存空間。我們可以直接操作儲存在棧記憶體空間的值,因此基礎資料型別都是按值訪問。資料在棧記憶體中的儲存與使用方式類似於資料結構中的堆疊資料結構,遵循後進先出的原則。
基礎資料型別: Number String Null Undefined Boolean
引用資料型別與堆記憶體
與其他語言不同,JS的引用資料型別,比如陣列Array、物件Object、函式Function,它們值的大小是不固定的。引用資料型別的值是儲存在堆記憶體中的物件。JavaScript不允許直接訪問堆記憶體中的位置,因此我們不能直接操作物件的堆記憶體空間。
在操作物件時,實際上是在操作物件的引用而不是實際的物件。因此,引用型別的值都是按引用訪問的。這裡的引用,我們可以粗淺地理解為儲存在棧記憶體中的一個地址,該地址與堆記憶體的實際值相關聯。
總結:
棧記憶體 | 堆記憶體 |
---|---|
儲存基礎資料型別 | 儲存引用資料型別 |
按值訪問 | 按引用訪問 |
儲存的值大小固定 | 儲存的值大小不定,可動態調整 |
由系統自動分配記憶體空間 | 由開發人員通過程式碼分配 |
主要用來執行程式 | 主要用來存放物件 |
空間小,執行效率高 | 空間大,但是執行效率相對較低 |
先進後出,後進先出 | 無序儲存,可根據引用直接獲取 |
JS執行上下文
JS變數物件詳解
請解釋變數提升
JS作用域及作用域鏈/閉包(closure),常用場景舉例說明
請簡述JS中的this
JS函式與函數語言程式設計
JS原型,原型鏈。實現繼承的方式
JS有哪幾種建立物件的方式
- 利用Object建構函式方式建立
- 利用物件字面量方式建立
- 利用工廠模式建立
- 利用建構函式模式建立
- 利用原型方式建立
- 利用建構函式和原型的組合模式建立
請解釋事件迴圈,呼叫堆疊和任務佇列的區別
- https://segmentfault.com/a/1190000012646373
- https://zhuanlan.zhihu.com/p/25407758
- https://juejin.im/post/58cf180b0ce4630057d6727c
- https://blog.csdn.net/lin_credible/article/details/40143961
談談對Promise的理解
ES6知識點
- var let const
- =>箭頭函式
- 模版字串
- 解析結構
- 函式預設引數
- ...展開運算子
- class
- Promise
防抖與節流
防抖與節流函式是一種最常用的 高頻觸發優化方式,能對效能有較大的幫助。
防抖 (debounce): 將多次高頻操作優化為只在最後一次執行,通常使用的場景是:使用者輸入,只需再輸入完成後做一次輸入校驗即可。
節流(throttle): 每隔一段時間後執行一次,也就是降低頻率,將高頻操作優化成低頻操作,通常使用場景: 滾動條事件 或者 resize 事件,通常每隔 100~500 ms執行一次即可。
function throttle(method, context) { clearTimeout(method.tID); method.tID = setTimeout(function () { method.call(context); }, 1000); }
用法:
function showTime() { console.log("nowDate:" + new Date().toLocaleDateString()); } setInterval(function () { throttle(showTime); }, 2000);
模組化
模組化開發在現代開發中已是必不可少的一部分,它大大提高了專案的可維護、可擴充和可協作性。通常,我們 在瀏覽器中使用 ES6 的模組化支援,在 Node 中使用 commonjs 的模組化支援。
分類:
- es6: import / export
- commonjs: require / module.exports / exports
- amd: require / defined
require與import的區別
- require支援 動態匯入,import不支援,正在提案 (babel 下可支援)
- require是 同步 匯入,import屬於 非同步 匯入
- require是 值拷貝,匯出值變化不會影響匯入值;import指向 記憶體地址,匯入值會隨匯出值而變化
oAuth實現方案
- http://www.ruanyifeng.com/blog/2019/04/oauth_design.html
- http://www.ruanyifeng.com/blog/2019/04/oauth-grant-types.html
如何實現單點登入(Single Sign On)
請解釋SPA(單頁應用),優缺點是什麼?如何使其對SEO友好
單頁Web應用(single page web application,SPA),就是隻有一張Web頁面的應用,是載入單個HTML 頁面並在使用者與應用程式互動時動態更新該頁面的Web應用程式。
單頁應用SPA | 多頁應用MPA | |
---|---|---|
組成 | 一個外殼頁面和多個頁面片段組成 | 多個完整頁面構成 |
資源共用(css,js) | 共用,只需在外殼部分載入 | 不共用,每個頁面都需要載入 |
重新整理方式 | 頁面區域性重新整理或更改 | 整頁重新整理 |
url 模式 | a.com/#/pageone a.com/#/pagetwo |
a.com/pageone.html a.com/pagetwo.html |
使用者體驗 | 頁面片段間的切換快,使用者體驗良好 由於要一次載入所有的資源(html/js),故首屏載入慢 |
頁面切換載入緩慢,流暢度不夠,使用者體驗比較差 首屏載入很快 |
轉場動畫 | 容易實現 | 無法實現 |
資料傳遞 | 容易 | 依賴 url傳參、或者cookie 、localStorage等 |
搜尋引擎優化(SEO) | 需要單獨方案、實現較為困難、不利於SEO檢索。 Prerender預渲染優化SEO |
實現方法簡易 |
試用範圍 | 高要求的體驗度、追求介面流暢的應用 | 適用於追求高度支援搜尋引擎的應用 |
開發成本 | 較高,常需藉助專業的框架 | 較低,但頁面重複程式碼多 |
維護成本 | 相對容易 | 相對複雜 |
前端效能優化方案
以下是移動端的優化方案,大部分Web端也同樣適用
正規表示式
設計模式舉例(實現、應用、優缺點)
前端常用框架對比
JS編碼規範
瀏覽器相關
瀏覽器架構
- 使用者介面
- 主程式
- 核心
- 渲染引擎
- JS 引擎
- 執行棧
- 事件觸發執行緒
- 訊息佇列
- 微任務
- 巨集任務
- 訊息佇列
- 網路非同步執行緒
瀏覽器下事件迴圈(Event Loop)
事件迴圈是指: 執行一個巨集任務,然後執行清空微任務列表,迴圈再執行巨集任務,再清微任務列表
- 微任務 microtask(jobs): promise / ajax / Object.observe(該方法已廢棄)
- 巨集任務 macrotask(task): setTimout / setInterval / script / IO / UI Rendering
瀏覽器解析流程
從輸入 url 到展示的過程
- DNS 解析
- TCP 三次握手
- 傳送請求,分析 url,設定請求報文(頭,主體)
- 伺服器返回請求的檔案 (html)
- 瀏覽器渲染
- HTML parser --> DOM Tree
- 標記化演算法,進行元素狀態的標記
- dom 樹構建
- CSS parser --> Style Tree
- 解析 css 程式碼,生成樣式樹
- attachment --> Render Tree
- 結合 dom樹 與 style樹,生成渲染樹
- layout: 佈局
- GPU painting: 畫素繪製頁面
- HTML parser --> DOM Tree
功能檢測、功能推斷、navigator.userAgent的區別
功能檢測(feature detection)
功能檢測包括確定瀏覽器是否支援某段程式碼,以及是否執行不同的程式碼(取決於它是否執行),以便瀏覽器始終能夠正常執行程式碼功能,而不會在某些瀏覽器中出現崩潰和錯誤。例如:
if ('geolocation' in navigator) {
// 可以使用 navigator.geolocation
} else {
// 處理 navigator.geolocation 功能缺失
}
Modernizr是處理功能檢測的優秀工具。
功能推斷(feature inference)
功能推斷與功能檢測一樣,會對功能可用性進行檢查,但是在判斷通過後,還會使用其他功能,因為它假設其他功能也可用,例如:
if (document.getElementsByTagName) {
element = document.getElementById(id);
}
非常不推薦這種方式。功能檢測更能保證萬無一失。
UA 字串
這是一個瀏覽器報告的字串,它允許網路協議對等方(network protocol peers)識別請求使用者代理的應用型別、作業系統、應用供應商和應用版本。它可以通過navigator.userAgent訪問。 然而,這個字串很難解析並且很可能存在欺騙性。例如,Chrome 會同時作為 Chrome 和 Safari 進行報告。因此,要檢測 Safari,除了檢查 Safari 字串,還要檢查是否存在 Chrome 字串。不要使用這種方式。
考慮到歷史原因及現代瀏覽器中使用者代理字串(userAgent)的使用方式,通過使用者代理字串來檢測特定的瀏覽器並不是一件輕鬆的事情。所以使用使用者代理檢測是最後的選擇。
使用者代理檢測一般適用以下的情形:
- 不能直接準確的使用功能檢測。
- 同一款瀏覽器在不同平臺下具備不同的能力。這個時候可能就有必要確定瀏覽器位於哪個平臺。
- 為了跟蹤分析等目的需要知道特定的瀏覽器。
瀏覽器版本檢測方式
可以使用navigator.userAgent。
JS同源策略(same-origin policy)
同源策略限制了從同一個源載入的文件或指令碼如何與來自另一個源的資源進行互動。這是一個用於隔離潛在惡意檔案的重要安全機制。
下表給出了相對http://store.company.com/dir/page.html同源檢測的示例:
URL | 結果 | 原因 |
---|---|---|
http://store.company.com/dir2/other.html | 成功 | 只有路徑不同 |
http://store.company.com/dir/inner/another.html | 成功 | 只有路徑不同 |
https://store.company.com/secure.html | 失敗 | 不同協議 ( https和http ) |
http://store.company.com:81/dir/etc.html | 失敗 | 不同埠 ( http:// 80是預設的) |
http://news.company.com/dir/other.html | 失敗 | 不同域名 ( news和store ) |
跨標籤頁通訊
不同標籤頁間的通訊,本質原理就是去運用一些可以 共享的中間介質,因此比較常用的有以下方法:
- 通過父頁面window.open()和子頁面postMessage
- 非同步下,通過 window.open('about: blank') 和 tab.location.href = '*'
- 設定同域下共享的localStorage與監聽window.onstorage
- 重複寫入相同的值無法觸發
- 會受到瀏覽器隱身模式等的限制
- 設定共享cookie與不斷輪詢髒檢查(setInterval)
- 藉助服務端或者中間層實現
跨域的解決方案
按實際使用量排序(個人理解):
- CORS 跨域
- nginx反向代理
- WebSockets
- JSONP 只支援GET請求
- hash + iframe 只支援GET請求
- postMessage 只支援GET請求
document.domain
瀏覽器資料本地儲存方法(localStroage、sessionStroage、cookie、indexedDB)
目前常見的儲存方式為以下三種:
- Cookie
- web儲存 (locaStorage和seesionStorage)
- IndexedDB
在H5出現之前,資料都是儲存在cookie中的。為了解決cookie的侷限性引入了Web儲存,indexedDB用於客戶端儲存大量結構化資料(包括, 檔案/ blobs)。
共同點:都是儲存在瀏覽器端、且同源的
區別:
Cookie | localStorage | sessionStorage | indexedDB | |
---|---|---|---|---|
容量大小 | 4kb左右 | 5M左右 | 5M左右 | 無限容量 |
過期時間 | 只在設定的過期時間之前一直有效, 即使視窗或者瀏覽器關閉 |
始終有效 | 當前瀏覽器視窗關閉前有效 | 始終有效 |
儲存方式 | 瀏覽器和伺服器間來回傳遞 | 本地儲存 | 本地儲存 | 本地儲存 |
作用域 | 在同源視窗中共享 | 在同源視窗中共享 | 在同源視窗並且同一視窗中共享 | 在同源視窗中共享 |
Web安全舉例
- XSS(跨站指令碼攻擊)幾種形式,防範手段,過濾哪些字元
- csrf(跨站請求偽造)原理,實現,防範手段
- sql注入
- 命令列注入
- DDoS(Distributed Denial of Service) 又叫分散式拒絕服務
- 流量劫持
DNS劫持
HTTP劫持 伺服器漏洞
狀態碼
Web Worker
現代瀏覽器為JavaScript創造的 多執行緒環境。可以新建並將部分任務分配到worker執行緒並行執行,兩個執行緒可 獨立執行,互不干擾,可通過自帶的 訊息機制 相互通訊。
限制:
- 同源限制
- 無法使用 document / window / alert / confirm
- 無法載入本地資源
記憶體洩露
- 意外的全域性變數: 無法被回收
- 定時器: 未被正確關閉,導致所引用的外部變數無法被釋放
- 事件監聽: 沒有正確銷燬 (低版本瀏覽器可能出現)
- 閉包: 會導致父級中的變數無法被釋放
- dom 引用: dom 元素被刪除時,記憶體中的引用未被正確清空
可用 chrome 中的 timeline 進行記憶體標記,視覺化檢視記憶體的變化情況,找出異常點。
HTTP快取機制
cookie和session的區別
常見相容性問題(移動端/PC端)
polyfill的作用
程式碼相關
44個 Javascript 題解析
https://lidaguang1989.github.io/2018/01/javascript-puzzlers/
43個 javascript 進階問題列表
https://github.com/lydiahallie/javascript-questions/blob/master/README-zh_CN.md
如何實現陣列去重
正則實現trim()功能
function myTrim(str) {
let reg = /^\s+|\s+$/g;
return str.replace(reg, "");
}
console.log(myTrim(' asdf '));