你应该了解的25个JS技巧

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"写代码的时候总有一些东西是会重复出现的,次数多了你就会想找找捷径了。这类问题中有很大一部分解决起来甚至连库都不用装。下面就是我多年来收集的前25个捷径和小技巧。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1. 类型检查小工具"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JavaScript不是强类型语言,对此我推荐的最佳解决方案是TypeScript。但有时你只是想要一个简单的类型检查,这种时候JavaScript允许你使用“typeof”关键字。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"“typeof”的问题在于,将其用于某些原语和函数时效果很好,但对于数组和对象来说,由于它们都被视为“对象”,因此很难把握它们之间的区别。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"const isOfType = (() => {\n \/\/ create a plain object with no prototype\n const type = Object.create(null);\n \n \/\/ check for null type\n type.null = x => x === null;\n \/\/ check for undefined type\n type.undefined = x => x === undefined;\n \/\/ check for nil type. Either null or undefined\n type.nil = x => type.null(x) || type.undefined(x);\n \/\/ check for strings and string literal type. e.g: 's', \"s\", `str`, new String()\n type.string = x => !type.nil(x) && (typeof x === 'string' || x instanceof String);\n \/\/ check for number or number literal type. e.g: 12, 30.5, new Number()\n type.number = x => !type.nil(x) \n && (\/\/ NaN & Infinity have typeof \"number\" and this excludes that\n (!isNaN(x) && isFinite(x)\n && typeof x === 'number'\n ) || x instanceof Number);\n \/\/ check for boolean or boolean literal type. e.g: true, false, new Boolean()\n type.boolean = x => !type.nil(x) && (typeof x === 'boolean' || x instanceof Boolean);\n \/\/ check for array type\n type.array = x => !type.nil(x) && Array.isArray(x);\n \/\/ check for object or object literal type. e.g: {}, new Object(), Object.create(null)\n type.object = x => ({}).toString.call(x) === '[object Object]';\n \/\/ check for provided type instance\n type.type = (x, X) => !type.nil(x) && x instanceof X;\n \/\/ check for set type\n type.set = x => type.type(x, Set);\n \/\/ check for map type\n type.map = x => type.type(x, Map);\n \/\/ check for date type\n type.date = x => type.type(x, Date);\n \n return type;\n})();\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"2. 检查是否为空"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有时你需要知道某些内容是否为空,并根据结果决定要使用的方法,例如检查长度、大小或是否包含任何子元素。下面这个工具打包了这些功能,你可以用它检查String、Object、Array、Map和Set的大小。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"function isEmpty(x) {\n if(Array.isArray(x)\n || typeof x === 'string'\n || x instanceof String\n ) {\n return x.length === 0;\n }\n \n if(x instanceof Map || x instanceof Set) {\n return x.size === 0;\n }\n \n if(({}).toString.call(x) === '[object Object]') {\n return Object.keys(x).length === 0;\n }\n \n return false;\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"3. 获取列表最后一项"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其他语言里这个功能被做成了可以在数组上调用的方法或函数,但在JavaScript里面,你得自己做点工作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"function lastItem(list) {\n if(Array.isArray(list)) {\n return list.slice(-1)[0];\n }\n \n if(list instanceof Set) {\n return Array.from(list).slice(-1)[0];\n }\n \n if(list instanceof Map) {\n return Array.from(list.values()).slice(-1)[0];\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"4. 带有范围的随机数生成器"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有时你需要生成随机数,但希望这些数字在一定范围内,那就可以用这个工具。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"function randomNumber(max = 1, min = 0) {\n if(min >= max) {\n return max;\n }\n \n return Math.floor(Math.random() * (max - min) + min);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"5. 随机ID生成器"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有时你只是需要一些ID?除非你要的是更复杂的ID生成器(例如UUID),否则用不着为此安装什么新库,下面这个选项足够了。你可以从当前时间(以毫秒为单位)或特定的整数和增量开始生成,也可以从字母生成ID。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"\/\/ create unique id starting from current time in milliseconds\n\/\/ incrementing it by 1 everytime requested\nconst uniqueId = (() => {\n const id = (function*() {\n let mil = new Date().getTime();\n \n while (true)\n yield mil += 1;\n })();\n \n return () => id.next().value;\n})();\n\/\/ create unique incrementing id starting from provided value or zero\n\/\/ good for temporary things or things that id resets\nconst uniqueIncrementingId = ((lastId = 0) => {\n const id = (function*() {\n let numb = lastId;\n \n while (true)\n yield numb += 1;\n })()\n \n return (length = 12) => `${id.next().value}`.padStart(length, '0');\n})();\n\/\/ create unique id from letters and numbers\nconst uniqueAlphaNumericId = (() => {\n const heyStack = '0123456789abcdefghijklmnopqrstuvwxyz';\n const randomInt = () => Math.floor(Math.random() * Math.floor(heyStack.length))\n \n return (length = 24) => Array.from({length}, () => heyStack[randomInt()]).join('');\n})();\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"6. 创建一个范围内的数字"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Python里我很喜欢的一个功能是range函数,而在JavaScript里我经常需要自己写这个功能。下面是一个简单的实现,非常适合for…of循环以及需要特定范围内数字的情况。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"function range(maxOrStart, end = null, step = null) { \n if(!end) {\n return Array.from({length: maxOrStart}, (_, i) => i)\n }\n \n if(end <= maxOrStart) {\n return [];\n }\n \n if(step !== null) {\n return Array.from(\n {length: Math.ceil(((end - maxOrStart) \/ step))}, \n (_, i) => (i * step) + maxOrStart\n );\n }\n \n return Array.from(\n {length: Math.ceil((end - maxOrStart))}, \n (_, i) => i + maxOrStart\n );\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"7. 格式化JSON字符串,stringify任何内容"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我在使用NodeJs将对象记录到控制台时经常使用这种方法。JSON.stringify方法需要第三个参数,该参数必须有多个空格以缩进行。第二个参数可以为null,但你可以用它来处理function、Set、Map、Symbol之类JSON.stringify方法无法处理或完全忽略的内容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/b8\/b880edc90693afbd76a370a6bf93fbcb.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"const stringify = (() => {\n const replacer = (key, val) => {\n if(typeof val === 'symbol') {\n return val.toString();\n }\n if(val instanceof Set) {\n return Array.from(val);\n }\n if(val instanceof Map) {\n return Array.from(val.entries());\n }\n if(typeof val === 'function') {\n return val.toString();\n }\n return val;\n }\n \n return (obj, spaces = 0) => JSON.stringify(obj, replacer, spaces)\n})();\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"8. 顺序执行promise"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你有一堆异步或普通函数都返回promise,要求你一个接一个地执行,这个工具就会很有用。它会获取函数或promise列表,并使用数组reduce方法按顺序解析它们。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"const asyncSequentializer = (() => {\n const toPromise = (x) => {\n if(x instanceof Promise) { \/\/ if promise just return it\n return x;\n }\n \n if(typeof x === 'function') {\n \/\/ if function is not async this will turn its result into a promise\n \/\/ if it is async this will await for the result\n return (async () => await x())();\n }\n \n return Promise.resolve(x)\n }\n \n return (list) => {\n const results = [];\n \n return list\n .reduce((lastPromise, currentPromise) => {\n return lastPromise.then(res => {\n results.push(res); \/\/ collect the results\n return toPromise(currentPromise);\n });\n }, toPromise(list.shift()))\n \/\/ collect the final result and return the array of results as resolved promise\n .then(res => Promise.resolve([...results, res]));\n }\n})();\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"9. 轮询数据"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你需要持续检查数据更新,但系统中没有WebSocket,则可以使用这个工具来执行操作。它非常适合上传文件时,想要持续检查文件是否已完成处理的情况,或者使用第三方API(例如dropbox或uber)并且想要持续检查过程是否完成或骑手是否到达目的地的情况。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"async function poll(fn, validate, interval = 2500) {\n const resolver = async (resolve, reject) => {\n try { \/\/ catch any error thrown by the \"fn\" function\n const result = await fn(); \/\/ fn does not need to be asynchronous or return promise\n \/\/ call validator to see if the data is at the state to stop the polling\n const valid = validate(result);\n if (valid === true) {\n resolve(result);\n } else if (valid === false) {\n setTimeout(resolver, interval, resolve, reject);\n } \/\/ if validator returns anything other than \"true\" or \"false\" it stops polling\n } catch (e) {\n reject(e);\n }\n };\n return new Promise(resolver);\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"10. 等待所有promise完成"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个算不上是代码解决方案,更多是对Promise API的强化。这个API在不断进化,以前我还为“allSettled”“race”和“any”做了代码实现,现在直接用API的就好了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ee\/ee3f617331392986f5c462d92aeb3add.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"11. 交换数组值的位置"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ES6开始,从数组中的不同位置交换值变得容易多了。这个做起来不难,但是了解一下也不错,"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/73\/736cc22b0cf2e944e5c193077ae820d3.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"12. 条件对象键"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我最喜欢这条技巧了,我在使用React更新状态时经常用它。你可以将条件包装在括号中来有条件地将一个键插入一个spread对象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/ee\/eea2f05d7f83a90fae4d374e46a72158.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"13. 使用变量作为对象键"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当你有一个字符串变量,并想将其用作对象中的键以设置一个值时可以用它。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/0d\/0d8f577213f874748b0973077f539123.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"14. 检查对象里的键"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这是一个很好的技巧,可以帮助你检查对象键。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/76\/761e6c5235fb0e4e357ba3cef818f19f.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"15. 删除数组重复项"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"数组中经常有重复的值,你可以使用Set数据结构来消除它。它适用于许多数据类型,并且set有多种检查相等性的方法,很好用。对于不同实例或对象的情况,你还是可以使用Set来跟踪特定事物并过滤出重复的对象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"16. 在ArrayforEach中执行“break”和“continue”"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我真的很喜欢使用数组“.forEach”方法,但有时我需要提早退出或继续进行下一个循环,而不想用for...loop。你可以复制“continue”语句行为来提前返回,但如果要复制“break”行为,则需要使用数组“.some”方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/7e\/7ee4cd162e860dc4f6788c22cc20514e.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"17. 使用别名和默认值来销毁"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Destructuring(销毁)是JavaScript最好用的功能之一,而且你可以使用“冒号”设置别名,并使用“等号”设置属性默认值。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/7b\/7be56a0829324f503f51940f70a51815.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"18. 可选链和空值合并"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"深入检查对象属性并处理null和undefined值时,你可以使用几个非常好用的JavaScript功能来解决常见的问题。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/c4\/c492cf5c63451459f2074671e2f5cb83.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"19. 用函数扩展类"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我经常对别人讲,JavaScript类只是构造函数和底层的原型,不是像Java中那样的真实概念。一个证据是,你可以只使用一个构造函数来扩展一个类。在私有内容里这个很好用,在类里“#”这些看着很奇怪,并且用于babel或WebPack时,编译出来的代码更少。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"20. 扩展构造函数"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"类的一个问题是你只能扩展一个其他类。使用构造函数,你可以使用多个构造函数来构成一个函数,这样就会灵活多了。你可以使用函数原型的.apply或.call方法来实现。你甚至可以只扩展函数的一部分,只要它是一个对象即可。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/5c\/5cab6feab17c06b65ba73c6e40a5489c.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"21. 循环任何内容"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"有时,你需要循环任何可迭代的内容(Set、Map、Object、Array、String等)。这个非常简单的forEach函数工具就可以做到这一点。如果回调返回true,它将退出循环。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"function forEach(list, callback) {\n const entries = Object.entries(list);\n let i = 0;\n const len = entries.length;\n \n for(;i < len; i++) {\n const res = callback(entries[i][1], entries[i][0], list);\n \n if(res === true) break;\n }\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"22. 使函数参数为required"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这是一种确保函数调用了完成工作所需内容的绝佳方法。你可以使用默认参数值的特性来调用函数,然后就会抛出一个错误。如果调用该函数时带上了它需要的值,则该值将替换该函数,并且什么也不会发生。使用undefined调用也有相同的效果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"function required(argName = 'param') {\n throw new Error(`\"${argName}\" is required`)\n}\nfunction iHaveRequiredOptions(arg1 = required('arg1'), arg2 = 10) {\n console.log(arg1, arg2)\n}\niHaveRequiredOptions(); \/\/ throws \"arg1\" is required\niHaveRequiredOptions(12); \/\/ prints 12, 10\niHaveRequiredOptions(12, 24); \/\/ prints 12, 24\niHaveRequiredOptions(undefined, 24); \/\/ throws \"arg1\" is required\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"23. 创建模块或单例"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很多时候,你需要在加载时初始化某些内容,设置它需要的各种事物,然后就可以在应用程序中到处使用它,而无需再做什么补充工作。你可以使用IIFE函数来做到这一点,这个函数太好用了。这种模块模式用来隔离事物非常好用,它可以只暴露需要交互的内容。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/7e\/7e51e332beff39b939cfbe7523177933.png","alt":null,"title":null,"style":null,"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"24. 深度克隆对象"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"开发人员通常会安装一些类似“lodash”的库来执行这一操作,但使用纯JavaScript来实现确实也很容易。这是一个简单的递归函数:只要是一个对象,就使用函数的构造器将其重新初始化为一个克隆,然后对所有属性重复该过程。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"const deepClone = obj => {\n let clone = obj;\n if (obj && typeof obj === \"object\") {\n clone = new obj.constructor();\n \n Object.getOwnPropertyNames(obj).forEach(\n prop => (clone[prop] = deepClone(obj[prop]))\n );\n }\n return clone;\n};\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"25. 深度冻结对象"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果你喜欢不变性,那么这个工具你一定要常备。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"const deepClone = obj => {\n let clone = obj;\n if (obj && typeof obj === \"object\") {\n clone = new obj.constructor();\n \n Object.getOwnPropertyNames(obj).forEach(\n prop => (clone[prop] = deepClone(obj[prop]))\n );\n }\n return clone;\n};\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"原文链接"},{"type":"text","text":":"},{"type":"link","attrs":{"href":"https:\/\/beforesemicolon.medium.com\/25-javascript-code-solutions-utility-tricks-you-need-to-know-about-3023f7ed993e","title":"","type":null},"content":[{"type":"text","text":"25 Javascript Code Solutions Utility Tricks You Need to Know About"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章