跳到主要内容

代码题

手写题

1.手写防抖函数

函数防抖是指在事件被触发 n 秒后再执行回调,如果在这 n 秒内事件又被触发,则重新计时。这可以使用在一些点击请求的事件上,避免因为用户的多次点击向后端发送多次请求。应用场景:输入框,下拉

按钮提交场景:防⽌多次提交按钮,只执⾏最后提交的⼀次 服务端验证场景:表单验证需要服务端配合,只执⾏⼀段连续的输⼊事件的最后⼀次,还有搜索联想词功能类似⽣存环境请⽤

节流是在事件持续触发时以一定的时间间隔去定时执行回调

函数节流是指规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。节流可以使用在 scroll 函数的事件监听上,通过事件节流来降低事件调用的频率。

防抖是每隔指定的时间发起请求 用户滑动时 定时 / 定滑动的高度

拖拽场景:固定时间内只执⾏⼀次,防⽌超⾼频次触发位置变动 缩放场景:监控浏览器resize 动画场景:避免短时间内多次触发动画引起性能问题

非立即防抖

非立即执行函数: 多次触发事件,只会在最后一次触发事件后等待设定的wait时间结束时执行一次

function debounce(fn,delay) {
let timer = null
return function(...args) {
clearTimeout(timer)
timer = null
timer = setTimeout(() => {
fn.apply(this,args)
},delay)
}
}
function task(){
console.log('run task')
}
const debounceTask=debounce(task,1000)
window.addEventListener('scroll',debounceTask);

立即防抖

立即执行:即多次触发事件,第一次会立即执行函数,之后在设定wait事件内触犯的事件无效,不会执行。

设置clearTimeout为什么还要timer=null 设置延时器之前先清除下延时器,不然每次事件触发都会多一个延时器,延时器之间互相干扰,造成紊乱。

function debounce(fn,wait){
let timerId = null;
let flag = true;
return function(){
clearTimeout(timerId);
if(flag){
fn.apply(this,arguments);
flag = false
}
timerId = setTimeout(() => { flag = true},wait)
}
}
//测试
function task(){
console.log('run task')
}
const debounceTask=debounce(task,1000)
window.addEventListener('scroll',debounceTask);

2.手写节流函数

非立即节流 连续点击的话,每过 wait 秒执行一次

function throttle(fn, time) {
let timer = null
return function (...args) {
if (!timer) {
timer = setTimeout(() => {
fn.apply(this,args)
timer = null
}, time)
}
}
}
function throttle(fn, wait) {
let timer = true
return function(...args) {
if(!timer) return
timer = false
setTimeout(() => {
// fn() // fn中this指向window
fn.apply(this,args) // fn中this指timer 下面同理
console.log(this)
timer = true
}, wait)
}
}
function task(){
console.log('run task')
}
const throttleTask=throttle(task,1000);
window.addEventListener('scroll',throttleTask);

立即节流 连续点击的话,第一下点击会立即执行一次 然后每过 wait 秒执行一次

function throttle(fn,delay){
let last=Date.now();
return function(...args){
const now =Date.now();
if(now-last>delay){
last=now;
fn.apply(this,args)
console.log(this)
}
}
}
function task(){
console.log('run task')
}
const throttleTask=throttle(task,1000);
window.addEventListener('scroll',throttleTask);

3.手写浅拷贝深拷贝

浅拷贝是指,一个新的对象对原始对象的属性值进行精确地拷贝,如果拷贝的是基本数据类型,拷贝的就是基本数据类型的值,如果是引用数据类型,拷贝的就是内存地址。如果其中一个对象的引用内存地址发生改变,另一个对象也会发生变化。

浅拷贝

Object.assign()

Object.assign() 是ES6中对象的拷贝方法,接受的第一个参数是目标对象,其余参数是源对象,用法: Object.assign(target, source1, ···) ,该方法可以实现浅拷贝,也可以实现一维对象 的深拷贝。

let target = {a: 1};
let object2 = {b: 2};
let object3 = {c: 3};
Object.assign(target,object2,object3);
console.log(target); // {a: 1, b: 2, c: 3}
扩展运算符

使用扩展运算符可以在构造字面量对象的时候,进行属性的拷贝。语法: let cloneObj = {...obj };

let obj1 = {a:1,b:{c:1}
let obj2 = {...obj1};
obj1.a = 2;
console.log(obj1);
//{a:2,b:{c:1}}
console.log(obj2);
//{a:1,b:{c:1}}
obj1.b.c = 2;
console.log(obj1);
//{a:2,b:{c:2}}
console.log(obj2);
//{a:1,b:{c:2
数组方法实现数组浅拷贝
Array.prototype.slice()

array.slice(start, end)

从已有数组中返回选定的元素,该方法有两个参数,两个参数都可选,如果两个参数都不写,就可以实现一个数组的浅拷贝

Array.prototype.concat()

concat() 用于合并两个或多个数组 返回一个新数组

手写浅拷贝
function shallowCopy(object){
if(!object||typeof object!=="object") return
let newObject=Array.isArray(object)?[]:{}
for(let key in object){
if(object.hasOwnProperty(key)){
newObject[key]=object[key]
}
}
return newObject
}

深拷贝

JSON.stringify()

JSON.parse(JSON.stringify(obj)) 原理就是利用 JSON.stringify 将 js 对象序列化(JSON字符串),再使用JSON.parse 来反序列化(还原)js 对象

拷贝的对象中如果有函数,undefined, symbol,当使用过 JSON.stringify() 进行处理之后,都会消失

let obj1 = { a: 0,b: {c: 0}};
let obj2 = JSON.parse(JSON.stringify(obj1)
obj1.a = 1;
obj1.b.c = 1;
console.log(obj1); // {a: 1, b: {c: 1}}
console.log(obj2); // {a: 0, b: {c: 0}}
手写深拷贝

没有处理 null 这种原始类型,也没有日期和正则这种比较常用的引用类型

 function clone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};

let obj={
a:function(){
console.log('a')
},
b:{
c:'d'
},
d:[1,2,3,4],
e: new Date(),
f: /abc/,
g: null
}
console.log(clone(obj));

处理 日期 正则 null

function deepClone (target) {
if (target === null) return target // 处理 null
if (target instanceof Date) return new Date(target) // 处理日期
if (target instanceof RegExp) return new RegExp(target) // 处理正则

if (typeof target !== 'object') return target // 处理原始类型

// 处理对象和数组
//const cloneTarget = new target.constructor() //创建一个新的克隆对象或克隆数组 不用在拷贝时去判断数组类型了
const cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) { // 递归拷贝每一层
cloneTarget[key] = deepClone(target[key])
}
return cloneTarget
}
let obj={
a:function(){
console.log('a')
},
b:{
c:'d'
},
d:[1,2,3,4],
e: new Date(),
f: /abc/,
g: null
}
console.log(deepClone(obj));
function deepClone(obj) {
let result;
if (typeof obj == "object") {
if (obj == null) {
result = obj;
} else if (obj instanceof RegExp) {
result = new RegExp(obj);
} else if (obj instanceof Date) {
result = new Date(obj);
} else if (Array.isArray(obj)) {
result = [];
for (let key of obj) {
result.push(deepClone(key));
}
} else {
result = {};
for (let key in obj) {
if (!result.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
}
} else {
result = obj;
}
return result;
}
let m = {
a: 1,
b: ["name", "xxx"],
c: /^g/,
d: { e: 1 },
f: function (a, b) {
console.log(666);
},
};

console.log(deepClone(m));
解决循环引用和symbol
const obj = {
name: "cc",
age: 30,
job: { type: "porgrammer", com: "ali" },
cars: ["passat", "bmw"],
working: function (str) {
console.log(`我是${this.name},我正在${str}`)
},
da: new Date(),
reg: new RegExp(),
xx: undefined ,
};
obj.f=obj

function clone(obj, map = new Map()) {
if (typeof obj != 'object') return
var newObj = Array.isArray(obj) ? [] : {}
if (map.get(obj)) {
return map.get(obj);
}
map.set(obj, newObj);
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] == 'object') {
newObj[key] = clone(obj[key], map);
} else {
newObj[key] = obj[key];
}
}
}
return newObj;
}

var copyobj = clone(obj)
console.log(copyobj);

可以把 for in 换成 Reflect.ownKeys 来解决

Reflect.ownKeys方法返回一个由目标对象自身的属性键组成的数组。它的返回值等同于Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))

// 终极解决方案,满足循环引用和symbol
//判断是否是基本数据类型
function isPrimitive(value){
return (typeof value === 'string' ||
typeof value === 'number' ||
typeof value === 'symbol' ||
typeof value === 'boolean')
}

//判断是否是一个js对象
function isObject(value){
return Object.prototype.toString.call(value) === "[object Object]"
}

//深拷贝一个值
function cloneEnDeep(value){
// 记录被拷贝的值,避免循环引用的出现
let memo = {};
function baseClone(value){
let res;
// 如果是基本数据类型,则直接返回
if(isPrimitive(value)){
return value;
// 如果是引用数据类型,我们浅拷贝一个新值来代替原来的值
}else if(Array.isArray(value)){
res = [...value];
}else if(isObject(value)){
res = {...value};
}

// 检测我们浅拷贝的这个对象的属性值有没有是引用数据类型。如果是,则递归拷贝
//同时使用Reflect可以检测到Symbol类型的属性
Reflect.ownKeys(res).forEach(key=>{
if(typeof res[key] === "object" && res[key]!== null){
//此处我们用memo来记录已经被拷贝过的引用地址。以此来解决循环引用的问题
if(memo[res[key]]){
res[key] = memo[res[key]];
}else{
memo[res[key]] = res[key];
res[key] = baseClone(res[key])
}
}
})
return res;
}
return baseClone(value)
}

//======================测试====================
//定义一个员工个人对象
let objP = {
name:"cc",
age:30,
job:{type:"porgrammer",com:"ali"},
cars:["passat","bmw"],
working:function(str){
console.log(`我是${this.name},我正在${str}`)
},
da:new Date(),
reg: new RegExp(),
xx:undefined
}

objP.job = objP;//循环引用
console.log(obj);
console.log(getPerInf(obj));
console.log(obj);
obj.working("吃饭");

完全版

支持对象、数组、日期、正则的拷贝。

处理原始类型(原始类型直接返回,只有引用类型才有深拷贝这个概念)。

处理 Symbol 作为键名的情况。

处理函数(函数直接返回,拷贝函数没有意义,两个对象使用内存中同一个地址的函数,问题不大)。

处理 DOM 元素(DOM 元素直接返回,拷贝 DOM 元素没有意义,都是指向页面中同一个)。

额外开辟一个储存空间 WeakMap,解决循环引用递归爆栈问题(引入 WeakMap 的另一个意义,配合垃圾回收机制,防止内存泄漏)。

function deepClone (target, hash = new WeakMap()) { // 额外开辟一个存储空间WeakMap来存储当前对象
if (target === null) return target // 如果是 null 就不进行拷贝操作
if (target instanceof Date) return new Date(target) // 处理日期
if (target instanceof RegExp) return new RegExp(target) // 处理正则
if (target instanceof HTMLElement) return target // 处理 DOM元素

if (typeof target !== 'object') return target // 处理原始类型和函数 不需要深拷贝,直接返回

// 是引用类型的话就要进行深拷贝
if (hash.get(target)) return hash.get(target) // 当需要拷贝当前对象时,先去存储空间中找,如果有的话直接返回
const cloneTarget = new target.constructor() // 创建一个新的克隆对象或克隆数组
hash.set(target, cloneTarget) // 如果存储空间中没有就存进 hash 里

Reflect.ownKeys(target).forEach(key => { // 引入 Reflect.ownKeys,处理 Symbol 作为键名的情况
cloneTarget[key] = deepClone(target[key], hash) // 递归拷贝每一层
})
return cloneTarget // 返回克隆的对象
}


const obj = {
a: true,
b: 100,
c: 'str',
d: undefined,
e: null,
f: Symbol('f'),
g: {
g1: {} // 深层对象
},
h: [], // 数组
i: new Date(), // Date
j: /abc/, // 正则
k: function () {}, // 函数
l: [document.getElementById('foo')] // 引入 WeakMap 的意义,处理可能被清除的 DOM 元素
}

obj.obj = obj // 循环引用

const name = Symbol('name')
obj[name] = 'lin' // Symbol 作为键

const newObj = deepClone(obj)

console.log(newObj)

4.手写ajax

  1. 创建 XMLHttpRequest 实例
  2. 发出 HTTP 请求
  3. 接收服务器传回的数据
  4. 更新网页数据
function handleGet(url) {
var response = "";
//1.创建对象
var xhr = new XMLHttpRequest();
//2.设置方法
xhr.open("GET", url);
//3.发送请求
xhr.send();
//4.返回结果
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if (xhr.status >= 200 && xhr.status < 300) {
response = xhr.responseText;
console.log(response, "1");
} else {
return "Error" + xhr.status;
}
}
};
}

5.使用promise实现ajax

// promise 封装实现:
function getJSON(url) {
// 创建一个 promise 对象
let promise = new Promise(function(resolve, reject) {
let xhr = new XMLHttpRequest();
// 新建一个 http 请求
xhr.open("GET", url, true);
// 设置状态的监听函数
xhr.onreadystatechange = function() {
if (this.readyState !== 4) return;
// 当请求成功或失败时,改变 promise 的状态
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
// 设置错误监听函数
xhr.onerror = function() {
reject(new Error(this.statusText));
};
// 设置响应的数据类型
xhr.responseType = "json";
// 设置请求头信息
xhr.setRequestHeader("Accept", "application/json");
// 发送 http 请求
xhr.send(null);
});
return promise;
}

6.手写 apply call bind

apply实现

判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

判断传入上下文对象是否存在,如果不存在,则设置为 window 。

将函数作为上下文对象的一个属性。

判断参数值是否传入

使用上下文对象来调用这个方法,并保存返回结果。

删除刚才新增的属性

返回结果

Function.prototype.myApply=function (context,args){
context = context || window;

if(typeof this !=="function"){
return new Error('typeError')
}
context.fn=this
if(args){
result=context.fn(...args)
}else{
result=context.fn()
}
delete context.fn
return result
}
function bar(age,sex) {
return {
name: this.name,
age,
sex
}
}
const obj = {
name: 'cyx'
}
bar.myApply( obj, [22, "男"] ) // {name: "cyx", age: 22, sex: "男"}

call实现

判断调用对象是否为函数,即使我们是定义在函数的原型上的,但是可能出现使用 call 等方式调用的情况。

判断传入上下文对象是否存在,如果不存在,则设置为 window 。

处理传入的参数,截取第一个参数后的所有参数。

将函数作为上下文对象的一个属性。

使用上下文对象来调用这个方法,并保存返回结果。

删除刚才新增的属性。

返回结果。

 Function.prototype.myCall = function (context) {
context = context || window;
if (typeof this !== "function") {
throw new Error("typeError");
}
let result;
context.fn = this;
let args = [...arguments].slice(1);
if (args) {
result = context.fn(...args);
} else {
result = context.fn();
}
delete context.fn;
return result;
};
var obj = {
n: 15,
};
function sum(n, m) {
console.log(this);
return n + m;
}

console.log(sum.myCall(obj, 10, 20));

bind实现

// bind 函数实现
Function.prototype.myBind = function(context) {
// 判断调用对象是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
var args = [...arguments].slice(1),
fn = this;
return function Fn() {
// 根据调用方式,传入不同绑定值
return fn.apply(
this instanceof Fn ? this : context,
args.concat(...arguments)
);
};
};
var obj = {
n: 15,
};
function sum(n, m) {
console.log(this);
return n + m;
}
var ans=sum.myBind(obj, 10, 20)
console.log(ans());

7.手写promise方法

手写Promise.all

Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果

function myPromiseAll(arrayList) {
return new Promise((resolve, reject) => {
let resultArr = [],
count = 0;
let length = arrayList.length;
for (let i = 0; i < length; i++) {
Promise.resolve(arrayList[i]).then(
(result) => {
count++;
resultArr[i] = result;
if (count == length) {
resolve(resultArr);
}
},
(err) => {
return reject(err);
}
);
}
});
}

function test(num, delay) {
return new Promise((resolve, reject) => {
setTimeout(() => {
num == 4 ? reject(num) : resolve(num);
}, delay);
});
}
let p1 = test(1, 1000);
let p2 = test(2, 2000);
let p3 = test(3, 3000);
let p4 = test(4, 4000);
myPromiseAll([p1, p2, p3]).then((result) => {
console.log(result);
});

手写promise.allSettled

Promise.allSettled()方法返回一个promise,该promise在所有给定的promise已被解析或被拒绝后解析,并且每个对象都描述每个promise的结果。

Promise.allSettled() 可用于并行执行独立的异步操作,并收集这些操作的结果

该函数接受一个 promise 数组(通常是一个可迭代对象)作为参数:

const statusesPromise = Promise.allSettled(promises);

当所有的输入 promises 都被 fulfilledrejected 时,statusesPromise 会解析为一个具有它们状态的数组

  1. { status: 'fulfilled', value: value } — 如果对应的 promise 已经 fulfilled
  2. 或者 {status: 'rejected', reason: reason} 如果相应的 promise 已经被 rejected
function PromiseAllSettled(list) {
return new Promise((resolve) => {
let result = []
let count = 0
list.forEach((item, index) => {
item.then((res) => {
result[index] = { status: 'fulfilled', value: res }
}).catch((err) => {
result[index] = { status: 'rejected', reason: err }
}).finally(() => {
count++
count === list.length && resolve(result)
})
})
})
}
const promiseList = [
new Promise((resolve) => setTimeout(() => resolve('成功1'), 2000)),
new Promise((resolve, reject) => setTimeout(() => reject('失败1'), 1000)),
new Promise((resolve) => setTimeout(() => resolve('成功3'), 2600)),
new Promise((resolve) => setTimeout(() => resolve('成功4'), 2300)),
]
PromiseAllSettled(promiseList).then((res) => console.log(res))
Promise.allSettled(promiseList).then((res) => console.log(res))

手写promise.finally

finally() 方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。

这避免了同样的语句需要在then()catch()中各写一次的情况。

简单理解:finally 的特点 无论如何都执行 ,但是如果返回的是一个promise需要等待这个promise之行完在继续向下执行

finally后面还可以.then .then 所以finally本质上就是一个then

Promise.prototype.finally = function (cb) {
return this.then((data) => {
// 如何保证Promise.then能够执行完毕
return Promise.resolve(cb()).then((n) => data);
}, (err) => {
// Promise.resolve 目的是等待cb()后的Promise执行完成
return Promise.resolve(cb()).then((n) => { throw err });
})
}

手写promise.race

Promise.race(iterable) 方法返回一个 promise,一旦迭代器中的某个promise解决或拒绝,返回的 promise就会解决或拒绝。

function PromiseRace(arrayList) {
return new Promise((resolve) => {
for (let i = 0; i < arrayList.length; i++) {
Promise.resolve(arrayList[i]).then((result) => {
resolve(result);
});
}
});
}
function test(num, time) {
return new Promise((resolve, reject) => {
setTimeout(() => {
num == 4 ? reject() : resolve(num);
}, time);
});
}
let p1 = test(1, 2000);
let p2 = test(2, 1000);
let p3 = test(3, 5000);
PromiseRace([p3, p1, p2]).then((res) => {
console.log(res);
});

手写promise.any

全部状态失败才返回失败,其中有成功的状态就返回那个成功的

function promiseAny(list) {
return new Promise((resolve, reject) => {
let count = 0
list.forEach((item, index) => {
item.then((res) => resolve(res)).catch((err) => {
count++
count === list.length && reject('都失败了')
})
})
})
}

const promiseList = [
new Promise((resolve) => setTimeout(() => resolve('成功1'), 3000)),
new Promise((resolve, reject) => setTimeout(() => reject('失败1'), 1000)),
new Promise((resolve) => setTimeout(() => resolve('成功3'), 2600)),
new Promise((resolve) => setTimeout(() => resolve('成功4'), 2300)),
]

promiseAny(promiseList).then((res) => console.log(res)).catch((err) => console.log(err))

Promise.any(promiseList).then((res) => console.log(res)).catch((err) => console.log(err))

实现 Promise.resolve

实现 resolve 静态方法有三个要点:

  • 传参为一个 Promise, 则直接返回它。
  • 传参为一个 thenable 对象,返回的 Promise 会跟随这个对象,采用它的最终状态作为自己的状态。
  • 其他情况,直接返回以该值为成功状态的promise对象。
Promise.resolve = (param) => {
if(param instanceof Promise) return param;
return new Promise((resolve, reject) => {
if(param && param.then && typeof param.then === 'function') {
// param 状态变为成功会调用resolve,将新 Promise 的状态变为成功,反之亦然
param.then(resolve, reject);
}else {
resolve(param);
}
})
}

实现 Promise.reject

Promise.reject 中传入的参数会作为一个 reason 原封不动地往下传, 实现如下:

Promise.reject = function (reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}

9.实现函数柯里化

1.固定参数

理解 arg数组是不断被扩展的 length指的是传入的test函数的参数长度

test实际上就是内部的 temp()

 function curry(fn) {
let length = fn.length;
return function temp() {
let arg = [...arguments];
if (arg.length >= length) {
return fn(...arg);
} else {
return function () {
return temp(...arg, ...arguments);
};
}
};
}
function add(a, b, c) {
return a + b + c;
}
let test = curry(add);
console.log(test(1)(2, 3));

2.不固定参数

通过最后一个不传参来判断执行结束

// 每次调用的传进来的参数做累计处理
function reduce (...args) {
return args.reduce((a, b) => a + b)
}
function currying (fn) {
// 存放每次调用的参数
let args = []
return function temp (...newArgs) {
if (newArgs.length) {
// 有参数就合并进去,然后返回自身
args = [ ...args, ...newArgs ]
return temp
} else {
// 没有参数了,也就是最后一个了,执行累计结果操作并返回结果
let val = fn.apply(this, args)
args = [] //保证再次调用时清空
return val
}
}
}
let add = currying(reduce)
console.log(add(1)(2, 3, 4)(5)) //temp{ }
console.log(add(1)(2, 3, 4)(5)()) //15
console.log(add(1)(2, 3)(4, 5)()) //15
function curry(fn) {
let arg = [...arguments].slice(1);
let temp = function temp() {
arg = [...arg, ...arguments];
return curry(fn, ...arg);
};
temp.toString = function () {
return fn.apply(null, arg);
};
return temp;
}

function fn() {
return [...arguments].reduce((pre, cur) => {
return pre + cur;
}, 0);
}
let test = curry(fn);
console.log(test())
console.log(test(1)(2, 3)(4)(5); //temp
console.log(test(1)(2, 3)(4)(5).toString());

3.固定参数的add函数

function add(x) {
let sum = x;
let temp = function (y) {
sum += y;
return temp;
};
temp.toString = function () {
return sum;
};
return temp;
}
let a = add(1)(2)(4);
console.log(a.toString());

4.不固定参数的add函数

 function add() {
let arg = [...arguments];
let add = function () {
arg.push(...arguments);
return add;
};
add.toString= function () {
return arg.reduce((pre, cru) => {
return pre + cru;
});
};
return add;
}
console.log(add(1)(2)(3)(4)(5).toString());
add(1);             // 1
add(1)(2); // 3
add(1)(2)(3)// 6
add(1)(2, 3); // 6
add(1, 2)(3); // 6
add(1, 2, 3); // 6
function add() {
let args = [].slice.call(arguments);

let fn = function(){
let fn_args = [].slice.call(arguments)
return add.apply(null,args.concat(fn_args))
}

fn.toString = function(){
return args.reduce((a,b)=>a+b)
}

return fn
}

10.实现new

  1. 创建了一个全新的对象。
  2. 新对象的__proto__属性指向构造函数的prototype属性。
  3. 生成的新对象会绑定到函数调用的this
  4. 通过new创建的每个对象将最终被链接到这个函数的prototype对象上。
  5. 如果函数没有返回对象类型Object(包含Functoin, Array, Date, RegExg, Error),那么new表达式中的函数调用会自动返回这个新的对象
function news(fn, ...arg) {
let obj = {};
obj.__proto__ = fn.prototype;
fn.apply(obj, arg);
return obj;
}

// 使用方法
function Person(name, age) {
this.name = name;
this.age = age;
}
let person1 = news(Person, "张三", 15);
console.log(person1);

let person2 = new Person("李四", 15);
console.log(person2);

ES6中 new.target 是指向构造函数 函数名.target

function ourNew(context) {  
// 1.创建一个空对象
var obj = new Object();
// 2.取出构造函数,假设此构造函数有返回值
var constructor = [].shift.call(arguments);
// 3.继承构造函数的原型
obj._proto_ = constructor.prototype; //Object.create
// 4.为新建的对象调用构造函数,生成内部属性,同时构造函数运行时返回它想返回的值
var Returned = constructor.apply(obj, arguments);
// 5.返回对象
return typeof Returned === 'object' ? Returned : person;
}

模拟 Object.create

Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的 proto

// 模拟 Object.create

function create(proto) {
function F() {}
F.prototype = proto;

return new F();
}

11.实现Instanceof

function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left),
prototype = right.prototype;

while (1) {
if (!proto) return false;
if (proto === prototype) return true;

proto = Object.getPrototypeOf(proto);
}
}
console.log(myInstanceof([1, 3, 4], Array));
console.log(myInstanceof({}, Array));
console.log(myInstanceof(new Date(), Object));
console.log(myInstanceof(2, Array));

