实现 new 操作符
/**
* 用法:创建一个实例化对象
* 思路:
* 1、判断传入的 constructor 是否是函数
* 2、创建一个空对象 obj
* 3、将空对象的隐式原型设置为构造函数的 prototype 属性
* 4、使用 obj 调用构造函数,并掺入参数,获取函数返回值
* 5、判断这个返回值 如果返回的是 Object || Function 类型,就返回该对对象,否则返回创建的对象
*
* @param {Function} constructor 构造函数
* @param {...any} args 构造函数参数数组
* @returns
*/
function myNew(constructor, ...args) {
// 判断传入的 constructor 是否是函数
if (typeof constructor !== 'function') {
return new TypeError('constructor 必须是一个函数!')
}
// 创建一个空对象 obj
// 将空对象的隐式原型设置为构造函数的 prototype 属性
const obj = Object.create(constructor.prototype)
// 使用 obj 调用构造函数,并掺入参数,获取函数返回值
const result = constructor.apply(obj, args)
// 判断这个返回值 如果返回的是 Object || Function 类型,就返回该对对象,否则返回创建的对象
const flag = result && (typeof result === 'object' || typeof result === 'function')
return flag ? result : obj
}
const p = myNew(Person, "ps", 18);
实现 instanceof
/**
* 原理:
* 我们拿到 instanceof 左侧对象的原型链
* 再拿到 instanceof 右侧构造函数的显式原型 prototype
* 如果原型链中存在右侧构造函数的显式原型 prototype,instanceof 返回 true,否则返回 false
*/
function myInstanceOf(obj, constructor) {
let objProptype = Object.getPrototypeOf(obj)
while (true) {
if (!objProptype) return false
if (objProptype === constructor.prototype) return true
objProptype = Object.getPrototypeOf(objProptype)
}
}
const obj = {}
console.log(myInstanceOf(obj, Object));
实现 Object.create()
/**
* 用法:创建一个新的对象,将传入的对象原型指向新对象并返回
* 思路:
* 1、创建一个 空对象,将 prototype 添加到 空对象的隐式原型属性上
* 1、将原型写入到一个函数里面,然后 new 函数并返回实例对象
*/
function myCreate(prototype) {
// 方法 1:
// const obj = {}
// obj.__proto__ = prototype
// return obj
// 方法 2:
function F() {}
F.prototype = prototype
return new F()
}
// 测试代码
function Person(name) {
this.name = name
}
const obj = Object.create(Person.prototype)
console.log(obj, myCreate(Person.prototype));
防抖 debounce
函数在 n 秒后执行,如果多次触发,重新计时,保证函数在 n 秒后执行。
非立即执行
function debounce(fn, delay = 500) {
let timer = null
// 使用闭包记录 timer 变量
return function() {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
立即执行
function debounce(fn, delay = 500) {
let timer = null
// 使用闭包记录 timer 变量
return function () {
if (timer) {
clearTimeout(timer)
} else {
fn.apply(this, arguments)
}
timer = setTimeout(() => {
timer = null
}, delay)
}
}
完整版
function debounce(fn, delay = 500, immediate = false) {
let timer = null
// 使用闭包记录 timer 变量
return function() {
if (timer) {
clearTimeout(timer)
} else {
immediate && fn.apply(this, arguments)
}
timer = setTimeout(() => {
!immediate && fn.apply(this, arguments)
timer = null
}, delay)
}
}
// 测试代码
function getData(e) {
console.log('getData', e);
}
const fn = debounce(getData)
window.addEventListener('resize', e => {
fn(e)
})
节流 throttle
在 n 秒内 函数只能执行一次,如果 n 秒内多次触发,则忽略执行。
非立即执行
function throttle(fn, delay = 500) {
let timer = null
return function () {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
}
立即执行
function throttle(fn, delay = 500) {
let timer = null
return function () {
if (!timer) {
fn.apply(this, arguments)
timer = setTimeout(() => {
timer = null
}, delay)
}
}
}
完整版
function throttle(fn, delay = 500, immediate = false) {
let timer = null
return function () {
if (!timer) {
immediate && fn.apply(this, arguments)
timer = setTimeout(() => {
!immediate && fn.apply(this, arguments)
timer = null
}, delay)
}
}
}
function getData(e) {
console.log('getData', e);
}
const fn = throttle(getData, 3000, false)
window.addEventListener('resize', e => {
fn(e)
})
浅拷贝
/**
* 原理:如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。
*/
function shadowClone(target) {
if (!target || typeof target !== 'object') {
// 对于基本数据类型直接返回
return target
}
// 创建一个新对象
const obj = Array.isArray(target) ? [] : {}
// 遍历 target 上的所有属性,并添加到 obj 上
// in 性能低,因为会去遍历原型
for (const key in target) {
// 判断是否是自身属性
if (target.hasOwnProperty(key)) {
obj[key] = target[key]
}
}
return obj
}
const obj = {
a: 1,
b: {
c: 0
}
}
const obj2 = shadowClone(obj)
obj2.a = 2
obj2.b.c = 2
console.log(obj2, obj);
深拷贝
/**
* 思路:
* 1、判断是否为对象
* 2、判段对象是否在 map 中 如果存在就不需要操作
* 3、将 target 放入 map 中 避免重复引用
* 4、for in 遍历对象 拿到 key 判断 key 是否在 obj 中
*/
function deepClone(target, map = new Map()) {
// 判断数据类型,基本数据类型,直接返回
if (!target || typeof target !== 'object') {
return target
}
if (map.get(target)) {
return target
}
// 创建对象
const obj = Array.isArray(target) ? [] : {}
map.set(target, true)
for (const key in target) {
if (target.hasOwnProperty(key)) {
obj[key] = deepClone(target[key], map)
}
}
return obj
}
// 测试代码
var obj = {
a: 1,
b: {
c: [3, 5, { e: 'eee', children: undefined }]
}
}
obj.b.c[2].children = obj
const obj2 = deepClone(obj)
console.log(obj2, obj);
call
/**
* 用法:call 方法用于调用一个函数,并指定函数内部 this 的指向,传入一个对象
* 思路:
* 1、判断 this 是否指向一个函数 只有函数才可以执行
* 2、获取传入的 context 上下文 也就是我们要指向的 如果不存在就指向 window
* 3、将当前 this 也就是外部需要执行的函数 绑定到 context 上 然后执行获取 result 传入 ...args 确保参数位置正确
* 4、删除 context 对象的 fn 属性 并将 result 返回
*/
Function.prototype.myCall = function(context, ...args) {
if (typeof this !== 'function') {
return new TypeError('type error')
}
context = context || window
// 缓存this
const key = Symbol()
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}
function fn(name, age) {
console.log(this, name, age);
return name
}
const obj = {}
console.log(fn.call(obj, 'ps', 19));
console.log(fn.myCall(obj, 'ps', 19));
apply
Function.prototype.myApply = function(context, args = []) {
if (typeof this !== 'function') {
return new TypeError('type error')
}
context = context || window
// 缓存this
const key = Symbol()
context[key] = this
const result = context[key](...args)
delete context[key]
return result
}
function fn(name, age) {
console.log(this, name, age);
return name
}
const obj = {}
console.log(fn.apply(obj, ['ps', 19]));
console.log(fn.myApply(obj, ['ps', 19]));
bind
Function.prototype.myBind = function (context, ...args) {
if (typeof this !== "function") {
return new TypeError("type error");
}
context = context || window;
// 缓存this
const key = Symbol();
context[key] = this;
return function () {
const result = context[key](...args);
delete context[key];
return result;
};
};
function fn(name, age) {
console.log(this, name, age);
return name;
}
const obj = {};
fn.bind(obj, "ps", 19)();
fn.myBind(obj, "ps", 19)();
函数柯里化 curry
function curry(fn, args = []) {
// 1. 获取函数形参数量
const length = fn.length
// 2. 返回一个函数
return function() {
// 3. 收集参数
args.push(...arguments)
if (args.length >= length) {
// 当收集到的参数列表大于函数的形参数量时,需要调用函数并返回结果
return fn.apply(this, args)
} else {
// 递归返回函数
return curry(fn, args)
}
}
}
// 测试代码
function sum(a, b, c) {
return a * b * c
}
const sumCurry = curry(sum)
sumCurry(2)
sumCurry(2)
sumCurry(3)
console.log(sumCurry(10))
实现 flat 数组扁平化
// 递归实现
// 判断当前项是否为数组 如果是数组递归调用 不是就push到新数组
function flat(arr, result = []) {
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
if (Array.isArray(item)) {
flat(item, result)
} else {
result.push(item)
}
}
return result
}
Array.prototype.flatten = function() {
const arr = this
return [].concat(...arr.map(item => Array.isArray(item) ? item.flatten() : item))
}
// 栈实现
// 新建一个栈来存储数据 每次从栈中取出一个数据 判断是否为数组
// 如果是 就将该数组放入到栈中 修改了栈的长度 开始下一次循环
// 如果不是 就放入新数组
function flatten(arr = []) {
if (!Array.isArray(arr)) {
throw new TypeError('不是一个数组')
}
const stack = [...arr]
const result = []
while (stack.length) {
const current = stack.pop()
if (Array.isArray(current)) {
stack.push(...current)
} else {
result.push(current)
}
}
return result
}
实现 map
Array.prototype.myMap = function(cb) {
cb = typeof cb !== 'function' ? () => {} : cb
const arr = this
const result = []
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
result.push(cb(item, i, arr))
}
return result
}
const arr1 = [1, 2].myMap()
console.log(arr1);
实现 forEach
Array.prototype.myForEach = function(cb) {
cb = typeof cb !== 'function' ? () => {} : cb
const arr = this
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
cb(item, i, arr)
}
}
实现 filter
Array.prototype.myFilter = function(cb) {
cb = typeof cb !== 'function' ? () => {} : cb
const arr = this
const result = []
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
const flag = cb(item, i, arr)
flag && result.push(item)
}
return result
}
实现 find
Array.prototype.myFind = function(cb) {
cb = typeof cb !== 'function' ? () => {} : cb
const arr = this
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
const flag = cb(item, i, arr)
if (flag) return item
}
}
实现 reduce
Array.prototype.myReduce = function(cb, initValue) {
cb = typeof cb !== 'function' ? () => {} : cb
const arr = this
let prev
// 判断是否设置了初始值,设置了就使用
// 没设置就用数组第一项
if (initValue !== undefined) {
prev = initValue
} else {
prev = arr.shift()
}
for (let i = 0; i < arr.length; i++) {
const next = arr[i]
prev = cb(prev, next, i, arr)
}
return prev
}
实现 push
Array.prototype.myPush = function(...rest) {
const arr = this
// 方法1:
for (let i = 0; i < rest.length; i++) {
arr[arr.length] = rest[i]
}
// 方法2:
// while (rest.length) {
// arr[arr.length] = rest.shift()
// }
return arr.length
}
数组转树
let source = [
{
id: 1,
parentId: 0,
name: 'body',
},
{
id: 2,
parentId: 1,
name: 'title',
},
{
id: 3,
parentId: 2,
name: 'div',
},
{
id: 4,
parentId: 0,
name: 'html',
},
{
id: 5,
parentId: 4,
name: 'div',
},
{
id: 6,
parentId: 5,
name: 'span',
},
{
id: 7,
parentId: 5,
name: 'img',
},
]
[
// 转为
({
id: 1,
parentId: 0,
name: 'body',
children: [
{
id: 2,
parentId: 1,
name: 'title',
children: [{ id: 3, parentId: 2, name: 'div' }],
},
],
},
{
id: 4,
parentId: 0,
name: 'html',
children: [
{
id: 5,
parentId: 4,
name: 'div',
children: [{ id: 7, parentId: 5, name: 'img' }],
},
],
})
]
用 map 对所有的 id 进行缓存,判断每一项的 parentId 是否在 map 中, 如果存在则是子元素,如果不存在则是根元素。
function arrToTree(arr = []) {
const map = {}
for (const item of arr) {
map[item.id] = item
}
const result = []
for (let i = 0; i < arr.length; i++) {
const item = arr[i]
const node = map[item.parentId]
if (node) {
node.children = node.children || []
node.children.push(item)
} else {
result.push(item)
}
}
return result
}
树转数组
let source1 = [
{
id: 1,
parentId: 0,
name: "body",
children: [
{
id: 2,
parentId: 1,
name: "title",
children: [{ id: 3, parentId: 2, name: "div" }],
},
],
},
{
id: 4,
parentId: 0,
name: "html",
children: [
{
id: 5,
parentId: 4,
name: "div",
children: [{ id: 7, parentId: 5, name: "img" }],
},
],
},
];
[
// 转为
({ id: 1, parentId: 0, name: 'body' },
{ id: 4, parentId: 0, name: 'html' },
{ id: 2, parentId: 1, name: 'title' },
{ id: 5, parentId: 4, name: 'div' },
{ id: 3, parentId: 2, name: 'div' },
{ id: 7, parentId: 5, name: 'img' })
]
// 递归实现
function treeToArr(arr = [], result = []) {
for (const { children, ...rest } of arr) {
result.push(rest);
if (Array.isArray(children)) {
treeToArr(children, result);
}
}
return result;
}
// 栈实现
function treeToArr(arr = []) {
const stack = [...arr]
const result = []
while (stack.length) {
// 从栈顶弹出
const current = stack.shift()
if (current.children && current.children.length) {
// 有 children,将 children 全部展开并添加到栈中
stack.unshift(...current.children)
// 删除 children 属性
delete current.children
}
result.push(current)
}
return result
}
实现每隔一秒打印 1,2,3,4
// let + setTimeout
// 使用 let 块级作用域
for (let i = 1; i < 5; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000)
}
// setInterval
let i = 1
const timer = setInterval(() => {
if (i >= 4) {
clearInterval(timer)
}
console.log(i);
i++
}, 1000)
使用 setTimeout 实现一个 setInterval
function setInterval(cd = () => {}, delay = 1000) {
setTimeout(() => {
cd()
setInterval(cd, delay)
}, delay)
}
setInterval(() => {
console.log('ps 你好!');
}, 1000)
实现斐波那契数列
递归方式(基础班)
// 斐波那契数列 1, 2, 3, 5, 8, 13, 21
function fbnq(n) {
if (n === 1 || n === 2) return n
return fbnq(n - 2) + fbnq(n - 1)
}
console.log(fbnq(7)) // 21
递归优化版
// 斐波那契数列 1, 2, 3, 5, 8, 13, 21
function fbnq(n, map = {}) {
if (n === 1 || n === 2) return n
if (map[n]) {
return map[n]
} else {
const value = fbnq(n - 2, map) + fbnq(n - 1, map)
map[n] = value
return value
}
}
console.log(fbnq(100))
将数字每千分位用逗号隔开
// 方法一
function format(num) {
if (typeof num !== 'number') {
throw new Error(`${num} is not a number!`)
}
let decimal
if (!Number.isInteger(num)) {
decimal = String(num).split('.')[1]
num = String(num).split('.')[0]
}
let strNum = String(num)
const list = strNum.split('')
const len = list.length
// 整数
// 从后往前每三个加一个逗号
for (let i = len - 3; i > 0; i -= 3) {
list.splice(i, 0, ',')
}
const result = decimal ? list.join('') + '.' + decimal : list.join('')
return result
}
// 10224 --> 10,224
console.log(format(10922423113.97)); // 10,922,423,113.97
// 方法二
function format(num) {
if (typeof num !== 'number') {
throw new Error(`${num} is not a number!`)
}
return num.toLocaleString()
}
// 10224 --> 10,224
console.log(format(10922423113.97)); // 10,922,423,113.97
发布-订阅模式
class EventEmitter {
// events: { type: [{ cb, once: false }] }
constructor() {
this.events = {}
}
on(type, cb = () => {}, once = false) {
if (!this.events[type]) {
this.events[type] = []
}
this.events[type].push({ cb, once })
}
emit(type, data) {
(this.events[type] || []).forEach(item => {
item.cb(data)
if (item.once) {
this.off(type, item.cb);
}
})
}
off(type, cb = () => {}) {
this.events[type] = this.events[type].filter(item => item.cb !== cb)
}
once(type, cb = () => {}) {
this.on(type, cb, true)
}
}
const eventEmitter = new EventEmitter()
function fn(val) {
console.log(`name: `+ val.name);
}
eventEmitter.on('event1', fn)
eventEmitter.emit('event1', { name: 'ps', age: 18 })
eventEmitter.off('event1', fn)
eventEmitter.once('event1', fn)
eventEmitter.emit('event1', { name: 'ps', age: 18 })
实现 Promise
class MyPromise {
// 定义三种状态
static PENGING = "pending";
static FULFILLED = "fulfilled";
static REJECT = "reject";
constructor(executor) {
// 初始化状态
this.status = MyPromise.PENGING;
// 初始化值
this.value = null;
this.reason = null;
// 成功的回调队列
this.onFulfilledCallback = [];
// 失败的回调队列
this.onRejectedCallback = [];
// 执行并绑定this
executor(this.reslove.bind(this), this.reject.bind(this));
}
reslove(value) {
// 状态一旦变更就不会再发生变化。
if (this.status !== MyPromise.PENGING) return;
this.value = value;
this.status = MyPromise.FULFILLED;
this.onFulfilledCallback.length && this.onFulfilledCallback.shift()();
}
reject(reason) {
// 状态一旦变更就不会再发生变化。
if (this.status !== MyPromise.PENGING) return;
this.reason = reason;
this.status = MyPromise.REJECT;
this.onRejectedCallback.length && this.onRejectedCallback.shift()();
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled !== "function" ? () => {} : onFulfilled;
onRejected = typeof onRejected !== "function" ? () => {} : onRejected;
return new MyPromise(() => {
if (this.status === MyPromise.PENGING) {
this.onFulfilledCallback.push(() =>
setTimeout(() => onFulfilled(this.value))
);
this.onRejectedCallback.push(() =>
setTimeout(() => onRejected(this.reason))
);
} else if (this.status === MyPromise.FULFILLED) {
onFulfilled(this.value);
} else if (this.status === MyPromise.REJECT) {
onRejected(this.reason);
}
});
}
}
const p1 = new MyPromise((resolve, reject) => {
resolve("p1成功");
console.log("p1-console");
setTimeout(() => {
resolve("p1成功-setTimeout");
reject("p1-失败-setTimeout");
console.log("p1-console-setTimeout");
}, 100);
});
p1.then(
(value) => {
console.log("value: ", value);
},
(reason) => {
console.log("reason: ", reason);
}
);
1. 快速排序
利用二分法 + 递归的原理
function quickSort(arr) {
// 退出递归的条件:数组中只剩下一个元素时 返回数组
if (arr.length <= 1) return arr;
const middleIndex = Math.floor(arr.length / 2);
const middle = arr[middleIndex];
const left = [];
const right = [];
const center = [];
for (let i = 0; i < arr.length; i++) {
if (arr[i] < middle) {
left.push(arr[i]);
} else if (arr[i] > middle) {
right.push(arr[i]);
} else {
center.push(arr[i]);
}
}
return quickSort(left).concat(center, quickSort(right));
}
const arr = [3, 1, 4, 9, 10, 1, 3, 2, 5, 9, 6];
console.log(quickSort(arr));
2. 插入排序
以下是插入排序的基本思想步骤:
- 从第一个元素开始,该元素可以认为已经被排序;
- 取出下一个元素,在已经排序的元素序列中从后向前扫描;
- 如果该元素(已排序)大于新元素,将该元素移到下一位置;
- 重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置;
- 将新元素插入到该位置后;
- 重复步骤 2~5,直到所有元素均排序完毕。
function insertSort(arr) {
for (let i = 1; i < arr.length; i++) {
let j = i - 1;
const current = arr[i];
// 必须使用变量将当前值保存起来,不然后面数组下标变化,导致出现问题。
while (j >= 0 && arr[j] > current) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = current;
}
return arr;
}
const arr = [2, 1, 4, 3, 8, 9, 5, 7, 10, 2, 33];
console.log(insertSort(arr));
3. 冒泡排序
function bubbleSort(arr) {
// 不易理解
// for (let i = 0; i < arr.length - 1; i++) {
// for (let j = i + 1; j < arr.length; j++) {
// if (arr[i] > arr[j]) {
// [arr[i], arr[j]] = [arr[j], arr[i]]
// }
// }
// }
// 原数组:[4, 3, 2, 1]
// [4, 3, 2, 1] i = 0, j = 0 --> [3, 4, 2, 1]
// i = 0, j = 1 --> [3, 2, 4, 1]
// i = 0, j = 2 --> [3, 2, 1, 4]
// [3, 2, 1, 4] i = 1 j = 0, 1 --> [2, 3, 1, 4], [2, 1, 3, 4]
// [2, 1, 3, 4] i = 2 j = 0 --> [1, 2, 3, 4]
// [1, 2, 3, 4] i = 3 --> [1, 2, 3, 4]
// 外层遍历控制行,内层遍历控制列
for (let i = 0; i < arr.length - 1; i++) {
for (let j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
}
}
}
return arr;
}
const arr = [2, 1, 4, 3, 8, 9, 5, 7, 10, 2, 33, 8];
console.log(bubbleSort(arr));
4. 选择排序
// 思想:查找最小值,记录索引。将未排序列最小值与已排序列的后一位进行交换。
function selectionSort(arr) {
let minIndex;
for (let i = 0; i < arr.length; i++) {
minIndex = i;
for (let j = i + 1; j < arr.length; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
[arr[i], arr[minIndex]] = [arr[minIndex], arr[i]];
}
return arr;
}
const arr = [2, 1, 4, 3, 8, 9, 5, 7, 10, 2, 33, 8];
console.log(selectionSort(arr));
按需求合并两个数组
['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2'] 和 ['A', 'B', 'C', 'D'] ,
合并为 ['A1', 'A2', 'A', 'B1', 'B2', 'B', 'C1', 'C2', 'C', 'D1', 'D2', 'D']
const a1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2']
const a2 = ['A', 'B', 'C', 'D']
function concat(a1, a2) {
return a1.concat(a2).sort((a, b) => a.charCodeAt() - b.charCodeAt())
}
改造下面的代码,使之输出 0 - 9
for (var i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// 方法1
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 1000);
}
// 方法2
for (var i = 0; i < 10; i++) {
((i) => {
setTimeout(() => {
console.log(i);
}, 1000);
})(i)
}