儘管秋招尚未拿到offer(好難過),可是一些知識點仍是要總結的,既然本身選了這條路,那就必定要堅決不移的走下去......javascript
new
運算符的優先級function Foo() { return this; } Foo.getName = function () { console.log('1'); }; Foo.prototype.getName = function () { console.log('2'); }; new Foo.getName(); // -> 1 new Foo().getName(); // -> 2
經過這段代碼能夠看出:new Foo()
的優先級高於 new Foo
.css
對於代碼1 來講:是將 Foo.getName
當成一個構造函數來執行,執行構造函數因此輸出爲1.html
對於代碼2來講:經過 new Foo()
建立了一個Foo的實例,經過實例訪問其原型鏈上的 方法因此輸出爲2.前端
var foo = 1; // 有名當即執行函數 (function foo() { foo = 10; console.log(foo); })(); // 執行這段代碼會輸出什麼呢? // -> ƒ foo() { foo = 10 ; console.log(foo) } // 再去訪問 foo 的值 foo // -> 1
當JS執行器遇到非匿名的當即執行函數時,會建立一個輔助的特定對象,而後將函數名稱做爲這個對象的屬性,所以行數內部才能夠訪問到 foo
,但這個值是隻讀的,因此對其從新賦值不會生效,因此打印結果仍是這個函數,而且外部的值也沒有發生改變。java
可使用 JSON.stringify
和 JSON.parse
這個兩個方法node
優勢:簡單web
缺點:會忽略掉 undefined
; 不能序列化函數 ; 不能解決循環引用的對象面試
function clone(obj) { return JSON.parse(JSON.stringify(obj)); }
使用遞歸循環賦值的方式ajax
優勢:能夠處理 undefined
、函數等各類狀況算法
缺點:實現相對麻煩,效率不高
function clone(obj) { if(!obj || typeof obj !== 'object') { return; } var _obj = obj.constructor === Object ? {} : []; for(let key in obj) { if(typeof obj[key] === 'object') { _obj[key] = clone(obj[key]); } else { _obj[key] = obj[key]; } } return _obj; } // 或者 function clone(obj) { if(!obj || typeof obj !== 'object') throw new TypeError('params typeError'); let _obj = obj.constructor === Object ? {} : []; Object.getOwnPropertyNames(obj).forEach(name => { if(typeof obj[name] === 'object') { _obj[name] = clone(obj[name]); } else { _obj[name] = obj[name]; } }); return _obj; }
使用內置 MessageChannel
對象
優勢:是內置函數中處理深拷貝性能最快的
缺點:不能處理函數(會報錯)
function clone(obj) { return new Promise(resolve => { let {port1, port2} = new MessageChannel(); port2.onmessage = ev => resolve(ev.data); port1.postMessage(obj); }); } clone(obj).then(console.log);
async/await
, promise
異步執行順序想解決這個問題,就必須知道 `await` 作了什麼?
剛開始覺得await
會一直等待表達執行的執行結果,以後纔會執行後面的代碼。 實際上await
是一個讓出線程的標誌(遇到await
會當即返回一個pending
狀態的promise)。await後面的函數會先執行一遍,而後就會跳出整個async
函數來執行後面js代碼。等本輪事件循環執行完又跳回到async
函數中等待await後面表達式的返回值,若是返回值爲非promise
則繼續執行async後面的代碼,不然將promse
加入隊列。
且看一道面試題(分析代碼執行 順序):
async function async1() { console.log("async1 start"); await async2(); console.log("async1 end"); } async function async2() { console.log("async2"); } console.log("script start"); setTimeout(function () { console.log("settimeout"); },0); async1(); new Promise(function (resolve) { console.log("promise1"); resolve(); }).then(function () { console.log("promise2"); }); console.log("script end");
OK,那接下來具體分析執行過程:
首先輸出 "script start" ,而後當即將定時器加入異步事件隊列。執行 async1() ,輸出 "async1 start" ,進入 async2() ,輸出 "async2" ,跳出整個 async1() 函數來執行後面js代碼,執行promise執行器中的內容,輸出 "promise1" ,執行resolve()回調,將then函數中的內容加入異步事件隊列,接着輸出 "script end" 。回到 async1() 的await等待 async2() 函數的返回值,由於返回值是一個promise實例,將promise加入異步事件隊列。此時的同步代碼執行完畢,輪詢並從隊列拿出代碼放入主線程執行,因此輸出 "promise2" ,繼續執行 async1() 中的後續內容,輸出 "async1 end" ,最後取出定時器中的內容並執行,輸出 "settimeout" 。
綜上所述:
script start async1 start async2 promise1 script end promise2 async1 end settimeout
那麼再看一個例子應該會簡單不少:
function testFunc() { console.log("testFunc..."); // 2 return "testFunc"; } async function testAsync() { console.log("testAsync..."); // 7 return Promise.resolve("hello testAsync"); } async function foo() { console.log("test start..."); // 1 const v1 = await testFunc(); connsole.log('hello world.'); // 5 console.log(v1); // 6 testFunc const v2 = await testAsync(); console.log(v2); // 9 hello testAsync } foo(); var promise = new Promise(resolve => { console.log("promise start.."); // 3 resolve("promise"); }); promise.then(val => console.log(val)); // 8 promise console.log("test end..."); // 4
一個簡單的防抖函數:
function debounce(func, wait) { let timer = null; return function(...params) { // 若是定時器存在則清除 if(timer){ clearTimeout(timer); } // 從新開始定時執行 timer = setTimeout(() => { func.apply(this, params); }, wait); } }
缺點:只能在最後執行,不能當即被執行,在某些狀況下不適用。
改進...
function debounce(func, wait, immediate) { let timer, context, args; // 定時器 let later = function() { return setTimeout(() => { timer = null; if(!immediate) { func.apply(context, args); } }, wait); } return function(...params) { if(!timer) { timer = later(); // immediate 爲 true,則當即執行 // 不然 緩存上下文 和 參數 if(immediate) { func.apply(this, params); } else { context = this; args = params; } } else { clearTimeout(timer); timer = later(); } } }
一個簡單的節流函數:
// 節流函數 // 快速的屢次執行,轉化爲等待wait時間再去執行 function throttle(func, wait) { var timer = null; var context = null; return function(...args) { context = this; if(!timer) { timer = setTimeout(function() { timer = null; func.apply(context, args); }, wait); } } } // 若是想讓第一次調用當即執行也很是簡單 僅須要將 func.apply(context, args) 提到定時器外邊便可。
節流函數除了可使用定時器實現之外,固然也能夠有其餘方式:
// 第一次調用會被當即執行 function throttle(func, wait) { var prev = 0; var context = null; return function(...args) { var now = +new Date(); context = this; if(now -prev > wait) { func.apply(context,args); prev = now; } } }
怎麼去模擬一個call函數呢?
思路:call一個很是重要的做用就是改變上下文環境也就是this,咱們能夠給用戶傳入的上下文對象上添加一個函數,經過這個上下文對象去執行函數,而後將這個函數刪除,返回結果就能夠了。
Function.prototype.myCall = function(context, ...args) { context = context || window; // 給上下文對象上添加這個函數 context.fn = this; // 經過這個上下文對象去執行函數 let result = context.fn(...args); // 將這個函數刪除 delete context.fn; return result; }
call既然都實現了,那麼apply也是相似的,只不過傳入的參數是一個數組而已。
Function.prototype.myApply = function(context, arr) { context = context || window; arr = arr || []; let type = {}.toString.call(arr).slice(8,-1); if(type !== 'Array') throw new TypeError('CreateListFromArrayLike called on non-object'); context.fn = this; let result = context.fn(...arr); delete context.fn; return result; }
模擬bind函數,bind函數應該返回一個新的函數。
Function.prototype.myBind = function(context, ...args) { // 保存當前的函數 let func = this; return function F(...params) { if(this instanceof F) { return new func(...args, ...params); } return func.apply(context,[...args,...params]); } }
function flattenDeep(arr) { if(!Array.isArray(arr)) return [arr]; return arr.reduce((prev,cur) => { return [...prev, ...flattenDeep(cur)]; },[]); } flattenDeep([1, [[2], [3, [4]], 5]]);
輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否可能爲該棧的彈出順序。假設壓入棧的全部數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不多是該壓棧序列的彈出序列。
function IsPopOrder(pushV,popV){ if(pushV.length === 0) return false; var stack = []; // 模擬棧 for(var i = 0, j = 0; i < pushV.length;){ stack.push(pushV[i]); i += 1; // 壓入棧的值須要被彈出 while(j < popV.length && stack[stack.length-1] === popV[j]){ stack.pop(); j++; if(stack.length === 0) break; } } return stack.length === 0; }
思路:
var stackA = []; var stackB = []; function push(node){ stackA.push(node); } function pop(){ if(!stackB.length){ while(stackA.length){ stackB.push(stackA.pop()); } } return stackB.pop(); }
fetch
fetch(url).then(function(response){ return response.json(); }).then(function(data){ console.log(data); }).catch(function(err){ console.log(err); });
ajax
var xhr = new XMLHttpRequest(); xhr.open('GET', url); xhr.responseType = 'json'; xhr.onload = function(){ console.log(xhr.response); } xhr.onerror = function(){ console.log('error'); } xhr.send();
fetch(url, {credentials: 'include'})
將一個完整的數組分紅兩部分,分別對其排序,而後將兩部分merge在一塊兒便可。
function merge(left, right) { var temp = []; while(left.length && right.length) { if(left[0] < right[0]) temp.push(left.shift()); else temp.push(right.shift()); } return temp.concat(left,right); } function mergeSort(arr) { if(arr.length === 1) return arr; var mid = (arr.length/2)|0; var left = arr.slice(0,mid); var right = arr.slice(mid); return merge(mergeSort(left), mergeSort(right)); }
this
指向固定化,並非由於箭頭函數內部有綁定 this
的機制,實際緣由是箭頭函數沒有本身的 this
,致使內部的this就是外層代碼的 this
,也正是由於沒有 this
,因此箭頭函數不能用做構造函數。
浮動元素參與計算
var content = document.querySelector('.content'); // 自定義事件 var evt = new Event('custom'); var customEvt = new CustomEvent('customEvt', { // 經過這個屬性傳遞參數 detail: { name: 'tom', age: 12 } }); content.addEventListener('custom', (e) => { console.log('自定義事件被觸發,無參數...'); console.log(e); }); content.addEventListener('customEvt', (e) => { console.log('自定義事件被觸發,有參數...'); console.log(e); console.log(e.detail); }); // 點擊時觸發這個自定義事件 content.addEventListener('click', (e) => { content.dispatchEvent(evt); content.dispatchEvent(customEvt); });
var foo = 3; // 不在同一個做用域 function hoistVariable() { // 內部變量提高致使 foo 的初始值爲undefined // 因此 foo = 5; var foo = foo || 5; console.log(foo); // 5 } hoistVariable();
上邊的比較簡單,看一個函數和變量同名,關於變量提高的小問題。
var a = 6; function b(){ console.log(a); // @1 var a = 4; function a(){ alert(4); } console.log(a); //@2 } b();
@1
輸出的是 function a(){alert(4);}
a=4;
這一句,從新對 a
進行賦值。@2
這裏天然會輸出 4
。若是還不能理解?且看預編譯後的代碼:
var a; a = 6; function b(){ var a; a = function a(){ // 函數先提高 alert(4); } console.log(a); // @1 a = 4; console.log(a); // @2 } b(); // 結果已經很是明瞭了
1. 先後端如何通訊?
2. 如何建立ajax?
// 建立xhr對象 var xhr = XMLHttpRequest ? new XMLHttpRequest() : new window.ActiveXObject('Microsoft.XMLHTTP'); // GET 請求 xhr.open('GET',url,true); xhr.send(); // POST 請求 xhr.open('POST',url,true); // 表單數據 , 也能夠提交json數據,相應的content-Type: application/json xhr.setRequestHeader('content-Type', 'application/x-www-from-urlencoded'); xhr.send(dataArr.join('&')); xhr.onload = function() { if(xhr.status === 200 || xhr.status === 304) { var data = xhr.responseText; // 拿到數據 } else { // 出問題了 } }
3. 跨域通訊的幾種方式?
JSONP:利用 script
標籤的跨域能力,服務返回一個js函數調用,數據做爲函數的一個參數來傳遞。
var script = document.createElement('script'); script.type = 'text/javascript'; script.src = url; // 跨域地址 document.head.appendChild(script); //有能耐把我這輩子都安排了,否則少他媽扯淡。 setTimeout(function() { document.head.removeChild(script); script = null; }); // 接收數據 function jsonpCallback(data) { console.log(data); }
WebSocket:不受同源政策限制。
var ws = new WebSocket('wss://echo.websocket.org'); ws.onopen = function(e) { ws.send('hello...'); } ws.onmessage = function(e) { var data = e.data; } ws.onclose = function() { console.log('close...'); }
Hash:利用 location.hash
來傳值。 缺點:數據直接暴露在url中,大小、類型都有限制。
一、父窗體能夠把信息寫在子窗體的href的hash上,子窗口經過監聽hashchange事件獲取信息。
二、子窗體改變父窗體的hash值,那麼就要藉助第三個子窗體,第三個子窗體是第二個子窗體的子窗體。(第三個子窗體要與父窗體同源)
三、第二個子窗體把信息設置在第三個子窗體的hash值上,而後第三個子窗體改變父窗體的hash值,從而實現跨域。
// 父窗體 var son = document.getElementByTagName('iframe'); son.src = son.src + '#' + data; // 子窗體 window.onhashchange = function() { var data = window.location.hash; }
postMessage :語法:window.postMessage(msg,targetOrigin)
// 窗口A 發送 BWindow.postMessage('發送的數據', 'http://B.com'); // 窗口B 接收 window.addEventListener('message', (event) => { event.origin: // http://A.com event.source; // AWindow event.data; // '發送的數據' });
CORS: 跨域資源共享。
fetch(url, { method: 'get', // 頭信息配置 }).then(() => {});
防護措施:
Token驗證(請求必須攜帶Token)
Referer 驗證(驗證請求的來源是否可信)
原理:注入腳本
防護措施:對用戶的輸入作驗證
1. 什麼是DOCTYPE及做用?
用來聲明文檔類型和DTD規範的。
DTD(document type definition)文檔類型定義,是一系列的語法規則,用來聲明XML或(X)HTML的文件類型,瀏覽器會使用它來決定文檔類型,決定使用何種協議來解析,以及切換瀏覽器模式。
2. 常見的doctype有哪些?
提高頁面性能的方式?
非核心代碼異步加載
異步加載方式:
預解析DNS
<meta http-equiv="x-dnns-prefetch-control" content="on"> <link rel="dns-prefetch" href="//host_name_to_prefetch.com">
運行錯誤捕獲:(1)try...catch (2)window.onerror
資源加載錯誤 :(1)object.onerror(資源錯誤不冒泡) (2)performance.getEntries() (3)Error事件捕獲(在事件流捕獲階段處理錯誤)
上報錯誤原理
利用Image對象上報
// 利用Image標籤上報錯(簡單、不須要藉助其餘庫) (new Image()).src = 'http://www.baidu.com/test?error=xxx';
function binarySearch(arr,val,leftIndex,rightIndex) { if(leftIndex > rightIndex){ return; } var midIndex = (leftIndex + rightIndex) / 2 | 0; var midVal = arr[midIndex]; if(val > midVal) { return binarySearch(arr,val,midIndex+1,rightIndex); }else if(val < midVal) { return binarySearch(arr,val,leftIndex,midIndex-1); }else{ return midIndex; } }
在一個字符串中找出連續的不重複的最大長度的字符串,解決這類問題的思路:
// 連續最長不重複字符串 function getMaxLenStr(str) { var cur = []; var maxLenStr = ''; for(var i = 0; i < str.length; i++) { if(!cur.includes(str[i])) { cur.push(str[i]); } else { cur = []; // 置爲空 cur.push(str[i]); } // 存儲最大長度的字符串 if(maxLenStr.length < cur.length) { maxLenStr = cur.join(''); } } return maxLenStr; } getMaxLenStr('ababcabcde'); // abcde
和上面這道題有同樣思路的是:求一個數組當中,連續子向量的最大和。
無非是將 對比字符串的長度 改成 對比值大小
function FindGreatestSumOfSubArray(arr) { let sum = arr[0]; let max = arr[0]; for(let i = 1; i < arr.length; i++) { if(sum < 0) { sum = arr[i]; }else{ sum += arr[i]; } // 記錄最大值 if(max < sum) { max = sum; } } return max; }
阿里的一道面試題:給定一個編碼字符,按編碼規則進行解碼,輸出字符串
編碼規則:coount[letter]
,將letter
的內容count
次輸出,count
是0或正整數,letter
是區分大小寫的純字母。
實例:
3[a]2[bc]
; decodeString(s); // 返回 ‘aaabcbc’
3[a2[c]]
; decodeString(s); // 返回 ‘accaccacc’
2[ab]3[cd]ef
; decodeString(s); // 返回 ‘ababcdcdcdef’
解題過程...
使用棧這種數據結構,若是push
的內容爲‘]’
,則循環pop
字符,直到碰到’[‘
,而後將pop
出來的字符串按規則整理後,從新push
進棧中,最後將棧內的內容拼接成字符串輸出便可。
代碼:
const s = '2[a2[c]]ef'; function decodeString(str) { let stack = []; // 存儲字符串的棧 for (let i = 0; i < str.length; i++) { let cur = str[i]; if (cur !== ']') { stack.push(cur); } else { // 彈出 let count = 0; let loopStr = []; let popStr = ''; while ((popStr = stack.pop()) !== '[') { loopStr.unshift(popStr); } count = stack.pop(); // 添加結果 let item = ''; for (let i = 0; i < count; i++) { item += loopStr.join(''); } stack.push(...(item.split(''))); } } return stack.join(''); } console.log(decodeString(s)); // accaccef
移動次數
與關鍵字的初始排列次序無關的是:基數排列。比較次數
與初始序列無關是:選擇排序。時間複雜度
與初始序列無關的是:選擇排序。BOM 即瀏覽器對象模型,BOM沒有相關標準,BOM的核心對象是window對象。
DOM即文檔對象模型,DOM是W3C標準,DOM的最根本對象是document(window.document), 這個對象其實是window對象的屬性,這個對象的獨特之處是惟一一個既屬於BOM有屬於DOM的對象。
都是創建於tcp鏈接之上,經過tcp協議來傳輸數據。
HTTP是一種單向的協議,即客戶端只能向服務器端請求信息。request永遠等於response,而且這個response是被動的,不能主動發起。一旦有一個任務超時,就會阻塞後續的任務(線頭阻塞)。
HTTP協議是無狀態的,如使用輪詢、long poll都須要將身份鑑別信息上傳。
WebSocket真正的全雙工通訊,只須要一次鏈接,這樣就避免了HTTP的無狀態性,服務器端能夠主動推送消息到達客戶端。
function unique(arr) { let obj = {}; let ret = []; for(let i = 0; i < arr.length; i++) { let cur = `${typeof arr[i]}-${arr[i]}`; if(!obj[cur]) { ret.push(arr[i]); obj[cur] = true; } } return ret; } var arr = [1, '1', 3, 3, 4]; unique(arr); // [1, '1', 3, 4]
function isSame(a, b) { if(a === b) return true; if(typeof a !== typeof b) return false; // 若是均爲object類型,判斷屬性個數是否相同 if(typeof a === 'object' && typeof b === 'object') { let aLen = Object.getOwnPropertyNames(a).length; let bLen = Object.getOwnPropertyNames(b).length; if(aLen !== bLen) return false; } return Object.getOwnPropertyNames(a).every(key => { if(typeof a[key] === 'object') { return isSame(a[key], b[key]); } return a[key] === b[key]; }); }
後記一波:
付出了總歸是有回報的。
但,學習是永無止境的。(先從作好總結開始)