12.手写promise

链式调用原理

我门常常用到 new Promise().then().then() ,这就是链式调用,用来解决回调地狱

1、为了达成链式,我们默认在第一个then里返回一个promise。规定了一种方法,就是在then里面返回一个新的promise,称为promise2: promise2 = new Promise((resolve, reject)=>{})

  • 将这个promise2返回的值传递到下一个then中
  • 如果返回一个普通的值,则将普通的值传递给下一个then中

2、当我们在第一个then中 return 了一个参数(参数未知,需判断)。这个return出来的新的promise就是onFulfilled()或onRejected()的值

规定onFulfilled()或onRejected()的值,即第一个then返回的值,叫做x,判断x的函数叫做resolvePromise

then(onFulfilled,onRejected) {
// 声明返回的promise2
let promise2 = new Promise((resolve, reject)=>{
if (this.state === 'fulfilled') {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
};
if (this.state === 'rejected') {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(()=>{
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
})
this.onRejectedCallbacks.push(()=>{
let x = onRejected(this.value);
resolvePromise(promise2, x, resolve, reject);
})
}
});
// 返回promise,完成链式
return promise2;
}

规定了一段代码,让不同的promise代码互相套用,叫做resolvePromise 如果 x === promise2,则是会造成循环引用,自己等待自己完成,则报“循环引用”错误

let p = new Promise(resolve => {
resolve(0);
});
var p2 = p.then(data => {
// 循环引用,自己等待自己完成,一辈子完不成
return p2;
})

1、判断x

  • x 不能是null
  • x 是普通值 直接resolve(x)
  • x 是对象或者函数(包括promise), let then = x.then

2、当x是对象或者函数(默认promise)

  • 声明了then
  • 如果取then报错,则走reject()
  • 如果then是个函数,则用call执行then,第一个参数是this,后面是成功的回调和失败的回调
  • 如果成功的回调还是pormise,就递归继续解析

成功和失败只能调用一个 所以设定一个called来防止多次调用

function resolvePromise(promise2, x, resolve, reject){
// 循环引用报错
if(x === promise2){
// reject报错
return reject(new TypeError('Chaining cycle detected for promise'));
}
// 防止多次调用
let called;
// x不是null 且x是对象或者函数
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
// A+规定,声明then = x的then方法
let then = x.then;
// 如果then是函数,就默认是promise了
if (typeof then === 'function') {
// 就让then执行 第一个参数是this 后面是成功的回调 和 失败的回调
then.call(x, y => {
// 成功和失败只能调用一个
if (called) return;
called = true;
// resolve的结果依旧是promise 那就继续解析
resolvePromise(promise2, y, resolve, reject);
}, err => {
// 成功和失败只能调用一个
if (called) return;
called = true;
reject(err);// 失败了就失败了
})
} else {
resolve(x); // 直接成功即可
}
} catch (e) {
// 也属于失败
if (called) return;
called = true;
// 取then出错了那就不要在继续执行了
reject(e);
}
} else {
resolve(x);
}
}

不完全实现(没有完全实现链式调用)

/*  Promise有三个状态,包括Pending,resolved,rejected */
//状态定义
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";

function MyPromise() {
//保存初始化状态
var self = this;
//初始化状态
this.state = PENDING;
//用于保存resolve或者rejected传入的值
this.value = null;
//用于保存resolve的回调函数
this.resolvedCallbacks = [];
//用于保存reject的回调函数
this.rejectedCallbacks = [];

//状态转变为resolved方法
function resolved(value) {
if (value instanceof MyPromise) {
return value.then(resolved, rejected);
}

//保证代码的执行顺序为本轮事件循环的末尾
setTimeout(() => {
// 只有状态pending才改变
if (self.state === PENDING) {
//改变状态
self.status = RESOLVED;
//设置传入的值
self.value = value;
//执行回调函数
self.resolvedCallbacks.forEach((callback) => {
callback(back);
});
}
}, 0);
}

//状态转变为rejected方法
function rejected(value) {
//保证代码的执行顺序为本轮事件循环的末尾
setTimeout(() => {
// 只有状态pending才改变
if (self.state === PENDING) {
//改变状态
self.status = REJECTED;
//设置传入的值
self.value = value;
//执行回调函数
self.rejectedCallbacks.forEach((callback) => {
callback(back);
});
}
}, 0);
}

//将两个方法传入函数执行
try {
fn(resolve, reject);
} catch (e) {
//遇到错误时,捕获错误,执行reject函数
reject(e);
}

MyPromise.prototype.then = function (onResolved, onRejected) {
//首先判断两个参数是否为函数类型,因为这两个参数是可选参数
onResolved =
typeof onResolved === "function"
? onResolved
: function (value) {
return value;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function (error) {
throw error;
};

//如果是等待状态,则将函数加入对应列表中
if (this.status === PENDING) {
this.resolvedCallbacks.push(onResolved);
this.rejectedCallbacks.push(onRejected);
}
if (this.state === RESOLVED) {
onResolved(this.value);
}
if (this.state === REJECTED) {
onRejected(this.value);
}
};
}

完全规范实现

class Promise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
};
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled,onRejected) {
// onFulfilled如果不是函数,就忽略onFulfilled,直接返回value
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
// onRejected如果不是函数,就忽略onRejected,直接扔出错误
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
// 异步
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
// 异步
setTimeout(() => {
// 如果报错
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
// 异步
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
// 异步
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
// 返回promise,完成链式
return promise2;
}
catch(fn) {
return this.then(null, fn);
}

}
function resolvePromise(promise2, x, resolve, reject) {
if (x === promise2) {
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if (called) return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if (called) return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
if (called) return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}

全部方法实现

class Promise{
constructor(executor){
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onResolvedCallbacks = [];
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onResolvedCallbacks.forEach(fn=>fn());
}
};
let reject = reason => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn=>fn());
}
};
try{
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled,onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err };
let promise2 = new Promise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'rejected') {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
};
if (this.state === 'pending') {
this.onResolvedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onFulfilled(this.value);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
let x = onRejected(this.reason);
resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
}, 0)
});
};
});
return promise2;
}
catch(fn){
return this.then(null,fn);
}
}
function resolvePromise(promise2, x, resolve, reject){
if(x === promise2){
return reject(new TypeError('Chaining cycle detected for promise'));
}
let called;
if (x != null && (typeof x === 'object' || typeof x === 'function')) {
try {
let then = x.then;
if (typeof then === 'function') {
then.call(x, y => {
if(called)return;
called = true;
resolvePromise(promise2, y, resolve, reject);
}, err => {
if(called)return;
called = true;
reject(err);
})
} else {
resolve(x);
}
} catch (e) {
if(called)return;
called = true;
reject(e);
}
} else {
resolve(x);
}
}
//resolve方法
Promise.resolve = function(val){
return new Promise((resolve,reject)=>{
resolve(val)
});
}
//reject方法
Promise.reject = function(val){
return new Promise((resolve,reject)=>{
reject(val)
});
}
//race方法
Promise.race = function(promises){
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(resolve,reject)
};
})
}
//all方法(获取所有的promise,都执行then,把结果放到数组,一起返回)
Promise.all = function(promises){
let arr = [];
let i = 0;
function processData(index,data){
arr[index] = data;
i++;
if(i == promises.length){
resolve(arr);
};
};
return new Promise((resolve,reject)=>{
for(let i=0;i<promises.length;i++){
promises[i].then(data=>{
processData(i,data);
},reject);
};
});
}

通过A+测试

学习文章

// 先定义三个常量表示状态
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';

// 新建 MyPromise 类
class MyPromise {
constructor(executor) {
// executor 是一个执行器,进入会立即执行
// 并传入resolve和reject方法
try {
executor(this.resolve, this.reject)
} catch (error) {
this.reject(error)
}
}

// 储存状态的变量,初始值是 pending
status = PENDING;

// resolve和reject为什么要用箭头函数?
// 如果直接调用的话,普通函数this指向的是window或者undefined
// 用箭头函数就可以让this指向当前实例对象
// 成功之后的值
value = null;
// 失败之后的原因
reason = null;

// 存储成功回调函数
onFulfilledCallbacks = [];
// 存储失败回调函数
onRejectedCallbacks = [];

// 更改成功后的状态
resolve = (value) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态修改为成功
this.status = FULFILLED;
// 保存成功之后的值
this.value = value;
// resolve里面将所有成功的回调拿出来执行
while (this.onFulfilledCallbacks.length) {
// Array.shift() 取出数组第一个元素,然后()调用,shift不是纯函数,取出后,数组将失去该元素,直到数组为空
this.onFulfilledCallbacks.shift()(value)
}
}
}

// 更改失败后的状态
reject = (reason) => {
// 只有状态是等待,才执行状态修改
if (this.status === PENDING) {
// 状态成功为失败
this.status = REJECTED;
// 保存失败后的原因
this.reason = reason;
// resolve里面将所有失败的回调拿出来执行
while (this.onRejectedCallbacks.length) {
this.onRejectedCallbacks.shift()(reason)
}
}
}

then(onFulfilled, onRejected) {
const realOnFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
const realOnRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason };

// 为了链式调用这里直接创建一个 MyPromise,并在后面 return 出去
const promise2 = new MyPromise((resolve, reject) => {

const fulfilledMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 获取成功回调函数的执行结果
const x = realOnFulfilled(this.value);
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}

const rejectedMicrotask = () => {
// 创建一个微任务等待 promise2 完成初始化
queueMicrotask(() => {
try {
// 调用失败回调,并且把原因返回
const x = realOnRejected(this.reason);
// 传入 resolvePromise 集中处理
resolvePromise(promise2, x, resolve, reject);
} catch (error) {
reject(error)
}
})
}
// 判断状态
if (this.status === FULFILLED) {
fulfilledMicrotask()
} else if (this.status === REJECTED) {
rejectedMicrotask()
} else if (this.status === PENDING) {
// 等待
// 因为不知道后面状态的变化情况,所以将成功回调和失败回调存储起来
// 等到执行成功失败函数的时候再传递
this.onFulfilledCallbacks.push(fulfilledMicrotask);
this.onRejectedCallbacks.push(rejectedMicrotask);
}
})

return promise2;
}

catch(onRejected) {
// 只需要进行错误处理
this.then(undefined, onRejected);
}

finally(fn) {
return this.then((value) => {
return MyPromise.resolve(fn()).then(() => {
return value;
});
}, (error) => {
return MyPromise.resolve(fn()).then(() => {
throw error
});
});
}

// resolve 静态方法
static resolve(parameter) {
// 如果传入 MyPromise 就直接返回
if (parameter instanceof MyPromise) {
return parameter;
}

// 转成常规方式
return new MyPromise(resolve => {
resolve(parameter);
});
}

// reject 静态方法
static reject(reason) {
return new MyPromise((resolve, reject) => {
reject(reason);
});
}

static all(promiseList) {
return new MyPromise((resolve, reject) => {
const result = [];
const length = promiseList.length;
let count = 0;

if (length === 0) {
return resolve(result);
}

promiseList.forEach((promise, index) => {
MyPromise.resolve(promise).then((value) => {
count++;
result[index] = value;
if (count === length) {
resolve(result);
}
}, (reason) => {
reject(reason);
});
});
});

}

static allSettled = (promiseList) => {
return new MyPromise((resolve) => {
const length = promiseList.length;
const result = [];
let count = 0;

if (length === 0) {
return resolve(result);
} else {
for (let i = 0; i < length; i++) {
const currentPromise = MyPromise.resolve(promiseList[i]);
currentPromise.then((value) => {
count++;
result[i] = {
status: 'fulfilled',
value: value
}
if (count === length) {
return resolve(result);
}
}, (reason) => {
count++;
result[i] = {
status: 'rejected',
reason: reason
}
if (count === length) {
return resolve(result);
}
});
}
}
});
}

static race(promiseList) {
return new MyPromise((resolve, reject) => {
const length = promiseList.length;

if (length === 0) {
return resolve();
} else {
for (let i = 0; i < length; i++) {
MyPromise.resolve(promiseList[i]).then((value) => {
return resolve(value);
}, (reason) => {
return reject(reason);
});
}
}
});
}
}

function resolvePromise(promise, x, resolve, reject) {
// 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
// 这是为了防止死循环
if (promise === x) {
return reject(new TypeError('The promise and the return value are the same'));
}

if (typeof x === 'object' || typeof x === 'function') {
// 这个坑是跑测试的时候发现的,如果x是null,应该直接resolve
if (x === null) {
return resolve(x);
}

let then;
try {
// 把 x.then 赋值给 then
then = x.then;
} catch (error) {
// 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
return reject(error);
}

// 如果 then 是函数
if (typeof then === 'function') {
let called = false;
// 将 x 作为函数的作用域 this 调用之
// 传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise
// 名字重名了,我直接用匿名函数了
try {
then.call(
x,
// 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
y => {
// 如果 resolvePromise 和 rejectPromise 均被调用,
// 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
// 实现这条需要前面加一个变量called
if (called) return;
called = true;
resolvePromise(promise, y, resolve, reject);
},
// 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
r => {
if (called) return;
called = true;
reject(r);
});
} catch (error) {
// 如果调用 then 方法抛出了异常 e:
// 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
if (called) return;

// 否则以 e 为据因拒绝 promise
reject(error);
}
} else {
// 如果 then 不是函数,以 x 为参数执行 promise
resolve(x);
}
} else {
// 如果 x 不为对象或者函数,以 x 为参数执行 promise
resolve(x);
}
}

MyPromise.deferred = function () {
var result = {};
result.promise = new MyPromise(function (resolve, reject) {
result.resolve = resolve;
result.reject = reject;
});

return result;
}


module.exports = MyPromise

13.手写数组方法

手写reduce

Array.prototype.myReduce=function (cb,initialValue){
const array=this;
let pre=initialValue||array[0]
const startIndex=initialValue?0:1
for(let i=startIndex;i<array.length;i++){
const cur=array[i]
pre=cb(pre,cur,i,array)
}
return pre
}

手写forEach

forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数。

    Array.prototype.myforEach = function (fn, thisValue) {
let index = 0;
let arr = thisValue || this;
if (typeof fn !== 'function') {
throw new TypeError(fn + 'is not a function')
}
while (index < arr.length) {
if (index in arr) {
fn.call (thisValue, arr[index], index, arr)
}
index ++
}
};

let arr = [1, 3, 5, 7]
arr.myforEach((item, i , arr) =>{
console.log('item:' + item + 'i:' + i)
})

手写map

    Array.prototype.mymap = function(fn,thisValue){
let arr = thisValue || this
let index = 0
let newArr = [];
if(typeof fn !== 'function'){
throw new TypeError(fn + 'is not function')
}
while(index<arr.length){
if(index in arr){
let result = fn.call(thisValue, arr[index], index, arr)
newArr[index] = result //返回一个新数组
}
index++
}
return newArr
}

let arr = [1,3,5,7]
let newArr = arr.mymap((item)=>{
return item*3
})
console.log(newArr)

手写flat

Array.prototype.myflat = function (num = 1) {
if (!Number(num) || Number(num) < 0) {
return this;
}
var arr = []
this.forEach((item) => {
if (Array.isArray(item)) {
arr = arr.concat(item.flat(--num))
} else {
arr.push(item)
}
})
return arr
}
const arr = [1, [2, [3, 'a', [4]]]]

console.log(arr.myflat('dsdsadf')); // [1, [2, [3, 'a', [4]]]]
console.log(arr.myflat(-32)); // [1, [2, [3, 'a', [4]]]]
console.log(arr.myflat(0)); // [1, [2, [3, 'a', [4]]]]
console.log(arr.myflat('1')); // [1, 2, [3, 'a', [4]]]
console.log(arr.myflat('2')); // [1, 2, 3, 'a', [4]]
console.log(arr.myflat(3)); // [1, 2, 3, 'a', 4]
console.log(arr.myflat(Infinity)); // [1, 2, 3, 'a', 4]
console.log(arr.myflat('Infinity')); // [1, 2, 3, 'a', 4]

实现 filter 方法

Array.prototype.myFilter=function(callback, context=window){

let len = this.length
newArr = [],
i=0

for(; i < len; i++){
if(callback.apply(context, [this[i], i , this])){
newArr.push(this[i]);
}
}
return newArr;
}

实现 every 方法

Array.prototype.myEvery=function(callback, context = window){
var len=this.length,
flag=true,
i = 0;

for(;i < len; i++){
if(!callback.apply(context,[this[i], i , this])){
flag=false;
break;
}
}
return flag;
}

实现 some 方法

Array.prototype.mySome=function(callback, context = window){
var len = this.length,
flag=false,
i = 0;

for(;i < len; i++){
if(callback.apply(context, [this[i], i , this])){
flag=true;
break;
}
}
return flag;
}

reduce 实现一个 map

Array.prototype.mapByreduce=function(fn,context=null){
let arr=this;
if(typeof fn!=='function'){
throw new TypeError('is not a function');
}
return arr.reduce((pre,cur,index,array)=>{
let res=fn.call(context,cur,index,array);
return [...pre,res]
},[])
}

14.手写promisify

function promisify(fn) {
console.log(fn,"fn"); // 保存的是原始函数(add)
return function (...args) {
console.log(...args,"...args"); // 2 6 保存的是调用时的参数
//返回promise对象
return new Promise(function (resolve, reject) {
// 将callback放到参数末尾,并执行callback函数
args.push(function (err, ...args) {
console.log(...args,"12"); // 2 6 callback,
if (err) {
reject(err);
return;
}
resolve(...args);
});

fn.apply(null, args);
});
}
}

// 示例
let add = (a,b, callback) => {
let result = a+b;
if(typeof result === 'number') {
callback(null,result)
}else {
callback("请输入正确数字")
}
}

const addCall = promisify(add);
addCall(2,6).then((res) => {
console.log(res);
})

手动实现一个promisify函数的意思是:我们把一个异步请求的函数,封装成一个可以具有 then方法的函数,并且在then方法中返回异步方法执行结果的这么一个函数

  1. 具有 then 方法
  2. then 方法里返回异步接口执行结果
// 首先定一个需要进行 promisify 的函数
function asyncFn(a, b, callback) {
// 异步操作,使用 setTimeout 模拟
console.log('异步请求参数', a, b)
setTimeout(function() {
callback('异步请求结果')
}, 3000)
}

// 我们希望调用的方式是
const proxy = promisify(asyncFn)
proxy(11,22).then(res => {
// 此处输出异步函数执行结果
console.log(res)
})

// 定义一个方法, 需要针对异步方法做封装,所以需要一个入参,既需要promisify的原异步方法
function promisify( asyncFn ) {
// 方法内部我们需要调用asyncFn方法,并传递原始参数,所以需要返回一个方法来接收参数
return function(...args) { // 由于需要接收参数,所以参数我们可以写为...args
// 我们需要执行异步操作,并返回一个结果,所以返回一个 promise实例
return new Promise(resolve => {
// asyncFn 需要执行一个回调,所以定义一个回调方法
const callback = function(...args) {
resolve(args)
}
args.push(callback)
asyncFn.apply(null, args)
})
}
}

function promisify(fn) {
return function (...args) {
return new Promise(function (resolve, reject) {
args.push(function (err, ...arg) {
if (err) {
reject(err);
return;
}
resolve(...arg);
});

fn.apply(null, args);
});
}
}

15.用setTimeout实现setInterval

setTimeout() :在指定的毫秒数后调用函数或计算表达式,只执行一次。setInterval() :按照指定的周期(以毫秒计)来调用函数或计算表达式。方法会不停地调用函数,直到 clearInterval() 被调用或窗口被关闭。

使用递归函数,不断地去执行setTimeout从而达到setInterval的效果

setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。

针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。

实现思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果

function mySetInterval(fn, delay){
function interval(){
setTimeout(interval, delay);
fn();
}
setTimeout(interval, delay)
}

限制调用次数

function mySetInterval(fn, delay,count){
function interval(){
if(typeof count==='undefined'||count-->0){
setTimeout(interval, delay);
try{
fn()
}catch(e){
count = 0;
throw e.toString();
}
}
}
setTimeout(interval, delay)
}

16.promise串行执行

简单例子

const p1 = () => {
return new Promise((resolve) => {
setTimeout(() => {
console.log('p1');
resolve();
}, 2000)
})
}
const p2 = () => {
return new Promise((resolve) => {
setTimeout(() => {
console.log('p2');
resolve();
}, 2000)
})
}

p1().then(() => {
return p2();
}).then(() => {
console.log('end')
})

利用reduce函数既能遍历数组,又能在前后元素之间建立联系的特点

const funcArr = [
() =>
new Promise((resolve) => {
setTimeout(() => resolve(1), 2000);
}),
() =>
new Promise((resolve) => {
setTimeout(() => resolve(2), 1000);
}),
() =>
new Promise((resolve) => {
setTimeout(() => resolve(3), 3000);
}),
];
/**
* @description: 实现Promise的串行
* @param {*}: 接收一个包含多个返回Promise对象的函数的数组
* @return {*}: 返回一个Promise对象
*/
// 函数最终返回一个promise
function runPromiseByQueue(promiseFuncArr) {
const res = [];
return new Promise((resolve, reject) => {
promiseFuncArr
.reduce( //数组每一项都返回了一个promise data相当于res
(acc,cur) => acc.then(cur).then((data) => res.push(data)),
Promise.resolve()
)
// reduce函数最终返回一个promise,在onResolved这一步骤执行resolve,将结果输出
.then(() => resolve(res));
});
}

runPromiseByQueue(funcArr).then(data => console.log(data)) // 6s后输出[1,2,3]
var urls = ['url1','url2','url3','url4'];
const getResponse = (url)=>{
return new Promise((resolve,reject)=>{
console.log('参数为:',url)
setTimeout(()=>{
console.log('异步请求后结果为','afeter'+url);
resolve("success")
},1000)
})
}

function serial(tasks){
var result = [];
return tasks.reduce((accumulator,item,index)=>{
return accumulator.then(res=>{
return getResponse(item).then(res=>{
result[index] = res
return index == tasks.length - 1 ? result : item
})
})
},Promise.resolve())
}
// 实现 `chainPromise` 函数
// 请在不使用 `async` / `await` 语法的前提下完成
// 完成promise的串行执行

function getPromise(time) {
return new Promise((resolve, reject) => {
setTimeout(Math.random() > 0.5 ? resolve : reject, time, time);
});
}

function chainPromise(arr) {
let res = [];
return new Promise((resolve, reject) => {
arr
.reduce((pre, cur) => {
return getPromise(pre)
.then((result) => {
res.push(result);
return getPromise(cur);
})
.catch((err) => {
res.push(err);
return getPromise(cur);
});
})
.then((result) => {
res.push(result);
})
.catch((err) => {
res.push(err);
})
.finally(() => {
resolve(res);
});
});
}

let time = [2000, 4000, 3000, 1000];
let res = chainPromise(time);
//等待10s后输出结果
res.then(console.log);

测试例子
const Task = (result, isSuccess = true) => {
return () => new Promise((resolve, reject) => {
setTimeout(() => {
if (isSuccess) {
console.log(`success: ${result}`);
resolve(result);
} else {
console.log(`error: ${result}`);
reject(result);
}
}, 1000);
});
}


execute([
Task('A'),
Task('B'),
Task('X', false),
Task('C'),
]).then(resultList => {
// 这里期望打印 ["A", "B", null, "C"]
console.log(resultList)
})

先做一个Promise实例,然后把每个Task循环的放置到上一个promisethen回调里。

