四、數據結構和算法
JavaScript編碼能力
-
多種方式實現數組去重、扁平化、對比優缺點
數組去重
1.遍歷數組:新建一個數組,遍歷需去重的數組,當值不再新數組時indexOf === -1 就加入新數組
2.排序後相鄰去除法:給傳入的數組排序,排序後相同的值會相鄰,然後遍歷排序後數組時,新數組只加入不與前一值重複的值。
3.優化遍歷數組法:雙層循環
4.ES6中的Set數據結構
數組扁平化
1.ES6中的flat(),可帶參數,表示拉平層數,默認1,未知Infinity
2.循環數組+遞歸調用
3.apply+some:
function steamroller2(arr){
while(arr.some(item=> Array.isArray(item))){
arr=[].concat.apply([],arr)
}
return arr
}
console.log(steamroller2(arr))
4.reduce方法
function steamroller3(arr){
return arr.reduce((prev,next)=>{
return prev.concat(Array.isArray(next)?steamroller3(next):next)
},[])
}
console.log(steamroller3(arr))
5.es6展開運算符 :
function steamroller4(arr){
while(arr.some(item=> Array.isArray(item))){
arr=[].concat(...arr)
}
return arr
}
-
多種方式實現深拷貝、對比優缺點
1.遞歸實現:
function deep(obj) {
//判斷拷貝的要進行深拷貝的是數組還是對象,是數組的話進行數組拷貝,對象的話進行對象拷貝
var objClone = Array.isArray(obj) ? [] : {};
//進行深拷貝的不能爲空,並且是對象或者是
if (obj && typeof obj === "object") {
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deep(obj[key]);
} else {
objClone[key] = obj[key];
}
}
}
}
return objClone;
}
2. 通過JSON對象實現:無法實現對對象中方法的深拷貝
//通過js的內置對象JSON來進行數組對象的深拷貝
function deepClone2(obj) {
var _obj = JSON.stringify(obj),
objClone = JSON.parse(_obj);
return objClone;
}
3.lodash函數庫實現: lodash.cloneDeep()
4.Jquery的extend()方法
注意:Object.assign(),slice(),concat()方法進行拷貝只能實現一級屬性的拷貝成功,二級以下仍脫離不了,算不上真正的深拷貝
-
手寫函數柯里化工具,並理解其應用場景和優勢
柯里化,是把接受多個參數的函數變換成接受一個單一參數的函數,並且返回接受餘下的參數而且返回結果的新函數的技術。
優勢:1.參數複用 2.提前確認 3.延遲運行
通用封裝方法:
// 初步封裝
var currying = function(fn) {
// args獲取第一個方法內的全部參數
var args = Array.prototype.slice.call(arguments, 1);
return function() {
// 將後面方法裏的全部參數和args進行合併
var newArgs = args.concat(Array.prototype.slice.call(arguments));
// 把合併後的參數通過apply作爲fn的參數並執行
return fn.apply(this, newArgs);
}
}
// 支持多參數傳遞
function progressCurrying(fn, args) {
var _this = this
var len = fn.length;
var args = args || [];
return function() {
var _args = Array.prototype.slice.call(arguments);
Array.prototype.push.apply(args, _args);
// 如果參數個數小於最初的fn.length,則遞歸調用,繼續收集參數
if (_args.length < len) {
return progressCurrying.call(_this, fn, _args);
}
// 參數收集完畢,則執行fn
return fn.apply(this, _args);
}
}
-
手寫防抖和節流工具函數,並理解其內部原理和應用場景
防抖:觸發事件後再n秒內函數只執行一次,如果再n秒內又觸發了事件,則重新計算函數執行時間。
應用場景:給按鈕家防抖防止表單多次提交;輸入欄連續輸入進行AJAX驗證,防抖減少請求次數;判斷scroll是否滑到底部;
適合多次事件一次響應的情況。
/**
* @desc 函數防抖
* @param func 函數
* @param wait 延遲執行毫秒數
*/
function debounce(func,wait) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
let callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait)
if (callNow) func.apply(context, args)
}
}
節流:指連續觸發事件但是再n秒內只執行一次函數。
適用場景:遊戲中的刷新率;DOM元素拖拽,Cancas畫筆功能
總體來說,適合大量事件按時間做平均分配觸發
function throttle(fn, gapTime) {
let _lastTime = null;
return function () {
let _nowTime = + new Date()
if (_nowTime - _lastTime > gapTime || !_lastTime) {
fn();
_lastTime = _nowTime
}
}
}
-
實現一個sleep函數
sleep函數:讓線程休眠等到值定時間再重新喚起
// 第一種實現方法
function sleep(numberMillis) {
var now = new Date();
var exitTime = now.getTime() + numberMillis;
while (true) {
now = new Date();
if (now.getTime() > exitTime){
break;
}
}
}
// 第二種實現方法
function sleep(numberMillis) {
var start = new Date().getTime();
while (true) {
if (new Date().getTime() - start > numberMillis) {
break;
}
}
}
// 方法三
function sleep(ms, callback) {
setTimeout(callback, ms);
}
// 方法四
function sleep(ms) {
return new Promise(
function(resolve, reject){
setTimeout(resolve, ms);
}
)
}
手動實現前端輪子
-
手動實現call、apply、bind
實現call
將目標函數的this指向傳入的第一個對象,參數不定長,且立即執行
function.prototype.mycall = function(obj){
var args = Array.prototype.slice.apply(arguments, [1]);
obj.fn = this; // 在obj上添加fn屬性,值是this
obj.fn(...args); // 在obj上調用函數,那函數的this值就是obj
delete obj.fn; // 傷處obj的fn屬性,去除影響
}
使用eval方法,回對傳入的字符串,當作JS代碼進行解析執行
Function.prototype.mycall = function(obj){
obj = obj||window;
var args = [];
for(var i = 1 ; i < arguments.length; i++) {
args.push('arguments[' + i + ']');
// 不能直接push值,因爲會導致參數爲數組時eval調用回將其轉換成字符串
}
obj.fn = this;
eval('obj.fn('+args+'));
delete obj.fn;
}
實現apply
apply和call區別只有一個,apply第二個參數爲數組
function.prototype.myApply = function(obj, arr) {
obj.fn = this;
if (!arr) {
obj.fn();
}else{
var args = []
for(let i = 0;i < arr.length;i++) {
args.push('arr[' + i + ']')
}
eval('obj.fn('+args+')');
}
delete obj.fn;
}
實現bind
返回一個被調用函數具有相同函數體的新函數,且之歌新函數也能使用new操作符。
Function.prototype.myFind = function(obj){
if(obj === null || obj === undefined){
obj = window;
} else {
obj = Object(obj);
}
let _this = this;
let argArr = [];
let arg1 = [];
for(let i = 1 ; i<arguments.length ; i++){
arg1.push( arguments[i] );
argArr.push( 'arg1[' + (i - 1) + ']' ) ;
}
// 下面可用apply
return function(){
let val ;
for(let i = 0 ; i<arguments.length ; i++){
argArr.push( 'arguments[' + i + ']' ) ;
}
obj._fn_ = _this;
console.log(argArr);
val = eval( 'obj._fn_(' + argArr + ')' ) ;
delete obj._fn_;
return val
};
}
-
手動實現符合Promise/A+規範的Promise、手動實現async await
Promise內部維護着三種狀態,即pending,resolved和rejected。初始狀態是pending,狀態可以有pending--->relolved,或者pending--->rejected.不能從resolve轉換爲rejected 或者從rejected轉換成resolved.
即 只要Promise由pending狀態轉換爲其他狀態後,狀態就不可變更。
-
手寫一個EventEmitter實現事件發佈、訂閱
-
可以手動實現兩種實現雙向綁定的方案
-
手寫JSON.stringify、JSON.parse
原生js實現JSON.parse()和JSON.stringify()
-
手寫一個模板引擎,並能解釋其中原理
正則匹配並替換字符串中 {{}} 中的值
function template(str, data) {
var reg = /{{([a-zA-Z0-9_$][a-zA-Z0-9\.]+)}}/g;
return str.replace(reg, function(raw, key, offset, string) {
var paths = data,
ary = key.split('.');
while(ary.length > 0) {
paths = paths[ary.shift()];
}
return paths || raw;
});
}
-
手寫懶加載、下拉刷新、上拉加載、預加載等效果
懶加載的目的是作爲服務器前端的優化,減少請求數或延遲請求數
實現方式:1.純粹的延遲加載,使用setTimeOut進行加載延遲
2.條件加載,符合條件或觸發事件再開始異步加載
3.可視區加載,監控滾動條來實現
var num = document.getElementsByTagName('img').length;
var img = document.getElementsByTagName("img");
// 存儲圖片加載到的位置,避免每次都從第一張圖片開始遍歷
var n = 0;
// 頁面載入完畢加載可視區域內的圖片
lazyLoad();
window.onscroll = lazyLoad;
// 監聽頁面滾動事件
function lazyLoad() {
// 可見區域高度
var seeHeight = document.documentElement.clientHeight;
// 滾動條距離頂部高度
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for (var i = n; i < num; i++) {
// 圖片距離上方的距離小於可視區高度加滾動條距上方高度
if (img[i].offsetTop < seeHeight + scrollTop) {
if (img[i].getAttribute("src") == "default.jpg") {
img[i].src = img[i].getAttribute("data-src");
}
n = i + 1;
}
}
}
預加載是犧牲服務器前端性能,換取更好的用戶體驗
實現方式:1.用CSS和JS實現預加載
2.僅使用JS實現預加載
3.使用Ajax實現預加載
function preLoadImg(url, callback) {
var img = new Image(); //創建一個Image對象,實現圖片的預下載
img.src = url;
if (img.complete) { // 如果圖片已經存在於瀏覽器緩存,直接調用回調函數
callback.call(img);
return; // 直接返回,不用再處理onload事件
}
img.onload = function () { //圖片下載完畢時異步調用callback函數。
callback.call(img);//將回調函數的this替換爲Image對象
};
};
數據結構
-
理解常見數據結構的特點,以及他們在不同場景下使用的優缺點
數據結構是以某種形式將數據組織在一起的集合,它不僅存儲數據,還支持訪問和處理數據的操作。
常見的數據結構:
1.線性表(數組):數組是基礎,爲數組封裝好一個List構造函數,增加長度、插入、刪除、索引等工具結構。
數組的索引下標需要在js語言內部轉換爲js對象的屬性名,因此效率打了折扣
2.棧:具有後進先出的特點,是一種高效的列表,只對棧頂的數據進行添加和刪除
3.隊列:具有先進先出的特點,是只能在隊首取出或刪除元素,在隊尾插入元素的列表。
4.鏈表:鏈表是由一組節點組成的集合。
5.字典及散列(object):散列也叫做散列表,在散列表上插入、刪除和取用數據非常快,但對查找操作來說效率低下。
6.集合(set):是一種包含不同元素的數據結構。特點是無序且各不相同。
7.樹:樹是非線性,分層存儲的數據結構,可用來存儲文件系統或有序列表。
-
理解數組、字符串的存儲原理,並熟練應用他們解決問題
數組是一個連續的內存分配,但在JS中不是連續分配的,類似哈希映射的方式存在的。
字符串是存儲在棧中的
-
理解二叉樹、棧、隊列、哈希表的基本結構和特點
二叉樹(Binary Tree)是一種樹形結構,它的特點是每個節點最多隻有兩個分支節點,一棵二叉樹通常由根節點,分支節點,葉子節點組成。而每個分支節點也常常被稱作爲一棵子樹。js數據結構-二叉樹(二叉搜索樹)
哈希表也稱爲散列表。是根據關鍵碼值(Key value)而直接進行訪問的數據結構。也就是說,它通過把關鍵碼值映射到表中一個位置來訪問記錄,以加快查找的速度。這個映射函數叫做散列函數,存放記錄的數組叫做散列表。
-
瞭解圖、堆的基本結構和使用場景
圖(Graph)是由頂點的有窮非空集合和頂點之間邊的集合組成,通常表示爲:G(V,E),其中,G表示一個圖,V(vertex)是圖G中頂點的集合,E(edge)是圖G中邊的集合。
算法
-
可計算一個算法的時間複雜度和空間複雜度,可估計業務邏輯代碼的耗時和內存消耗
-
至少理解五種排序算法的實現原理、應用場景、優缺點,可快速說出事件、空間複雜度
-
瞭解遞歸和循環的優缺點、應用場景、並可在開發中熟練應用
-
可應用回溯算法、貪心算法、分治算法、動態規劃等解決複雜問題
-
前端處理海量數據的算法方案