科技改變生活 · 科技引領未來
今天遇到一個需求,已知月份,得到這個月的第一天和最后一天作為查詢條件查范圍內的數據
new Date(year, month, date, hrs, min, sec),new Date 可以接受這些參數創建一個時間對象 其中當我們把 date 設置為 0 的時候,可以直接通過 getDate() 獲取到最后一天的日期然后得到我們要的最后一天
new Date(2019, 12, 0).getDate(); // 31new 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[&39;1&39;] = &39;嘿嘿&39;;arr[&39;cym&39;] = &39;cym&39;;console.log(arr); // [1, &39;嘿嘿&39;, cym: &39;cym&39;]console.log(arr.length); // 2
undefined 是一個內置標志符,它的值為 undefined(除非被重新定義過),通過 void 運算符即可得到該值
在 void 之后的語句或表達式都將返回 undefined。void 并不會改變表達式的結果,只是讓表達式不返回值
void true; // undefinedvoid 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); // &34;42&34;JSON.stringify(&39;42&39;); // &34;&34;42&34;&34;(含有雙引號的字符串)JSON.stringify(null); // &34;null&34;JSON.stringify(true); // &34;true&34;
所有安全的 JSON 值都可以使用 JSON.stringify 序列化,不安全的 JSON 值有:undefined、function、symbol 和 循環引用。JSON.stringify
在對象中遇到這些不安全的 JSON 值的時候會自動將其忽略,在數組中遇到則會返回 null,以保證數組成員位置不變
JSON.stringify(undefined); // nullJSON.stringify(function () {}); // nullJSON.stringify([1, undefined, 2, function () {}, 3]); // &34;1, null, 2, null, 3&34;JSON.stringify({ a: 2, b: function () {} }); // &34;{&34;a&34;:2}&34;
如果對象中定義了 toJSON 方法,那么在 JSON 序列化的時候優先調用該方法,主要是為了處理循環引用的時候,我們讓其返回一個合理的值
也就是說 toJSON 方法應該返回一個能夠被字符串安全化的 JSON 值
const o = { a: &39;cym&39;, toJSON() { return { c: &39;b&39; }; },};JSON.stringify(o); // {&34;c&34;:&34;b&34;}
我們可以向 JSON.stringify 中傳遞一個可選參數 replacer,他可以書數組也可以書函數,用來指定對象序列化的時候哪些屬性應該被處理,哪些應該被排除,和 toJSON 很像
const obj = { a: 42, b: 30, c: 100,};JSON.stringify(obj, [&39;a&39;, &39;c&39;]); // {&34;a&34;:42,&34;c&34;:100}
const obj = { a: 42, b: 30, c: 100,};JSON.stringify(obj, (k, v) => { // 注意:第一次 k 是 undefined,v 是原對象 if (k !== &39;c&39;) return v;}); // &34;{&34;a&34;:42,&34;b&34;:30}&34;
我們都知道一個字符串轉換為數字,可以使用 + &34;12&34; 轉換為數字 12,也可以使用 -,這樣的 +、- 是一元運算符,這樣將數字轉換為字符串的方法屬于顯示轉換
- 運算符還有反轉符號位的功能,當然不能把一元操作符連在一起寫,不然會變成 --,當做遞減運算符號來計算了,我們可以理解為 - 運算符出在單數次數會轉符號位,出現雙次數會抵消反轉,比如說 1 - - 1 === 2
這是 js 代碼哦,不是 python1 + - + - + - 1 01 - - 1 21 - - - 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、&39;&39;,其他都為真值,所以負數也是真值,那么我們就可以拿著 ~ 和 indexOf 一體檢結果強制類型轉換為 真/假 值
const str = &39;hello world&39;;~str.indexOf(&39;lo&39;); // -4,真值if (~str.indexOf(&39;lo&39;)) { // true // 找到匹配}~str.indexOf(&39;ol&39;); // 0,假值!~str.indexOf(&39;ol&39;); // trueif (!~str.indexOf(&39;ol&39;)) { // 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; // 11.9 | 0; // 1~~-1.9; // -1-1.9 | 0; // -1
原題是這樣的:給定一組 url,利用 js 第一步實現并發請求,并按順序輸出結果
首先我們可以想到的是利用 Promise.all 來實現,代碼實現如下
const urls = [&39;./1.json&39;, &39;./2.json&39;, &39;./3.json&39;];function getData(url) { // 返回一個 Promise 利用 Promise.all 接受 return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); xhr.responseType = &39;json&39;; xhr.onreadystatechange = () => { if (xhr.readyState === 4) { if (xhr.status === 200) { resolve(xhr.response); } } }; xhr.open(&39;GET&39;, 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 來實現,我們可以寫一個方法,加個回調函數,等數據全部回來之后,觸發回調函數傳入得到的數據,那么數據全部回來的就是我們要考慮的核心問題,我們可以用個數組或者對象,然后判斷一下數組的 length 和傳入的 url 的長度是否一樣來做判斷
const urls = [&39;./1.json&39;, &39;./2.json&39;, &39;./3.json&39;];function getAllDate(urls, cd) { const result = {}; function getData(url, idx) { const xhr = new XMLHttpRequest(); xhr.responseType = &39;json&39;; 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 = &39;json&39;; xhr.onreadystatechange = _ => { if (xhr.readyState === 4) { if (xhr.status === 200) { results.push(xhr.response); if (++count === urls.length) { cb && cb(results); } } } }; xhr.open(&39;GET&39;, url, true); xhr.send(null); }; urls.forEach(url => getData(url));}getGroupData(urls, data => { console.log(data);});
原題:如何讓 (a == 1 && a == 2 && a == 3) 的值為 true?
這個問題考查的數據類型轉換,== 類型轉換有個基本規則
那么這個問題我們重寫 toString 或者 valueOf 方法就可以了
const a = { val: 1, toString() { return this.val++; },};if (a == 1 && a == 2 && a == 3) { console.log(&39;ok&39;);}
還有一種方法實現
var i = 1;Object.defineProperty(window, &39;a&39;, { get() { return i++; },});if (a == 1 && a == 2 && a == 3) { console.log(&39;OK&39;);}
上面隱式類型轉換規則中提到,其他類型比較都要轉換成數字做比較,這個就是對應那條規則的
有時候我們看到別人的代碼中會寫到數字調其他類型的方法的時候會寫成 1..toString() 這樣的寫法
因為直接用整數型數字調方法就會報錯,但是如果是一個浮點數的話就不會報錯了
因為可能在 . 上面存在爭議,一個數字后面加點,解釋器他不知道你這是小數還是要調取方法,所以就跑異常了
1.toString() // Uncaught SyntaxError: Invalid or unexpected token1..toString() // &39;1&39;1.2.toString() // &39;1.2&39;
類數組對象的特征:必須有長度、索引、能夠被迭代,否則這個對象不可以使用 ... 語法轉數組,我們可以使用 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 4for (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 = &39;as&39;;let gen = generator(str);console.log(gen.next());console.log(gen.next());console.log(gen.next());console.log(gen.next());gen = generator(&39;str&39;);console.log(gen.next());console.log(gen.next());console.log(gen.next());console.log(gen.next());// { value: &39;a&39;, done: false }// { value: &39;s&39;, done: false }// { value: undefined, done: true }// { value: undefined, done: true }// { value: &39;s&39;, done: false }// { value: &39;t&39;, done: false }// { value: &39;r&39;, done: false }// { value: undefined, done: true }
模擬一下 co 的實現
首先來看一則例子
const fs = require(&39;fs&39;);const path = require(&39;path&39;);const { promisify } = require(&39;util&39;);const readFile = promisify(fs.readFile);function* read() { const name = yield readFile(path.resolve(__dirname, &39;name.txt&39;), &39;utf8&39;); const age = yield readFile(path.resolve(__dirname, name), &39;utf8&39;); return age;}const it = read();let { value, done } = it.next();value.then(data => { let { value, done } = it.next(data); // console.log(data, &39;???&39;) value.then(data => { let { value, done } = it.next(data); console.log(value); });});
使用 co 庫可以很容易解決這個問題
const co = require(&39;co&39;);// 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(); });}
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(&39;Observer -> update -> args&39;, 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(&39;嘿嘿嘿&39;);
發布訂閱模式會有一個調度中心的概念。是面向調度中心編程的,對發布者與訂閱者解耦
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(&39;a&39;, console.log);ps.subscribe(&39;a&39;, console.log);ps.subscribe(&39;a&39;, console.log);ps.subscribe(&39;a&39;, console.log);ps.publish(&39;a&39;, &39;hello world&39;);
有個要求:純前端實現,不可以使用 nodejs
實現原理也很簡單,就像我們平時下載一個本地文件一樣,可以動態的創建一個可以下載的 a 標簽,給它設置 download 屬性,然后把下載的內容轉 blob 創建下載鏈接下載即可
具體實現如下:
function exportTxt(text, filename) { const elelink = document.createElement(&39;a&39;); elelink.download = filename; elelink.style.display = &39;none&39;; // 將內容轉為 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, &39;,&39;);}
也可以不使用正則然后優雅的處理
function formatPrice(price) { return String(price) .split(&39;&39;) .reverse() .reduce((prev, next, index) => { return (index % 3 ? next : next + &39;,&39;) + prev; });}
上面是兩種提到的比較常用的方案,但是 js 還有個比較牛逼的 API 可以直接實現這個需求哦,它就是 toLocaleString,我們可以直接數字調用這個方法就可以實現,金額的格式化
(999999999).toLocaleString(); // 999,999,999// 當然還可以更秀一點const options = { style: &39;currency&39;, currency: &39;CNY&39;,};(123456).toLocaleString(&39;zh-CN&39;, 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 === &39;object&39; && prop !== null) { deepFreeze(prop); } }); return Object.freeze(o);};
在一些涉及到用戶隱私情況下,可能會遇到對用戶的手機號身份證號之類的信息脫敏,但是這個脫敏數據的規則是根據用戶信息要脫敏字段動態的生成的,此時我們動態拼接正則來實現一個動態脫敏規則
const encryptReg = (before = 3, after = 4) => { return new RegExp(&39;(\d{&39; + before + &39;})\d*(\d{&39; + after + &39;})&39;);};// 使用:&39;13456789876&39;.replace(encryptReg(), &39;$1****$2&39;) -> &34;134****9876&34;
對于樹結構的遍歷一般有深度優先和廣度優先
廣度優先和深度優先的概念很簡單,區別如下:
先序遍歷
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); });};
廣度優先的思路是,維護一個隊列,隊列的初始值為樹結構根節點組成的列表,重復執行以下步驟直到隊列為空。取出隊列中的第一個元素,進行訪問相關操作,然后將其后代元素(如果有)全部追加到隊列最后。
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, &39;_$1&39;).toLowerCase();};
業務中遇到一個校驗一下傳入時間格式是否為一個時間格式,下面的方法可以完美校驗
const isDate = str => { return typeof str !== &39;number&39; && str !== null && new Date(str) !== &39;Invalid Date&39;;};
陳熙