无论每个Task是成功还是失败,它都不能阻断下一个Task的执行 2. 最后的then需要把每个Task的执行结果"决议"出去

  1. 每一个Task外层包装一层Promise,捕获Task的reject状态
  2. 可以利用一个中间变量,缓存所有Task的输出结果,然后在最后一个Promise的then里把中间变量“决议”出去

img

function execute(tasks) {
let resultList = [];
return tasks.reduce(
(previousPromise, currentPromise) => previousPromise.then((resultList) => {
return new Promise(resolve => {
currentPromise().then(result => {
resultList.push(result);
resolve()
}).catch(() => {
resultList.push(null);
resolve(resultList.concat(null))
})
})
}),
Promise.resolve()
).then(() => resultList);
}

其实Promise的链式操作是可以传递值的,所以可以利用这个特性,省去中间变量,

img

代码如下:

function execute(tasks) {
return tasks.reduce(
(previousPromise, currentPromise) => previousPromise.then((resultList) => {
return new Promise(resolve => {
currentPromise().then(result => {
resolve(resultList.concat(result))
}).catch(() => {
resolve(resultList.concat(null))
})
})
}),
Promise.resolve([])
)
}

aycns/await要与迭代器进行配合,所以应该利用for of来配合使用。

代码如下:

const execute = async (tasks = []) => {
const resultList = [];
for(task of tasks) {
try {
resultList.push(await task());
} catch (e) {
resultList.push(null);
}
}
return resultList;
}

Promise串行是在上一个promise的then回调里去执行,再将本步骤的结果决议出去,所以很容易想到用Array.prototype.reduce()方法进行处理。

17.基于Promise的fetch

const log = console.log;
function maxRequest(url = ``, times = 3) {
// 1. 闭包,保存私有属性
function autoRetry (url, times) {
console.log('times = ', times);
times--;
// 2. fetch 本身返回值就是 Promise,不需要再次使用 Promise 包裹
return fetch(url).then(value => {
if(value.status === 200) {
console.log(`OK`, value);
// 3. 手动返回 Promise 的 value, 没有返回值 默认返回 undefined
return value;
} else {
throw new Error(` http code error: ${value.status }`);
}
}).catch((err) => {
console.log(`Error`, err);
if (times < 1) {
// 4. 方便后续的 thenable 处理 error
throw new Error('over max request times!');
} else {
// 5. 返回递归方法
return autoRetry(url, times);
}
});
}
// 6. 返回一个 Promise 的结果 (成功 Promise 或失败 Promise)
return autoRetry(url, times);
}

// error test case
maxRequest(`https://cdn.xgqfrms.xyz/json/badges.js`)
.then(res => res.json())
.then(json=> console.log('json =', json))
.catch(err => console.error(`err =`, err))
.finally(() => {
console.log('whatever close loading...');
});

// sucess test case
maxRequest(`https://cdn.xgqfrms.xyz/json/badges.json`)
.then(res => res.json())
.then(json=> console.log('json =', json))
.catch(err => console.error(`err =`, err))
.finally(() => {
console.log(' whatever close loading...');
});

18.Promise reject的时候自动retry

function fetchWithAutoRetry(fetcher, maximumRetryCount) {
return new Promise((resolve,reject)=> {
let count = 0;
const retry = () => {
fetcher().then((data) => {
resolve(data);
}).catch((error) => {
if(count<maximumRetryCount) {
count++;
retry();
} else {
reject(error);
}
})
}
retry();
});
}
let arr = [ 
   new Promise(res=>{
       setTimeout(()=>{
           res(1)
      },1000)

  }),
   new Promise(res=>{
       setTimeout(()=>{
           res(2)
      },1000)

  }),
   new Promise(res=>{
       setTimeout(()=>{
           res(3)
      },1000)

  })]

function iteratorPromise(arr){
arr.reduce((prevPromise, currPromise) => {
return prevPromise.then(num => {
console.log(num)
return currPromise
})
}).then(num => console.log(num))
}

iteratorPromise(arr);

19.实现async await

generator函数是不会自动执行的,每一次调用它的next方法,会停留在下一个yield的位置。

利用这个特性,我们只要编写一个自动执行的函数,就可以让这个generator函数完全实现async函数的功能

asyncToGenerator接受一个generator函数,返回一个promise

下一次调用next的时候,传的参数会被作为上一个yield前面接受的值

简单示例

const getData = () => new Promise(resolve => setTimeout(() => resolve("data"), 1000))
function* testG() {
// await被编译成了yield
const data = yield getData()
console.log('data: ', data);
const data2 = yield getData()
console.log('data2: ', data2);
return 'success'
}

var gen = testG()

var dataPromise = gen.next()

dataPromise.then((value1) => {
// data1的value被拿到了 继续调用next并且传递给data
var data2Promise = gen.next(value1)

// console.log('data: ', data);
// 此时就会打印出data

data2Promise.value.then((value2) => {
// data2的value拿到了 继续调用next并且传递value2
gen.next(value2)

// console.log('data2: ', data2);
// 此时就会打印出data2
})
})

完整实现

function asyncToGenerator(generatorFunc) {
// 返回的是一个新的函数
return function() {

// 先调用generator函数 生成迭代器
// 对应 var gen = testG()
const gen = generatorFunc.apply(this, arguments)

// 返回一个promise 因为外部是用.then的方式 或者await的方式去使用这个函数的返回值的
// var test = asyncToGenerator(testG)
// test().then(res => console.log(res))
return new Promise((resolve, reject) => {

// 内部定义一个step函数 用来一步一步的跨过yield的阻碍
// key有next和throw两种取值,分别对应了gen的next和throw方法
// arg参数则是用来把promise resolve出来的值交给下一个yield
function step(key, arg) {
let generatorResult

// 这个方法需要包裹在try catch中
// 如果报错了 就把promise给reject掉 外部通过.catch可以获取到错误
try {
generatorResult = gen[key](arg)
} catch (error) {
return reject(error)
}

// gen.next() 得到的结果是一个 { value, done } 的结构
const { value, done } = generatorResult

if (done) {
// 如果已经完成了 就直接resolve这个promise
// 这个done是在最后一次调用next后才会为true
// 以本文的例子来说 此时的结果是 { done: true, value: 'success' }
// 这个value也就是generator函数最后的返回值
return resolve(value)
} else {
// 除了最后结束的时候外,每次调用gen.next()
// 其实是返回 { value: Promise, done: false } 的结构,
// 这里要注意的是Promise.resolve可以接受一个promise为参数
// 并且这个promise参数被resolve的时候,这个then才会被调用
return Promise.resolve(
// 这个value对应的是yield后面的promise
value
).then(
// value这个promise被resove的时候,就会执行next
// 并且只要done不是true的时候 就会递归的往下解开promise
// 对应gen.next().value.then(value => {
// gen.next(value).value.then(value2 => {
// gen.next()
//
// // 此时done为true了 整个promise被resolve了
// // 最外部的test().then(res => console.log(res))的then就开始执行了
// })
// })
function onResolve(val) {
step("next", val)
},
// 如果promise被reject了 就再次进入step函数
// 不同的是,这次的try catch中调用的是gen.throw(err)
// 那么自然就被catch到 然后把promise给reject掉啦
function onReject(err) {
step("throw", err)
},
)
}
}
step("next")
})
}
}

20.promise请求超时终止

请求五秒未完成则终止 提供两个模拟的 api api = ()=> {}; warnning = ()=> {}; 实现:

function timing() {
return new Promise((resolve,reject)=>{
setTimeout(()=>{
reject();
},5000)
})
}
function apiTiming() {
const arr = [api(),timing()];
Promise.race(arr).then(res=>{
console.log(res);
}).catch(e=>{
warnning(e);
})
}

21.限制promise并发请求(使用队列)

function multiRequest(tasks = [], maxNum) {
let len = tasks.length;
let index = 0;
const result = [];
let allsettled = 0;
return new Promise((resolve) => {
function run() {
let cur = index++;// 当前是第几个,因为一直都在变,所有保存下
tasks[cur]
.then((res) => {
result[cur] = res;
})
.catch((err) => {
result[cur] = err;
})
.finally(() => {
allsettled++;
if (allsettled === len) {
console.log(result);
resolve(result);
}
if(index < len){
run()
}
});
}
while (index < maxNum) {
run();
}
});
}
const tasks = [
new Promise((resolve) => {
setTimeout(() => {
resolve(1);
}, 200);
}),
new Promise((resolve) => {
setTimeout(() => {
resolve(2);
}, 100);
}),
new Promise((resolve) => {
setTimeout(() => {
resolve(3);
}, 300);
}),
new Promise((resolve) => {
setTimeout(() => {
resolve(4);
}, 1000);
}),
];
multiRequest(tasks, 3);

队列实现

function limitQueue(urls,limit){
let i=0;
//执行队列
for(let i=0;i<limit;i++){
run()
}
function run (){
new Promise((resolve,reject)=>{
const url=urls[i]
i++;
request(url).then(()=>{
resolve()
})
.catch((e)=>{
reject(e)
})
}).then(()=>{
if(i<urls.length) run()
})
}
}

所谓请求,就是指在一个时间点多个请求同时执行。当并发的请求超过一定数量时,会造成网络堵塞,服务器压力大崩溃或者其他高并发问题,此时需要限制并发请求的数量

等待请求接口1000个,限制每次只能发出100个。即同一时刻最多有100个正在发送的请求。每当100个之中有一个请求完成时,则从待请求的接口(即剩余的900个待请求接口)中再取出一个发出。保证当前并发度仍旧为100。直至全部接口请求完成

模拟生成请求

    // api接口请求列表
const apiList = [
'url___A',
'url___B',
'url___C',
'url___D',
'url___E',
'url___F',
'url___G',
]

// 记录当前正在请求的接口,调试代码用
let currentRequestList = []

// 模拟请求数据
const request = api => {
console.log(`${api} 请求start`)
// 请求时间 0 ~ 3 秒
const wait = Math.random() * 3000
console.log(`${api} 请求时间 ${wait}`)
// currentRequestList 收集正在请求的接口
currentRequestList.push(api)
// 满足限制条件时,控制台显示当前正在请求的接口
if (currentRequestList.length === 2) {
console.log('当前正在请求的接口 -> ', currentRequestList.toString())
}
return new Promise(resolve => {
setTimeout(() => {
console.log(`${api} 请求end`)
// 请求完成时,currentRequestList 删除该请求
const index = currentRequestList.findIndex(c => c === api)
currentRequestList.splice(index, 1)
resolve(`获取接口“${api}”的数据`)
}, wait)
})
}

并发请求且限制请求数量

    /**
* @descriptio 并发请求且限制请求数量
* @param {apiList} 请求接口列表
* @param {limit} 限制请求接口的数量,默认每次最多发送3次请求
* @param {callback} 回调函数
*/
const requestWithLimit = (apiList, limit = 3, callback) => {

// 请求数量记录,默认为 0
let count = 0

// 递归调用,请求接口数据
const run = () => {
// 接口每调用一次,记录数加 1
count++
const api = apiList.shift()
request(api).then(res => {
// 接口调用完成,记录数减 1
count--
console.log(res)
// apiList 长度不为 0 且记录小于限制的数量时递归调用
if (apiList.length && count < limit) {
run()
}
// apiList 为空且记录数减为初始值 0 时调用回调函数
if (!apiList.length && !count) {
// 这里可以对所有接口返回的数据做处理,以便输出
callback('全部执行完毕!')
}
})
}

// 根据 limit 并发调用
for (let i = 0; i < limit; i++) {
run()
}
}
requestWithLimit(apiList, 2, res => {
console.log('回调函数', res)
})

使用Promise实现:限制异步操作的并发个数,并尽可能快的完成全部

有8个图片资源的url,已经存储在数组urls中。

urls类似于['https://image1.png', 'https://image2.png', ....]

而且已经有一个函数function loadImg,输入一个url链接,返回一个Promise,该Promise在图片下载完成的时候resolve,下载失败则reject。

但有一个要求,任何时刻同时下载的链接数量不可以超过3个。

请写一段代码实现这个需求,要求尽可能快速地将所有图片下载完成

var urls = [
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting1.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting2.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting3.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting4.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/AboutMe-painting5.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn6.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn7.png",
"https://hexo-blog-1256114407.cos.ap-shenzhen-fsi.myqcloud.com/bpmn8.png",
];
function loadImg(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = function() {
console.log("一张图片加载完成");
resolve(img);
};
img.onerror = function() {
reject(new Error('Could not load image at' + url));
};
img.src = url;
});

既然题目的要求是保证每次并发请求的数量为3,那么我们可以先请求urls中的前面三个(下标为0,1,2),并且请求的时候使用Promise.race()来同时请求,三个中有一个先完成了,我们就把这个当前数组中已经完成的那一项(第1项)换成还没有请求的那一项(urls中下标为3)。

直到urls已经遍历完了,然后将最后三个没有完成的请求(也就是状态没有改变的Promise)用Promise.all()来加载它们

function limitLoad(urls, handler, limit) {
let sequence = [].concat(urls); // 复制urls
// 这一步是为了初始化 promises 这个"容器"
let promises = sequence.splice(0, limit).map((url, index) => {
return handler(url).then(() => {
// 返回下标是为了知道数组中是哪一项最先完成
return index;
});
});
// 注意这里要将整个变量过程返回,这样得到的就是一个Promise,可以在外面链式调用
return sequence
.reduce((pCollect, url) => {
return pCollect
.then(() => {
return Promise.race(promises); // 返回已经完成的下标
})
.then(fastestIndex => { // 获取到已经完成的下标
// 将"容器"内已经完成的那一项替换
promises[fastestIndex] = handler(url).then(
() => {
return fastestIndex; // 要继续将这个下标返回,以便下一次变量
}
);
})
.catch(err => {
console.error(err);
});
}, Promise.resolve()) // 初始化传入
.then(() => { // 最后三个用.all来调用
return Promise.all(promises);
});
}
limitLoad(urls, loadImg, 3)
.then(res => {
console.log("图片全部加载完毕");
console.log(res);
})
.catch(err => {
console.error(err);
});

能够实现 通过输入限制并发的数量,对Promise 请求队列 进行控制

const createMockHttpRequest = (mockDelayList) => {
let mockDelay = mockDelayList.map((delay, index) => ({id: index, delay}))
return mockDelay.map((delayInfo) => () => new Promise(resolve => {
console.log('run: ', delayInfo.id, delayInfo.delay)
setTimeout(() => {
console.log('resolved: ------->', delayInfo.id, delayInfo.delay)
resolve(delayInfo.id);
}, delayInfo.delay)
}))
}

const httpRequestList = createMockHttpRequest([200, 20, 100, 300, 600, 30])
createLimitPromise(3, httpRequestList);

思路:

  1. 通过闭包维护 结果数组正在处理的数量已经处理完的数量 当然也可以用class 实现
  2. 封装run 方法,run方法为核心代码
    1. 能够判断正在执行的promise 数量
    2. 当promise 全部处理结束,触发resolve方法,输出resArr
  3. 并发 通过for循环实现,执行run 方法
//简化的type : (limitNum,promiseList)=>Promise
function createLimitPromise(limitNum, promiseListRaw) {
let resArr = [];
let handling = 0;
let resolvedNum = 0;
let promiseList = [...promiseListRaw]
let runTime = promiseListRaw.length

return new Promise(resolve => {
//并发执行limitNum 次
for (let i = 1; i <= limitNum; i++) {
run();
}

function run() {
if(!promiseList.length) return
handling += 1;
handle(promiseList.shift())
.then(res => {
resArr.push(res);
})
.catch(e => {
//ignore
console.log("catch error");
})
.finally(() => {
handling -= 1;
resolvedNum += 1;
if(resolvedNum === runTime){
resolve(resArr)
}
run();
});
}
function handle(requestFn) {
return new Promise((resolve, reject) => {
requestFn().then(res => resolve(res)).catch(e => reject(e));
});
}
});
}

22.封装localstorage

class Storage {
constructor (time) {
this.time = maxAge;
}
set(key, value, maxAge) {
let obj = {
data: value,
cTime: Date.now(),
maxAge: maxAge || this.maxAge
};
localStorage.setItem(key, JSON.stringify(obj));
}
get(key) {
let item = localStorage.getItem(key);
if (!item) {
return null;
}
item = JSON.parse(item);
let nowTime = Date.now();
if (item.maxAge && item.maxAge < (nowTime - item.cTime)) {
this.remove(key);
return null;
} else {
return item.data;
}
}
remove(key) {
localStorage.removeItem(key);
}
clear(){
localStorage.clear();
}
}

23.实现通用类型判断

简单实现

function getType(obj){
return typeof obj==='object'
? Object.prototype.toString.call(obj).slice(8,-1)
: typeof obj
}
console.log(getType(null)) //Null
function getType(value) {
// 判断数据是 null 的情况
if (value === null) {
return value + "";
}

// 判断数据是引用类型的情况
if (typeof value === "object") {
let valueClass = Object.prototype.toString.call(value),
type = valueClass.split(" ")[1].split("");

type.pop();

return type.join("").toLowerCase();
} else {
// 判断数据是基本数据类型的情况和函数的情况
return typeof value;
}
}

自定义的类实例化的对象返回定义的类名

function myTypeof(data) {
var toString = Object.prototype.toString;
var dataType = data instanceof Element ? "Element" : toString.call(data).replace(/\[object\s(.+)\]/, "$1")

if(dataType === 'Object'){
return data.constructor.name
}
return dataType
};

24.实现lodash中的get方法

使用 lodash 中的 get 函数可避免长链的 key 时获取不到属性而出现问题,此时进行异常避免时及其服务,如 o.a && o.a.b && o.a.b.c && o.a.b.c.d

实现类似 lodash.get ,有以下测试用例:

var object = { 'a': [{ 'b': { 'c': 3 } }] };

get(object, 'a[0].b.c');
// => 3

get(object, ['a', '0', 'b', 'c']);
// => 3

get(object, 'a.b.c', 'default');
// => 'default'

参考答案

/**
* object: 对象
* path: 输入的路径
* defaultVal: 默认值
**/

function get(object, path, defaultVal='undefined') {
// 先将path处理成统一格式
let newPath = [];
if (Array.isArray(path)) {
newPath = path;
} else {
// 先将字符串中的'['、']'去除替换为'.',split分割成数组形式
newPath = path.replace(/\[/g,'.').replace(/\]/g,'').split('.');
}

// 递归处理,返回最后结果
return newPath.reduce((o, k) => {
console.log(o, k); // 此处o初始值为下边传入的 object,后续值为每次取的内部值
return (o || {})[k]
}, object) || defaultVal;
}

25.实现mergePromise函数(串行)

实现mergePromise函数,把传进去的数组按顺序先后执行,并且把返回的数据先后放到数组data中。实际上就是简化的promise串行执行

const time = (timer) => {
return new Promise(resolve => {
setTimeout(() => {
resolve()
}, timer)
})
}
const ajax1 = () => time(2000).then(() => {
console.log(1);
return 1
})
const ajax2 = () => time(1000).then(() => {
console.log(2);
return 2
})
const ajax3 = () => time(1000).then(() => {
console.log(3);
return 3
})

function mergePromise () {
// 在这里写代码
}

mergePromise([ajax1, ajax2, ajax3]).then(data => {
console.log("done");
console.log(data); // data 为 [1, 2, 3]
});

// 要求分别输出
// 1
// 2
// 3
// done
// [1, 2, 3]

这道题有点类似于Promise.all(),不过.all()不需要管执行顺序,只需要并发执行就行了。但是这里需要等上一个执行完毕之后才能执行下一个。

解题思路:

  • 定义一个数组data用于保存所有异步操作的结果
  • 初始化一个const promise = Promise.resolve(),然后循环遍历数组,在promise后面添加执行ajax任务,同时要将添加的结果重新赋值到promise上。
   function mergePromise (ajaxArray) {
2 // 存放每个ajax的结果
3 const data = [];
4 let promise = Promise.resolve();
5 ajaxArray.forEach(ajax => {
6 // 第一次的then为了用来调用ajax
7 // 第二次的then是为了获取ajax的结果
8 promise = promise.then(ajax).then(res => {
9 data.push(res);
10 return data; // 把每次的结果返回
11 })
12 })
13 // 最后得到的promise它的值就是data
14 return promise;
15 }

26.实现promise.map

假设有一个 Promise 为 get 和一个待请求数组为 list,使用它们进行请求数据。但是为了避免 IO 过大,需要限定三个并发数量

function get (i) {
console.log('In ', i)
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(i * 1000)
console.log('Out', i, 'Out')
}, i * 1000)
})
}

const list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

写一段能够实现功能的松散的代码是很简单的,不过对提供 API 的设计思路也是相当重要的。简单实现如下,使用 count 维护一个并发数量的计数器即可

// 并发数量计数
let count = 0
function run () {
if (count < 3 && list.length) {
count+=1
get(list.shift()).then(() => {
count-=1
run()
})
}
}

// 限定三个并发数量
run()
run()
run()
class Limit {
constructor (n) {
this.limit = n
this.count = 0
this.queue = []
}

enqueue (fn) {
// 关键代码: fn, resolve, reject 统一管理
return new Promise((resolve, reject) => {
this.queue.push({ fn, resolve, reject })
})
}

dequeue () {
if (this.count < this.limit && this.queue.length) {
// 等到 Promise 计数器小于阈值时,则出队执行
const { fn, resolve, reject } = this.queue.shift()
this.run(fn).then(resolve).catch(reject)
}
}

// async/await 简化错误处理
async run (fn) {
this.count++
// 维护一个计数器
const value = await fn()
this.count--
// 执行完,看看队列有东西没
this.dequeue()
return value
}

build (fn) {
if (this.count < this.limit) {
// 如果没有到达阈值,直接执行
return this.run(fn)
} else {
// 如果超出阈值,则先扔到队列中,等待有空闲时执行
return this.enqueue(fn)
}
}
}

Promise.map = function (list, fn, { concurrency }) {
const limit = new Limit(concurrency)
return Promise.all(list.map((...args) => {
return limit.build(() => fn(...args))
}))
}

27.实现一个迭代器生成函数

JS 原生的集合类型数据结构,只有Array(数组)和Object(对象);而ES6中,又新增了MapSet。四种数据结构各自有着自己特别的内部实现,但我们仍期待以同样的一套规则去遍历它们,所以ES6在推出新数据结构的同时也推出了一套统一的接口机制——迭代器(Iterator)。

ES6约定,任何数据结构只要具备Symbol.iterator属性(这个属性就是Iterator的具体实现,它本质上是当前数据结构默认的迭代器生成函数),就可以被遍历——准确地说,是被for...of...循环和迭代器的 next 方法遍历。 事实上,for...of...的背后正是对next方法的反复调用。

在 ES6 中,针对ArrayMapSetStringTypedArray、函数的 arguments 对象、NodeList 对象这些原生的数据结构都可以通过for...of...进行遍历。原理都是一样的,此处我们拿最简单的数组进行举例,当我们用for...of...遍历数组时:

const arr = [1, 2, 3]
const len = arr.length
for(item of arr) {
console.log(`当前元素是${item}`)
}

之所以能够按顺序一次一次地拿到数组里的每一个成员,是因为我们借助数组的Symbol.iterator生成了它对应的迭代器对象,通过反复调用迭代器对象的next方法访问了数组成员,像这样:

const arr = [1, 2, 3]
// 通过调用iterator,拿到迭代器对象
const iterator = arr[Symbol.iterator]()

// 对迭代器对象执行next,就能逐个访问集合的成员
iterator.next()
iterator.next()
iterator.next()

for...of...做的事情,基本等价于下面这通操作:

// 通过调用iterator,拿到迭代器对象
const iterator = arr[Symbol.iterator]()

// 初始化一个迭代结果
let now = { done: false }

// 循环往外迭代成员
while(!now.done) {
now = iterator.next()
if(!now.done) {
console.log(`现在遍历到了${now.value}`)
}
}

实现迭代器生成函数

我们说迭代器对象全凭迭代器生成函数帮我们生成。在ES6中,实现一个迭代器生成函数并不是什么难事儿,因为 ES6 早帮我们考虑好了全套的解决方案,内置了贴心的生成器Generator)供我们使用:

// 编写一个迭代器生成函数
function *iteratorGenerator() {
yield '1号选手'
yield '2号选手'
yield '3号选手'
}

const iterator = iteratorGenerator()

iterator.next()
iterator.next()
iterator.next()

