计算距离下次生日还有多少天
借助 moment 实现。
function getNextBirthday(birthday) {
// 首先要获取到今年的生日
let birthdayTime = moment().format('YYYY-') + birthday;
// 通过时间戳 去判断当前的时间戳是否大于今年生日的时间戳
if (moment().unix() >= moment(birthdayTime).unix()) {
// 如果大于的话,那么就在今年的生日上再添加一年,已达到获取下次生日的时间
birthdayTime = moment(birthdayTime).add(1, 'y').format('YYYY-MM-DD');
}
// 这个直接通过计算 (下次生日的时间戳 - 当前日期的时间戳) / (60 * 60 * 24) 最后求出来的就是 XX 天
return parseInt(
(moment(birthdayTime).unix() - moment().unix()) / (60 * 60 * 24);
)
}
console.log(getNextBirthday('07-18')); // 28
回到顶部
function backTop() {
window.scrollTo({
top: 0, // 距离顶部0px
behavior: "smooth" // 平滑
})
}
将文本写入剪切板
function exeCommandCopyText(text) {
try {
const t = document.createElement('textarea');
t.nodeValue = text;
t.value = text;
document.body.appendChild(t);
t.select();
document.execCommand('copy');
document.body.removeChild(t);
return true;
} catch (e) {
console.log(e);
return false;
}
}
防抖
/*
防抖:一段时间内触发多次,以最后一次为准
fn:需要防抖的函数
delay:定时器时间
*/
function debounce(fn, delay) {
let timer = null; // 用于保存定时器
return function () {
// 如果timer存在 就清除定时器,重新计时
if (timer) clearTimeout(timeout);
//设置定时器,规定时间后执行真实要执行的函数
timer = setTimeout(() => {
fn.apply(this);
}, delay);
}
}
节流
/*
节流:一段时间内触发多次,以第一次为准
fn:需要防抖的函数
delay:定时器时间
*/
function throttle(fn, delay) {
let timer = null; // 首先设定一个变量,没有执行定时器时,默认为 null
return function () {
// 当定时器没有执行的时候timer永远是false,后面无需执行
if (timer) return;
timer = setTimeout(() => {
fn.apply(this);
// 最后在setTimeout执行完毕后再把标记设置为true(关键)
// 表示可以执行下一次循环了。
timer = null;
}, delay);
};
}
过滤特殊字符
function filterCharacter(str) {
let pattern = new RegExp("[`~!@#$^&*()=:”“'。,、?|{}':;'%,\\[\\].<>/?~!@#¥……&*()&;—|{ }【】‘;]", "g");
return str.replace(pattern, '')
}
将RGB转化为十六进制
function rgbToHexadecimal(r, g, b) {
return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)
}
动态加载JS文件
/*
动态加载JS文件
filse:JS文件的数组
done:回调函数
*/
function loadJS(files, done) {
// 获取 head 标签
const head = document.getElementsByTagName('head')[0];
Promise.all(files.map(file => {
return new Promise(resolve => {
// 创建 script 标签
const s = document.createElement('script');
// 设置 script 标签的 type 属性:类型
s.type = "text/javascript";
// 设置 script 标签的 async 属性:实现 script 标签的异步加载,并在加载完毕后立即执行,提升页面的加载速度
s.async = true;
// 设置 script 标签的 src 属性:路径
s.src = file;
// 监听 load 事件,如果加载完成则 resolve
s.addEventListener('load', (e) => resolve(), false);
// 添加 script 标签
head.appendChild(s);
});
})).then(done); // 所有均完成,执行用户的回调事件
}
// 调用 loadJS 方法
loadJS(["test1.js", "test2.js"], () => {
// 用户的回调逻辑
});
实现模板引擎
// 要实现的模板语法的字符串,%%之间的是js语法
var 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("");
}`
// 动态渲染的数据
const options = {
sports: ["swimming", "basketball", "football"],
showSports: true
}
// 构建可行的函数并传入参数,改变函数执行时this的指向
result = new Function("obj", code).apply(options, [options]);
console.log(result);
利用 reduce 进行数据结构的转换
// 要求:将这个数组转化为对象,且对象的每一项为按 classId 的不同来划分的数组
/*
Object {
"1": Array [
{ classId: "1", name: "张三", age: 16 },
{ classId: "1", name: "李四", age: 15 }
],
"2": Array [
{ classId: "2", name: "王五", age: 16 },
{ classId: "2", name: "孔七", age: 16 }
],
"3": Array [
{ classId: "3", name: "赵六", age: 15 },
]
}
*/
// 要进行数据结构转换的数组
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 }
];
/*
arr:原数组
key:划分的属性
*/
function groupArrayByKey(arr = [], key) {
return arr.reduce((item, index) => (!item[index[key]] && (item[index[key]] = []), item[index[key]].push(index), item), {});
}
groupArrayByKey(arr, "classId");
添加默认值
有时候一个方法需要用户传入一个参数,通常情况下我们有两种处理方式:
-
如果用户不传,我们通常会给一个默认值
function double(value = 0) { return value * 2; };
-
亦或是用户必须要传一个参数,不传直接抛错
const required = () => { throw new Error("This function requires one parameter."); }; function double(value = required()) { return value * 2; };
函数只执行一次
/*
通过闭包实现
fn:函数的名称
*/
function once (fn) {
// 利用闭包判断函数是否执行过
let called = false;
return function () {
if (!called) {
called = true;
fn.apply(this, arguments);
}
};
};
实现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)); // 3
console.log(curriedAdd(1, 2)); // 3
通过柯里化,我们可以将一些常见的功能模块化,例如验证、缓存等等。这样可以提高代码的可维护性和可读性,减少出错的机会。
实现单例模式
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); // true
实现 CommonJs 规范
CommonJS 规范的核心思想是将每个文件都看作一个模块,每个模块都有自己的作用域,其中的变量、函数和对象都是私有的,不能被外部访问。要访问模块中的数据,必须通过导出(exports)和导入(require)的方式。
const path = require('path');
const fs = require('fs');
/*
定义模块
id:完整的文件名
*/
function Module(id){
// id:用来唯一标识模块
this.id = id;
// exports:用来导出模块的属性和方法
this.exports = {};
}
/*
获取文件内容
filePath:文件名
*/
function myRequire(filePath) {
// 直接调用 Module 的静态方法进行文件的加载
return Module._load(filePath);
}
/*
cache 缓存
*/
Module._cache = {};
/*
加载 JS 文件
filePath:文件名
*/
Module._load = function(filePath) {
// 因为在 CommnJS 中,模块的唯一标识是文件的绝对路径,所以先获取文件的绝对路径
// realPath:文件的绝对路径
const realPath = Module._resoleveFilename(filePath);
// 缓存优先
// cacheModule:缓存中该路径的值
let cacheModule = Module._cache[realPath];
// 如果缓存中存在,则直接返回模块的 exports 属性
if(cacheModule) return cacheModule.exports;
// 如果第一次加载,需要 new 一个模块,参数是文件的绝对路径
let module = new Module(realPath);
// 调用模块的 load 方法去编译模块
module.load(realPath);
return module.exports;
}
/*
模块处理对象(这里没有写对 node 进行处理的方法)
*/
Module._extensions = {
// 对 JS 文件调用 handleJS 方法
".js": handleJS,
// 对 JSON 文件调用 handleJSON 方法
".json": handleJSON
};
/*
对 JSON 文件进行处理的方法
module:模块实例
*/
function handleJSON(module) {
// 以同步的方式对文件进行读取(utf-8 解码)
const json = fs.readFileSync(module.id, 'utf-8');
// 将 JSON 字符串转化为 JSON 并暴露
module.exports = JSON.parse(json);
}
/*
对 JS 文件进行处理的方法
module:模块实例
*/
function handleJS(module) {
// 以同步的方式对文件进行读取(utf-8 解码)
const js = fs.readFileSync(module.id, 'utf-8');
// 定义回调函数
/*
等同于
let fn = function(exports, myRequire, module, __filename, __dirname) {
js;
};
*/
let fn = new Function('exports', 'myRequire', 'module', '__filename', '__dirname', js);
// exports:用来导出模块的属性和方法
let exports = module.exports;
// 组装后的函数直接执行即可
fn.call(exports, exports, myRequire, module, module.id, path.dirname(module.id));
}
/*
查找文件的绝对路径
filePath:文件名
*/
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;
}
};
/*
加载文件
realPath:
*/
Module.prototype.load = function(realPath) {
// 获取文件扩展名
// extname:文件的扩展名
let extname = path.extname(realPath);
// 将 JS 文件或 JSON 文件交由不同的方法进行处理
Module._extensions[extname](this);
};
上面对 CommonJs 规范进行了简单的实现,核心解决了作用域的隔离,并提供了 Myrequire 方法进行方法和属性的加载,对于上面的实现,笔者专门有一篇文章《38 行代码带你实现 CommonJS 规范》进行了详细的说明,感兴趣的小伙伴可自行阅读。
递归获取对象属性
如果让我挑选一个用的最广泛的设计模式,我会选观察者模式,如果让我挑一个我所遇到的最多的算法思维,那肯定是递归,递归通过将原始问题分割为结构相同的子问题,然后依次解决这些子问题,组合子问题的结果最终获得原问题的答案。
const user = {
info: {
name: "张三",
address: { home: "Shaanxi", company: "Xian" },
},
};
/*
obj:要获取属性的的对象
path:对象的路径
defaultVal:默认值(当在对象中找不到该属性时)
*/
function get(obj, path, defaultVal) {
// parts:路径按 "." 分隔的数组
const parts = path.split(".");
// key:最上层的路径
const key = parts.shift();
// 判断路径是否被迭代完全,没有则继续迭代
if (typeof obj[key] !== "undefined") return parts.length > 0 ? get(obj[key], parts.join("."), defaultVal) : obj[key];
// 如果没有找到 key 则返回 defaultVal
return defaultVal;
}
console.log(get(user, "info.name")); // 张三
console.log(get(user, "info.address.home")); // Shaanxi
console.log(get(user, "info.address.company")); // Xian
console.log(get(user, "info.address.abc", "defaultVal")); // defaultVal