一年前寫了一篇JavaScript八張思惟導圖,主要是對前端JavaScript知識點的一個系統的整理和總結。本篇文章用了近一個月時間,蒐集整理了網上各類面試筆試題及本人針對前端的一些理解以及各路大神針對前端難點部分的詳細介紹,能夠做爲之後面試或者考察面試人員的參考。
相信經過這兩篇文章的學習,必定會讓你對前端有一個更深的認識。javascript
後續會持續更新......css
string number boolean undefined nullhtml
typeof null === 'object' // true
typeof undefined === 'undefined' // true
==比較以前會先進行類型轉換,即不會對類型進行比較。例如:前端
12 == '12' // true true == 1 // true false == '0' // true
===會比較數值和類型。例如:vue
12 === '12' // false 12 === 12 // true true === 1 // false false === '0' // false
JS在使用運算符號或者對比符時,會自帶隱式轉換,規則以下:java
+:node
NaN !== NaN 、+undefined 爲 NaNjquery
"Attribute"是在HTML中定義的,而"property"是在DOM上定義的。爲了說明區別,假設咱們在HTML中有一個文本框:
<input type="text" value="Hello">
git
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。必須使用 Number.isNaN() 或 isNaN() 函數。
NaN === NaN; // false Number.NaN === NaN; // false isNaN(NaN); // true isNaN(Number.NaN); // true
須要考慮三個問題:
檢查對象的「值相等」的一個強大的方法,最好是依靠完善的測試庫,涵蓋了各類邊界狀況。Underscore和Lo-Dash有一個名爲_.isEqual()方法,用來比較好的處理深度對象的比較。您可使用它們像這樣:
// Outputs: true console.log(_.isEqual(obj1, obj2));
'use strict' 是用於對整個腳本或單個函數啓用嚴格模式的語句。嚴格模式是可選擇的一個限制 JavaScript 的變體一種方式 。
優勢:
缺點:
總的來講,我認爲利大於弊,我歷來不使用嚴格模式禁用的功能,所以我推薦使用嚴格模式。
.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
摘自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]'; }; }
數組亂序:
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 ) }
以上4種操做均會改變數組自己
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方法沒有返回值
Object.prototype.hasOwnProperty()
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( 當即調用函數表達式)是一個在定義時就會當即執行的 JavaScript 函數。
(function () { statements })();
這是一個被稱爲 自執行匿名函數 的設計模式,主要包含兩部分。第一部分是包圍在 圓括號運算符 () 裏的一個匿名函數,這個匿名函數擁有獨立的詞法做用域。這不只避免了外界訪問此 IIFE 中的變量,並且又不會污染全局做用域。
第二部分再一次使用 () 建立了一個當即執行函數表達式,JavaScript 引擎到此將直接執行函數。
當初始的 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()
添加操做
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腳本時,會禁止並行下載(稱爲腳本阻塞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文件的執行順序就是它們在頁面中出現的順序。
經過判斷Global對象是否爲window,若是不爲window,則當前腳本運行在node.js環境中。
this === window ? 'browser' : 'node';
基礎數據類型與棧內存
JS中的基礎數據類型,這些值都有固定的大小,每每都保存在棧內存中,由系統自動分配存儲空間。咱們能夠直接操做保存在棧內存空間的值,所以基礎數據類型都是按值訪問。
數據在棧內存中的存儲與使用方式相似於數據結構中的堆棧數據結構,遵循後進先出的原則。
基礎數據類型: Number String Null Undefined Boolean
引用數據類型與堆內存
與其餘語言不一樣,JS的引用數據類型,好比數組Array、對象Object、函數Function,它們值的大小是不固定的。引用數據類型的值是保存在堆內存中的對象。JavaScript不容許直接訪問堆內存中的位置,所以咱們不能直接操做對象的堆內存空間。
在操做對象時,其實是在操做對象的引用而不是實際的對象。所以,引用類型的值都是按引用訪問的。這裏的引用,咱們能夠粗淺地理解爲保存在棧內存中的一個地址,該地址與堆內存的實際值相關聯。
總結:
棧內存 | 堆內存 |
---|---|
存儲基礎數據類型 | 存儲引用數據類型 |
按值訪問 | 按引用訪問 |
存儲的值大小固定 | 存儲的值大小不定,可動態調整 |
由系統自動分配內存空間 | 由開發人員經過代碼分配 |
主要用來執行程序 | 主要用來存放對象 |
空間小,運行效率高 | 空間大,可是運行效率相對較低 |
先進後出,後進先出 | 無序存儲,可根據引用直接獲取 |
防抖與節流函數是一種最經常使用的 高頻觸發優化方式,能對性能有較大的幫助。
防抖 (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 的模塊化支持。
分類:
require與import的區別
單頁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端也一樣適用
事件循環是指: 執行一個宏任務,而後執行清空微任務列表,循環再執行宏任務,再清微任務列表
功能檢測(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。
同源策略限制了從同一個源加載的文檔或腳本如何與來自另外一個源的資源進行交互。這是一個用於隔離潛在惡意文件的重要安全機制。
下表給出了相對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 ) |
不一樣標籤頁間的通信,本質原理就是去運用一些能夠 共享的中間介質,所以比較經常使用的有如下方法:
按實際使用量排序(我的理解):
document.domain
目前常見的存儲方式爲如下三種:
在H5出現以前,數據都是存儲在cookie中的。爲了解決cookie的侷限性引入了Web存儲,indexedDB用於客戶端存儲大量結構化數據(包括, 文件/ blobs)。
共同點:都是保存在瀏覽器端、且同源的
區別:
Cookie | localStorage | sessionStorage | indexedDB | |
---|---|---|---|---|
容量大小 | 4kb左右 | 5M左右 | 5M左右 | 無限容量 |
過時時間 | 只在設置的過時時間以前一直有效, 即便窗口或者瀏覽器關閉 |
始終有效 | 當前瀏覽器窗口關閉前有效 | 始終有效 |
存儲方式 | 瀏覽器和服務器間來回傳遞 | 本地保存 | 本地保存 | 本地保存 |
做用域 | 在同源窗口中共享 | 在同源窗口中共享 | 在同源窗口而且同一窗口中共享 | 在同源窗口中共享 |
服務器漏洞
現代瀏覽器爲JavaScript創造的 多線程環境。能夠新建並將部分任務分配到worker線程並行運行,兩個線程可 獨立運行,互不干擾,可經過自帶的 消息機制 相互通訊。
限制:
可用 chrome 中的 timeline 進行內存標記,可視化查看內存的變化狀況,找出異常點。
https://lidaguang1989.github.io/2018/01/javascript-puzzlers/
https://github.com/lydiahallie/javascript-questions/blob/master/README-zh_CN.md
function myTrim(str) { let reg = /^\s+|\s+$/g; return str.replace(reg, ""); } console.log(myTrim(' asdf '));