ES5写一个能够生成迭代器对象的迭代器生成函数

// 定义生成器函数,入参是任意集合
function iteratorGenerator(list) {
// idx记录当前访问的索引
var idx = 0
// len记录传入集合的长度
var len = list.length
return {
// 自定义next方法
next: function() {
// 如果索引还没有超出集合长度,done为false
var done = idx >= len
// 如果done为false,则可以继续取值
var value = !done ? list[idx++] : undefined

// 将当前值与遍历是否完毕(done)返回
return {
done: done,
value: value
}
}
}
}

var iterator = iteratorGenerator(['1号选手', '2号选手', '3号选手'])
iterator.next()
iterator.next()
iterator.next()

28.实现 ES6 的 extends

function B(name){
this.name = name;
};
function A(name,age){
//1.将A的原型指向B
Object.setPrototypeOf(A,B);
//2.用A的实例作为this调用B,得到继承B之后的实例,这一步相当于调用super
Object.getPrototypeOf(A).call(this, name)
//3.将A原有的属性添加到新实例上
this.age = age;
//4.返回新实例对象
return this;
};
var a = new A('poetry',22);
console.log(a);
function Parent(name) {
this.parent = name
}
Parent.prototype.say = function() {
console.log(`${this.parent}: 你打篮球的样子像kunkun`)
}
function Child(name, parent) {
// 将父类的构造函数绑定在子类上
Parent.call(this, parent)
this.child = name
}

/**
1. 这一步不用Child.prototype =Parent.prototype的原因是怕共享内存,修改父类原型对象就会影响子类
2. 不用Child.prototype = new Parent()的原因是会调用2次父类的构造方法(另一次是call),会存在一份多余的父类实例属性
3. Object.create是创建了父类原型的副本,与父类原型完全隔离
*/
Child.prototype = Object.create(Parent.prototype);
Child.prototype.say = function() {
console.log(`${this.parent}好,我是练习时长两年半的${this.child}`);
}

// 注意记得把子类的构造指向子类本身
Child.prototype.constructor = Child;

var parent = new Parent('father');
parent.say() // father: 你打篮球的样子像kunkun

var child = new Child('cxk', 'father');
child.say() // father好,我是练习时长两年半的cxk

29.实现JSON方法

实现一个 JSON.stringify

JSON.stringify(value[, replacer [, space]])
  • Boolean | Number| String类型会自动转换成对应的原始值。
  • undefined、任意函数以及symbol,会被忽略(出现在非数组对象的属性值中时),或者被转换成 null(出现在数组中时)。
  • 不可枚举的属性会被忽略如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
  • 如果一个对象的属性值通过某种间接的方式指回该对象本身,即循环引用,属性也会被忽略
function jsonStringify(obj) {
let type = typeof obj;
if (type !== "object") {
if (/string|undefined|function/.test(type)) {
obj = '"' + obj + '"';
}
return String(obj);
} else {
let json = []
let arr = Array.isArray(obj)
for (let k in obj) {
let v = obj[k];
let type = typeof v;
if (/string|undefined|function/.test(type)) {
v = '"' + v + '"';
} else if (type === "object") {
v = jsonStringify(v);
}
json.push((arr ? "" : '"' + k + '":') + String(v));
}
return (arr ? "[" : "{") + String(json) + (arr ? "]" : "}")
}
}
jsonStringify({x : 5}) // "{"x":5}"
jsonStringify([1, "false", false]) // "[1,"false",false]"
jsonStringify({b: undefined}) // "{"b":"undefined"}"

实现一个 JSON.parse

JSON.parse(text[, reviver])

用来解析 JSON 字符串,构造由字符串描述的 JavaScript 值或对象。提供可选的 reviver 函数用以在返回之前对所得到的对象执行变换(操作)

第一种:直接调用 eval

function jsonParse(opt) {
return eval('(' + opt + ')');
}
jsonParse(jsonStringify({x : 5}))
// Object { x: 5}
jsonParse(jsonStringify([1, "false", false]))
// [1, "false", falsr]
jsonParse(jsonStringify({b: undefined}))
// Object { b: "undefined"}

避免在不必要的情况下使用 evaleval() 是一个危险的函数,他执行的代码拥有着执行者的权利。如果你用eval()运行的字符串代码被恶意方(不怀好意的人)操控修改,您最终可能会在您的网页/扩展程序的权限下,在用户计算机上运行恶意代码。它会执行 JS 代码,有 XSS 漏洞。

如果你只想记这个方法,就得对参数 json 做校验。

var rx_one = /^[\],:{}\s]*$/;
var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g;
var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g;
var rx_four = /(?:^|:|,)(?:\s*\[)+/g;
if (
rx_one.test(
json
.replace(rx_two, "@")
.replace(rx_three, "]")
.replace(rx_four, "")
)
) {
var obj = eval("(" +json + ")");
}

第二种:Function

核心:Function 与 eval 有相同的字符串参数特性

var func = new Function(arg1, arg2, ..., functionBody);

在转换 JSON 的实际应用中,只需要这么做

var jsonStr = '{ "age": 20, "name": "jack" }'
var json = (new Function('return ' + jsonStr))();

evalFunction都有着动态编译 js 代码的作用,但是在实际的编程中并不推荐使用

数据处理题

实现日期格式化函数

dateFormat(new Date('2020-12-01'), 'yyyy/MM/dd') // 2020/12/01
dateFormat(new Date('2020-04-01'), 'yyyy/MM/dd') // 2020/04/01
dateFormat(new Date('2020-04-01'), 'yyyy年MM月dd日') // 2020年04月01日

const dateFormat = (dateInput, format)=>{
var day = dateInput.getDate()
var month = dateInput.getMonth() + 1
var year = dateInput.getFullYear()
format = format.replace(/yyyy/, year)
format = format.replace(/MM/,month)
format = format.replace(/dd/,day)
return format
}

求两个日期中间的年月日

//获取两日期之间日期列表函数
function getdifflist(start_time,end_time,type){ // type 为 days(天) , months(月), years(年)
var dateArray = []; // dateArray 起止日期中间的所有日期列表
var currentDate = moment(start_time); //起止日期
var stopDate = moment(etime); //截止日期
//开始日期小于等于结束日期,并循环
while(currentDate <= stopDate){
if (type==='days') dateArray.push( moment(currentDate).format('YYYY-MM-DD') ); //两个日期间的所有日期,图一
if (type==='months') dateArray.push( moment(currentDate).format('YYYY-MM') ); //两个月份间的所有月份,图二
if (type==='years') dateArray.push( moment(currentDate).format('YYYY') ); //两个年份间的所有年份,图三
currentDate = moment(currentDate).add(1, type); //根据类型+1
}
return dateArray;
}
//初始化日期列表,数组
var allDate = new Array();
var i = 0;
//开始日期小于等于结束日期,并循环
while (startTime <= endTime) {
allDate[i] = startTime;
//获取开始日期时间戳
var startTime_ts = new Date(startTime).getTime();
//增加一天时间戳后的日期
var next_date = startTime_ts + 24 * 60 * 60 * 1000;
//拼接年月日,这里的月份会返回(0-11),所以要+1
var next_dates_y = new Date(next_date).getFullYear() + "-";
var next_dates_m =
new Date(next_date).getMonth() + 1 < 10
? "0" + (new Date(next_date).getMonth() + 1) + "-"
: new Date(next_date).getMonth() + 1 + "-";
var next_dates_d =
new Date(next_date).getDate() < 10
? "0" + new Date(next_date).getDate()
: new Date(next_date).getDate();
startTime = next_dates_y + next_dates_m + next_dates_d;
//增加数组key
i++;
}

数组扁平化

简单版

(1)递归实现

普通的递归思路很容易理解,就是通过循环递归的方式,一项一项地去遍历,如果每一项还是一个数组,那么就继续往下遍历,利用递归程序的方法,来实现数组的每一项的连接:

let arr = [1, [2, [3, 4, 5]]];
function flatten(arr) {
let result = [];

for(let i = 0; i < arr.length; i++) {
if(Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result.push(arr[i]);
}
}
return result;
}
flatten(arr); // [1, 2, 3, 4,5]

(2)reduce 函数迭代

从上面普通的递归函数中可以看出,其实就是对数组的每一项进行处理,那么其实也可以用reduce 来实现数组的拼接,从而简化第一种方法的代码,改造后的代码如下所示:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.reduce(function(acc, cur){
return a.concat(Array.isArray(cur) ? flatten(cur) : cur)
}, [])
}
console.log(flatten(arr));// [1, 2, 3, 4,5]

(3)扩展运算符实现

这个方法的实现,采用了扩展运算符和 some 的方法,两者共同使用,达到数组扁平化的目的:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
while (arr.some(item => Array.isArray(item))) {
arr = [].concat(...arr);
}
return arr;
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]

(4)split 和 toString

可以通过 split 和 toString 两个方法来共同实现数组扁平化,由于数组会默认带一个 toString 的方法,所以可以把数组直接转换成逗号分隔的字符串,然后再用 split 方法把字符串重新转换为数组,如下面的代码所示:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.toString().split(',');
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]

通过这两个方法可以将多维数组直接转换成逗号连接的字符串,然后再重新分隔成数组。

(5)ES6 中的 flat

我们还可以直接调用 ES6 中的 flat 方法来实现数组扁平化。flat 方法的语法:arr.flat([depth])

其中 depth 是 flat 的参数,depth 是可以传递数组的展开深度(默认不填、数值是 1),即展开一层数组。如果层数不确定,参数可以传进 Infinity,代表不论多少层都要展开:

let arr = [1, [2, [3, 4]]];
function flatten(arr) {
return arr.flat(Infinity);
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]

可以看出,一个嵌套了两层的数组,通过将 flat 方法的参数设置为 Infinity,达到了我们预期的效果。其实同样也可以设置成 2,也能实现这样的效果。在编程过程中,如果数组的嵌套层数不确定,最好直接使用 Infinity,可以达到扁平化。

(6)正则和 JSON 方法 在第4种方法中已经使用 toString 方法,其中仍然采用了将 JSON.stringify 的方法先转换为字符串,然后通过正则表达式过滤掉字符串中的数组的方括号,最后再利用 JSON.parse 把它转换成数组:

let arr = [1, [2, [3, [4, 5]]], 6];
function flatten(arr) {
let str = JSON.stringify(arr);
str = str.replace(/(\[|\])/g, '');
str = '[' + str + ']';
return JSON.parse(str);
}
console.log(flatten(arr)); // [1, 2, 3, 4,5]

限制层数

function flat(arr,num=1){
return num>0?
arr.reduce(acc,cur)=>acc.concat(Array.isArray(cur))?flat(cur,num-1):cur),[])
:arr.slice();
}
function flatten(arr,level){
function walk(arr,currLevel){
let res=[];
for(let item of arr){
if(Array.isArray(item)&&currLevel<level){
res=res.concat(walk(item,currLevel+1))
}else{
res.push(item)
}
}
return res
}
return walk(arr,1)
}

数组去重

数组去重

给定某无序数组,要求去除数组中的重复数字并且返回新的无重复数组。

ES6方法(使用数据结构集合):

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];

Array.from(new Set(array)); // [1, 2, 3, 5, 9, 8]

ES5方法:使用map存储不重复的数字

const array = [1, 2, 3, 5, 1, 5, 9, 1, 2, 8];

uniqueArray(array); // [1, 2, 3, 5, 9, 8]

function uniqueArray(array) {
let map = {};
let res = [];
for(var i = 0; i < array.length; i++) {
if(!map.hasOwnProperty([array[i]])) {
map[array[i]] = 1;
res.push(array[i]);
}
}
return res;
}

数组中的对象数据根据 key 去重

给定一个任意数组,实现一个通用函数,让数组中的数据根据 key 排重:

const dedup = (data, getKey = () => {} ) => {
// todo
}
let data = [
{ id: 1, v: 1 },
{ id: 2, v: 2 },
{ id: 1, v: 1 },
];

// 以 id 作为排重 key,执行函数得到结果
// data = [
// { id: 1, v: 1 },
// { id: 2, v: 2 },
// ];

实现

const dedup = (data, getKey = () => { }) => {
const dateMap = data.reduce((pre, cur) => {
const key = getKey(cur)
if (!pre[key]) {
pre[key] = cur
}
return pre
}, {})
return Object.values(dateMap)
}

使用

let data = [
{ id: 1, v: 1 },
{ id: 2, v: 2 },
{ id: 1, v: 1 },
];
console.log(dedup(data, (item) => item.id))

// 以 id 作为排重 key,执行函数得到结果
// data = [
// { id: 1, v: 1 },
// { id: 2, v: 2 },
// ];

大数运算

大数相加

function add(a,b){
var a=a.split("");
var b=b.split("");
let sum=[],carry=0;
while(a.length||b.length||carry){ //carry可以判断最后计算是否需要进位
let aa =parseInt(a.pop())||0;
let bb=parseInt(b.pop())||0;
let s=aa+bb+carry;
carry=Math.floor(s/10);
sum.unshift(s%10);
}
sum=sum.join("");
return sum;
}
console.log(add("163","956"));
function add(a,b){
let a=a.split(''),b=b.split('');
let sum=[],flag=0;
while(a.length||b.length){
let num1=parseInt(a.pop())||0;
let num2=parseInt(b.pop())||0;
let temp=num1+num2+flag;
if(temp>9){
flag=1
temp=temp%10
}else{
flag=0
}
sum.unshift(temp)
}
if(flag)sum.unshift(1) //最后一次运算是否需要进位
return sum.join('')
}

大数相乘

multiply('11', '99')为例,思路如下:

  1. 初始化一个数组res用来存放计算结果

  2. 双重for循环,从后往前,遍历str1和str2,将

    str1[i]*str2[j]+res[i+j]

    的结果存储到res中。

    • 一次循环后,res = [empty, 9,9]
    • 二次循环后,res = [9, 18,9]
  3. 从后往前遍历res

    • res[index - 1] += parseInt(res[index] / 10),res当前项取整和res前一项相加(完成>10进一的操作)
    • res[index] %= 10,res当前项取余

    实验111*222

    打印res数组得到

    [ 2, 4, 6, 4, 2 ]


function multiply(str1, str2) {
if (str1 == '0' || str2 == '0') return '0'
let res = [],
i = str1.length - 1,
j = str2.length - 1;
for (; i >= 0; i--) {
let n1 = str1[i] - '0'
j = str2.length - 1
for (; j >= 0; j--) {
let n2 = str2[j] - '0'
res[i + j] = res[i + j] || 0
res[i + j] += n1 * n2
}
}
let index = res.length - 1
while (index >= 1) {
res[index - 1] += parseInt(res[index] / 10)
res[index] %= 10
index--
}
return res.join('')
}
var num1 = '546212878237823';
var num2 = '42362078923598';
console.log(multiply(num1, num2))

数字千分位逗号隔开

简单写法

let number =123456789
function formatNum(number){
let str=''
let arr=number.toString().split('')
let length=arr.length
while(length>3){
str=`,${arr.splice(-3).join('')}`+str
length=arr.length
}
return arr.join('')+str
}
console.log(formatNum(number))

考虑小数

function formatNum(number) {
var arr = (number + '').split('.');
var int = arr[0].split('');
var fraction = arr[1] || '';
var r = "";
var len = int.length;
int.reverse().forEach(function (v, i) {
if (i !== 0 && i % 3 === 0) {
r = v + "," + r;
} else {
r = v + r;
}
})
return r + (!!fraction ? "." + fraction : '');
}
//!!相当于将转换成布尔类型

考虑负数

function formatNum(num) {
let numPrefix = ''
let numArr = ''
let numDist = ''

// 处理负数情况
if (num < 0) {
numPrefix = '-'
numArr = String(num).slice(1).split('').reverse()
} else {
numArr = String(num).split('').reverse()
}

for (let i = 0; i < numArr.length; i++) {
numDist += numArr[i]
if ((i + 1) % 3 === 0 && (i + 1) < numArr.length) {
numDist += ','
}
}

return numPrefix + numDist.split('').reverse().join('')
}

正则

    let num = '12345678'
let reg = /(?=\B(\d{3})+$)/g
console.log(num.replace(reg,",")) //12,345,678

解析URL

Location 对象是 Window 对象的一个部分,可通过 window.location 属性来访问。

参数含义
hash设置或返回从井号 (#) 开始的 URL(锚)。
host设置或返回主机名和当前 URL 的端口号。
hostname设置或返回当前 URL 的主机名。
href设置或返回完整的 URL。
pathname设置或返回当前 URL 的路径部分。
port设置或返回当前 URL 的端口号。
protocol设置或返回当前 URL 的协议。
search设置或返回从问号 (?) 开始的 URL(查询部分)。

解析params

let url = 'http://www.domain.com/?user=anonymous&id=123&id=456&city=%E5%8C%97%E4%BA%AC&enabled';
parseParam(url)
/* 结果
{ user: 'anonymous',
id: [ 123, 456 ], // 重复出现的 key 要组装成数组,能被转成数字的就转成数字类型
city: '北京', // 中文需解码
enabled: true, // 未指定值得 key 约定为 true
}
*/
function parseParam(url) {
const paramsStr = /.+\?(.+)$/.exec(url)[1]; // 将 ? 后面的字符串取出来
const paramsArr = paramsStr.split('&'); // 将字符串以 & 分割后存到数组中
let paramsObj = {};
// 将 params 存到对象中
paramsArr.forEach(param => {
if (/=/.test(param)) { // 处理有 value 的参数
let [key, val] = param.split('='); // 分割 key 和 value
val = decodeURIComponent(val); // 解码
val = /^\d+$/.test(val) ? parseFloat(val) : val; // 判断是否转为数字
if (paramsObj.hasOwnProperty(key)) { // 如果对象有 key,则添加一个值
paramsObj[key] = [].concat(paramsObj[key], val);
} else { // 如果对象没有这个 key,创建 key 并设置值
paramsObj[key] = val;
}
} else { // 处理没有 value 的参数
paramsObj[param] = true;
}
})
return paramsObj;
}

获取 url 中的参数

获取 url 中的参数

1.指定参数名称,返回该参数的值 或者 空字符串 2.不指定参数名称,返回全部的参数对象 或者 {}

3.如果存在多个同名参数,则返回数组

4.不支持URLSearchParams方法

function getUrlParam(sUrl, sKey) {
var left = sUrl.indexOf("?") + 1
var right = sUrl.lastIndexOf("#")
var parasString = sUrl.slice(left, right)
var paras = parasString.split('&');
var parasjson = {}
paras.forEach(function (item, index, arr) {
var v = item.split('=');
var key=v[0],value=v[1];
parasjson[key] !== undefined ? parasjson[key] = [].concat(parasjson[key], value) : parasjson[key] = value;
});

let result = arguments[1] !== void 0 ? (parasjson[arguments[1]] || '') : parasjson;
return result
}
function getUrlParam(sUrl, sKey) {
var paramArr = sUrl.split('?')[1].split('#')[0].split('&'); // 取出每个参数的键值对放入数组
const obj = {};
paramArr.forEach(element => {
const [key, value] = element.split('='); // 取出数组中每一项的键与值
if(obj[key] === void 0){ // 表示第一次遍历这个元素,直接添加到对象上面
obj[key]=value
} else{
obj[key]=[].concat(obj[key],value); // 表示不是第一次遍历说明这个键已有,通过数组存起来。
}});
return sKey===void 0? obj:obj[sKey]||'' // 如果该方法为一个参数,则返回对象。
//如果为两个参数,sKey存在,则返回值或数组,否则返回空字符。
}

为什么要用void 0 替代undefined

源码涉及到 undefined 表达都会被编译成 void 0

①某些情况下用undefined判断存在风险,因undefined有被修改的可能性,但是void 0返回值一定是undefined

②兼容性上void 0 基本所有的浏览器都支持

③ void 0比undefined字符所占空间少。

正则

/[\?&]?(\w+)=(\w+)/g


function getUrlParam2(sUrl, sKey) {
var result, Oparam = {};
sUrl.replace(/[\?&]?(\w+)=(\w+)/g, function ($0, $1, $2)
console.log('$0:' + $0 + " $1:" + $1 + " $2:" + $2);
Oparam[$1] === void 0 ? Oparam[$1] = $2 : Oparam[$1] = [].concat(Oparam[$1], $2);
});
sKey === void 0 || sKey === '' ? result = Oparam : result = Oparam[sKey] || '';
return result;
}

/(\w+)=(\w+)/g


function getUrlParam3(sUrl, sKey) {
var resObj = {};
var reg = /(\w+)=(\w+)/g;
while (reg.exec(sUrl)) {
resObj[RegExp.$1] ? resObj[RegExp.$1] = [].concat(resObj[RegExp.$1], RegExp.$2) : resObj[RegExp.$1] = RegExp.$2;
}
if (sKey) {
return (resObj[sKey] ? resObj[sKey] : '');
}
return resObj;
}

完全解析url

js解析url

