在实际的开发过程中,积累了一些常见的、超级好用的 Javascript 技能和代码片段,包括其他大神的整理 JS 今天筛选了使用技巧 9 个,供大家参考。
1、动态加载 JS 文件在一些特殊场景下,特别是在一些图书馆和框架的开发中,我们有时会动态加载 JS 并执行文件,以下是使用 Promise 简单的包装。
function loadJS(files, done) { // 获取head标签 const head = document.getElementsByTagName('head')[0]; Promise.all(files.map(file => { return new Promise(resolve => { // 创建script标签,添加head const s = document.createElement('script'); s.type = "text/javascript"; s.async = true; s.src = file; // 监控load事件,如果加载完成,resolve s.addEventListener('load', (e) => resolve(), false); head.appendChild(s); }); })).then(done); // 一切都完成了,执行用户回调事件}loadJS(test1.js", "test2.js"], () => { // 用户回调逻辑});
上面的代码核心有两点,一是使用 Promise 处理异步逻辑,而是使用异步逻辑 script 标签进行 js 加载并执行。
2、模板引擎的实现实现动态模板渲染引擎的示例很少,不仅支持普通动态变量的替换,还支持包括 for 循环,if 动态判断等 JS 语法逻辑
// 这是一个动态模板varr,包含js代码 template ='My avorite sports:' +'<%if(this.showSports) {%>' + '<% for(var index in this.sports) { %>' + '<a><%this.sports[index]%></a>' + '<%}%>' +'<%} else {%>' + '<p>none</p>' +'<%}%>';// 这是我们要拼接的函数字符串const code = `with(obj) { var r=[]; r.push("My avorite sports:"); if(this.showSports) { for(var index in this.sports) { r.push("<a>"); r.push(this.sports[index]); r.push("</a>"); } } else { r.push("<span>none</span>"); } return r.join("");}`// 数据constt动态渲染 options = { sports: ["swimming", "basketball", "football"], showSports: true}// 构建可行的函数并输入参数,改变函数执行时this指向resultt = new Function("obj", code).apply(options, [options]);console.log(result);
3、利用 reduce 转换数据结构
有时前端需要转换后端传输的数据,以适应前端的业务逻辑,或将组件的数据格式转换到后端进行处理 reduce 这是一个非常强大的工具。
const arr = [ { classId: "1", name: "张三", age: 16 }, { classId: "1", name: "李四", age: 15 }, { classId: "2", name: "王五", age: 16 }, { classId: "3", name: "赵六", age: 15 }, { classId: "2", name: "孔七", age: 16 }];groupArrayByKey(arr, "classId");function groupArrayByKey(arr = [], key) { return arr.reduce((t, v) => (!t[v[key]] && (t[v[key]] = []), t[v[key]].push(v), t), {}}
如果使用许多复杂的逻辑,许多复杂的逻辑 reduce 去处理,都很简单。
4、添加默认值有时一种方法需要用户传输一个参数。通常,我们有两种处理方法。如果用户不传输,我们通常会给出默认值,或者用户必须传输一个参数,而不是直接错误。
function double() { return value *2}// 如果不传,给出默认值0function double(value = 0) { return value * 2}// 用户必须传输一个参数,不传输参数,抛出错误的constt required = () => { throw new Error("This function requires one parameter.")}function double(value = required()) { return value * 2}double(3) // 6double() // throw
listen 创建一个方法 NodeJS 的原生 http 在服务回调函数中创建服务和监控端口 context,然后调用户注册的回调函数并传输生成的回调函数 context。让我们以前看看 createContext 和 handleRequest 的实现。
5、函数只执行一次在某些情况下,我们有一些特殊的场景,某个函数只允许执行一次,或者某个绑定方法只允许执行一次。
export function once (fn) { // 使用闭包判断函数是否执行过 let called = false return function () { if (!called) { called = true fn.apply(this, arguments) } }}
6、实现 Curring
JavaScript 柯里化是指将多个参数的函数转换为一系列只接受一个参数的函数的过程。这可以更灵活地使用函数,减少重复代码,增加代码的可读性。
function curry(fn) { return function curried(...args) { if (args.length >= fn.length) { return fn.apply(this, args); } else { return function(...args2) { return curried.apply(this, args.concat(args2); }; } };}function add(x, y) { return x + y;}const curriedAdd = curry(add);console.log(curriedAdd(1)(2)); // 输出 3console.log(curriedAdd(1, 2)); // 输出 3
通过柯里化,我们可以模块化一些常见的功能,如验证、缓存等。这可以提高代码的可维护性和可读性,减少错误的机会。
7、实现单例模式JavaScript 单例模式是一种常用的设计模式,它可以保证一个类只有一个例子,并在这个例子中提供全局访问点 JS 购物车、缓存对象、全局状态管理等应用场景广泛。
let cache;class A { // ...}function getInstance() { if (cache) return cache; return cache = new A();}const x = getInstance();const y = getInstance();console.log(x === y); //
8、实现 CommonJs 规范
CommonJS 标准化的核心思想是将每个文件视为一个模块,每个模块都有自己的功能域,其中变量、函数和对象是私有的,不能外部访问。访问模块中的数据必须导出(exports)和导入(require)的方式。
// id:consttt完整的文件名 path = require('path');const fs = require('fs');function Module(id){ // 用于唯一的识别模块 this.id = id; // 导出模块的属性和方法 this.exports = {};}function myRequire(filePath) { // 直接调用Module的静态方法加载文件 return Module._load(filePath);}Module._cache = {};Module._load = function(filePath) { // 首先,filePath搜索文件的绝对路径是由用户传输的 // 因为在再ComnJS中,模块的唯一标志是文件的绝对路径 const realPath = Module._resoleveFilename(filePath); // 优先考虑缓存,如果存在直接返回模块的exports属性 let cacheModule = Module._cache[realPath]; if(cacheModule) return cacheModule.exports; // 如果第一次加载,需要new模块,参数是文件的绝对路径 let module = new Module(realPath); // 调用模块的load方法编译模块 module.load(realPath); return module.exports;}// Modulee文件暂不讨论._extensions = { // 处理js文件 ".js": handleJS, // 处理json文件 ".json": handleJSON}function handleJSON(module) { // 如果是json文件,直接使用fson.读取readFilesync, // 然后用JSON.转化parse,直接返回即可 const json = fs.readFileSync(module.id, 'utf-8') module.exports = JSON.parse(json)}function handleJS(module) { const js = fs.readFileSync(module.id, 'utf-8') let fn = new Function('exports', 'myRequire', 'module', '__filename', '__dirname', js) let exports = module.exports; // 组装后的函数可以直接执行 fn.call(exports, exports, myRequire, module,module.id,path.dirname(module.id))}Module._resolveFilename = function (filePath) { // 拼接绝对路径,然后找到,存在即返回 let absPath = path.resolve(__dirname, filePath); let exists = fs.existsSync(absPath); if (exists) return absPath; // 如果不存在,依次拼接.js,.json,.尝试node let keys = Object.keys(Module._extensions); for (let i = 0; i < keys.length; i++) { let currentPath = absPath + keys[i]; if (fs.existsSync(currentPath)) return currentPath; }};Module.prototype.load = function(realPath) { // 获取文件扩展名,交由相应的方法处理 let extname = path.extname(realPath) Module._extensions[extname](this)}
上面对 CommonJs 简单实现了规范,核心解决了作用域的隔离,并提供了 Myrequire 加载方法和属性的方法。
9、递归获取对象属性如果我选择最广泛使用的设计模式,我会选择观察者模式。如果我选择我遇到的最常见的算法思维,那一定是递归。递归将原始问题分为结构相同的子问题,然后依次解决这些子问题。组合子问题的结果最终得到了原始问题的答案。
const user = { info: { name: "张三", address: { home: "Shaanxi", company: "Xian" }, },};// obj是获取属性的对象,path是路径,fallback是function的默认值 get(obj, path, fallback) { const parts = path.split("."); const key = parts.shift(); if (typeof obj[key] !== "undefined") { return parts.length > 0 ? get(obj[key], parts.join("."), fallback) : obj[key]; } // 如果没有找到key返回falback return fallback;}console.log(get(user, "info.name")); // 张三console.log(get(user, "info.address.home")); // Shaanxiconsole.log(get(user, "info.address.company")); // Xianconsole.log(get(user, "info.address.abc", "fallback")); // fallback