// 简单版本
// function deepClone1(obj) {
// return JSON.parse(JSON.stringify(obj));
// }
function deepClone(obj = {}) {
if (typeof obj !== "object" && obj !== null) return obj;
let result = obj instanceof Array ? [] : {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result;
}
let obj = {
name: "kunyuan",
age: 15,
da: {
name: "45",
},
};
let newObj = deepClone(obj);
newObj.name = "laj";
newObj.da.name = "aaaaaaaa";
console.log(obj);
console.log(newObj);
<img src="" alt="" width="30%" />
// 第一种:固定传入参数,参数够了才执行
/** * 实现要点:柯里化函数接收到足够参数后,就会执行原函数,那么我们如何去确定何时达到足够的参数 呢?
* * 柯里化函数需要记住你已经给过他的参数,如果没给的话,则默认为一个空数组。
* * 接下来每次调用的时候,需要检查参数是否给够,如果够了,则执行fn,
* 没有的话则返回一个新的 curry 函数,将现有的参数塞给他。 **/
// 待柯里化处理的函数
let sum = (a, b, c, d) => {
return a + b + c + d;
};
// 柯里化函数,返回一个被处理过的函数
let curry = (fn,...args) => {
// arr 记录已有参数
return args.length >= fn.length ? fn(...args) : (...arr)=> curry(fn,...args.concat(...arr))
}
var sumPlus = curry(sum);
console.log(sumPlus(1)(2)(3)(4));
console.log(sumPlus(1, 2)(3)(4));
console.log(sumPlus(1, 2, 3)(4));
function myInstanceOf(left, right) {
let leftValue = left.__proto__;
let rightValue = right.prototype;
while (true) {
if (leftValue == null) return false;
if (leftValue === rightValue) {
return true;
}
leftValue = leftValue.__proto__;
}
}
function getType(type) {
return Object.prototype.toString.call(type).slice(8, -1);
}
console.log(myInstanceOf(Object, Function));
console.log(getType(1));
function mynew(func, ...args) {
let obj = {};
obj.__proto__ = func.prototype;
let result = func.apply(obj, args);
return result instanceof Object ? result : obj;
}
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.say = function () {
console.log(this.name + ' ' + this.age);
}
let p = mynew(Person, "邬坤源", 12);
console.log(p);
p.say();
var array = [1, 2, 1, 1, '1'];
// Array.from去重
function uniques(array){
return Array.from(new Set(array));
}
// 简化
function SampleUniques(array){
return [...new Set(array)]
}
console.log(uniques(array));
// 也可以使用es5中的indexOf方法
function es5Uniques(array){
let res = array.filter(function(item,index,array){
return array.indexOf(item) === index;
})
return res;
}
console.log("es5去重");
console.log(es5Uniques(array));
const arr = [1, [2, 3, [4, [[5]]]]]
console.log(arr.flat(1));
console.log(arr.flat(2));
console.log(arr.flat(3));
console.log(arr.flat(4));
// depth<=0时,返回的数组和原数组维数一样(注意只是维数一样,空位情况见第3点)
console.log("depth<=0时,返回的数组和原数组维数一样(注意只是维数一样,空位情况见第3点)");
console.log(arr.flat(0));
console.log([].concat(...arr));
// 自己实现一个flat扁平化数组
function myflat(arr){
while(arr.some(item=>Array.isArray(item))){
arr = [].concat(...arr)
}
return arr;
}
console.log("我的实现");
console.log(myflat(arr));
// 重写原型上的方法
Array.prototype.newflat = function(n=1){
let arr = this;
while(n && this.some(item=>Array.isArray(item))){
arr = [].concat(...arr);
n--;
}
return arr;
}
console.log("重写原型上的方法");
console.log([1, 2, [3, 4, [5, [6, [7]]]]].newflat(5))
// 使用reduce方法拍平数组
function FlatReduce(arr){
return arr.reduce((pre,cur) => {
return pre.concat(Array.isArray(cur)?FlatReduce(cur):cur)
},[])
}
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Documenttitle>
head>
<body>
<div id="app">
<input type="text" value="" name="txt" id="txt">
<p id="Show_text">p>
div>
<script>
var obj = {};
Object.defineProperty(obj, 'value', {
get: function () {
return obj;
},
set: function (newValue) {
document.getElementById('txt').value = newValue
document.getElementById('Show_text').innerHTML = newValue
},
})
document.addEventListener('keyup', function (e) {
obj.value = e.target.value
})
script>
body>
html>
const mySetTimeout = (fn, delay) => {
const timer = setInterval(() => {
fn()
clearInterval(timer)
}, delay)
}
const mySetInterval = (fn, delay) => {
let timer = null
const interval = () => {
fn()
timer = setTimeout(interval, delay)
}
timer = setTimeout(interval, delay)
return {
cancel: () => {
clearTimeout(timer)
}
}
}
Function.prototype.myApply = function(context=globalThis,...args){
let key = Symbol('key');
context[key] = this;
let result = context[key](args);
delete context[key];
return result;
}
function f(a,b){
console.log(a+b)
console.log(this.name)
}
let obj={
name:'张三'
}
f.myApply(obj,[1,2])
Function.prototype.newCall = function(context=globalThis,...args) {
let key = Symbol('key')
context[key] = this
let result = context[key](...args)
delete context[key]
return result;
}
function f(a,b){
console.log(a+b)
console.log(this.name)
}
let obj={
name:1
}
f.newCall(obj,1,2)
const obj = {
name: "11",
fun() {
console.log(this.name);
},
};
Function.prototype._bind = function (ctx, ...args) {
// 获取函数体
const _self = this;
// 用一个新函数包裹,避免立即执行
const bindFn = (...reset) => {
return _self.call(ctx, ...args, ...reset);
};
return bindFn;
};
const obj2 = { name: "22" };
obj.fun(); // 11
const fn = obj.fun.bind(obj2);
const fn2 = obj.fun._bind(obj2);
fn(); // 22
fn2(); // 22
function promiseAll(promises){
if(!Array.isArray(promises)){
throw new TypeError("promises must be an array")
}
return new Promise(function(resolve, reject){
// 数组长度
let promiseNum = promises.length;
// 成功的数量
let resolveCount = 0;
// 成功的值的数组
let resolveValues = new Array(promiseNum);
// 先遍历
for(let i=0; i<promiseNum; i++){
// 为什么不直接 promise[i].then, 因为promise[i]可能不是一个promise
Promise.resolve(promises[i]).then(function(value){
resolveValues[i] = value;
resolveCount++;
if(resolveCount == promiseNum){
return resolve(resolveValues)
}
},
function(err){
return reject(err);
}
)
}
})
}
Promise.prototype.finally = function(callback) {
this.then(value => {
return Promise.resolve(callback()).then(() => {
return value;
})
}, error => {
return Promise.resolve(callback()).then(() => {
throw error;
})
})
}
// function promiceRaces(promises){
// if(!Array.isArray(promises)){
// throw new TypeError("promise must be an array")
// }
// return new Promise(function(resolve, reject){
// promises.forEach( p =>
// Promise.resolve(p).then(data=>{
// resolve(data)
// },err =>{
// reject(err)
// }
// )
// )
// })
// }
function PromiseRace(promises) {
if(promises instanceof Array){
throw new TypeError("promises must be an array")
}
return new Promise(function(resole,reject){
promises.forEach( item =>{
Promise.resolve(item).then(data=>{
resole(data)
},
err =>{
reject(err)
}
)
})
})
}
Promise.reject = function(reason){
return new Promise((resolve, reject)=>reject(reason))
}
Promise.resolve = function(value) {
if(value instanceof Promise){
return value;
}
return new Promise((resolve, reject)=>resolve(value))
}
// 防抖
// 防抖: n 秒后在执行该事件,若在 n 秒内被重复触发,则重新计时
const debounce = (func,wait = 500) => {
var timer = 0;
return function(...args) {
if(timer) clearTimeout(timer);
timer = setTimeout(()=>{
func.apply(this,args);
},wait);
}
}
const fn = debounce(()=>{console.log(3);},3000)
setInterval(fn, 2000)
// 节流
// 节流: n 秒内只运行一次,若在 n 秒内重复触发,只有一次生效
// func是用户传入需要防抖的函数
// wait是等待时间
const throttle = (func, wait = 500) => {
// 上一次执行该函数的时间
let lastTime = 0
return function (...args) {
// 当前时间
let now = +new Date()
// 将当前时间和上一次执行函数时间对比
// 如果差值大于设置的等待时间就执行函数
if (now - lastTime > wait) {
lastTime = now
func.apply(this, args)
}
}
}
const fn = throttle(() => {
console.log(3);
}, 3000)
setInterval(fn, 2000)
// 适用场景:
// 拖拽场景:固定时间内只执行一次,防止超高频次触发位置变动
// 缩放场景:监控浏览器resize
// 动画场景:避免短时间内多次触发动画引起性能问题
// 快速排序
function QuickSort(arr) {
const n = arr.length;
if (n <= 1) return arr;
let pivot = arr[0];
let left = [];
let right = [];
for (let i = 1; i < n; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
// return QuickSort(left).concat([pivot], QuickSort(right));
return [...QuickSort(left), pivot, ...QuickSort(right)]
}
let list = [4, 6, 8, 5, 9, 1, 2, 3, 2];
let sortArr = QuickSort(list)
console.log("快速排序", sortArr);
function selectionSort(arr) {
var len = arr.length;
var minIndex, temp;
for (var i = 0; i < len - 1; i++) {
minIndex = i;
for (var j = i + 1; j < len; j++) {
if (arr[j] < arr[minIndex]) { // 寻找最小的数
minIndex = j; // 将最小数的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
return arr;
}
function insertionSort(arr) {
var len = arr.length;
var preIndex, current;
for (var i = 1; i < len; i++) {
preIndex = i - 1;
current = arr[i];
while (preIndex >= 0 && arr[preIndex] > current) {
arr[preIndex + 1] = arr[preIndex];
preIndex--;
}
arr[preIndex + 1] = current;
}
return arr;
}
const mergeSort = arr => {
//采用自上而下的递归方法
const len = arr.length;
if (len < 2) {
return arr;
}
// length >> 1 和 Math.floor(len / 2) 等价
let middle = Math.floor(len / 2),
left = arr.slice(0, middle),
right = arr.slice(middle); // 拆分为两个子数组
return merge(mergeSort(left), mergeSort(right));
};
const merge = (left, right) => {
const result = [];
while (left.length && right.length) {
// 注意: 判断的条件是小于或等于,如果只是小于,那么排序将不稳定.
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length) result.push(left.shift());
while (right.length) result.push(right.shift());
return result;
};
// 测试
const arr = [3, 44, 38, 5, 47, 15, 36, 26, 27, 2, 46, 4, 19, 50, 48];
console.time('归并排序耗时');
console.log('arr :', mergeSort(arr));
console.timeEnd('归并排序耗时');
// arr : [2, 3, 4, 5, 15, 19, 26, 27, 36, 38, 44, 46, 47, 48, 50]
// 归并排序耗时: 0.739990234375ms
function bubbleSort(arr) {
var len = arr.length;
for (var i = 0; i < len - 1; i++) {
for (var j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) { // 相邻元素两两对比
var temp = arr[j + 1]; // 元素交换
arr[j + 1] = arr[j];
arr[j] = temp;
}
}
}
return arr;
}
function shellSort(arr) {
// 1. 获取数组长度
let length = arr.length
// 2.获取初始的间隔长度
let interval = Math.floor(length / 2)
// 3. 不断地缩小间隔的大小,进行分组插入排序
while (interval >= 1) {
// 4. 从 arr[interval] 开始往后遍历,将遍历到的数据与其小组进行插入排序
for (let i = interval; i < length; i++) {
let temp = arr[i]
let j = i
while (arr[j - interval] > temp && j - interval >= 0) {
arr[j] = arr[j - interval]
j -= interval
}
arr[j] = temp
}
// 5. 缩小间隔
interval = Math.floor(interval / 2)
}
return arr
}
function Parent(){
this.name = '邬坤源',
this.play = [1,2,3]
}
function Child(){
this.age = 29
}
Child.prototype = new Parent();
let myChild = new Child();
console.log(myChild.name);
// 缺点:改变s1的play属性,会发现s2也跟着发生变化了,这是因为两个实例使用的是同一个原型对象,内存空间是共享的
var s1 = new Child();
var s2 = new Child();
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4]
function Parent(){
this.name = 'parent1';
}
Parent.prototype.getName = function () {
return this.name;
}
function Child(){
Parent.call(this);
this.type = 'child'
}
let child = new Child();
console.log(child);
// 缺点 :父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法
console.log(child.getName());
// 组合式继承就是把原型链继承和函数式继承结合起来
function Parent3 () {
this.name = 'parent3';
this.play = [1, 2, 3];
}
Parent3.prototype.getName = function () {
return this.name;
}
function Child3() {
// 第二次调用 Parent3()
Parent3.call(this);
this.type = 'child3';
}
// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
// Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play); // 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'
这里主要借助Object.create方法实现普通对象的继承
let parent = {
name: 'li',
friends: ['ha','mo','gui'],
getName:function(){
return this.name;
}
}
let person = object.create(parent);
person4.name = "tom";
person4.friends.push("jerry");
console.log(person4.name); // tom
console.log(person4.name === person4.getName()); // true
let person5 = Object.create(parent4);
person5.friends.push("lucy");
console.log(person5.name); // parent4
console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"]
console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]
// 缺点:因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能
let parent5 = {
name: "parent5",
friends: ["p1", "p2", "p3"],
getName: function() {
return this.name;
}
};
function clone(original) {
let clone = Object.create(original);
clone.getFriends = function() {
return this.friends;
};
return clone;
}
let person5 = clone(parent5);
console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ["p1", "p2", "p3"]
function Parent6() {
this.name = 'parent6';
this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {
return this.name;
}
function Child6() {
Parent6.call(this);
this.friends = 'child5';
}
function clone (parent, child) {
// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程
child.prototype = Object.create(parent.prototype);
child.prototype.constructor = child;
}
clone(Parent6, Child6);
Child6.prototype.getFriends = function () {
return this.friends;
}
let person6 = new Child6();
console.log(person6); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6}
console.log(person6.getName()); // parent6
console.log(person6.getFriends()); // child5
class Person {
constructor(name) {
this.name = name
}
// 原型方法
// 即 Person.prototype.getName = function() { }
// 下面可以简写为 getName() {...}
getName = function () {
console.log('Person:', this.name)
}
}
class Gamer extends Person {
constructor(name, age) {
// 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。
super(name)
this.age = age
}
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法
/*
大数相加的意思是超过 JS 存放数据的安全范围(一般指的是数字)的数字进行相加
*/
console.log(Number.MAX_SAFE_INTEGER) //9007199254740991
console.log(Number.MIN_SAFE_INTEGER) //-9007199254740991
// 9007199254740991 + 1234567899999999999
let a = "9007199254740991", b = '1234567899999999999'
function add(a, b) {
// 位数补全
const MAX_LEN = Math.max(a.length, b.length)
// padStart() 方法用于从字符串左侧填充 0
a = a.padStart(MAX_LEN, 0)
b = b.padStart(MAX_LEN, 0)
let flag = 0 // 进位标志
let str = '', j = MAX_LEN-1
while(j >= 0) {
let res = Number(a[j]) + Number(b[j]) + flag
flag = res >= 10 ? 1 : 0
res = res % 10
str = res + str
j--
}
// 处理最高位溢出
if(flag === 1) {//增加多一位
str = '1' + str
}
return str
}
// 正确结果:1243575099254740990
// 输出答案:1243575099254740990
console.log(add(a, b))
/*
哈夫曼树 —— 最优二叉树
*/
/**
* @param {*} val
* @param {*} left
* @param {*} right
*/
function TreeNode(val, char, left, right) {
this.val = val || 0 // 字符出现的次数
this.char = char || '' // 待编码的字符(当前节点是叶子节点才给char赋值)
this.left = left || null
this.right = right || null
}
/**
*
* @param {Map} map
* @returns
*/
function HuffmanTree(str) {
if(str === '') { return null }
//1. 统计字符出现的频率
let hash = {}
for(let i=0; i<str.length; i++) {
hash[str[i]] ??= 0 // 前者为 null / undefined 才赋值
hash[str[i]] = hash[str[i]] + 1
}
//2. 构造哈夫曼树
const huffmanTree = this.getHuffmanTree(hash)
console.log('===哈夫曼树===', huffmanTree)
//3. 遍历哈夫曼树得到编码表
const map = this.getHuffmanCode(huffmanTree)
console.log('===哈夫曼编码表===', map)
//4. 根据编码对照表,返回最终的二进制代码
let res = ''
for(let item in hash) {
res += map.get(item)
}
console.log('===哈夫曼总编码===', res)
}
HuffmanTree.prototype.getHuffmanTree = function(hash) {
// 构建叶子节点
let forest = []
for(let char in hash) {
const node = new TreeNode(hash[char], char)
forest.push(node)
}
console.log(forest)
let allNodes = []
while(forest.length != 1) {
forest.sort((a, b) => a.val - b.val)
let node = new TreeNode(forest[0].val + forest[1].val)
allNodes.push(forest[0])
allNodes.push(forest[1])
node.left = allNodes[allNodes.length - 2] // 左子树放置词频低的
node.right = allNodes[allNodes.length - 1] // 右子树放置词频高的
forest = forest.slice(2)
forest.push(node) // 将新生成的节点放入森林中
}
return forest[0] // 整棵树的根节点
}
// 树的遍历(只统计子结点)
HuffmanTree.prototype.getHuffmanCode = function(huffmanTree) {
let map = new Map()
// 层数大于二才有路径
const search = (node, curPath) => {
if(!node) { return }
if(!node.left && !node.right) {
map.set(node.char, curPath)
}
if(node.left) {
search(node.left, curPath + '0')
}
if(node.right) {
search(node.right, curPath + '1')
}
}
search(huffmanTree, '')
return map
}
const huff = new HuffmanTree('ABBCCCDDDDEEEEE')
/*
实现对数字进行千位分隔符的分隔
19,351,235.235767
*/
// 将数字转为字符串,返回的是字符串
var throusandDot = function(num) {
num = String(num)
let [zheng, xiao] = num.split('.') // 切成整数部分和小数部分
console.log(zheng, xiao)
let sum = 0, res = []
for(let i=zheng.length-1; i>=0; i--) {
res.push(zheng[i])
sum++
if(sum === 3 && i!=0) { res.push(','); sum = 0 }
}
return res.reverse().join('') + '.' + xiao
}
console.log(throusandDot(119351235.235767))
/*
直接使用 api => 可能出现的问题是位数比较多的时候会被截取
*/
var throusandDot = function(num) {
return num.toLocaleString()
}
console.log(throusandDot(119351235.235767))
/*
使用正则表达式
https://www.runoob.com/regexp/regexp-syntax.html
*/
var throusandDot = function(num) {
var res=num.toString().replace(/\d+/, function(n){ // 先提取整数部分
return n.replace(/(\d)(?=(\d{3})+$)/g, function($1){ //?= 匹配的是前面的内容
// console.log('$1', $1)
return $1+",";
});
})
return res;
}
console.log(throusandDot(119351235.235767))
/*
求最大公约数
1. 辗转相除法
2. 更相减损法
3. 普通解法
*/
// 辗转相除法
function greatestCommonDivisor(a, b) {
if(b == 0) {
return a
}
return greatestCommonDivisor(b, a % b)
}
// 更相减损法
function greatestCommonDivisor2(a, b) {
if(a > b) {
a -= b
} else if(a < b) {
b -= a
} else {
return a
}
}
// 普通解法
更多算法实现可移步我的 GitHub,欢迎 start
https://github.com/tomato-wu/JS-algorithm.git