/** 
*@param {string} url 完整的URL地址
*@returns {object} 自定义的对象
*@description 用法示例:var myURL = parseURL('http://abc.com:8080/dir/index.html?id=255&m=hello#top');
myURL.file='index.html'

myURL.hash= 'top'

myURL.host= 'abc.com'

myURL.query= '?id=255&m=hello'

myURL.params= Object = { id: 255, m: hello }

myURL.path= '/dir/index.html'

myURL.segments= Array = ['dir', 'index.html']

myURL.port= '8080'

myURL.protocol= 'http'

myURL.source= 'http://abc.com:8080/dir/index.html?id=255&m=hello#top'

*/
function parseURL(url) {
var a = document.createElement('a');
a.href = url;
return {
source: url,
protocol: a.protocol.replace(':',''),
host: a.hostname,
port: a.port,
query: a.search,
params: (function(){
var ret = {},
seg = a.search.replace(/^\?/,'').split('&'),
len = seg.length, i = 0, s;
for (;i<len;i++) {
if (!seg[i]) { continue; }
s = seg[i].split('=');
ret[s[0]] = s[1];
}
return ret;
})(),
file: (a.pathname.match(/\/([^\/?#]+)$/i) || [,''])[1],
hash: a.hash.replace('#',''),
path: a.pathname.replace(/^([^\/])/,'/$1'),
relative: (a.href.match(/tps?:\/\/[^\/]+(.+)/) || [,''])[1],
segments: a.pathname.replace(/^\//,'').split('/')
};
}

解析url query

给你一个 url: https://a.b.com/c/d?foo=bar&alo=ha 要求按以下格式输出:

{
"foo": "bar",
"alo": "ha"
}

聪明的小伙伴一定很快能想到使用 URL接口来处理,直接看代码:

let url = `https://a.b.com/c/d?foo=bar&alo=ha`;

function parse(url) {
return [...new URL(url).searchParams].reduce(
(cur, [key, value]) => ((cur[key] = value), cur),
{}
);
}

parse(url); // {foo: "bar", alo: "ha"}

URL 接口为我们提供了一个非常方便的解析 url 的方法 基本用法如下

let url = `https://a.b.com/c/d?foo=bar&alo=ha`;

let urlParsed = new URL(url);

解析出来的 urlParsed 格式如下:

img

可以看到 URL 接口已经很贴心地帮我们把需要解析的大部分关键字都提取了出来,并且把 querystring 中的关键字都封装在了一个可迭代对象 searchParams 中。

常规操作让我们来看看这个小妖精究竟包含了啥:

img

简单格式化一下就是我们想要的东西了:

img

循环

function getQuery(str) {
//const url = decodeURI(location.search); // 获取url中"?"符后的字串(包括问号)
const url = /.+\?(.+)$/.exec(str)[1]; // 将 ? 后面的字符串取出来
let query = {};
if (url.indexOf("?") != -1) {
const str = url.substr(1);
const pairs = str.split("&");
for(let i = 0; i < pairs.length; i ++) {
const pair = pairs[i].split("=");
query[pair[0]] = pair[1];
}
}
return query ; // 返回对象
}

//查询是否有name
function getQueryVariable(name) {
//const url = decodeURI(location.search); // 获取url中"?"符后的字串(包括问号)
const url = /.+\?(.+)$/.exec(str)[1]; // 将 ? 后面的字符串取出来
let query = {};
if (url.indexOf("?") != -1) {
const str = url.substr(1);
const pairs = str.split("&");
for(let i = 0; i < pairs.length; i ++) {
const pair = pairs[i].split("=");
if(pair[0] === name) return pair[1]; // 返回 参数值
}
}
return(false);
}

正则

function  getQueryVariable(name) {
const reg = new RegExp("(^|&)" + name+ "=([^&]*)(&|$)", "i");
const result = window.location.search.substr(1).match(reg);
if ( result != null ){
return decodeURI(result[2]);
}else{
return null;
}
}

获取参数

function getQueryString(name) {
// 如果链接没有参数,或者链接中不存在我们要获取的参数,直接返回空
if (location.href.indexOf("?") == -1 || location.href.indexOf(name + '=') == -1) {
return '';
}
// 获取链接中参数部分
var queryString = location.href.substring(location.href.indexOf("?") + 1);
queryString = decodeURI(queryString);
// 分离参数对 ?key=value&key2=value2
var parameters = queryString.split("&");
var pos, paraName, paraValue;
for (var i = 0; i < parameters.length; i++) {
// 获取等号位置
pos = parameters[i].indexOf('=');
if (pos == -1) {
continue;
}
// 获取name 和 value
paraName = parameters[i].substring(0, pos);
paraValue = parameters[i].substring(pos + 1);

// 如果查询的name等于当前name,就返回当前值,同时,将链接中的+号还原成空格
if (paraName == name) {
return unescape(paraValue.replace(/\+/g, " "));
}
}
return '';
}
var params = sUrl.split("?")[1].split("#")[0].split("&")
if (sKey) {
var res = []
for (var i = 0; i < params.length; i++) {

var curPar = params[i].split("=")
if (sKey == curPar[0]) {
res.push(curPar[1])
}
}
if (res.length > 1) {
return res
} else if (res.length == 1) {
return res[0]
} else {
return ""
}
} else {
var res = {}
for (var i = 0; i < params.length; i++) {
var curPar = params[i].split("=")
if (res[curPar[0]]) {
res[curPar[0]].push(curPar[1])
} else {
res[curPar[0]] = [curPar[1]]
}
}
for (var k in res) {
if (res[k].length == 1) {
res[k] = res[k][0]
}
}
return res
}

字符串和驼峰式转换

字符串转驼峰

数组法
//空格式
let a='hello world'
var b=a.split(' ').map(item=>{
return item[0].toUpperCase()+item.substr(1,item.length)
}).join('')
console.log(b)

//横线式
let str='hello-world'
function getCamelCase(str) {
let arr = str.split('-');
return arr.map((item, index) => {
if (index === 0) {
return item;
} else {
return item.chartAt(0).toUpperCase() + item.slice(1);
}
}).join('');
}
console.log(getCamelCase(str))
正则法
let a='hello world'
let b = a.replace((/\s\w/g),function(v){
return v.substring(1).toUpperCase()
})
console.log(b)

let a='hello-world'
let b = a.replace((/-\w/g),function(v){
return v.substring(1).toUpperCase()
})
console.log(b)

驼峰转字符串

数组法
//横线式
const str = 'helloWorld';
function getKebabCase(str) {
let arr = str.split('');
let result = arr.map((item) => {
if (item.toUpperCase() === item) {
return '-' + item.toLowerCase();
} else {
return item;
}
}).join('');
return result;
}
console.log(getKebabCase(str));
const str = 'helloWorld';
function getKebabCase(prev, cur, index, array) {
if (/[A-Z]/.test(cur)) {
cur = cur.toLowerCase();
if (index === 0) {
return prev + cur;
} else {
return prev + '-' + cur;
}
} else {
return prev + cur;
}
}

function toKebabCase(arr) {
if (typeof arr === 'string') {
arr = arr.split('');
}
return arr.reduce(getKebabCase, '');
}

let test1 = toKebabCase(str);
let test2 = [].reduce.call(st, getKebabCase, '');
正则法
const str = 'helloWorld';
function getKebabCase(str) {
let temp = str.replace(/[A-Z]/g, function(i) {
return '-' + i.toLowerCase();
})
if (temp.slice(0,1) === '-') {
temp = temp.slice(1); //如果首字母是大写,执行replace时会多一个-,需要去掉
}
return temp;
}
console.log(getKebabCase(str));

字符串去重和反转

字符串去重

let str = '11223344aabbcc'
function strSeparate(s) {
return [...new Set([...s])].join('');
// or return [...new Set(s.split(''))].join('')
}
console.log(strSeparate(str))

let str = '11223344aabbcc'
function strSeparate(s) {
// 使用展开运算符,字符串转换成数组
s = [...str];
let arr = [];
for(let i = 0; i < s.length; i++) {
if(arr.indexOf(s[i]) == -1) {
arr.push(s[i])
}
}
return arr.join('');
}
console.log(strSeparate(str))

数字字符串反转

var reverse = function(x) {
let res = 0;
while(x){
res = res * 10 + x % 10;
if(res > Math.pow(2, 31) - 1 || res < Math.pow(-2, 31)) return 0;
x = ~~(x / 10);
}
return res;
};

var reverse = function (x) {
let y = parseInt(x.toString().split("").reverse().join(""));
if (x < 0)
y = - y;
return y > 2147483647 || y < -2147483648 ? 0 : y;
};

字符串反转

function converse(arr,result){
if(arr.length==0){
return result
}
result=result+arr[arr.length-1]
arr=arr.slice(0,arr.length-1)
return converse(arr,result)
}

Js对象转树形结构

// 转换前:
source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
// 转换为:
tree = [{
id: 1,
pid: 0,
name: 'body',
children: [{
id: 2,
pid: 1,
name: 'title',
children: [{
id: 3,
pid: 2,
name: 'div'
}]
}]
}]

source = [{
id: 1,
pid: 0,
name: 'body'
}, {
id: 2,
pid: 1,
name: 'title'
}, {
id: 3,
pid: 2,
name: 'div'
}]
//
function treeing (arr) {
let tree = []
const map = {}
for (let item of arr) {
// 一个新的带children的结构
let newItem = map[item.id] = {
...item,
children: []
}
if (map[item.pid]) { // 父节点已存进map则在父节点的children添加新元素
let parent = map[item.pid]
parent.children.push(newItem)
} else { // 没有父节点,在根节点添加父节点
tree.push(newItem)
}
}
return tree
}
//
function toTree(data) {
let result = []
if(!Array.isArray(data)) {
return result
}
data.forEach(item => {
delete item.children;
});
let map = {};
data.forEach(item => {
map[item.id] = item;
});
data.forEach(item => {
let parent = map[item.pid];
if(parent) {
(parent.children || (parent.children = [])).push(item);
} else {
result.push(item);
}
});
return result;
}

console.log(treeing(source))

DOM转化

DOM2JSON

let domtree={
tagName: 'DIV',
children: [
{
tagName: 'SPAN',
children: [
{ tagName: 'A', children: [] }
]
},
{
tagName: 'SPAN',
children: [
{ tagName: 'A', children: [] },
{ tagName: 'A', children: [] }
]
}
]
}

function dom2Json(domtree) {
let obj = {};
obj.name = domtree.tagName;
obj.children = [];
domtree.children.forEach((child) => obj.children.push(dom2Json(child)));
return obj;
}
console.log(dom2Json(domtree))

将虚拟 Dom 转化为真实 Dom

{
tag: 'DIV',
attrs:{
id:'app'
},
children: [
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] }
]
},
{
tag: 'SPAN',
children: [
{ tag: 'A', children: [] },
{ tag: 'A', children: [] }
]
}
]
}
把上诉虚拟Dom转化成下方真实Dom
<div id="app">
<span>
<a></a>
</span>
<span>
<a></a>
<a></a>
</span>
</div>

计算目录树的深度

得出depth即为树的高度得出depth即为树

定义变量depth为0

定义一个空数组temp,然后遍历tree,如果tree有children,就push到temp里面

开始while循环,如果temp长度不为0,depth++;如果temp长度为0,停止

得出depth即为树的高度

得出depth即为树的高度得出depth即为树的高度得出depth即为树的高度

const tree = {
name: 'root',
children: [
{ name: '叶子1-1' },
{ name: '叶子1-2' },
{
name: '叶子2-1',
children: [{
name: '叶子3-1',
children: [{
name: '叶子4-1'
}]
}]
}
]
}

function getDepth(tree) {
let depth = 0

if (tree) {
let arr = [tree]
let temp = arr
while (temp.length) {
arr = temp
temp = []
for (let i = 0; i < arr.length; i++) {
if (arr[i].children && arr[i].children.length) {
for (let j = 0; j < arr[i].children.length; j++) {
temp.push(arr[i].children[j])
}
}
}
depth++
}
}
return depth
}console.log(getDepth(tree)); //输出4
function getLevel(tree) {
if (tree == null) return 0;
const queue = [tree]
let dep = 0;
while (queue.length) {
dep++;
const size = queue.length
for (let i = 0; i < size; i++) {
const node = queue.shift()
if (node.children) {
for (const child of node.children) {
queue.push(child)
}
}
}
}
return dep
}
console.log(getLevel(tree));

树形结构获取路径名

const treeData = [
{
name: "root",
children: [
{ name: "src", children: [{ name: "index.html" }] },
{ name: "public", children: [] },
],
},
];

const RecursiveTree = (data) => {
data.map((item) => {
console.log(item.name);
if (item.children) {
RecursiveTree(item.children);
}
});
};

RecursiveTree(treeData);

const RecursiveTree = (data) => {
const res = []
const dfs = (data) => {
data.forEach(ele => {
res.push(ele.name)
ele.children && dfs(ele.children)
});
}
dfs(data)
return res;
}
console.log(RecursiveTree(treeData));

查找对象(二叉树)中指定节点路径

现有如下json(简化为对象),已知每个节点id唯一,编写findNode(id),返回路径,如findNode(5) 输出 1->4->5

{
id: 1,
children: [
{ id: 2, children: [{ id: 3, children: [] }] },
{
id: 4,
children: [
{ id: 5, children: [] },
{ id: 6, children: [] },
],
},
{ id: 7, children: [] },
],
};
function dfs(obj,path,target){
if(obj.id==target){
console.log(path.slice())
return
}
for(let son of obj.children){
if(son!==[]){
path.push(son.id)
dfs(son,path,target)

}
}
}
function findNode(obj) {
const res = []
function dfs(obj, currPath, target) {
if (!obj) return;
if (obj.id === target) {
currPath += obj.id
res.push(currPath)
return
}
currPath += obj.id + '->'
obj.children && obj.children.forEach(ele => {
dfs(ele, currPath, target)
});
}
dfs(obj, '', 5)
return res;
}
const RecursiveTree = (data) => {
const res = []
const dfs = (data) => {
data.forEach(ele => {
res.push(ele.name)
ele.children && dfs(ele.children)
});
}
dfs(data)
return res;
}
function solve(root) {
const res = []
function dfs(root) {
for (const key in root) {
if (key === 'name') {
res.push(root[key])
} else if (key === 'children') {
root[key].forEach(ele => {
dfs(ele)
});
}
}
}
dfs(root)
return res;
}

树的所有路径

const data3 = [
{
id: 1,
name: '前端',
children: [
{
id: 2,
name: 'html',
children: [
{ id: 5, name: 'vue', children: [] },
{ id: 6, name: 're', children: [] },
]
},
{
id: 3,
name: 'html',
children: [
{ id: 7, name: 'vue', children: [] },
{ id: 8, name: 're', children: [] },
]
}
]
}
]
function findData(data, id = 1) {
let queue = [data[0]]
let map = new Map()
map.set(data[0], [data[0].name])
while (queue.length) {
let length = queue.length
for (let i = 0; i < length; i++) {
let node = queue.shift()
for (let i = 0; i < node.children.length; i++) {
if (node.children[i].id === id) {
return [...map.get(node), node.children[i].name].join('-')
} else {
queue.push(node.children[i])
map.set(node.children[i], [...map.get(node), node.children[i].name])
}
}
}
console.log(map);
}

}
console.log(findData(data3, 5));//前端-html-vue
let treeArray = [{
id: 1,
nodeName: 'a',
children: [{
id: 11,
nodeName: 'ab',
children: [{
id: 111,
nodeName: 'abc'
},
{
id: 112,
nodeName: 'abd'
}
]
},
{
id: 12,
nodeName: 'ac',
children: [{
id: 121,
nodeName: 'aca'
}]
}
]
},
{
id: 2,
nodeName: 'b'
},
{
id: 3,
nodeName: 'c',
children: [{
id: 31,
nodeName: 'cb',
children: [{
id: 311,
nodeName: 'cbc'
},
{
id: 312,
nodeName: 'cbd'
}
]
},
{
id: 32,
nodeName: 'cc',
children: [{
id: 321,
nodeName: 'cca'
}]
}
]
},
{
id: 4,
nodeName: 'd'
}
];

// 首先我们先定义个数组,用来保存路径节点id
let nodePathArray = []

// (tree为目标树,targetId为目标节点id)
function getNodeRoute(tree, targetId) {
for (let index = 0; index < tree.length; index++) {
if (tree[index].children) {
let endRecursiveLoop = getNodeRoute(tree[index].children, targetId)
if (endRecursiveLoop) {
nodePathArray.push(tree[index].id)
return true
}
}
if (tree[index].id === targetId) {
nodePathArray.push(tree[index].id)
return true
}
}
}

getNodeRoute(treeArray, 112) //查找id为112的节点路径
console.log(nodePathArray.reverse().join('->'));

数组转化成树形结构

递归形式

  const arr = [
{id:"01", name: "张大大", pid:"", job: "项目经理"},
{id:"02", name: "小亮", pid:"01", job: "产品leader"},
{id:"03", name: "小美", pid:"01", job: "UIleader"},
{id:"04", name: "老马", pid:"01", job: "技术leader"},
{id:"05", name: "老王", pid:"01", job: "测试leader"},
{id:"06", name: "老李", pid:"01", job: "运维leader"},
{id:"07", name: "小丽", pid:"02", job: "产品经理"},
{id:"08", name: "大光", pid:"02", job: "产品经理"},
{id:"09", name: "小高", pid:"03", job: "UI设计师"},
{id:"10", name: "小刘", pid:"04", job: "前端工程师"},
{id:"11", name: "小华", pid:"04", job: "后端工程师"},
{id:"12", name: "小李", pid:"04", job: "后端工程师"},
{id:"13", name: "小赵", pid:"05", job: "测试工程师"},
{id:"14", name: "小强", pid:"05", job: "测试工程师"},
{id:"15", name: "小涛", pid:"06", job: "运维工程师"}
]
function toTree(list,parId){
let len = list.length
function loop(parId){
let res = [];
for(let i = 0; i < len; i++){
let item = list[i]
if(item.pid === parId){
item.children = loop(item.id)
res.push(item)
}
}
return res
}
return loop(parId)
}

let result = toTree(arr,'')
console.log(result);

非递归形式

  const arr = [
{ 'id': '29', 'pid': '', 'name': '总裁办' },
{ 'id': '2c', 'pid': '', 'name': '财务部' },
{ 'id': '2d', 'pid': '2c', 'name': '财务核算部' },
{ 'id': '2f', 'pid': '2c', 'name': '薪资管理部' },
{ 'id': 'd2', 'pid': '', 'name': '技术部' },
{ 'id': 'd3', 'pid': 'd2', 'name': 'Java研发部' }
]

function tranListToTreeData(list) {
// 1. 定义两个中间变量
const treeList = [], // 最终要产出的树状数据的数组
map = {} // 存储映射关系


// 2. 建立一个映射关系,并给每个元素补充children属性.
// 映射关系: 目的是让我们能通过id快速找到对应的元素
// 补充children:让后边的计算更方便
list.forEach(item => {
if (!item.children) {
item.children = []
}
map[item.id] = item
})
// {
// "29": { 'id': '29', 'pid': '', 'name': '总裁办', children:[] },
// '2c': { 'id': '2c', 'pid': '', 'name': '财务部', children:[] },
// '2d': { 'id': '2d', 'pid': '2c', 'name': '财务核算部', children:[]},
// '2f': { 'id': '2f', 'pid': '2c', 'name': '薪资管理部', children:[]},
// 'd2': { 'id': 'd2', 'pid': '', 'name': '技术部', children:[]},
// 'd3': { 'id': 'd3', 'pid': 'd2', 'name': 'Java研发部', children:[]}
// }

// 3. 循环
list.forEach(item => {
// 对于每一个元素来说,先找它的上级
// 如果能找到,说明它有上级,则要把它添加到上级的children中去
// 如果找不到,说明它没有上级,直接添加到 tree3List
const parent = map[item.pid]
if (parent) {
parent.children.push(item)
} else {
treeList.push(item)
}
})
// 4. 返回出去
return treeList
}

const treeList = tranListToTreeData(arr)
console.log(treeList);

对象树遍历

const tree = {
name: 'root',
children: [
{
name: 'c1',
children: [
{
name: 'c11',
children: []
},
{
name: 'c12',
children: []
}
]
},
{
name: 'c2',
children: [
{
name: 'c21',
children: []
},
{
name: 'c22',
children: []
}
]
}
]
}

// 深度优先的方式遍历 打印 name
// ['root', 'c1','c11', 'c12', 'c2', 'c21', 'c22']

function solve(root) {
let stack = [],
result = [];
if(!root) return [];
stack.push(root)
while(stack.length) {
let node = stack.pop()
if(node == null ) continue
result.push(node.name)
for(let i = node.children.length-1; i >= 0; i--) {
// 这里就是面试的重点,应该从后面的节点压入栈中
stack.push(node.children[i])
}
}
return result
}

let nodeList = [
{ id: '1-1', children: [{ id: '1-2-1' }, { id: '1-2-2' }] },
{ id: '2-1' },
{ id: '3-1', children: [{ id: '3-2-1', children: [{ id: '3-3-1' }] }] }
]

​ 如上图的nodeList是一个标准的树形结构数组,他的层级最深是三层,在实际工作中我们碰到的树形结构层级不定,有可能更深,每个节点的属性也复杂的多, 所以能够访问任意层级的方法是首选。这里就以遍历nodeList并输出所有id为例。

​ 方法一: 普通递归

function readNodes (nodes = [], arr = []) {
for (let item of nodes) {
arr.push(item.id)
if (item.children && item.children.length) readNodes(item.children, arr)
}
return arr
}

img

这个方法比较常见,第一点就是你需要用额外的变量保存最后的结果,第二点就是可递归的条件是当前访问节点具有子节点,nodeList的children属性便是记录了子节点数据,我们不仅要判断children是否存在,还得判断children的长度是否为空。在函数中写上默认值是一个比较好的习惯,因为你无法保证别人在使用你方法的时候会不会给你传值,同时可以保证你函数的健壮性。

方法二:利用generator 函数返回的遍历器

function* gen (nodes = []) {
for (let item of nodes) {
yield item.id
if (item.children && item.children.length) yield* gen(item.children)
}

获取树对象属性

var tree = {
name: '中国',
children: [
{
name: '北京',
children: [
{
name: '朝阳群众'
},
{
name: '海淀区'
},
{
name: '昌平区'
}
]
},
{
name: '浙江省',
children: [
{
name: '杭州市',
code: '0571',
},
{
name: '嘉兴市'
},
{
name: '绍兴市'
},
{
name: '宁波市'
}
]
}
]
};

function fn(tree, name) {
const obj = {}
function dfs(tree) {
for (const key in tree) {
if (tree.name === name) {
obj[key] = tree[key]
} else {
tree.children && tree.children.forEach(ele => {
dfs(ele)
});
}
}
}
dfs(tree)
return obj
}

function fn(tree, name) {
//bfs
let Queue = [[tree.name, tree.children]];
while (Queue.length > 0) {
//出队当前节点
let cur = Queue.shift();
if (cur[0] === name) return { name: name, code: cur[1] }
//将children入队
if (cur.length === 1) continue;

for (let node of cur[1]) {
let obj = [node.name];
if (node.hasOwnProperty("children")) obj.push(node.children);
if (node.hasOwnProperty("code")) obj.push(node.code);
Queue.push(obj);
}
}
return -1;
}
var node = fn(tree, '杭州市');
console.log(node); // { name: '杭州市', code: 0571 }

对象扁平化

1.带数组的

输入

{
a: 'a',
b: [1, { c: true }, [3]],
d: { e: undefined, f: 3 },
g: null,
}

输出

{
a: "a",
b[0]: 1,
b[1].c: true,
b[2][0]: 3,
d.f: 3
// null和undefined直接舍去
}
var myObj={
a: 'a',
b: [1, { c: true }, [3]],
d: { e: undefined, f: 3 },
g: null,
}

function treeToObj(myObj) {
function getObj(myObj, str, toObj) {
const level = Object.keys(myObj)
let levelStr = str.slice()
if (levelStr != '') {
levelStr = levelStr + '.'
}
for (key of level) {
if (myObj[key]) {
if (typeof (myObj[key]) === 'object') {
getObj(myObj[key], levelStr + key, toObj)
} else {
toObj[levelStr + key] = myObj[key]
}
}
}
return toObj
}
return getObj(myObj,'',{})
}

console.log(treeToObj(myObj))
var myObj = {
a: 'a',
b: [1, { c: true }, [3]],
d: { e: undefined, f: 3 },
g: null,
}

function treeToObj(myObj) {
const res = {}
function toObj(obj, str = null) {
for (let key in obj) {
if (typeof obj[key] === "number" || typeof obj[key] === "string" || typeof obj[key] === "boolean") {
if (str === null) {
res[key] = obj[key]
} else if (Array.isArray(obj)) {
res[str + '[' + key + ']'] = obj[key]
} else {
res[str + '.' + key] = obj[key]
}
} else if (Array.isArray(obj[key])) {
if (str === null) {
toObj(obj[key], key)
} else if (Array.isArray(obj)) {
toObj(obj[key], str + '[' + key + ']')
} else {
toObj(obj[key], str + '.' + key)
}
} else if (typeof obj[key] === 'object') {
if (str === null) {
toObj(obj[key], key)
} else if (Array.isArray(obj)) {
toObj(obj[key], str + '[' + key + ']')
} else {
toObj(obj[key], str + '.' + key)
}
}
}
return res;
}
return toObj(myObj,'')
}
console.log(treeToObj(myObj))

实现以下转换:

const obj = {
a: {
b: 1,
c: 2,
d: {
e: 5
}
},
b: [1, 3, {a: 2, b: 3}],
c: 3
}

flatten(obj){} 结果返回如下

// {
// 'a.b': 1,
// 'a.c': 2,
// 'a.d.e': 5,
// 'b[0]': 1,
// 'b[1]': 3,
// 'b[2].a': 2,
// 'b[2].b': 3
// c: 3
// }

从结果入手,可以看出需要对象进行遍历,把里面的属性值依次输出。

核心方法体就是:传入对象的 key 值和 value,对 value 再进行递归遍历。

而 js 的数据类型可以分为基础数据类型引用数据类型,对于题目而言,基础数据类型无需再进行深层次遍历,引用数据类型需要再次进行递归。

function flat(obj, key = "", res = {}, isArray = false) { 
for (let [k, v] of Object.entries(obj)) {
if (Array.isArray(v)) {
let tmp = isArray ? key + "[" + k + "]" : key + k
flat(v, tmp, res, true)
} else if (typeof v === "object") {
let tmp = isArray ? key + "[" + k + "]." : key + k + "."
flat(v, tmp, res)
} else {
let tmp = isArray ? key + "[" + k + "]" : key + k
res[tmp] = v
}
}
return res
}
function objectFlat(obj = ''){
const res = {}
function flat(item , preKey = ''){
Object.entries(item).forEach(([key,value]) => {
let newKey = key
if (Array.isArray(item)){
// console.log('是数组')
newKey = preKey ? `${preKey}[${key}]` : key
}else{
newKey = preKey ? `${preKey}.${key}` : key
}
if (value && typeof value === 'object'){
flat(value , newKey)
}else{
res[newKey] = value
}
})
}
flat(obj)
return res
}

const source = { a: { b: { c: 1, d: 2 }, e: 3 }, f: { g: 2 } }
console.log(objectFlat(source));
const obj = {
a: 1,
b: [1, 2, { c: true }],
c: { e: 2, f: 3 },
g: null,
};
console.log(objectFlat(obj));

2.纯对象

var entry = {
a: {
b: {
c: {
dd: 'abcdd'
}
},
d: {
xx: 'adxx'
},
e: 'ae'
}
}

// 要求转换成如下对象
var output = {
'a.b.c.dd': 'abcdd',
'a.d.xx': 'adxx',
'a.e': 'ae'
}
var entry = {
a: {
b: {
c: {
dd: 'abcdd'
}
},
d: {
xx: 'adxx'
},
e: 'ae'
}
}
function flatObj(obj,parentKey="",result={}){
for(const key in obj){
if(obj.hasOwnProperty(key)){
let keyName=`${parentKey}${key}`
if(typeof obj[key]==='object'){
flatObj(obj[key],keyName+".",result)
}else{
result[keyName]=obj[key]
}
}
}
return result
}
console.log(flatObj(entry))

对象字符串转化成树形结构

let strarr = {  
'a-b-c-d':1,
'a-b-c-e':2,
'a-b-f':3,
'a-j':4
}
let obj = {
a:{
b:{
c:{
d:1,
e:2
},
f:3
},
j:4
}
}
function parse(obj) {
let ans = {}
for (let att in obj) {
let temp = ans;
for (let i = 0, len = att.length; i < len; i += 2) {
let cur = att[i];
if (i == len - 1) {
temp[cur] = obj[att];
} else {
if (!temp[cur]) {
temp[cur] = {};
}
temp = temp[cur];
}
}
}
return ans;
}
const input = {
'a.b.c': 1,
'a.b.d': 2,
'a.e': 3,
'f': 4,
};
const output = {
a: {
b: {
c: 1,
d: 2
},
e: 3
},
f: 4
};

console.log(process(input))
console.log(output)

function process(input) {
const keys = Object.keys(input)
const res = {}
for (const key of keys) {
const chain = key.split('.')
let temp = res
for (let i = 0; i < chain.length; i++) {
const k = chain[i]
temp[k] = temp[k] ?? (i === chain.length - 1 ? input[key] : {})
temp = temp[k]
}
}
return res
}

根据表达式计算字母数量

描述:输入一串字符串,根据字符串求出每个字母的数量并返回结果对象。(数字为1时可省略) 示例一:输入:A3B2,输出:{"A": 3, "B": 2} 示例二:输入:A(A(A2B)2)3C2,输出:{"A": 16, "B": 6, "C": 2}


var countOfAtoms = function(formula) {
//记录各个原子的值
let dict = {}
//栈中初始数字。用于表示单个原子的数字情况
let stack = [1]
//原子名
let str = ''
//暂且记录的原子数字情况,压入栈或者消耗后要重置。
let count = 1;
//从后往前遍历。
for(let i = formula.length - 1; i >= 0; i-- ){
const value = formula[i];
//如果是数字。则记录
if(!isNaN(value)){
if(count === 1){
count = value
}else{
count = parseInt(value + count)
}
};
//如果是小写字母,则记录
if(value >= 'a' && value <= 'z'){
str = str + value;
}
//如果是右括号,则将记录的数字和栈顶部数字相乘并输出
if(value === ')'){
stack.push(stack[stack.length - 1] * count);
count = 1;
}
//如果是左括号,则出栈
if(value === '('){
stack.pop();
}
//如果是大写字母,则将栈顶的值赋值给该原子。如果字典里有这个原子,则累加。重置临时记录的数字和原子值
if(value >= 'A' && value <= 'Z'){
let atomsValue = count * stack[stack.length-1];

str = value + str;
if(dict[str] === undefined){
dict[str] = atomsValue;
}else{
dict[str] = dict[str] + atomsValue
}
count = 1;
str = '';
}
}
//按照字典排序
let newkey = Object.keys(dict).sort();
var result = {};//创建一个新的对象,用于存放排好序的键值对
for (var i = 0; i < newkey.length; i++) {//遍历newkey数组
result[newkey[i]] = dict[newkey[i]];//向新创建的对象中按照排好的顺序依次增加键值对
}
//按照要求的格式输出。
let resultString = ''
for(var key in result){
resultString = resultString + key;
if(result[key] > 1){
resultString = resultString + result[key];
}
}
return resultString
};

类似于726. 原子的数量

模板语法解析

let obj = {
name: 'Wow',
age: 20
}
let str = "My name is ${name},I am ${age} ,I take 1000$ to buy a computer and write code function(){console.log('Hello World')}"

function templateString(str, obj) {
let newStr = ''
let flag = false
let temp = ''
for (let i = 0; i < str.length; i++) {
let ch = str[i]
if (ch === '$' && str[i + 1] === '{') {
flag = true
i++
continue
}
if (flag) {
if (ch === '}') {
newStr = newStr + obj[temp]
temp = ''
flag = false
} else {
temp = temp + ch
}
} else {
newStr = newStr + ch
}
}
return newStr
}

console.log(templateString(str,obj))

实现函数使得将str字符串中的{}内的变量替换,如果属性不存在保持原样(比如{a.d})

1var a = {
2 b: 123,
3 c: '456',
4 e: '789',
5}
6var str=`a{a.b}aa{a.c}aa {a.d}aaaa`;
7// => 'a123aa456aa {a.d}aaaa'
const fn1 = (str, obj) => {
let res = '';
// 标志位,标志前面是否有{
let flag = false;
let start;
for (let i = 0; i < str.length; i++) {
if (str[i] === '{') {
flag = true;
start = i + 1;
continue;
}
if (!flag) res += str[i];
else {
if (str[i] === '}') {
flag = false;
res += match(str.slice(start, i), obj);
}
}
}
return res;
}
// 对象匹配操作
const match = (str, obj) => {
const keys = str.split('.').slice(1);
let index = 0;
let o = obj;
while (index < keys.length) {
const key = keys[index];
if (!o[key]) {
return `{${str}}`;
} else {
o = o[key];
}
index++;
}
return o;
}
let template = '我是{{name}},年龄{{age}},性别{{sex}}';
let data = {
name: '姓名',
age: 18
}
render(template, data); // 我是姓名,年龄18,性别undefined
function render(template, data) {
const reg = /\{\{(\w+)\}\}/; // 模板字符串正则
if (reg.test(template)) { // 判断模板里是否有模板字符串
const name = reg.exec(template)[1]; // 查找当前模板里第一个模板字符串的字段
template = template.replace(reg, data[name]); // 将第一个模板字符串渲染
return render(template, data); // 递归的渲染并返回渲染后的结构
}
return template; // 如果模板没有模板字符串直接返回
}

判断对象相等 isEqual

编程题

写一个函数 判断类型

function({} , {}) true

function(1 , 1) true

function('s' , 's') true

function([] , []) true

function(function () , function ()) false

const compare = (a , b) =>{
// 先判断全等的情况
if(a === b){return true};
// 考虑函数类型
if(typeof a === typeof b === 'function'){
return false;
}
// 基本类型
if(typeof a !== typeof b){
return false
}
// 数组 类型时
if(a instanceof Array && b instanceof Array){
if(a.length !== b.length){
return false
}
if(a.join('') === b.join('')){
return true
}else{
return false
}
}
// 对象类型
if(a instanceof Object && b instanceof Object){
const c1 = Object.keys(a);
const c2 = Object.keys(b);
if(c1.length !== c2.length){
return false
}
// 长度一致时
for(let i in a){
if(a[i] !== b[i]){
return false
}
if(compare(a[i] , b[i]) === false){
return false
}
}
return true
}
const obj1  = {a:100,b:{x:10,y:20}}
const obj2 = {a:100,b:{x:10,y:20}}
isEqual(obj1,obj2) === true

function isObject(obj) {
return typeof obj === 'object' && obj !== null
}
function isEqual(obj1,obj2){
if(!isObject(obj1)||!isObject(obj2)) return obj1===obj2
if(obj1===obj2) return true
const obj1Keys=Object.keys(obj1)
const obj2Keys=Object.keys(obj2)
if(obj1Keys.length!==obj2Keys.length) return false
for(let key in obj1){
const res=isEqual(obj1[key],obj2[key])
if(!res) return res
}
return true
}

数组对象去重

var arr = [{name: 'a',id: 1}, {name: 'a',id: 2}, {name: 'b',id: 3}, {name: 'c',id: 4},
{name: 'c',id: 6}, {name: 'b',id: 6}, {name: 'd',id: 7}];
function deWeight() {
for (var i = 0; i < arr.length - 1; i++) {
for (var j = i + 1; j < arr.length; j++) {
if (arr[i].name == arr[j].name) {
arr.splice(j, 1);
//因为数组长度减小1,所以直接 j++ 会漏掉一个元素,所以要 j--
j--;
}
}
}
return arr;
}
var newArr = deWeight();
console.log('%c%s', 'color:red;', '方法一:es5,newArr', newArr);
var arr = [{
"name": "ZYTX",
"age": "19",
"gender": "f"
}, {
"name": "ZYTA",
"age": "18",
"gender": "m"
}, {
"name": "ZDTX",
"age": "20",
"gender": "f"
}, {
"name": "ZYTX",
"age": "21",
"gender": "m"
}];
var hash = {};
arr = arr.reduce(function(item, next) {
hash[next.name] ? '' : hash[next.name] = true && item.push(next);
return item
}, [])
console.log(arr);

对于不指定的属性的对象比较 可以运用isEqual来判断是否相等 然后再对数组进行遍历判断 来进行去重

实现对象嵌套

input ["a","b","c","d","e","f","g"]

output {"a":{"b":{"c":{"d":{"e":{"f":"g"}}}}}}

function handler(arr){
const len = arr.length;
let prev = {
[arr[len-2]]:arr[len-1]
}
for(let i=len-3;i>=0;i--){
prev = {
[arr[i]]:prev
}
}
return prev
}

获取页面dom元素

得到一个数组,数组的每一项是一个标签名

const all = Array.from(document.querySelectorAll("*"));
const hash = {};
const res = [];
all.forEach(it=>{
if(!hash[it.tagName]){
res.push(it.tagName);
hash[it.tagName] = true;
}
})
console.log(res)

压缩字符串

字符串aabcccccaaa会变为a2b1c5a3

var compressString = function (S) {
let result = ''
let index = 0 //记录字符出现次数,默认为1
let count = 1
while (index < S.length) {
//如果当前字符不等于下一个字符,则停止当前字符的计数,统计到result中,否则计数+1
if (S[index] !== S[index + 1]) {
result += S[index] + count //当前字符计数完成后 重置计数为默认值
count = 1
} else {
count++
}
index++
}
return result.length >= S.length ? S : result
};

输入50a6we8y20x

输出50个a,6个we,8个y,20个x

function getStr(str) {
let res = [];
for (let i = 0; i < str.length; i++) {
if (str[i] >= 0 && str[i] <= 9) {
res.push(str[i]);
} else {
let count = Number(res.join(""));
while (count > 0) {
console.log(str[i]);
count--;
}
count = 0;
res = [];
}
}
}
getStr("50a6we8y20x");

利用正则来匹配字符串中有两个或两个以上的连续字符的子字符串

var reg =/(.)\1{0,}/g;实现了对字符串中连续两个或两个以上的字符匹配,改正则表达式实际上等价于:/(.)\1+/g。 其中(.)表示匹配任意字符,在正则表达式中的小括号"()“代表分组的意思。 如果再其后面出现\1则是代表与第一个小括号中要匹配的内容相同,同理,例如“/(.)/1(\d)/2/g”中的“/2”代表的就是”(\d)",其余的以此类推,$1和$2则分别表示第一个小括号和第二个小括号所匹配到的内容。“1”后面的“+”表示“\1”的次数,这里其实就相当于至少有一个“\1”,也就是再加上前面的“(.)”就表示可以匹配至少两个连续的字符了。


这里其实是实现字符串去重的其中一种方法了,具体是通过字符的replace方法来实现的,replace接收两个参数,第一个为要替换的部分,也可以是正则表达式,第二个参数表示要替换的目标内容,由上面的打印结果可知,[“bb”, “ccc”, “eeee”, “aa”]为正则表达式所匹配到的内容,由于是全局匹配,所以"$1"则表示第一个小括号"(.)"所匹配到的单个字符,然后再将通过正则表达式所匹配到的内容全部换成单个字符就可以实现字符串去重了。

    var reg =/(.)\1{1,}/g;//正则表达式
var str = "abbcccdeeeefgaa";//要演示的目标字符串

var res1 = str.match(reg)//匹配两个或两个以上连续字符
var res2 = str.replace(reg,"$1")//其中一种应用,实现字符串去重

console.log("匹配结果:",res1)
console.log("字符串去重结果:",res2)

实现以下转换,合并连续的数字

[1,2,3,4,6,7,9,13,15]=>['1->4','6->7','9','13','15']

function shortenArray(arr) {
// 处理边界
if (!Array.isArray(arr) || arr.length <= 1) {
return arr;
}

// 记录结果
const result = [];

// 记录连续数字的开始位置
let start = 0;
// 记录连续数字的结束位置
let last = 0;

function pushArr(arrStart, arrEnd) {
if (arrStart === arrEnd) {
result.push(arr[arrStart].toString());
} else {
result.push(`${arr[arrStart]}->${arr[arrEnd]}`);
}
}

// 一次循环获取结果
for (let i = 1; i < arr.length; i++) {
const temp = arr[i];
if (arr[last] + 1 === temp) {
last = i;
} else {
pushArr(start, last);
start = i;
last = i;
}
}

// 处理剩余数据
pushArr(start, last);

return result;
}

shortenArray([1, 2, 3, 4, 6, 7, 9, 13, 15]); // ['1->4','6->7','9','13','15']

求数组交集并集差集(数组对象)

数组

取并集
let a=new Set([1,2,3,4,5]);
let b=new Set([1,2,3,4,5,6,7,8,9]);
let arr = Array.from(new Set([...a, ...b]));
console.log('arr',arr);

结果: [1,2,3,4,5,6,7,8,9]

取交集
let a=new Set([1,2,3,4,5]);
let b=new Set([1,2,3,4,5,6,7,8,9]);
let arr = Array.from(new Set([...b].filter(x => a.has(x))));

结果: [1,2,3,4,5]

多个数组

遍历这个参数数组,将参数数组第一项去重后暂存结果数组中,然后根据剩余数组去重后的集合是否存在该数组的值,来对该数组进行筛选。

参数不确定时,利用arguments获取实参即可。

var interstion=function(arrs)
{
var res=[]
var arr=Array.from(arguments) //获取参数
arr.forEach((item)=>
{
if(!res.length) //没有长度就把第一项的去重结果加入
{
res.push(...new Set(item))
}
else{ //根据去重结果筛选剩余项数组值
let tempSet=new Set(item)
res= res.filter(items=>tempSet.has(items) )
}

})
return res
}

参数为一个二维数组

function intersection(arr) {
let result = [];
console.log(arr)
arr.forEach(arr => {

if( !result.length ) {
result.push(...new Set(arr));
return ;
}
// 如果是第二个及以后的数组,则先去重然后用目标数组过滤
else{
const tempSet = new Set(arr);
result = result.filter(item=>tempSet.has(item));
}

});
return result;
}

取差集
let a=new Set([1,2,3,4,5]);
let b=new Set([1,2,3,4,5,6,7,8,9]);
let arr = Array.from(new Set([...b].filter(x => !a.has(x))));
console.log('arr',arr);

结果: [6,7,8,9]

数组对象

取交集
let a=[{id:1,a:123,b:1234},{id:2,a:123,b:1234}];
let b=[{id:1,a:123,b:1234},{id:2,a:123,b:1234},{id:3,a:123,b:1234},{id:4,a:123,b:1234}];
let arr = [...b].filter(x => [...a].some(y => y.id === x.id));
console.log('arr',arr)

结果:

[{id:1,a:123,b:1234},{id:2,a:123,b:1234}]
取差集
let a=[{id:1,a:123,b:1234},{id:2,a:123,b:1234}];
let b=[{id:1,a:123,b:1234},{id:2,a:123,b:1234},{id:3,a:123,b:1234},{id:4,a:123,b:1234}];
let arr = [...b].filter(x => [...a].every(y => y.id !== x.id));
console.log('arr',arr);

结果

[{id:3,a:123,b:1234},{id:4,a:123,b:1234}]

实现(5).add(3).minus(2)

通过在原型上挂载方法实现 返回this

~(function () {
function check(n) {
n = Number(n);
return isNaN(n) ? 0 : n;
}
function add(num) {
n = check(n);
return this + num;
}
function minus(num) {
n = check(n);
return this - num;
}
Number.prototype.add = add;
Number.prototype.minus = minus;
})();
console.log((5).add(3).minus(2));

把字符串字母大小写变换

如何把一个字符串的大小写反写(大写变小写,小写变大写),例如‘AbC’变成‘aBc’

 let strs = "zhufengPEIxun的xxx老师很帅!,哦*100!HaHa";
strs = strs.replace(/[a-zA-Z]/g, (content) => {
//content:每一次正则匹配的结果
//验证是否为大写字母:把字母转换为大写后看和之前是否一样,如果一样,之前也是大写的;在
//ASCII表中找到大写字母的取值范围进行判断;
//content.toUpperCase() === content;
//content.charCodeAt()>=65&&content.charCodeAt()<=90
return content.toUpperCase() === content
? content.toLowerCase()
: content.toUpperCase();
});
console.log(strs);

实现只执行一次的函数

function once(func) {
var num = true;
return function () {
if (num != true) {
return;
} else {
func.apply(null, arguments);
num = false;
}
};
}

function shuchu() {
console.log(666);
}
getLog = once(shuchu);
getLog();
getLog();

实现对象数组分类

// 将arr转换成下面这样子

const arr = [
{ a: 1, b: 2 },
{ a: 2, b: 3 },
{ a: 1, b: 4 },
{ a: 2, b: 5 },
];
// [[{ a: 1, b: 2 },{ a: 1, b: 4 }],[{ a: 2, b: 3 },{ a: 2, b: 5 }]]

通过map进行记录

function getName(arr) {
let result = [];
const map = new Map();
let index = 0;
for (let key of arr) {
if (!map.has(key.a)) {
map.set(key.a, index);
let current = [key];
result.push(current);
index++;
} else {
let suoyin = map.get(key.a);
result[suoyin].push(key);
}
}
return result;
}
console.log(getName(arr));

JS对象转URL参数

JS对象转URL参数

var url ='http://baidu.com';

var data ={a:1,b:2}

url=url+urlEncode(data,1);
//http://baidu.com?a=1&b=2
/**
* param 将要转为URL参数字符串的对象
* key URL参数字符串的前缀
* encode true/false 是否进行URL编码,默认为true
* idx ,循环第几次,用&拼接
* return URL参数字符串
*/
var urlEncode = (param,idx, key, encode)=> {
console.log('idx',idx)
if(param==null) return '';
var paramStr = '';
var t = typeof (param);
if (t == 'string' || t == 'number' || t == 'boolean') {
var one_is =idx<3?'?':'&';
paramStr += one_is + key + '=' + ((encode==null||encode) ? encodeURIComponent(param) : param);
} else {
for (var i in param) {
var k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i);
idx++
paramStr += urlEncode(param[i],idx, k, encode);
}
}
return paramStr;
};

实现jsonp

JSONP 核心原理:script 标签不受同源策略约束,所以可以用来进行跨域请求,优点是兼容性好,但是只能用于 GET 请求;

const jsonp = ({ url, params, callbackName }) => {
const generateUrl = () => {
let dataSrc = ''
for (let key in params) {
if (params.hasOwnProperty(key)) {
dataSrc += `${key}=${params[key]}&`
}
}
dataSrc += `callback=${callbackName}`
return `${url}?${dataSrc}`
}
return new Promise((resolve, reject) => {
const scriptEle = document.createElement('script')
scriptEle.src = generateUrl()
document.body.appendChild(scriptEle)
window[callbackName] = data => {
resolve(data)
document.removeChild(scriptEle)
}
})
}

实现compose组合方法

var list = [['热', '冷', '冰'], ['大', '中', '小'], ['重辣', '微辣'], ['重麻', '微麻']];

// 输出所有维度的组合,如 [['热', '冷''], ['大', '中']] => 热+大,热+中,冷+大,冷+中

function compose(list) {
console.log('hello world');
}

compose(list);

笛卡尔乘积是指在数学中,两个集合X和Y的笛卡尔积(Cartesian product),又称直积,表示为X×Y,第一个对象是X的成员而第二个对象是Y的所有可能有序对的其中一个成员。假设集合A={a, b},集合B={0, 1, 2},则两个集合的笛卡尔积为{(a, 0), (a, 1), (a, 2), (b, 0), (b, 1), (b, 2)}。

全组合的实现,可以借鉴笛卡尔积的求法,实现思路如下:

  • 如果输入list长度为0,则直接返回空数组
  • 如果输入list长度大于等于1,则直接返回list[0]的每一项构成的单元素数组组成的数组,比如[[1,2]] => [[1],[2]],记为result。对于list的第二项以及以后的每一项,都依次和result做笛卡尔积,并把笛卡尔积的每一项(类似[[ 1 ],'a' ])中的第二项(值)追加到第一项(数组)后面,收集到一个新数组里,用这个数组替换result,只到list遍历完成。

for循环实现

function combination (list) {
let result = []
if (!list.length) return result
for (let subList of list) {
if (!result.length) {
result = subList.map(item => [item])
} else {
let subResult = []
for (let r of result) {
let tailList = subList.map(item => [...r, item])
subResult.push(...tailList)
}
result = subResult
}
}
return result
}

reduce实现

function compose(list){
var res = list.reduce( (result, property) => {
return property.reduce( (acc, value) => {
return acc.concat(result.map( ele => [].concat(ele, value)));
}, []);
});
return res.map(arr=>arr.join('+'))
}

实现repeat方法

实现字符串的复制拼接

function repeat(target, n) {
var s = target, total = "";
while (n > 0) {
if (n % 2 === 1) {
total += s;
}
if (n === 1) {
break;
}
s += s;
n = n >> 1;
}
return total;
}

实现compose 函数(redux)

组合多个函数,从右到左,比如:compose(f, g, h) 最终得到这个结果 (...args) => f(g(h(...args))).

实现

function compose(...funcs) {

if (funcs.length === 1) {
return funcs[0]
}

return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

compose 创建了一个从右向左执行的数据流。如果要实现从左到右的数据流,可以直接更改 compose 的部分代码即可实现

  • 更换 Api 接口:把reduce改为reduceRight
  • 交互包裹位置:把a(b(...args))改为b(a(...args))

compose 函数演化

传入一个数值,计算数值乘以 10 再加上 10,再减去 2

实现起来很简单。

const calc = (num) => num * 10 + 10 - 2;
calc(10); // 108

但这样写有个问题,不好扩展,比如我想乘以 10 时就打印出结果。 为了便于扩展,我们分开写成三个函数。

const multiply = (x) => {
const result = x * 10;
console.log(result);
return result;
};
const add = (y) => y + 10;
const minus = (z) => z - 2;

// 计算结果
console.log(minus(add(multiply(10))));
// 100
// 108
// 这样我们就把三个函数计算结果出来了。

再来实现一个相对通用的函数,计算这三个函数的结果

const compose = (f, g, h) => {
return function(x){
return f(g(h(x)));
}
}
const calc = compose(minus, add, multiply);
console.log(calc(10));
// 100
// 108

这样还是有问题,只支持三个函数。我想支持多个函数。 我们了解到数组的reduce方法就能实现这样的功能。 前一个函数

const compose = (...funcs) => {
return funcs.reduce((a, b) => {
return function(x){
return a(b(x));
}
})
}
const calc = compose(minus, add, multiply);
console.log(calc(10));
// 100
// 108

实现清除字符串前后空格

function trim(str) {
if (str & typeof str === "string") {
return str.replace(/(^s*)|(s*)$/g,""); //去除前后空白符
}
}

实现对象数组属性排序

根据 born 的值降序排列


const singers = [
{ name: 'Steven Tyler', band: 'Aerosmith', born: 1948 },
{ name: 'Karen Carpenter', band: 'The Carpenters', born: 1950 },
{ name: 'Kurt Cobain', band: 'Nirvana', born: 1967 },
{ name: 'Stevie Nicks', band: 'Fleetwood Mac', born: 1948 },
];

Array.prototype.sort() 方法用原地算法对数组的元素进行排序,并返回数组。在很多排序场景下推荐使用。

语法:

arr.sort([compareFunction])

function compare(a, b) {
return a.born < b.born ? 1: -1
}

singers.sort(compare);

/* returns [
{ name: 'Steven Tyler', band: 'Aerosmith', born: 1948 },
{ name: 'Stevie Nicks', band: 'Fleetwood Mac', born: 1948 },
{ name: 'Kurt Cobain', band: 'Nirvana', born: 1967 },
{ name: 'Karen Carpenter', band: 'The Carpenters', born: 1950 }
] */

场景题

循环打印黄绿蓝

普通递归

const task = (timer, light, callback) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
callback()
}, timer)
}
const step = () => {
task(3000, 'red', () => {
task(2000, 'green', () => {
task(1000, 'yellow', step)
})
})
}
step()

