导出TXT文件
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>exportTxt</title>
</head>
<body>
<button onclick="exportTxt('文件','内容')">click</button>
<script>
function exportTxt(name, data) {
// MouseEvent对象记录着鼠标触发事件时的所有属性
let ev = new MouseEvent("click");
// Blob为js的一个不可变的、原始数据的类似文件对象
let export_blob = new Blob([data]);
// 创建带有指定命名空间的元素节点
let save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
let urlObject = window.URL || window.webkitURL || window;
// 根据传入的参数创建一个指向该参数对象的URL
save_link.href = urlObject.createObjectURL(export_blob);
save_link.download = name;
save_link.dispatchEvent(ev);
}
</script>
</body>
</html>
call
// 核心思路:在不改变原有对象的前提下
// 利用this动态地增删window对象下的属性/方法
Function.prototype.myCall = function(obj, ...arg) {
// 1. 判断有无传入参数
// 没有则默认指向window对象
obj = obj ? Object(obj) : window;
// 2. 给形参对象obj新增属性指向window对象
obj.unique = this;
// 3. 接收剩余参数
const res = obj.unique(...arg);
// 4. 删除这个新增的属性
delete obj.unique;
return res;
}
let a = 1;
const obj = {
a: 2
}
function fn1(b, c) {
console.log(this.a, b, c)
}
fn1.call(obj, 3, 4); // 2, 3, 4
fn1.myCall(obj, 3, 4); // 2, 3, 4
apply
// 核心思路:利用this动态地增删window对象下的属性/方法
Function.prototype.myApply = function(obj, arg = []) {
// 1. 判断有无传入参数
// 没有则默认指向window对象
obj = obj ? Object(obj) : window;
// 2. 给形参对象obj新增属性指向window对象
obj.unique = this;
// 3. 接收剩余参数
const res = obj.unique(...arg);
// 4. 删除这个新增的属性
delete obj.unique;
return res;
}
let a = 1;
const obj = {
a: 2
}
function fn1(b, c) {
console.log(this.a, b, c)
}
fn1.apply(obj, [3, 4]); // 2, 3, 4
fn1.myApply(obj, [3, 4]); // 2, 3, 4
bind
// 核心思路:通过改变this指向多次传递单一参数
Function.prototype.myBind = function(obj, ...arg1) {
const fn = this;
return function(...arg2) {
return fn.call(obj, ...arg1.concat(arg2));
}
}
let a = 1;
const obj = {
a: 2,
}
function fn1(...arg) {
console.log(this.a, ...arg)
}
fn1.bind(obj, 3, 4)(5, 6); // 2, 3, 4, 5, 6
fn1.myBind(obj, 3, 4)(5, 6); // 2, 3, 4, 5, 6
生成指定范围的随机数
- 借助Math.random()返回0-1范围的随机数
- 乘以倍数(需要加一)
- 补充最低值
function randomNum(min, max) {
return Math.floor
(Math.random() * (max - min + 1))
+ min;
}
数组扁平化
手写JS之数组扁平化
也叫数组展开,是将多维数组转化成一维数组
数组扁平化的基本思路:逐个判断子项是否为数组
有3种方法
第一种方法:使用数组和字符串的转换
- 数组转换成字符串
- 借助map方法将字符串拼接成数组
第二种方法:使用递归
- 借助map方法
- 进行递归
- 拼接子项
第三种方法:使用while和some
- 使用while循环
- 借助some方法
- 拼接子项
// 数组扁平化,也叫数组展开,将多维数组转化成一维数组
// 基本思路:逐个判断子项是否为数组
// 有3种方法
// 第一种方法:使用数组和字符串的转换
Array.prototype.flat = function(arr) {
// 1. 数组转换成字符串
return arr.join(',').split(',').map(item => {
// 2. 借助map方法将字符串拼接成数组
return parseInt(item);
})
}
// 第二种方法:使用递归
Array.prototype.flat = function() {
// 1. 借助map方法
const res = this.map(item => {
if (Array.isArray(item)) {
// 2. 进行递归
return item.flat();
}
return [item];
})
// 3. 拼接子项
return [].concat(...res);
}
// 第三种方法:使用while和some
Array.prototype.flat = function() {
let res = this;
// 1. 使用while循环
// 2. 借助some方法
while (res.some(item => Array.isArray(item))) {
// 3. 拼接子项
res = [].concat(...res);
}
return res;
}
new的过程
-
内存中创建新对象
-
让新对象的隐式原型指向构造函数的显式原型
-
this指针指向新对象
执行构造函数的方法,给新对象添加属性、方法 -
返回新对象
function newProcess(fn, ...args) {
// 1. 内存中创建新对象
const newObj = {};
// 2. 让新对象的隐式原型指向构造函数的显式原型
newObj.__proto__ = fn.prototype;
// 3. this指针指向新对象
// 执行构造函数的方法,给新对象添加属性、方法
fn.apply(newObj, ...args);
// 4. 返回新对象
return newObj;
}
深拷贝
关键在于遍历+递归
-
判断是否为对象
-
判断是否为数组
-
遍历对象
-
避免拷贝原型的属性、方法
-
进行递归,直至拷贝到最深层的属性、方法
-
返回深拷贝的结果
// 深拷贝的关键:遍历+递归
function deepClone(obj = {}) {
// 1. 判断是否为对象
if (typeof obj !== 'object' || obj == null) return obj;
// 2. 判断是否为数组
let newObj;
if (obj instanceof Array) {
newObj = [];
} else {
newObj = {};
}
// 3. 遍历对象
for (let key in obj) {
// 4. 避免拷贝原型的属性、方法
if (obj.hasOwnProperty(key)) {
// 5. 进行递归,直至拷贝到最深层的属性、方法
newObj[key] = deepClone(obj[key]);
}
}
// 6. 返回深拷贝的结果
return newObj;
}
JSON.parse
// 第一种方法:直接调用eval()
function jsonParse(data) {
return eval('(' + data + ')')
}
// 第二种方法:使用Function
const newData = (new Function('return ' + data))()
节流
节流的关键点有两个,一个是节流开关阀,另一个是延时时间
基本思路是:
一,每次执行函数前都需要判断节流阀的开关状态
二,如果节流阀关闭,则直接退出
三,如果节流阀打开,则把节流阀关闭
四,在延时时间过后执行对应的回调函数,再把节流阀打开
// 节流的关键:1. 节流开关阀 2. 延时时间
function throttle(callback, delayTime) {
let state = true; // 节流阀打开
// 每次执行该函数都需要判断开关阀状态
return function () {
// 如果节流阀关闭,则直接退出该函数
// (永远不会执行callback)
if (!state) {
return;
}
// 如果节流阀打开,则先把节流阀关闭
// 然后在设置的时间间隔(delayTime)后自动打开
state = false;
setTimeout(() => {
callback && callback();
state = true;
}, delayTime);
};
}
防抖
防抖的关键在于对定时器的开始和清零
-
定义一个定时器
-
每次调用都需要判断定时器是否为空
-
定时器不为空,则需要重新计时
-
定时器为空,则开始倒计时
// 防抖的关键:对定时器的开始和清零
function debounce(callback, delayTime) {
// 定义一个定时器
let timer = null;
return function() {
// 如果定时器不是空,则需要重新计时
if (timer != null) {
clearTimeout(timer);
}
// 如果定时器还是空,则开始倒计时
timer = setTimeout(() => {
callback && callback();
}, delayTime)
}
}
柯里化
思路是:使用数组存储每次接收的参数,并且返回新函数处理剩下的参数,直到最后才调用
-
定义函数的参数列表
-
判断是否为最后一个参数组
-
如果不是,则递归调用function
-
如果是,则把接收的参数拼接成数组
// 关键:使用数组存储每次接收的参数,并且返回新函数处理剩下的参数,直到最后才调用
function currying(fn, ...args) {
// 定义函数的参数列表
let len = fn.length;
// 判断是否为最后一个参数组
// 如果不是,则递归调用fn()
if (args.length >= len) {
return fn(...args)
}
// 如果是,则把接收的参数拼接成数组
return function() {
let _args = args.concat([...arguments])
return currying.call(this, fn, ..._args)
}
}
正则表达式
// 匹配手机号:
// 1. 所有号码都是以1开头
/^1/
// 2. 第2位数都是3、4、5、7、8之一
/^1[34578]/
// 3. 剩下的9位数可以取任意数字
/^1[34578]\d{9}$/
// 4. 使用全局搜索
/^1[34578]\d{9}$/g
// 匹配16进制颜色:
// 1. 以#号开头(考虑用户体验也可以省略)
/#?/g
// 2. 第一种情况:两两缩写,如#000,#fff
/#?[0-9a-fA-f]{3}/g
// 3. 第二种情况:如:#010203
/#?[0-9a-fA-f]{3}|[0-9a-fA-F]{6}/g
// 匹配身份证
// 1. 首数字不能为0
/^[1-9]/g
// 2. 第2-6位为任意数字
/^[1-9][0-9]{5}/g
// 3. 第7-10位为出生年份,如19xx,20xx
/^[1-9][0-9]{5}(19|20)[0-9]{2}/g
// 4. 第11-12位为出生月份
/^[1-9][0-9]{5}(19|20)[0-9]{2}(0[1-9]|1[0-2])/g
// 5. 第13-14位为出生天
/^[1-9][0-9]{5}(19|20)[0-9]{2}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])/g
// 6. 最后4位为随机数
/^[1-9][0-9]{5}(19|20)[0-9]{2}(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01])[0-9]{3}[0-9Xx]$/g
手写Promise
function Promise(executor) {
// 初始化state为等待态
this.state = 'pending';
// 成功的值
this.value = undefined;
// 失败的原因
this.reason = undefined;
// 存放 fn1 的回调
this.fn1Callbacks = [];
// 存放 fn2 的回调
this.fn2Callbacks = [];
// 成功
let resolve = () => {};
// 失败
let reject = () => {};
// 立即执行
executor(resolve, reject);
}