簡述同步和異步的區(qū)別
眾所周知,javascript是單線程的語言,所謂的單線程,就是從上至下,依次執(zhí)行,當(dāng)然這里的依次執(zhí)行要拋開javascript的預(yù)解析機制。
這樣做的原因是因為javascript最初是為了操作DOM,運行在瀏覽器環(huán)境下的,而操作DOM的時候,不能是異步的,不然的話兩個異步任務(wù)同時修改DOM結(jié)構(gòu)的話,會導(dǎo)致瀏覽器不知道該執(zhí)行哪一個。
但是這樣做也有缺點,當(dāng)遇到一個響應(yīng)時間特別長的任務(wù)時,容易導(dǎo)致頁面加載錯誤或者瀏覽器未響應(yīng)的情況。
同步就是所有的任務(wù)都處在同一隊列中,不可以插隊,一個任務(wù)執(zhí)行完接著開始執(zhí)行下一個,相對于瀏覽器而言,同步的效率過低,一些耗費時間比較長的任務(wù)應(yīng)該用異步來執(zhí)行。
異步就是將一個任務(wù)放入到異步隊列中,當(dāng)這個任務(wù)執(zhí)行完成之后,再從異步隊列中提取出來,插隊到同步隊列中,拿到異步任務(wù)的結(jié)果,可以提升代碼執(zhí)行的效率,不需要因為一個耗費時長的代碼而一直等待。
javascript異步的幾種實現(xiàn)方式
-
回調(diào)函數(shù) callback
最簡單的回調(diào)函數(shù)的實現(xiàn)方式,利用setTimeout會進(jìn)入計時器隊列,而不是正常隊列的特性,來實現(xiàn)回調(diào)函數(shù)
計時器隊列與正常隊列同時運行,不影響正常隊列的運行,在正常隊列空閑的時候,并且計時器隊列的任務(wù)倒計時已經(jīng)結(jié)束,就將計時器隊列的任務(wù)提取到正常隊列中獲得最終結(jié)果。-
function syncMethod(callback) { setTimeout(function() { callback(count); console.log('第一次調(diào)用回調(diào)函數(shù)!'); setTimeout(function() { callback(count); console.log('第二次調(diào)用回調(diào)函數(shù)!'); }) for(let i=0;i<100;i++) { count++; } }, 0); let count = 1; for(let i=0;i<100;i++) { count++; } } syncMethod(function(count) { console.log(count); // 調(diào)用兩次,第一次返回101,第二次返回201 })callback示例返回值
-
-
事件驅(qū)動
- javascript是基于事件驅(qū)動的語言,當(dāng)我們給元素綁定事件的時候,并不會立即執(zhí)行,而是當(dāng)觸發(fā)指定的事件時,才會執(zhí)行對應(yīng)的方法
const btn = document.querySelector('button'); btn.addEventlistener('click', function() { console.log('clickBtn') })
-
發(fā)布者訂閱者模式(觀察者模式)
- 發(fā)布者訂閱者模式是通過保存事件,然后在需要使用的時候直接發(fā)布事件,就可以觸發(fā)保存的回調(diào)
-
function Watcher() { this.events = {}; // 定義事件對象,用來保存需要觸發(fā)的事件 }; // $on方法,用來監(jiān)聽事件,訂閱者 Watcher.prototype.$on = function(event, handler) { this.events[event] = this.events[event] || []; // 如果當(dāng)前事件已經(jīng)被監(jiān)聽,則在原基礎(chǔ)上進(jìn)行添加新的事件,如果沒有的話新建一個數(shù)組,用來存儲事件 this.events[event].push({ handler: handler }) // 將事件存儲到對應(yīng)的事件模型中 }; // $emit方法,用來發(fā)布事件,發(fā)布者 Watcher.prototype.$emit = function(event,...arg) { // 如果沒有訂閱當(dāng)前事件的話,直接返回 if(!this.events[event]) { return } // 循環(huán)遍歷對應(yīng)事件,將對應(yīng)的事件根據(jù)填入的先后順序觸發(fā) for(let i = 0; i< this.events[event].length; i++) { this.events[event][i].handler(...arg) } }; // 關(guān)閉監(jiān)聽事件 Watcher.prototype.$close = function(event) { // 如果沒有訂閱當(dāng)前事件的話,直接返回 if(!this.events[event]) { return } // 刪除訂閱事件 delete this.events[event]; }; // 實例化一個觀察者 let watcher= new Watcher(); // 監(jiān)聽事件,并傳入事件需要觸發(fā)的函數(shù) watcher.$on('handler1', function(name, age) { console.log(name, age, '我是第一個函數(shù)') }); // 監(jiān)聽不同的事件 watcher.$on('handler2', function(sex) { console.log(sex) }); // 監(jiān)聽同一個事件,觸發(fā)不同的函數(shù) watcher.$on('handler1', function(name, age) { console.log(name, age, '我是第二個函數(shù)') }); // 發(fā)布事件,并傳參 watcher.$emit('handler2', "男"); watcher.$emit('handler1', '張三',20); // 關(guān)閉事件 watcher.$close('handler1'); // 事件不會觸發(fā),因為已經(jīng)關(guān)閉了對應(yīng)事件的監(jiān)聽 watcher.$emit('handler1', '張三',20);Image 3.jpg
-
promise 異步編程的解決方案
- Promise是ES6新增的異步編程的解決方案,它有三種狀態(tài)分別是
pending進(jìn)行中、resolved已完成、rejected已失敗。 - Promise可以鏈?zhǔn)秸{(diào)用,避免層層嵌套,異步操作更加容易方便,提升代碼可讀性。
- Promise一旦創(chuàng)建就不可以取消,一旦在
resolved與rejected中確立一種狀態(tài),就不可以再發(fā)生改變 function asynMethod(flag) { return new Promise((resolve, reject) => { setTimeout(() => { if(flag) { resolve('郝晨光精心寫作,如有錯誤請指出,謝謝!'); }else { reject('錯誤信息!') } }, 3000) }) } asynMethod(true).then(data => { console.log(data); return asynMethod(true) }).then(data => { console.log(data); return asynMethod(false) }).catch(err =>{ console.log(err); })
- Promise是ES6新增的異步編程的解決方案,它有三種狀態(tài)分別是
數(shù)組去重(手寫代碼)
- 使用ES6的Set去重
- Set是ES6新增的數(shù)據(jù)類型,Set 的成員具有唯一性
function distinct(arr) { return Array.from(new Set(arr)); }
- 使用ES6的Set去重(超級簡化版)
[...new Set(arr)] // [...new Set(需要去重的數(shù)組)]
- 使用splice配合兩重for循環(huán)去重
function distinct(arr) { for(let i = 0; i < arr.length; i++) { for(let j = i + 1; j < arr.length; j++) { if(arr[i] === arr[j]) { arr.splice(j, 1); j--; } } } return arr; }
- 使用for循環(huán)配合indexOf去重
function distinct(arr) { let newArr = []; for(let i = 0; i < arr.length; i++) { if(newArr.indexOf(arr[i]) === -1) { newArr.push(arr[i]); } } return newArr; }
- 使用for循環(huán)配合sort排序去重
function distinct(arr) { arr = arr.sort(); let newArr = []; for(let i = 0; i < arr.length; i++) { if(arr[i] !== arr[i-1]) { newArr.push(arr[i]); } } return newArr; }
- 使用for循環(huán)配合includes去重
function distinct(arr) { let newArr = []; for(let i = 0; i < arr.length; i++) { if(!newArr.includes(arr[i])) { newArr.push(arr[i]); } } return newArr; }
- 使用filter配合indexOf去重
function distinct(arr) { return arr.filter((item,index, arr) => arr.indexOf(item) === index); }
- 使用reduce去重
function distinct(arr) { return arr.reduce((prev,next) => { if (!prev.includes(next)) { prev.push(next) } return prev }, []); }
在JavaScript中什么是偽數(shù)組?如何將偽數(shù)組轉(zhuǎn)化為標(biāo)準(zhǔn)數(shù)組
javascript中的偽數(shù)組(類數(shù)組):不具有數(shù)組的push,pop等方法,但是具有l(wèi)ength,以及可以利用for循環(huán)遍歷等特性,例如函數(shù)的 arguments 參數(shù)集合,還有通過document.getElementsByTagName等方法獲取的NodeList等都是類數(shù)組
如何將偽數(shù)組轉(zhuǎn)化為標(biāo)準(zhǔn)數(shù)組
let nodeList = document.getElementsByTagName('li'); let nodeArr = Array.from(nodeList);let nodeList = document.getElementsByTagName('li'); let nodeArr = Array.prototype.slice.call(nodeList);
當(dāng)轉(zhuǎn)化為標(biāo)準(zhǔn)數(shù)組以后,就可以調(diào)用數(shù)組實例上的方法了,如push、pop、splice、filter、map、forEach等等
SPA路由history模式,打包上線都遇到了哪些問題?你是如何解決的?
- 資源路徑404問題
- vue-cli3環(huán)境下,在根目錄新建vue.config.js,在該文件中寫入如下
module.exports = { publicPath: './' }
- 頁面刷新404問題
- 服務(wù)器端配置,詳見 前端常見面試題(六)@郝晨光 中 SPA 路由history模式上線后刷新404
JavaScript中callee和caller的作用
-
callee是函數(shù)arguments對象內(nèi)的指針,它指向當(dāng)前的函數(shù),使得在函數(shù)內(nèi)部遞歸調(diào)用當(dāng)前函數(shù)時,不需要調(diào)用函數(shù)名稱,減少函數(shù)內(nèi)部對于函數(shù)名的依賴
function test() { console.log(arguments.callee); } test(); -
caller是函數(shù)的一個屬性,它指向調(diào)用當(dāng)前函數(shù)的函數(shù),如果當(dāng)前函數(shù)在其他函數(shù)內(nèi)被調(diào)用,則返回調(diào)用它的那個函數(shù),如果是在全局環(huán)境下被調(diào)用,則返回 null
我們可以利用caller的特性跟蹤函數(shù)的調(diào)用鏈function fn1() { console.log(fn1.caller); console.log(arguments.callee.caller); } function fn2() { fn1() } fn2();
本文 CSDN 地址: 前端常見面試題(七)@郝晨光