使用promise实现

const task = (timer, light) => 
new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
resolve()
}, timer)
})
const step = () => {
task(3000, 'red')
.then(() => task(2000, 'green'))
.then(() => task(2100, 'yellow'))
.then(step)
}
step()

用 async/await 实现

const task = (timer, light) => 
new Promise((resolve, reject) => {
setTimeout(() => {
if (light === 'red') {
red()
}
else if (light === 'green') {
green()
}
else if (light === 'yellow') {
yellow()
}
resolve()
}, timer)
})

const taskRunner = async () => {
await task(3000, 'red')
await task(2000, 'green')
await task(2100, 'yellow')
taskRunner()
}
taskRunner()

图片懒加载

image-20220713103625158

image-20220713103916471

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Document</title>
</head>
<style>
.container {
width: 1000px;
margin: 0 auto;
background-color: pink;
}
.container > img {
display: block;
width: 400px;
height: 400px;
margin-bottom: 50px;
}
</style>
<body>
<div class="container">
<img src="./img/loading.jpg" data-src="./img/pic.png" />
<img src="./img/loading.jpg" data-src="./img/pic.png" />
<img src="./img/loading.jpg" data-src="./img/pic.png" />
<img src="./img/loading.jpg" data-src="./img/pic.png" />
<img src="./img/loading.jpg" data-src="./img/pic.png" />
<img src="./img/loading.jpg" data-src="./img/pic.png" />
</div>
<script>
function lazyLoad() {
var scrollTop =
document.body.scrollTop || document.documentElement.scrollTop;
var winHeight = window.innerHeight;
for (var i = 0; i < imgs.length; i++) {
图片距离顶部<滚动距离+内容区域高度
if (imgs[i].offsetTop < scrollTop + winHeight) {
imgs[i].src = imgs[i].getAttribute("data-src");
}
}
}
window.onscroll = lazyLoad();
</script>
</body>
</html>
使用IntersectionObserver
<!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>Document</title>
<style>
img {
background: url('./img/loading.gif') no-repeat center;
width: 250px;
height: 250px;
display: block;
}
</style>
</head>

