您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關開發中經常遇到的JavaScript問題有哪些,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
今天遇到一個需求,已知月份,得到這個月的第一天和最后一天作為查詢條件查范圍內的數據
new Date(year, month, date, hrs, min, sec),new Date 可以接受這些參數創建一個時間對象 其中當我們把 date 設置為 0 的時候,可以直接通過 getDate() 獲取到最后一天的日期然后得到我們要的最后一天
new Date(2019, 12, 0).getDate(); // 31 new Date(2018, 2, 0).getDate(); // 28 // 根據這個我們可以得到一個方法 function getMonthLength(month) { const date = new Date(month); const year = date.getFullYear(); // 月份是從 0 開始計算的 const _month = date.getMonth() + 1; return new Date(year, _month, 0).getDate(); }
360 面試過程遇到一個很有趣的問題,是關于函數的 length 屬性的,題簡寫如下
(() => 1).length === 0; // 輸出什么
我所理解的擁有 length 的對象一般都是數組或者類數組對象,或者定義了 length 屬性的對象,所以我回答說這個應該是 false 吧,后來面試告訴我函數是有 length 屬性的,函數的 length 屬性就是函數參數的個數,瞬間我恍然大悟,函數的參數就是 arguments,而 arguments 也是一個類數組對象所以他是有 length 屬性的
// so (() => 1).length === 0; // 輸出 true (a => a).length; // 輸出 1
在 JavaScript 中數組是通過數字進行索引,但是有趣的是他們也是對象,所以也可以包含 字符串 鍵值和屬性,但是這些不會被計算在數組的長度(length)內
如果字符串鍵值能夠被強制類型轉換為十進制數字的話,它就會被當做數字索引來處理
const arr = []; arr[0] = 1; arr['1'] = '嘿嘿'; arr['cym'] = 'cym'; console.log(arr); // [1, '嘿嘿', cym: 'cym'] console.log(arr.length); // 2
undefined 是一個內置標志符,它的值為 undefined(除非被重新定義過),通過 void 運算符即可得到該值
在 void 之后的語句或表達式都將返回 undefined。void 并不會改變表達式的結果,只是讓表達式不返回值
void true; // undefined void 0; // undefined
void 運算符在其他地方也可以派上用場,比如不讓表達式返回任何結果。
// 該函數不需要有任何返回結果 function doSomething(sign) { if (!sign) { return void setTimeout(doSomething, 100); } } // 或許你經常向下面一樣這么寫 function doSomething(sign) { if (!sign) { setTimeout(doSomething, 100); return; } }
JSON.stringify 和 toString() 效果基本相同,只不過序列化的結果總是字符串
JSON.stringify(42); // "42" JSON.stringify('42'); // ""42""(含有雙引號的字符串) JSON.stringify(null); // "null" JSON.stringify(true); // "true"
不安全的 JSON 值
所有安全的 JSON 值都可以使用 JSON.stringify 序列化,不安全的 JSON 值有:undefined、function、symbol 和 循環引用。JSON.stringify
在對象中遇到這些不安全的 JSON 值的時候會自動將其忽略,在數組中遇到則會返回 null,以保證數組成員位置不變
JSON.stringify(undefined); // null JSON.stringify(function () {}); // null JSON.stringify([1, undefined, 2, function () {}, 3]); // "1, null, 2, null, 3" JSON.stringify({ a: 2, b: function () {} }); // "{"a":2}"
toJSON 方法
如果對象中定義了 toJSON 方法,那么在 JSON 序列化的時候優先調用該方法,主要是為了處理循環引用的時候,我們讓其返回一個合理的值
也就是說 toJSON 方法應該返回一個能夠被字符串安全化的 JSON 值
const o = { a: 'cym', toJSON() { return { c: 'b' }; }, }; JSON.stringify(o); // {"c":"b"}
JSON.stringify 的第二個參數
我們可以向 JSON.stringify 中傳遞一個可選參數 replacer,他可以書數組也可以書函數,用來指定對象序列化的時候哪些屬性應該被處理,哪些應該被排除,和 toJSON 很像
1.當 replacer 是一個數組時,那么他必須是一個字符串數組,其中包含序列化要處理的對象的屬性名稱,除此之外的屬性就會被忽略
const obj = { a: 42, b: 30, c: 100, }; JSON.stringify(obj, ['a', 'c']); // {"a":42,"c":100}
2.當 replacer 是一個函數時,他會對對象本身調用一次,然后在對對象中的每個屬性各調用一次。每次傳遞兩個參數(對象的鍵和值)。如果要忽略某個鍵就返回 2.undecided,否則就返回指定的值
const obj = { a: 42, b: 30, c: 100, }; JSON.stringify(obj, (k, v) => { // 注意:第一次 k 是 undefined,v 是原對象 if (k !== 'c') return v; }); // "{"a":42,"b":30}"
我們都知道一個字符串轉換為數字,可以使用 + "12" 轉換為數字 12,也可以使用 -,這樣的 +、- 是一元運算符,這樣將數字轉換為字符串的方法屬于顯示轉換
- 運算符還有反轉符號位的功能,當然不能把一元操作符連在一起寫,不然會變成 --,當做遞減運算符號來計算了,我們可以理解為 - 運算符出在單數次數會轉符號位,出現雙次數會抵消反轉,比如說 1 - - 1 === 2
# 這是 js 代碼哦,不是 python 1 + - + - + - 1 # 0 1 - - 1 # 2 1 - - - 1 # 0
~~ 返回 2 的補碼,~x 大致等同于 -(x+1)
~42; // -(42+1) ===> -43
在 -(x+1) 中唯一能夠得到 0(或者嚴格來說時候 -0)的 x 值是 -1,也就是說 ~ 和一些數字在一起會返回一個假值 0,其他情況下則返回真值
-1 是一個 哨位值,哨位值是那些在各個類型中被賦予了特殊含義的值。在 C 語言中 -1 代表函數執行失敗,大于等于 0 的值代表函數執行成功
比如在 JavaScript 中字符串的 indexOf 方法也遵循這一慣例,該方法在字符串中搜索指定的字符串,如果找到就返回該子字符串所在的位置,否則返回 -1
~ 的用途
我們知道在 JavaScript 中假值有:undefined、null、false、+0、-0、NaN、'',其他都為真值,所以負數也是真值,那么我們就可以拿著 ~ 和 indexOf 一起檢結果強制類型轉換為 真/假 值
const str = 'hello world'; ~str.indexOf('lo'); // -4,真值 if (~str.indexOf('lo')) { // true // 找到匹配 } ~str.indexOf('ol'); // 0,假值 !~str.indexOf('ol'); // true if (!~str.indexOf('ol')) { // true // 沒有找到匹配 }
~ 要比 >=0 和 == -1 更簡潔
字位截除
我們經常使用 ~~ 來截取數字值的小數部分,以為這是和 Math.floor 效果是一樣的,實際上并非如此
~~ 中第一個 ~ 執行 ToInt32 并反轉字位,然后第二個在進行一次字位反轉,就是將所有的字位反轉回原值,最后得到的結果仍是 ToInt32 的結果
~~ 只適用于 32 位的數字,更重要的是他對負數的處理與 Math.floor 不同,所以使用時要多加注意
Math.floor(1.9); // 1 ~~1.9; // 1 // 操作負數 Math.floor(-1.9); // -2 ~~-1.9; // -1
~~x 能將值截除為一個 32 位的整數,x | 0 也可以,而且看起來更簡潔哦,不過出于對運算符優先級的考慮,我們更傾向于使用 ~~x
~~1.9; // 1 1.9 | 0; // 1 ~~-1.9; // -1 -1.9 | 0; // -1
原題是這樣的:給定一組 url,利用 js 的異步實現并發請求,并按順序輸出結果
Promise.all
首先我們可以想到的是利用 Promise.all 來實現,代碼實現如下
const urls = ['./1.json', './2.json', './3.json']; function getData(url) { // 返回一個 Promise 利用 Promise.all 接受 return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.responseType = 'json'; xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { resolve(xhr.response); } } }; xhr.open('GET', url, true); xhr.send(null); }); } function getMultiData(urls) { // Promise.all 接受一個包含 promise 的數組,如果不是 promise 數組會被轉成 promise Promise.all(urls.map(url => getData(url))).then(results => { console.log(results); }); }
不用 Promise
原題是不用 Promise 來實現,我們可以寫一個方法,加個回調函數,等數據全部回來之后,觸發回調函數傳入得到的數據,那么數據全部回來的就是我們要考慮的核心問題,我們可以用個數組或者對象,然后判斷一下數組的 length 和傳入的 url 的長度是否一樣來做判斷
使用對象做映射
const urls = ['./1.json', './2.json', './3.json']; function getAllDate(urls, cd) { const result = {}; function getData(url, idx) { const xhr = new XMLHttpRequest(); xhr.responseType = 'json'; xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { result[idx] = xhr.response; // 如果兩者 length 相等說明都請求完成了 if (Object.keys(result).length === urls.length) { // 給對象添加length屬性,方便轉換數組 result.length = urls.length; cd && cd(Array.from(result)); } } } }; } // 觸發函數執行 urls.forEach((url, idx) => getData(url, idx)); } // 使用 getAllDate(urls, data => { console.log(data); });
使用數組實現
和上面的基本思路差不多,不過這次換成了數組,也可以給個信號量來做判斷
function getGroupData(urls, cb) { const results = []; let count = 0; const getData = url => { const xhr = new XMLHttpRequest(); xhr.responseType = 'json'; xhr.onreadystatechange = _ => { if (xhr.readyState === 4) { if (xhr.status === 200) { results.push(xhr.response); if (++count === urls.length) { cb && cb(results); } } } }; xhr.open('GET', url, true); xhr.send(null); }; urls.forEach(url => getData(url)); } getGroupData(urls, data => { console.log(data); });
原題:如何讓 (a == 1 && a == 2 && a == 3) 的值為 true?
這個問題考查的數據類型轉換,== 類型轉換有個基本規則
NaN 與任何值都不相等,包括自己本身
undefined 與 null 相等(==),其他都不等
對象與字符串類型做比較,會把對象轉換成字符串然后做比較
其他類型比較都要轉換成 數字 做比較
那么這個問題我們重寫 toString 或者 valueOf 方法就可以了
const a = { val: 1, toString() { return this.val++; }, }; if (a == 1 && a == 2 && a == 3) { console.log('ok'); }
還有一種方法實現
var i = 1; Object.defineProperty(window, 'a', { get() { return i++; }, }); if (a == 1 && a == 2 && a == 3) { console.log('OK'); }
拓展一下 [] == ![]為什么是 true
上面隱式類型轉換規則中提到,其他類型比較都要轉換成數字做比較,這個就是對應那條規則的
首先 [].toString() 會得到一個 '' 字符串
![] 得到一個布爾值 false
'' 與 false 比較肯定要轉換成數字比較
那么 '' 轉換則為 0, false 轉換也是 0
所以這道題就是 true
有時候我們看到別人的代碼中會寫到數字調其他類型的方法的時候會寫成 1..toString() 這樣的寫法
因為直接用整數型數字調方法就會報錯,但是如果是一個浮點數的話就不會報錯了
因為可能在 . 上面存在爭議,一個數字后面加點,解釋器他不知道你這是小數還是要調取方法,所以就跑異常了
1.toString() // Uncaught SyntaxError: Invalid or unexpected token 1..toString() // '1' 1.2.toString() // '1.2'
對象增加迭代器
類數組對象的特征:必須有長度、索引、能夠被迭代,否則這個對象不可以使用 ... 語法轉數組,我們可以使用 Array.from 轉,當然我們也可以給對象添加一個迭代器
const obj = { 0: 1, 1: 2, 2: 3, 3: 4, length: 4, [Symbol.iterator]() { let idx = 0 return { next() { return { value: obj[idx], done: idx++ >= obj.length, } } } } } // 此時對象就被添加了迭代器 [...obj] // 1 2 3 4 for (const val of obj) { console.log(val) // 1 2 3 4 }
上面的問題可以字節使用生成器來實現,生成器返回一個迭代器,迭代器有 next 方法,調用 next 方法可以返回 value 和 done
const obj = { 0: 1, 1: 2, 2: 3, 3: 4, length: 4, [Symbol.iterator]: function* () { let idx = 0 while (idx !== this.length) { yield this[idx++] } }
實現一個字符串的迭代器
實現一個字符串的迭代器:傳入一組字符串并返回單個字符的范例。一旦更新的字符串,輸出也跟著替換掉舊的
function generator(str) { let idx = 0; return { next() { return { value: str[idx], done: idx++ >= str.length, }; }, }; } // 測試 const str = 'as'; let gen = generator(str); console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); gen = generator('str'); console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); console.log(gen.next()); // { value: 'a', done: false } // { value: 's', done: false } // { value: undefined, done: true } // { value: undefined, done: true } // { value: 's', done: false } // { value: 't', done: false } // { value: 'r', done: false } // { value: undefined, done: true }
簡單模擬 co
模擬一下 co 的實現
首先來看一則例子
const fs = require('fs'); const path = require('path'); const { promisify } = require('util'); const readFile = promisify(fs.readFile); function* read() { const name = yield readFile(path.resolve(__dirname, 'name.txt'), 'utf8'); const age = yield readFile(path.resolve(__dirname, name), 'utf8'); return age; } const it = read(); let { value, done } = it.next(); value.then(data => { let { value, done } = it.next(data); // console.log(data, '???') value.then(data => { let { value, done } = it.next(data); console.log(value); }); });
使用 co 庫可以很容易解決這個問題
const co = require('co'); // co 接受一個生成器 co(read()).then(data => { console.log(data); }); // 那模擬一下 function _co(it) { // 首先返回一個 promise return new Promise((resolve, reject) => { // 因為可以傳值的原因,不可以直接使用循環實現,需要使用 遞歸 function next(data) { const { value, done } = it.next(data); if (done) return resolve(value); // 保證值是一個 promise Promise.resolve(value).then(data => { next(data); }, reject); } next(); }); }
今天新東方的面試還提到了菲波那切數列,其實這個東西蠻很有趣,簡單介紹一下
1、1、2、3、5、8、13、21、34 ....
這道題有個規律,第一項加上第二項永遠等于第三項:1 + 1 = 2;1 + 2 = 3;2 + 3 = 5;3 + 5 = 8 ....
要求是傳入第幾項,得到該值,根據這個規律來實現一下
簡單寫法
function fibonacci(n) { // 第一項和第二項都返回1 if (n === 1 || n === 2) return 1; // 我們只要返回 n - 1(n的前一項)與 n - 2(n的前兩項)的和便是我們要的值 return fibonacci(n - 1) + fibonacci(n - 2); }
優化版本
上面的寫法,求 20 次以內的總和運行會很快,50 次以上特別慢,100 次 以上可能就爆棧了,所以我們需要優化寫法,緩存每次計算后的值
function feibo(n, sum1 = 1, sum2 = 1) { if (n === 1 || n === 2) return sum2; return feibo(n - 1, sum2, sum1 + sum2); }
這種寫法緩存了,每次計算后的值,執行效率會很高,100 次以上也會秒返回結果,這個也叫作尾遞歸優化
一直以來,我以為發布訂閱和觀察者是一個思路,一次偶然的機會我發現他們是兩種不同的設計思路
雖然他們都是實現了對象的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴它的對象都將得倒通知,然后自動更新。但是他們之間是有一定區別的。
觀察者模式
觀察者模式會有 觀察者 與 被觀察者(觀察目標) 兩個對象存在,觀察者可以有多個,觀察目標可以添加多個觀察者,可以通知觀察者。觀察者模式是面向與目標和觀察者編程的,耦合目標和觀察者
// 被觀察者 class Subject { constructor() { this.subs = []; } add(observer) { this.subs.push(observer); } notify(...args) { this.subs.forEach(ob => ob.update(...args)); } } // 觀察者 class Observer { update(...args) { console.log('Observer -> update -> args', args); } } // 使用 const o1 = new Observer(); const o2 = new Observer(); const o3 = new Observer(); const o5 = new Observer(); const sub = new Subject(); // 添加觀察者 sub.add(o1); sub.add(o2); sub.add(o3); // 通知觀察者 sub.notify('嘿嘿嘿');
發布訂閱模式
發布訂閱模式會有一個調度中心的概念。是面向調度中心編程的,對發布者與訂閱者解耦
class PubSub { constructor() { this.handlers = {}; } subscribe(type, fn) { if (!this.handlers[type]) { this.handlers[type] = []; } this.handlers[type].push(fn); } publish(type, ...args) { if (!this.handlers[type]) return; this.handlers[type].forEach(fn => fn(...args)); } } const ps = new PubSub(); ps.subscribe('a', console.log); ps.subscribe('a', console.log); ps.subscribe('a', console.log); ps.subscribe('a', console.log); ps.publish('a', 'hello world');
有個要求:純前端實現,不可以使用 nodejs
實現原理也很簡單,就像我們平時下載一個本地文件一樣,可以動態的創建一個可以下載的 a 標簽,給它設置 download 屬性,然后把下載的內容轉 blob 創建下載鏈接下載即可
具體實現如下:
function exportTxt(text, filename) { const eleLink = document.createElement('a'); eleLink.download = filename; eleLink.style.display = 'none'; // 將內容轉為 blob const blob = new Blob([text]); eleLink.href = URL.createObjectURL(blob); document.body.appendChild(eleLink); eleLink.click(); document.body.removeChild(eleLink); }
可能會遇到一個做奇偶數判斷的方法吧,反正我遇到了,一句話搞定
const isEven = num => num % 2 === 0;
項目中我們經常會遇到金錢格式化需求,或者說數字格式化一下,方便閱讀(數字比較大的情況下)
比如說 999999999,直接閱讀很不直觀,格式化后 999,999,999
通常我們會使用正則來處理
function formatPrice(price) { return String(price).replace(/\B(?=(\d{3})+(?!\d))/g, ','); }
也可以不使用正則然后優雅的處理
function formatPrice(price) { return String(price) .split('') .reverse() .reduce((prev, next, index) => { return (index % 3 ? next : next + ',') + prev; }); }
上面是兩種提到的比較常用的方案,但是 js 還有個比較牛逼的 API 可以直接實現這個需求哦,它就是 toLocaleString,我們可以直接數字調用這個方法就可以實現,金額的格式化
(999999999).toLocaleString(); // 999,999,999 // 當然還可以更秀一點 const options = { style: 'currency', currency: 'CNY', }; (123456).toLocaleString('zh-CN', options); // ¥123,456.00
toLocaleString 可以接收兩個可選參數:locales 和 options,而且這個 api 在各大瀏覽器通用不存在兼容問題并且這個 api 不止存在 Number 的原型上,Array、Object、Date 原型上都有這個 api,并且格式化出來的值可以根據我們傳入的參數出現各種結果
參數及用法可以參考 MDN
在 vue 項目開發中,有些不變的常量,我們不想 vue 為他做雙向綁定,以減少一些性能上消耗,我們可以把使用 Object.freeze 將對象凍結,此時 vue 將不會對這個對象進行凍結,但是這個凍結只是凍結對象第一層,所以遇到對象層級比較深的話,我們可以寫個深度凍結的 api,來對常量對象做一些凍結優化
const deepFreeze = o => { const propNames = Object.getOwnPropertyNames(o); propNames.forEach(name => { const prop = o[name]; if (typeof prop === 'object' && prop !== null) { deepFreeze(prop); } }); return Object.freeze(o); };
在一些涉及到用戶隱私情況下,可能會遇到對用戶的手機號身份證號之類的信息脫敏,但是這個脫敏數據的規則是根據用戶信息要脫敏字段動態的生成的,此時我們動態拼接正則來實現一個動態脫敏規則
const encryptReg = (before = 3, after = 4) => { return new RegExp('(\\d{' + before + '})\\d*(\\d{' + after + '})'); }; // 使用:'13456789876'.replace(encryptReg(), '$1****$2') -> "134****9876"
對于樹結構的遍歷一般有深度優先和廣度優先
廣度優先和深度優先的概念很簡單,區別如下:
鴻蒙官方戰略合作共建——HarmonyOS技術社區
深度優先,訪問完一顆子樹再去訪問后面的子樹,而訪問子樹的時候,先訪問根再訪問根的子樹,稱為先序遍歷;先訪問子樹再訪問根,稱為后序遍歷。
廣度優先,即訪問樹結構的第 n+1 層前必須先訪問完第 n 層
1.深度優先
先序遍歷
const treeForEach = (tree, func) => { tree.forEach(data => { func(data); data.children && treeForEach(data.children, func); }); };
后序遍歷,只需要調換一下節點遍歷和子樹遍歷的順序即可
const treeForEach = (tree, func) => { tree.forEach(data => { data.children && treeForEach(data.children, func); func(data); }); };
2.廣度優先
廣度優先的思路是,維護一個隊列,隊列的初始值為樹結構根節點組成的列表,重復執行以下步驟直到隊列為空。取出隊列中的第一個元素,進行訪問相關操作,然后將其后代元素(如果有)全部追加到隊列最后。
const treeForEach = (tree, func) => { let node, list = [...tree]; while ((node = list.shift())) { func(node); node.children && list.push(...node.children); } };
開發移動端的時候,遇到一個首頁菜單改版的需求,首頁菜單根據權限控制顯隱,而菜單每頁展示八個小菜單,超過八個做 swipe 滑動切換,當時項目用了 vant 做的 UI 框架,菜單那模塊就選擇了他的輪播插件,菜單做成了一個扁平化的 list 配置,首先根據權限過濾出所有有權限的菜單項,然后每八個一分組,處理成一個二維數據來遍歷菜單
const arrayGroupBySize = (arr, size = 2) => { const result = []; for (let i = 0, len = arr.length; i < len; i += size) { result.push(arr.slice(i, i + size)); } return result; };
做一些數據持久化的工作的時候經常會出現下劃線命名和駝峰命名的轉化,因為在前端處理中規范是駝峰命名,而像 mysql 之類的規范是下劃線命名,所以在處理后返回給前端的數據需要轉換為駝峰命名,而對數據庫的讀寫需要下劃線命名
const toHump = name => { return name.replace(/\_(\w)/g, function (all, letter) { return letter.toUpperCase(); }); }; const toLine = name => { return name.replace(/([A-Z])/g, '_$1').toLowerCase(); };
業務中遇到一個校驗一下傳入時間格式是否為一個時間格式,下面的方法可以完美校驗
const isDate = str => { return typeof str !== 'number' && str !== null && new Date(str) !== 'Invalid Date'; };
看完上述內容,你們對開發中經常遇到的JavaScript問題有哪些有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。