手写JavaScript

导出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

生成指定范围的随机数

  1. 借助Math.random()返回0-1范围的随机数
  2. 乘以倍数(需要加一)
  3. 补充最低值
function randomNum(min, max) {
	return Math.floor
	(Math.random() * (max - min + 1)) 
	+ min;
}

数组扁平化

手写JS之数组扁平化
也叫数组展开,是将多维数组转化成一维数组

数组扁平化的基本思路:逐个判断子项是否为数组

有3种方法

第一种方法:使用数组和字符串的转换

  1. 数组转换成字符串
  2. 借助map方法将字符串拼接成数组

第二种方法:使用递归

  1. 借助map方法
  2. 进行递归
  3. 拼接子项

第三种方法:使用while和some

  1. 使用while循环
  2. 借助some方法
  3. 拼接子项
// 数组扁平化,也叫数组展开,将多维数组转化成一维数组
// 基本思路:逐个判断子项是否为数组

// 有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的过程

  1. 内存中创建新对象

  2. 让新对象的隐式原型指向构造函数的显式原型

  3. this指针指向新对象
    执行构造函数的方法,给新对象添加属性、方法

  4. 返回新对象

function newProcess(fn, ...args) {
	// 1. 内存中创建新对象
	const newObj = {};

	// 2. 让新对象的隐式原型指向构造函数的显式原型	
	newObj.__proto__ = fn.prototype;

	// 3. this指针指向新对象
	// 执行构造函数的方法,给新对象添加属性、方法
	fn.apply(newObj, ...args);

	// 4. 返回新对象
	return newObj;
}

深拷贝

关键在于遍历+递归

  1. 判断是否为对象

  2. 判断是否为数组

  3. 遍历对象

  4. 避免拷贝原型的属性、方法

  5. 进行递归,直至拷贝到最深层的属性、方法

  6. 返回深拷贝的结果

// 深拷贝的关键:遍历+递归
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);
  };
}

防抖

防抖的关键在于对定时器的开始和清零

  1. 定义一个定时器

  2. 每次调用都需要判断定时器是否为空

  3. 定时器不为空,则需要重新计时

  4. 定时器为空,则开始倒计时

// 防抖的关键:对定时器的开始和清零
function debounce(callback, delayTime) {
    // 定义一个定时器
    let timer = null;

    return function() {
        // 如果定时器不是空,则需要重新计时
        if (timer != null) {
            clearTimeout(timer);
        }

        // 如果定时器还是空,则开始倒计时
        timer = setTimeout(() => {
            callback && callback();
        }, delayTime)
    }
}

柯里化

思路是:使用数组存储每次接收的参数,并且返回新函数处理剩下的参数,直到最后才调用

  1. 定义函数的参数列表

  2. 判断是否为最后一个参数组

  3. 如果不是,则递归调用function

  4. 如果是,则把接收的参数拼接成数组

// 关键:使用数组存储每次接收的参数,并且返回新函数处理剩下的参数,直到最后才调用
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);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘泽宇Developer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值