<body>
<img src="./img/pixel.gif" data-url="./img/1.jpeg">
<img src="./img/pixel.gif" data-url="./img/2.jfif">
<img src="./img/pixel.gif" data-url="./img/3.jfif">
<img src="./img/pixel.gif" data-url="./img/4.jfif">
<img src="./img/pixel.gif" data-url="./img/5.jfif">
<img src="./img/pixel.gif" data-url="./img/6.webp">

<script>

let imgs = document.getElementsByTagName('img')
// 1. 一上来立即执行一次
let io = new IntersectionObserver(function (entires) {
//图片进入视口时就执行回调
entires.forEach(item => {
// 获取目标元素
let oImg = item.target
// console.log(item);
// 当图片进入视口的时候,就赋值图片的真实地址
if (item.intersectionRatio > 0 && item.intersectionRatio <= 1) {
oImg.setAttribute('src', oImg.getAttribute('data-url'))
}
})
})
Array.from(imgs).forEach(element => {
io.observe(element) //给每一个图片设置监听
});
</script>
</body>

</html>

promise实现图片异步加载

let imageAsync=(url)=>{
return new Promise((resolve,reject)=>{
let img=new Image();
img.src=url;
img.onload=()=>{
resolve(image)
}
img.onerror=(err)=>{
reject(err)
}
})
}
imageAsync("url").then(()=>{
console.log('加载成功')
}).catch((error)=>{
console.log('加载失败')
})

每隔1秒打印

// 使用闭包实现
for (var i = 0; i < 5; i++) {
(function(i) {
setTimeout(function() {
console.log(i);
}, i * 1000);
})(i);
}
// 使用 let 块级作用域
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, i * 1000);
}
function sleep(time){
for(let i=0;i<5;i++){
Promise.resolve(i).then((result)=>{
setTimeout(()=>{
console.log(result)
},time*result)
})
}
}
sleep(1000)

双向数据绑定

 //=>发布订阅的类
class EventEmitter {
constructor() {
this.arrayList = {};
}
on(name, fn) {
if (this.arrayList[name] && !this.arrayList[name].includes(fn)) {
this.arrayList[name].push(fn);
} else {
this.arrayList[name] = [fn];
}
console.log(this.arrayList);
}
off(name, fn) {
if (this.arrayList[name]) {
let index = this.arrayList[name].indexOf(fn);
this.arrayList[name].splice(index, 1);
console.log(this.arrayList);
}
}
emit(name, once = false, ...arg) {
if (this.arrayList[name]?.length > 0) {
for (let key of this.arrayList[name]) {
key.call(this, arg);
}
}
if (!once) {
delete this.arrayList[name];
}
console.log(this.arrayList);
}
}
let s1 = new EventEmitter();
let f1 = function () {
console.log(666);
};
let f2 = function () {
console.log(777);
};
var input = document.querySelector("#ipt");
input.oninput = function (e) {
obj.value = e.target.value;
};
let obj = {
value: "",
};
Object.defineProperty(obj, "value", {
get() {
console.log("我被读了");
},
set(newVal) {
s1.on("value1", function () {
console.log("我的值是" + newVal);
console.log("我被改了");
});
input.value = newVal;
return newVal;
},
});

观察者模式

在观察者模式中,只有两个主体,分别是目标对象Subject,观察者Observer

  • 观察者需Observer要实现update方法,供目标对象调用。update方法中可以执行自定义的业务代码。
  • 目标对象Subject也通常被叫做被观察者或主题,它的职能很单一,可以理解为,它只管理一种事件。Subject需要维护自身的观察者数组observerList,当自身发生变化时,通过调用自身的notify方法,依次通知每一个观察者执行update方法。

观察者模式

// 观察者
class Observer {
/**
* 构造器
* @param {Function} cb 回调函数,收到目标对象通知时执行
*/
constructor(cb){
if (typeof cb === 'function') {
this.cb = cb
} else {
throw new Error('Observer构造器必须传入函数类型!')
}
}
/**
* 被目标对象通知时执行
*/
update() {
this.cb()
}
}

// 目标对象
class Subject {
constructor() {
// 维护观察者列表
this.observerList = []
}
/**
* 添加一个观察者
* @param {Observer} observer Observer实例
*/
addObserver(observer) {
this.observerList.push(observer)
}
/**
* 通知所有的观察者
*/
notify() {
this.observerList.forEach(observer => {
observer.update()
})
}
}

const observerCallback = function() {
console.log('我被通知了')
}
const observer = new Observer(observerCallback)

const subject = new Subject();
subject.addObserver(observer);
subject.notify();

  • 角色很明确,没有事件调度中心作为中间者,目标对象Subject和观察者Observer都要实现约定的成员方法。
  • 双方联系更紧密,目标对象的主动性很强,自己收集和维护观察者,并在状态变化时主动通知观察者更新。

发布订阅模式

发布订阅模式图解

class PubSub {
constructor() {
// 维护事件及订阅行为
this.events = {}
}
/**
* 注册事件订阅行为
* @param {String} type 事件类型
* @param {Function} cb 回调函数
*/
subscribe(type, cb) {
if (!this.events[type]) {
this.events[type] = []
}
this.events[type].push(cb)
}
/**
* 发布事件
* @param {String} type 事件类型
* @param {...any} args 参数列表
*/
publish(type, ...args) {
if (this.events[type]) {
this.events[type].forEach(cb => {
cb(...args)
})
}
}
/**
* 移除某个事件的一个订阅行为
* @param {String} type 事件类型
* @param {Function} cb 回调函数
*/
unsubscribe(type, cb) {
if (this.events[type]) {
const targetIndex = this.events[type].findIndex(item => item === cb)
if (targetIndex !== -1) {
this.events[type].splice(targetIndex, 1)
}
if (this.events[type].length === 0) {
delete this.events[type]
}
}
}
/**
* 移除某个事件的所有订阅行为
* @param {String} type 事件类型
*/
unsubscribeAll(type) {
if (this.events[type]) {
delete this.events[type]
}
}
}

发布订阅模式中,对于发布者Publisher和订阅者Subscriber没有特殊的约束,他们好似是匿名活动,借助事件调度中心提供的接口发布和订阅事件,互不了解对方是谁。

松散耦合,灵活度高,常用作事件总线

易理解,可类比于DOM事件中的dispatchEventaddEventListener

Eventbus

在 Vue 中,有时候 A 组件和 B 组件中间隔了很远,看似没什么关系,但我们希望它们之间能够通信。这种情况下除了求助于 Vuex 之外,我们还可以通过 Event Bus 来实现我们的需求。

创建一个 Event Bus(本质上也是 Vue 实例)并导出:

const EventBus = new Vue()
export default EventBus

在主文件里引入EventBus,并挂载到全局:

import bus from 'EventBus的文件路径'
Vue.prototype.bus = bus

订阅事件:

// 这里func指someEvent这个事件的监听函数
this.bus.$on('someEvent', func)

发布(触发)事件:

// 这里params指someEvent这个事件被触发时回调函数接收的入参
this.bus.$emit('someEvent', params)
//仓库
class EventBus{
constructor(){
this.events={}
}
//添加
on(name,fn){
//有这个属性则添加,没有则赋值
if(this.events[name]){
this.events[name].push(fn)
}else{
this.events[name]=[fn]
}
}
//移除(事件名下的某个fn)
off(name,fn){
//获得事件数组
let tasks=this.events[name]
if(tasks){
//找到fn(函数名或者匿名函数),找不到返回-1
const index=tasks.findIndex(f=>f===fn||f.callback===fn)
if(index>=0){
//删除
tasks.splice(index,1)
}
}
}
//触发,once只触发一次
emit(name,once=false,...args){
if(this.events[name]){
//触发的事件里如果又添加了相同的事件名,会陷入死循环
let tasks=this.events[name].slice()
for(let fn of tasks){
//执行
fn(...args)
}
if(once){
delete this.events[name]
}
}
}
}
let eventBus = new EventBus()
let fn1 = function(name, age) {
console.log(`${name} ${age}`)
}
let fn2 = function(name, age) {
console.log(`hello, ${name} ${age}`)
}
eventBus.on('aaa', fn1)
eventBus.on('aaa', fn2)

eventBus.off("aaa",fn1)
eventBus.emit('aaa', false, '布兰', 12)
class EventEmitter {
constructor() {
// handlers是一个map,用于存储事件与回调之间的对应关系
this.handlers = {}
}

// on方法用于安装事件监听器,它接受目标事件名和回调函数作为参数
on(eventName, cb) {
// 先检查一下目标事件名有没有对应的监听函数队列
if (!this.handlers[eventName]) {
// 如果没有,那么首先初始化一个监听函数队列
this.handlers[eventName] = []
}

// 把回调函数推入目标事件的监听函数队列里去
this.handlers[eventName].push(cb)
}

// emit方法用于触发目标事件,它接受事件名和监听函数入参作为参数
emit(eventName, ...args) {
// 检查目标事件是否有监听函数队列
if (this.handlers[eventName]) {
// 如果有,则逐个调用队列里的回调函数
this.handlers[eventName].forEach((callback) => {
callback(...args)
})
}
}

// 移除某个事件回调队列里的指定回调函数
off(eventName, cb) {
const callbacks = this.handlers[eventName]
const index = callbacks.indexOf(cb)
if (index !== -1) {
callbacks.splice(index, 1)
}
}

// 为事件注册单次监听器
once(eventName, cb) {
// 对回调函数进行包装,使其执行完毕自动被移除
const wrapper = (...args) => {
cb.apply(...args)
this.off(eventName, wrapper)
}
this.on(eventName, wrapper)
}
}

实现checkbox全选反选

js实现

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<style type="text/css">
* {
padding: 0px;
margin: 0px auto;
}
body {
padding: 30px;
width: 300px;
height: 300px;
}
</style>
<script type="text/javascript">
window.onload = function() {
//获取五个多选框items
var items = document.getElementsByName("items");

//1.checkAllBtn绑定单击响应函数
var checkAllBtn = document.getElementById("checkAllBtn");
checkAllBtn.onclick = function() {
//遍历items,使每个复选框都被选中
for(var i = 0; i < items.length; i++) {
items[i].checked = true;
}
};

//2.checkNoBtn绑定单击响应函数
var checkNoBtn = document.getElementById("checkNoBtn");
checkNoBtn.onclick = function() {
//遍历items,使每个复选框都不被选中
for(var i = 0; i < items.length; i++) {
items[i].checked = false;
}
};

//3.checkRevBtn绑定单击响应函数
var checkRevBtn = document.getElementById("checkRevBtn");
checkRevBtn.onclick = function() {
//遍历items,进行反选
for(var i = 0; i < items.length; i++) {
// if(items[i].checked) {
// items[i].checked = false;
// } else if(items[i].checked == false) {
// items[i].checked = true;
// }
items[i].checked = !items[i].checked;
}

};

};
</script>
</head>

<body>
<input type="button" id="checkAllBtn" value="全选" />
<input type="button" id="checkNoBtn" value="不选" />
<input type="button" id="checkRevBtn" value="反选" />
<div id="select">
<input type="checkbox" name="items" value="旅游" />旅游<br />
<input type="checkbox" name="items" value="阅读" />阅读<br />
<input type="checkbox" name="items" value="运动" />运动<br />
<input type="checkbox" name="items" value="音乐" />音乐<br />
<input type="checkbox" name="items" value="跳舞" />跳舞
</div>
</body>
</html>

vue实现

<template>
<div>
<span>全选</span>
<input type="checkbox" v-model="checkAll" />
<div v-for="(item,index) in test" :key="index">
<span>{{item.name}}</span>
<input type="checkbox" v-model="item.isSelected" />
</div>
</div>
</template>
<script>
export default {
data() {
return {
test: [
{ name: "测试1", isSelected: true },
{ name: "测试2", isSelected: true },
{ name: "测试3", isSelected: true },
{ name: "测试4", isSelected: true },
{ name: "测试5", isSelected: true }
]
};
},
computed: {
checkAll: {
get() {
// 返回什么结果接赋予给 checkAll 属性
return this.test.every(item => item.isSelected);
},
set(val) {
// val 是给 checkAll 赋予值的时候传递过来的
return this.test.forEach(item => (item.isSelected = val));
}
}
}
}
</script>

实现可拖拽div

<div id="box"></div>
<style>
#box {
position: absolute; // 重要
border: 1px solid red;
height: 100px;
width: 100px;
}
</style>
<script>
let dragging = false
let position = null
box.addEventListener('mousedown', function (e) {
dragging = true // 正在移动
position = [e.clientX, e.clientY]
})
document.addEventListener('mousemove', function (e) {
if (dragging === false) {
return
}
const x = e.clientX
const y = e.clientY
const deltaX = x - position[0]
const deltaY = y - position[1]
const left = parseInt(box.style.left || 0)
const top = parseInt(box.style.top || 0)
box.style.left = left + deltaX + 'px'
box.style.top = top + deltaY + 'px'
position = [x, y]
})
document.addEventListener('mouseup', function (e) {
dragging = false
})
</script>

//实现拖拽圆形
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<style>
.rad{
height: 100px;
width: 100px;
background-color: red;
border-radius: 50%;
position: absolute;
}
</style>
</head>
<body>
<div class="rad"></div>
<script>
let screenLog=document.querySelector('.rad');
document.addEventListener('mousemove',(e)=>{
screenLog.style.left=e.x-screenLog.offsetWidth/2+'px';
screenLog.style.top=e.y-screenLog.offsetHeight/2+'px';
})

</script>
</body>
</html>

拖拽案例

<!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>Document</title>
<style>
.parent {
height: 200px;
border: 1px solid cyan;
margin: 0 auto;
}
.child {
width: 100px;
height: 100px;
border: 1px solid red;
float: left;
margin: 10px;
}
body {
height: 400px;
}
</style>
<script>
window.onload = function () {
var parent = document.querySelector('.parent');
var childs = document.querySelectorAll('.child');
childs = Array.from(childs);
// console.log(childs);
childs.forEach(function (item) {
//拖放对象
//拖放事件
//开始拖放
item.ondragstart = function (event) {
// console.log('ondragstart');
//console.log(event.dataTransfer);
event.dataTransfer.setData('id', item.id);
};
//正在拖放的事件
item.ondrag = function () {
// console.log('ondrag');
}
//拖放结束
item.ondragend = function () {
// console.log('ondragend');
}
})
//放置对象事件
//将拖放元素 拖放放置 对象中
// 进入目标元素
parent.ondragenter = function (event) {

}
// 在目标元素内活动
parent.ondragover = function () {
//取消事件默认行为
event.preventDefault();
}
// 将拖放元素放置到目标元素内
parent.ondrop = function (event) {
// console.log('ondrop');
var id = event.dataTransfer.getData('id');
// id='one'
var dom = document.querySelector('#' + id);
this.appendChild(dom);
event.stopPropagation();

}
document.body.ondragover = function (event) {
event.preventDefault();
}
document.body.ondrop = function (event) {
var id = event.dataTransfer.getData('id');
var dom = document.querySelector('#' + id);
this.appendChild(dom);
}
}
</script>
</head>

<body>
<div class="parent">
</div>
<div class="child" draggable="true" id="one">1</div>
<div class="child" draggable="true" id="two">2</div>
<div class="child" draggable="true" id='three'>3</div>
<div class="child" draggable="true" id='four'>4</div>
</body>

</html>

封装异步的fetch,使用async await方式来使用

(async () => {
class HttpRequestUtil {
async get(url) {
const res = await fetch(url);
const data = await res.json();
return data;
}
async post(url, data) {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async put(url, data) {
const res = await fetch(url, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
async delete(url, data) {
const res = await fetch(url, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
},
data: JSON.stringify(data)
});
const result = await res.json();
return result;
}
}
const httpRequestUtil = new HttpRequestUtil();
const res = await httpRequestUtil.get('http://golderbrother.cn/');
console.log(res);
})();

实现简单路由

// hash路由
class Route{
constructor(){
// 路由存储对象
this.routes = {}
// 当前hash
this.currentHash = ''
// 绑定this,避免监听时this指向改变
this.freshRoute = this.freshRoute.bind(this)
// 监听
window.addEventListener('load', this.freshRoute, false)
window.addEventListener('hashchange', this.freshRoute, false)
}
// 存储
storeRoute (path, cb) {
this.routes[path] = cb || function () {}
}
// 更新
freshRoute () {
this.currentHash = location.hash.slice(1) || '/'
this.routes[this.currentHash]()
}
}

判断对象是否存在循环引用

const isCycleObject = (obj,parent) => {
const parentArr = parent || [obj];
for(let i in obj) {
if(typeof obj[i] === 'object') {
let flag = false;
parentArr.forEach((pObj) => {
if(pObj === obj[i]){
flag = true;
}
})
if(flag) return true;
flag = isCycleObject(obj[i],[...parentArr,obj[i]]);
if(flag) return true;
}
}
return false;
}


const a = 1;
const b = {a};
const c = {b};
const o = {d:{a:3},c}
o.c.b.aa = a;

console.log(isCycleObject(o)

并行限制的Promise调度器(Scheduler)

同时进行2个并发请求

class Scheduler{
list=[];
maxNum=2;
workingNum=0;
add(promiseCreator){
this.list.push(promiseCreator)
}
start(){
for(let i=0;i<this.maxNum;i++){
this.doNext()
}
}
doNext(){
if(this.list.length&&this.workingNum<this.maxNum){
this.workingNum++
this.list.shift()().then(()=>{
this.workingNum--
this.doNext()
})
}
}
}
var timeout=time=>new Promise(resolve=>setTimeout(resolve,time))

var scheduler=new Scheduler()

var addTask=(time,order)=>{
scheduler.add(()=>timeout(time).then(()=>console.log(order)))
}
addTask(1000,1)
addTask(500,2)
addTask(300,3)
addTask(400,4)

scheduler.start()

我们在需要保证代码在多个异步处理之后执⾏,我们通常会使⽤ Promise.all(promises: []).then(fun: function); Promise.all可以保证,promises数组中所有promise对象都达到resolve状态,才执⾏then回调 那么会出现的情况是,你在瞬间发出⼏⼗万http请求(tcp连接数不⾜可能造成等待),或者堆积了⽆数调⽤栈导致内存溢出. 这个时候需要我们对HTTP的连接数做限制

思路如下:定义⼀个 PromisePool 对象,初始化⼀个 pool 作为并发池,

然后先循环把并发池塞满,不断地调⽤ setTask 然后通过⾃⼰⾃定义的任务函数

(任务函数可以是⽹络请求封装的 promise 对象,或者是其他的),

⽽且每个任务是⼀个Promise对象包装的,执⾏完就 pop 出连接池,任务push 进并发池 pool 中

class PromisePool{
constructor(max,fn){
this.max = max;
this.fn = fn;
this.pool=[]
this.urls=[]
}
start(urls){
this.urls=urls;
while(this.pool.length<this.max){
let url=this.urls.shift()
this.setTask(url)
}
let race=Promise.race(this.pool)
return this.run(race)
}
run(race){
race.then(res=>{
//跑完一个就塞进去一个
let url=this.urls.shift()
this.setTask(url)
return this.run(Promise.race(this.pool))
})
}
setTask(url){
if(!url) return
let task=this.fn(url)
this.pool.push(task)
console.log(`${url}开始,当前并发数${this.pool.length})`)
task.then(res=>{
this.pool.splice(this.pool.indexOf(task), 1)
console.log(`${url}结束 当前并发数${this.pool.length}`)
})
}

}
const URLS=[
'bytedance.com',
'tencent.com',
'alibaba.com',
'microsoft.com',
'apple.com',
'hulu.com',
'amazon.com',
]
var requestFn=url=>{
return new Promise(resolve=>{
setTimeout(()=>{
resolve(`任务${url}完成`)
},1000)
}).then(res=>{
console.log('外部逻辑',res);
})
}
const pool=new PromisePool(5,requestFn)
pool.start(URLS)
/**
* 关键点
* 1. new promise 一经创建,立即执行
* 2. 使用 Promise.resolve().then 可以把任务加到微任务队列,防止立即执行迭代方法
* 3. 微任务处理过程中,产生的新的微任务,会在同一事件循环内,追加到微任务队列里
* 4. 使用 race 在某个任务完成时,继续添加任务,保持任务按照最大并发数进行执行
* 5. 任务完成后,需要从 doingTasks 中移出
*/
function limit(count, array, iterateFunc) {
const tasks = []
const doingTasks = []
let i = 0
const enqueue = () => {
if (i === array.length) {
return Promise.resolve()
}
const task = Promise.resolve().then(() => iterateFunc(array[i++]))
tasks.push(task)
const doing = task.then(() => doingTasks.splice(doingTasks.indexOf(doing), 1))
doingTasks.push(doing)
const res = doingTasks.length >= count ? Promise.race(doingTasks) : Promise.resolve()
return res.then(enqueue)
};
return enqueue().then(() => Promise.all(tasks))
}

// test
const timeout = i => new Promise(resolve => setTimeout(() => resolve(i), i))
limit(2, [1000, 1000, 1000, 1000], timeout).then((res) => {
console.log(res)
})

异步求和

假设有一台本地机器,无法做加减乘除运算,因此无法执行 a + b、a+ = 1 这样的 JS 代码,然后我们提供一个服务器端的 HTTP API,可以传两个数字类型的参数,响应结果是这两个参数的和,这个 HTTP API 的 JS SDK(在本地机器上运行)的使用方法如下:

asyncAdd(3, 5, (err, result) => {

console.log(result); // 8

});

模拟实现:

function asyncAdd(a, b, cb) {

setTimeout(() => {

cb(null, a + b);

}, Math.floor(Math.random()*100))

}

现在要求在本地机器上实现一个 sum 函数,支持以下用法:

(async () => {

const result1 = await sum(1, 4, 6, 9, 1, 4);

const result2 = await sum(3, 4, 9, 2, 5, 3, 2, 1, 7);

const result3 = await sum(1, 6, 0, 5);

console.log([result1, result2, result3]); // [25, 36, 12]

})();

要求 sum 能在最短的时间里返回以上结果

function asyncAdd(a, b, cb) {
setTimeout(() => {
cb(null, a + b);
}, Math.floor(Math.random() * 100))
}
function sum(...args) {
const result = []
function _sum(resolve, reject) {
new Promise((r, j) => {
let a = args.pop()
let b = args.pop()
a = a !== undefined ? a : 0
b = b !== undefined ? b : 0
// 如果访问的元素超出了数组范围,则转为 0
asyncAdd(a, b, (err, res) => {
if (err) j(err)
r(res)
})
if (args.length) {
_sum(resolve, reject)
}
}).then(val => {
result.push(val)
setTimeout(() => {
if (args.length <= 0) {
resolve(sum(...result))
}
}, 100)
})
}
return new Promise((resolve, reject) => {
if (!args || !args.length) resolve(0)
if (args.length == 1) resolve(args[0])
_sum(resolve, reject)
})
}
(async () => {
const result1 = await sum(1, 4, 6, 9, 1, 4)
const result2 = await sum(3, 4, 9, 2, 5, 3, 2, 1, 7)
const result3 = await sum(1, 6, 0, 5)
console.log([result1, result2, result3]) // [25, 36, 12]
})()

实现css颜色生成

function getColor() {
var str = '#';
var arr = ['1', '2', '3', '4', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
for (var i = 0; i < 6; i++) {
var num = parseInt(Math.random() * 16);
str += arr[num];
}
return str;
}

随机数生成

给你一个最小值和一个最大值,要求你返回位于这个区间的随机数。随机数包含最小值和最大值

function getRandom(min, max) {
if (min > max) {
[min, max] = [max, min]; // 交换
}
min = Math.floor(min);
max = Math.ceil(max);
return Math.floor(Math.random() * (max - min + 1) + min);
}
var randomColor = Math.floor(Math.random()*16777215).toString(16);
if((''+randomColor).length<6) randomColor=randomColor+'0';

lazyman(链式调用)

实现一个 LazyMan 方法

LazyMan("Tony");
// Hi I am Tony

LazyMan("Tony")
.sleep(10)
.eat("lunch");
// Hi I am Tony
// 等待了10秒...
// I am eating lunch

LazyMan("Tony")
.eat("lunch")
.sleep(10)
.eat("dinner");
// Hi I am Tony
// I am eating lunch
// 等待了10秒...
// I am eating diner

LazyMan("Tony")
.eat("lunch")
.eat("dinner")
.sleepFirst(5)
.sleep(10)
.eat("junk food");
// Hi I am Tony
// 等待了5秒...
// I am eating lunch
// I am eating dinner
// 等待了10秒...
// I am eating junk food
class _LazyMan {
constructor (name) {
this.taskQueue = [];
this.runTimer = null;
this.sayHi(name);
}

run () {
if (this.runTimer) {
clearTimeout(this.runTimer);
}

this.runTimer = setTimeout(async () => {
for (let asyncFun of this.taskQueue) {
await asyncFun()
}
this.taskQueue.length = 0;
this.runTimer = null;
})
return this;
}

sayHi (name) {
this.taskQueue.push(async () => console.log(`Hi, this is ${name}`));
return this.run();
}

eat (food) {
this.taskQueue.push(async () => console.log(`Eat ${food}`));
return this.run();
}

sleep (second) {
this.taskQueue.push(async () => {
console.log(`Sleep ${second} s`)
return this._timeout(second)
});
return this.run();
}

sleepFirst (second) {
this.taskQueue.unshift(async () => {
console.log(`Sleep first ${second} s`)
return this._timeout(second);
});
return this.run();
}

async _timeout (second) {
await new Promise(resolve => {
setTimeout(resolve, second * 1e3);
})
}
}

// 测试
var LazyMan = name => new _LazyMan(name)

// lazyMan('Hank');
lazyMan('Hank').sleep(10).eat('dinner');
// lazyMan('Hank').eat('dinner').eat('supper');
// lazyMan('Hank').sleepFirst(5).eat('supper');

实现类的方法链式调用

const boy = new PlayBoy('Tom') 
boy.sayHi().sleep(1000).play('王者').sleep(2000).play('跳一跳')
// 输出
// 大家好我是Tom
// 1s 之后
// 我在玩王者
// 2s 之后
// 我在玩跳一跳

创建一个任务队列,在每个方法中都往任务队列里追加一个函数,利用队列的先进先出的思想来控制函数的执行顺序。

// 首先 定义一个类 PlayBoy 
class PlayBoy {
constructor(name) {
this.name = name
this.queue = [] //创建一个任务队列(利用队列的先进先出性质来模拟链式调用函数的执行顺序)
setTimeout(()=>{ // 进入异步任务队列 也是开启 自定义任务队列 queue 的入口
this.next() // next是类PlayBoy 原型上的方法,用来从queue 任务队列中取出函数执行
},0)

return this
}
}

PlayBoy.prototype.sayHi = function () {

const fn = () => {
console.log('hi')
this.next()
}
this.queue.push(fn)
return this
}

PlayBoy.prototype.sleep = function (timer) {

const fn = () => {
setTimeout(() => {
this.next()
}, timer)
}
this.queue.push(fn)
return this
}

PlayBoy.prototype.play = function () {

const fn = () => {
console.log('play')
this.next()
}
this.queue.push(fn)
return this
}

PlayBoy.prototype.next = function () {
const fn = this.queue.shift() // 任务队列中取出函数 函数存在的话即调用
fn && fn()
}

new PlayBoy().sayHi().sleep(5000).play()

查找英语文章中出现频率最高得单词

统计单词数量

var countSegments = function(s) {
let num=0,str="";
for(let i=0;i<s.length;i++){
if(s[i]===" "){
str && num++;
str="";
}else{
str+=s[i];
}
}
str && num++;
return num;
};

统计最多次数

function findMostWord(article) {
// 合法性判断
if (!article) return;

// 参数处理
article = article.trim().toLowerCase();

let wordList = article.match(/[a-z]+/g),
visited = [],
maxNum = 0,
maxWord = "";

article = " " + wordList.join(" ") + " ";

// 遍历判断单词出现次数
wordList.forEach(function(item) {
if (visited.indexOf(item) < 0) {
let word = new RegExp(" " + item + " ", "g"),
num = article.match(word).length;

if (num > maxNum) {
maxNum = num;
maxWord = item;
}
}
});

return maxWord + " " + maxNum;
}

统计次数并排序

function word(text) {
var arr = text.value.match(/[\w\-]+/g) || [];
console.log(arr);
var k = {}, p = {};
for (var i = 0; i < arr.length; i++) {
var v = arr[i].toLowerCase();
if (k[v]) {
k[v]++;
} else {
k[v] = 1;
}
}
function sortObj(obj) {
var arr = [];
for (var i in obj) {
arr.push([obj[i],i]);
};
arr.sort(function (a,b) {
return b[0] - a[0];
});
var len = arr.length,
obj = {};
for (var i = 0; i < len; i++) {
obj[arr[i][1]] = arr[i][0];
}
return obj;
}
k = sortObj(k);

function f1(k) {
if(k == null) {
k = {};
}

};
f1(k);
}

返回字符串出现次数最多的字母

let str = 'aabbbccccc'
function countCase(str){
//计数器
let map=new Map()
let str2 = str.split('')

for(let i = 0 ; i < str2.length;i++){
let val =map.get(str2[i])
if(val){
map.set(str[i],val+1)
}else{
map.set(str[i],1)
}
}
let max=0;
let key
map.forEach((v,k) => {
if(max<v){
max=v
key=k
}
})
// console.log(key);
return key
}
console.log(countCase(str))
let numbers = {}
let maxNumbers = []
let maxNum = 0
for (let i = 0, len = arr.length; i < len; i++) {
if (!numbers[arr[i]]) {
numbers[arr[i]] = 1
} else {
numbers[arr[i]]++
}

if (numbers[arr[i]] > maxNum) {
maxNum = numbers[arr[i]]
}
}
for (let item in numbers) {
if (numbers[item] === maxNum) {
maxNumbers.push(item)
}
}
console.log(maxNumbers)

实现一个简单响应式函数

let person = {
    name: '',
    age: 0
}
// 实现一个响应式函数
function defineProperty(obj, key, val) {
    Object.defineProperty(obj, key, {
        get() {
            console.log(`访问了${key}属性`)
            return val
        },
        set(newVal) {
            console.log(`${key}属性被修改为${newVal}`)
            val = newVal
        }
    })
}
// 实现一个遍历函数Observer
function Observer(obj) {
Object.keys(obj).forEach((key) => {
        defineProperty(obj, key, obj[key])
    })
}
Observer(person)
console.log(person.age)
person.age = 18
console.log(person.age)

reactive

// Dep module
class Dep {
static stack = []
static target = null
deps = null

constructor() {
this.deps = new Set()
}

depend() {
if (Dep.target) {
this.deps.add(Dep.target)
}
}

notify() {
this.deps.forEach(w => w.update())
}

static pushTarget(t) {
if (this.target) {
this.stack.push(this.target)
}
this.target = t
}

static popTarget() {
this.target = this.stack.pop()
}
}

// reactive
function reactive(o) {
if (o && typeof o === 'object') {
Object.keys(o).forEach(k => {
defineReactive(o, k, o[k])
})
}
return o
}

function defineReactive(obj, k, val) {
let dep = new Dep()
Object.defineProperty(obj, k, {
get() {
dep.depend()
return val
},
set(newVal) {
val = newVal
dep.notify()
}
})
if (val && typeof val === 'object') {
reactive(val)
}
}

// watcher
class Watcher {
constructor(effect) {
this.effect = effect
this.update()
}

update() {
Dep.pushTarget(this)
this.value = this.effect()
Dep.popTarget()
return this.value
}
}

// 测试代码
const data = reactive({
msg: 'aaa'
})

new Watcher(() => {
console.log('===> effect', data.msg);
})

setTimeout(() => {
data.msg = 'hello'
}, 1000)

实现一个双向绑定

defineProperty 版本

// 数据
const data = {
text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
Object.defineProperty(data, 'text', {
// 数据变化 --> 修改视图
set(newVal) {
input.value = newVal;
span.innerHTML = newVal;
}
});
// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
data.text = e.target.value;
});

proxy 版本

// 数据
const data = {
text: 'default'
};
const input = document.getElementById('input');
const span = document.getElementById('span');
// 数据劫持
const handler = {
set(target, key, value) {
target[key] = value;
// 数据变化 --> 修改视图
input.value = value;
span.innerHTML = value;
return value;
}
};
const proxy = new Proxy(data, handler);

// 视图更改 --> 数据变化
input.addEventListener('keyup', function(e) {
proxy.text = e.target.value;
});

实现一个轮播图

需要实现的效果:

  • 图片自动轮播
  • 轮播有动画效果
  • 点击左右按钮可切换
  • 点击数字按钮切换到对应图片
  • 数字按钮有选中的效果
  • 鼠标移入停止自动播放

HTML布局

从上图可以看出,HTML布局很简单,主要分为了三部分:左右切换按钮、图片列表、底部数字切换按钮。

代码如下:

<div class="container">
<!-- 图片列表 -->
<ul class="ul-img">
<li class="li-img">1</li>
<li class="li-img">2</li>
<li class="li-img">3</li>
<li class="li-img">4</li>
<li class="li-img">5</li>
</ul>

<!-- 上一张、下一张按钮 -->
<div class="prev">
<span>&lt;</span>
</div>
<div class="next">
<span>&gt;</span>
</div>

<!-- 数字切换按钮 -->
<div class="num-box">
<ul class="num-ul">
<li data-index="0">1</li>
<li data-index="1">2</li>
<li data-index="2">3</li>
<li data-index="3">4</li>
<li data-index="4">5</li>
</ul>
</div>
</div>

这里需要注意的一点是我们给数字切换按钮的li标签添加了一个自定义属性,因为后面我们在js中需要用到,用来判断与哪一张图片对应,方便设置选中效果。

CSS样式

我们需要将图片列表排成一排,并且让最外层的盒子设置超出隐藏,其它两个部分可以定位到对应的位置,代码如下:

.container {
position: relative;
width: 600px;
height: 400px;
margin: 0 auto;
background-color: gray;
overflow: hidden;
}

.ul-img {
position: absolute;
display: flex;
width: 4200px;
height: 400px;
left: 0;
padding: 0;
margin: 0;
}

.li-img {
list-style: none;
width: 600px;
height: 400px;
display: flex;
align-items: center;
justify-content: center;
background-color: aquamarine;
font-size: 30px;
font-weight: 800;
border: 1px solid #ccc;
}

/* 上一张、下一张 */
.prev,
.next {
position: absolute;
height: 400px;
width: 80px;
display: flex;
justify-content: center;
align-items: center;
top: 0;
}

.prev {
left: 0;
}

.next {
right: 0;
}

.prev span,
.next span {
display: block;
color: #fff;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
border-radius: 50%;
cursor: pointer;
}

/* 数字切换按钮 */
.num-box {
position: absolute;
left: 50%;
bottom: 20px;
transform: translate(-50%, 0);
z-index: 2;
}

.num-ul {
list-style: none;
margin: 0;
padding: 0;
display: flex;
}

.num-ul li {
height: 20px;
width: 20px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
font-size: 9px;
color: #fff;
margin: 0 4px;
cursor: pointer;
user-select: none;
}

JS代码

// 获取元素节点
var containerDom = document.getElementsByClassName("container")[0]; // 容器
var ulDom = document.getElementsByClassName("ul-img")[0]; // 图片盒子
var prevDom = document.getElementsByClassName("prev")[0].firstElementChild; // 上一张按钮
var nextDom = document.getElementsByClassName("next")[0].firstElementChild; // 下一张按钮
var numUlDom = document.getElementsByClassName("num-ul")[0]; // 数字按钮父级容器
var numList = document
.getElementsByClassName("num-ul")[0]
.getElementsByTagName("li"); // 数字切换按钮列表

// 定义全局变量
var currentIndex = 0; // 当前显示的图片索引
var timer = null; // 自动播放定时器
numList[currentIndex].style.backgroundColor = "#ccc"; // 默认选中第一个数字
// 上一张
prevDom.addEventListener("click", prevFun);
// 下一张
nextDom.addEventListener("click", nextFun);
// 鼠标移入容器,停止自动播放
containerDom.addEventListener("mouseenter", stopAutoPlay);
// 鼠标移出容器,开启自动播放
containerDom.addEventListener("mouseleave", autoPlay);
// 数字按钮点击事件
numUlDom.addEventListener("click", numClick);

// 开启自动播放
autoPlay();

// 切换上一张
function prevFun() {
ulDom.style.transition = "0.5s";
numList[currentIndex].style.backgroundColor = ""; // 清空上一个按钮的样式
if (currentIndex === 0) {
ulDom.style.transition = "0s"; // 为了实现无缝滚动,清除动画
currentIndex = 4;
} else {
--currentIndex;
}
ulDom.style.left = `-${currentIndex * 600}px`;
numList[currentIndex].style.backgroundColor = "#ccc";
}

// 切换下一张
function nextFun() {
ulDom.style.transition = "0.5s";
numList[currentIndex].style.backgroundColor = ""; // 清空上一个按钮的样式
if (currentIndex === 4) {
ulDom.style.transition = "0s"; // 为了实现无缝滚动,清除动画
currentIndex = 0; // 重新播放第一张
} else {
++currentIndex;
}
ulDom.style.left = `-${currentIndex * 600}px`;
numList[currentIndex].style.backgroundColor = "#ccc"; // 设置按钮选中样式
}

// 数字按钮点击事件
function numClick(e) {
ulDom.style.transition = "0.5s";
let index = e.target.dataset.index;
if (index == undefined) {
return;
}
numList[currentIndex].style.backgroundColor = ""; // 清空上一个按钮的样式
currentIndex = Number(index);
numList[currentIndex].style.backgroundColor = "#ccc";
ulDom.style.left = `-${currentIndex * 600}px`;
}

// 循环播放
function autoPlay() {
timer = setInterval(nextFun, 1000);
}

// 关闭自动播放
function stopAutoPlay() {
// 清除定时器
clearInterval(timer);
}

当我们点击切换按钮式,首先清除掉上一个数字按钮的选中样式,然后判断是否是最后一张图片或者第一张图片。我们声明了一个全局变量currentIndex用来存储当前展示的是第几张图片。

然后通过currentIndex动态计算改变需要展示的图片的left距离。

自动播放和点击数字按钮切换图片得到原理都和这个方法类似,都是需要计算出需要展示的图片的left。

function nextFun() {
ulDom.style.transition = "0.5s";
numList[currentIndex].style.backgroundColor = ""; // 清空上一个按钮的样式
if (currentIndex === 4) {
ulDom.style.transition = "0s"; // 为了实现无缝滚动,清除动画
currentIndex = 0; // 重新播放第一张
} else {
++currentIndex;
}
ulDom.style.left = `-${currentIndex * 600}px`;
numList[currentIndex].style.backgroundColor = "#ccc"; // 设置按钮选中样式
}

js部分我们大概有五个主要的方法:

  • prevFun():点击切换上一张
  • nextFun():点击切换下一张
  • numClick(e):点击数字按钮
  • autoPlay():循环播放轮播
  • stopAutoPlay():关闭自动播放

基础算法

快速排序

  • 从数列中挑出一个元素,称为"基准"(pivot)
  • 重新排序数列,所有比基准值小的元素摆放在基准前面,所有比基准值大的元素摆在基准后面(相同的数可以到任何一边)。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区(partition)操作
  • 递归地(recursively)把小于基准值元素的子数列和大于基准值元素的子数列排序
function quick_sort(arr,l,r) {
if(l>=r) return
let x=arr[l],i=l-1,j=r+1;
while(i<j){
do{
j--;
}while(arr[j]>x);
do{
i++;
}while(arr[i]<x);
if(i<j){
let temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}
}
quick_sort(arr,l,j);
quick_sort(arr,j+1,r );
}
let arr=[10,9,8,7,6,5,4,3,2,1];
quick_sort(arr,0,arr.length-1);
console.log(arr);
  • 最佳情况:T(n) = O(nlogn)
  • 最差情况:T(n) = O(n2)
  • 平均情况:T(n) = O(nlogn)

归并排序

  • 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
  • 设定两个指针,最初位置分别为两个已经排序序列的起始位置
  • 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
  • 重复步骤3直到某一指针到达序列尾
  • 将另一序列剩下的所有元素直接复制到合并序列尾

let arr=[20,18,12,8,5,3,2,1];
let tmp=[];
merge_sort(arr,0,arr.length-1);
console.log(arr);


function merge_sort(arr, l, r) {
if (l >= r) return;
if (l < r) {
var mid = Math.floor((l + r) / 2);
merge_sort(arr, l, mid);
merge_sort(arr, mid + 1, r);
}
let k = 0, i = l, j = mid + 1;
while (i <= mid && j <= r) {
if (arr[i] < arr[j]) {
tmp[k++] = arr[i++];
} else {
tmp[k++] = arr[j++];
}
}
while (i <= mid) {
tmp[k++] = arr[i++];
}
while(j <= r) {
tmp[k++] = arr[j++];
}
for(let i = 0; i < k; i++) {
arr[l+i] = tmp[i];
console.log(arr[l + i],l+i);
}
}
  • 最佳情况:T(n) = O(n)
  • 最差情况:T(n) = O(nlogn)
  • 平均情况:T(n) = O(nlogn)

冒泡排序

function bubbleSort(list) {
var n = list.length;
if (!n) return [];

for (var i = 0; i < n; i++) {
// 注意这里需要 n - i - 1
for (var j = 0; j < n - i - 1; j++) {
if (list[j] > list[j + 1]) {
var temp = list[j + 1];
list[j + 1] = list[j];
list[j] = temp;
}
}
}
return list;
}

堆排序

堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

具体算法描述如下:

  • 将初始待排序关键字序列(R1,R2….Rn)构建成大顶堆,此堆为初始的无序区;
  • 将堆顶元素R[1]与最后一个元素R[n]交换,此时得到新的无序区(R1,R2,……Rn-1)和新的有序区(Rn),且满足R[1,2…n-1]<=R[n]
  • 由于交换后新的堆顶R[1]可能违反堆的性质,因此需要对当前无序区(R1,R2,……Rn-1)调整为新堆,然后再次将R[1]与无序区最后一个元素交换,得到新的无序区(R1,R2….Rn-2)和新的有序区(Rn-1,Rn)。不断重复此过程直到有序区的元素个数为n-1,则整个排序过程完成。
/*方法说明:堆排序
@param array 待排序数组*/
function heapSort(array) {
console.time('堆排序耗时');
if (Object.prototype.toString.call(array).slice(8, -1) === 'Array') {
//建堆
var heapSize = array.length, temp;
for (var i = Math.floor(heapSize / 2) - 1; i >= 0; i--) {
heapify(array, i, heapSize);
}

//堆排序
for (var j = heapSize - 1; j >= 1; j--) {
temp = array[0];
array[0] = array[j];
array[j] = temp;
heapify(array, 0, --heapSize);
}
console.timeEnd('堆排序耗时');
return array;
} else {
return 'array is not an Array!';
}
}
/*方法说明:维护堆的性质
@param arr 数组
@param x 数组下标
@param len 堆大小*/
function heapify(arr, x, len) {
if (Object.prototype.toString.call(arr).slice(8, -1) === 'Array' && typeof x === 'number') {
var l = 2 * x + 1, r = 2 * x + 2, largest = x, temp;
if (l < len && arr[l] > arr[largest]) {
largest = l;
}
if (r < len && arr[r] > arr[largest]) {
largest = r;
}
if (largest != x) {
temp = arr[x];
arr[x] = arr[largest];
arr[largest] = temp;
heapify(arr, largest, len);
}
} else {
return 'arr is not an Array or x is not a number!';
}
}
var arr=[91,60,96,13,35,65,46,65,10,30,20,31,77,81,22];
console.log(heapSort(arr));//[10, 13, 20, 22, 30, 31, 35, 46, 60, 65, 65, 77, 81, 91, 96]

计数排序

计数排序(Counting sort)是一种稳定的排序算法。计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。它只能对整数进行排序。

具体算法描述如下:

  • 找出待排序的数组中最大和最小的元素;
  • 统计数组中每个值为i的元素出现的次数,存入数组C的第i项;
  • 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加);
  • 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1。
function countingSort(array) {
var len = array.length,
B = [],
C = [],
min = max = array[0];
console.time('计数排序耗时');
for (var i = 0; i < len; i++) {
min = min <= array[i] ? min : array[i];
max = max >= array[i] ? max : array[i];
C[array[i]] = C[array[i]] ? C[array[i]] + 1 : 1;
}
for (var j = min; j < max; j++) {
C[j + 1] = (C[j + 1] || 0) + (C[j] || 0);
}
for (var k = len - 1; k >= 0; k--) {
B[C[array[k]] - 1] = array[k];
C[array[k]]--;
}
console.timeEnd('计数排序耗时');
return B;
}
var arr = [2, 2, 3, 8, 7, 1, 2, 2, 2, 7, 3, 9, 8, 2, 1, 4, 2, 4, 6, 9, 2];
console.log(countingSort(arr)); //[1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 4, 4, 6, 7, 7, 8, 8, 9, 9]

当输入的元素是n 个0到k之间的整数时,它的运行时间是 O(n + k)。计数排序不是比较排序,排序的速度快于任何比较排序算法。由于用来计数的数组C的长度取决于待排序数组中数据的范围(等于待排序数组的最大值与最小值的差加上1),这使得计数排序对于数据范围很大的数组,需要大量时间和内存。

  • 最佳情况:T(n) = O(n+k)
  • 最差情况:T(n) = O(n+k)
  • 平均情况:T(n) = O(n+k)

整数二分

function bsearch(){
var arr = [1,2,3,4,5,6,6,7,8,9,10,10];
var l=0,r=arr.length-1;

var target = 6;
while(l<=r){
var mid = Math.floor((l+r)/2);
if(arr[mid]==target){
return mid;
}else if(arr[mid]>target){
r = mid-1;
}else{
l = mid+1;
}

}
return -1;
}
console.log(bsearch());

洗牌算法(shuffle)

其算法思想就是 从原始数组中随机抽取一个新的元素到新数组中

  1. 从还没处理的数组(假如还剩n个)中,产生一个[0, n]之间的随机数 random
  2. 从剩下的n个元素中把第 random 个元素取出到新数组中
  3. 删除原数组第random个元素
  4. 重复第 2 3 步直到所有元素取完
  5. 最终返回一个新的打乱的数组

按步骤一步一步来就很简单的实现

function shuffle(arr){
var result = [],
random;
while(arr.length>0){
random = Math.floor(Math.random() * arr.length);
result.push(arr[random])
arr.splice(random, 1)
}
return result;
}