ajax
function ajax(url) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
resolve(JSON.parse(xhr.responseText))
} else if (xhr.status === 404) {
reject(new Error('404 not found'))
}
}
}
xhr.send(null)
})
return p
}
const url = '/a.json'
ajax(url)
.then(res => console.log(res))
.catch(err => console.log(err))
ajax 的串行、并行、并发限制
- 串行:一个异步请求完了之后再进行下一个请求
- 并行:多个异步请求同时进行
- 并发限制:每个时刻并发执行的异步请求数量是固定的
串行
```javascript const createPromise = (time, id) => () => new Promise(resolve => setTimeout(() => {
}, time) );console.log("promise", id);
resolve();
let promiseArr = [ createPromise(3000, 1), createPromise(2000, 2), createPromise(1000, 3) ]
// 方法一 function execute() { if (promiseArr.length === 0) return const fn = promiseArr.shift() fn().then(() => { execute() }) }
execute()
// 方法二 !(async function () { for (let fn of promiseArr) { await fn() } })()
// 方法三 const [p1, p2, p3] = promiseArr
p1().then(() => { return p2() }).then(() => { return p3() }).then(() => { console.log(‘end’) })
<a name="3lmD6"></a>
### 并行
```javascript
const createPromise = (time, id) => () =>
new Promise(resolve =>
setTimeout(() => {
console.log("promise", id);
resolve();
}, time)
);
let promiseArr = [
createPromise(3000, 1),
createPromise(2000, 2),
createPromise(1000, 3)
]
Promise.all(promiseArr.map(fn => fn())).then(() => {
console.log('end')
})
并发限制
整体采用递归调用来实现:最初发送的请求数量上限为允许的最大值,并且这些请求中的每一个都应该在完成时继续递归发送,通过传入的索引来确定了urls里面具体是那个URL,保证最后输出的顺序不会乱,而是依次输出。
function multiRequest(urls = [], maxNum) {
// 请求总数量
const len = urls.length;
// 根据请求数量创建一个数组来保存请求的结果
const result = new Array(len).fill(false);
// 当前完成的数量
let count = 0;
return new Promise((resolve, reject) => {
// 请求maxNum个
while (count < maxNum) {
next();
}
function next() {
let current = count++;
// 处理边界条件
if (current >= len) {
// 请求全部完成就将promise置为成功状态, 然后将result作为promise值返回
!result.includes(false) && resolve(result);
return;
}
const url = urls[current];
console.log(`开始 ${current}`, new Date().toLocaleString());
fetch(url)
.then((res) => {
// 保存请求结果
result[current] = res;
console.log(`完成 ${current}`, new Date().toLocaleString());
// 请求没有全部完成, 就递归
if (current < len) {
next();
}
})
.catch((err) => {
console.log(`结束 ${current}`, new Date().toLocaleString());
result[current] = err;
// 请求没有全部完成, 就递归
if (current < len) {
next();
}
});
}
});
}
事件绑定
function bindEvent(elem, type, selector, fn) {
if (fn == null) {
fn = selector
selector = null
}
elem.addEventListener(type, event => {
const target = event.target
if (selector) {
// 代理绑定
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
// 普通绑定
fn.call(target, event)
}
})
}
// 普通绑定
const btn1 = document.getElementById('btn1')
bindEvent(btn1, 'click', event => {
console.log(event.target)
event.preventDefault()
alert(this.innerComments)
})
// 代理绑定
const div3 = document.getElementById('div3')
bindEvent(div3, 'click', 'a', event => {
event.preventDefault()
alert(this.innerComments)
})
Call、apply、bind
call
Function.prototype.myCall = function (context = window, ...args) {
context.fn = this
const result = context.fn(...args)
delete context.fn
return result
}
apply
Function.prototype.myApply = function (context = window, args) {
context.fn = this
const result = !args ? context.fn() : context.fn(...args)
delete context.fn
return result
}
bind
Function.prototype.myBind = function (context, ...args) {
const self = this
return function () {
return self.apply(context, args)
}
}
function fn1(a, b, c) {
console.log('this', this)
console.log(a, b, c)
return 'this is fn1'
}
const fn2 = fn1.myBind({x: 100}, 10, 20, 30)
const res = fn2()
console.log(res)
深拷贝
/**
* 深拷贝
*/
const obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])
/**
* 深拷贝
* @param {Object} obj 要拷贝的对象
*/
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用!!!
result[key] = deepClone(obj[key])
}
}
// 返回结果
return result
}
全相等 isEqual
const obj1 = {
a: 100,
b: {
x: 100,
y: 200,
}
}
const obj2 = {
a: 100,
b: {
x: 100,
y: 200
},
}
// 判断是否是对象或数组
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
// 两个都是对象或数组,而且不相等
// 1. 先取出 obj1 和 obj2 的 keys,比较个数
const obj1Keys = Object.keys(obj1)
const obj2Keys = Object.keys(obj2)
if (obj1Keys.length !== obj2Keys.length) {
return false
}
// 2. 以 obj1 为基准,和 obj2 依次递归比较
return obj1Keys.every(key => isEqual(obj1[key], obj2[key]))
}
console.log(isEqual(obj1, obj2))
jquery
class jQuery {
constructor(selector) {
const result = document.querySelectorAll(selector)
const length = result.length
for (let i = 0; i < length; i++) {
this[i] = result[i]
}
this.length = length
this.selector = selector
}
get(index) {
return this[index]
}
each(fn) {
for (let i = 0; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
// 扩展很多 DOM API
}
// 插件
jQuery.prototype.dialog = function (info) {
alert(info)
}
// “造轮子”
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展自己的方法
addClass(className) {
}
style(data) {
}
}
// const $p = new jQuery('p')
// $p.get(1)
// $p.each((elem) => console.log(elem.nodeName))
// $p.on('click', () => alert('clicked'))
promise.all
function promiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw new Error('promises must be array')
}
let result = []
let count = 0
promises.forEach((promise, index) => {
promise.then((res) => {
result[index] = res
count++
count === promises.length && resolve(result)
}, (err) => {
reject(err)
})
})
})
}
// 测试用例
let p1 = Promise.resolve(1),
p2 = Promise.resolve(2),
p3 = Promise.resolve(3);
promiseAll([p1, p2, p3]).then(values => {
console.log(values)
}, (err)=>{
console.log(err, 'err')
})
promise
链式调用还需优化
const STATUS_PENDING = 'pending'
const STATUS_FULFILLED = 'fulfilled'
const STATUS_REJECTED = 'rejected'
class myPromise {
constructor(executor) {
this.status = STATUS_PENDING
this.value = ''
this.reason = ''
// 成功存放的数组
this.onResolvedCallbacks = [];
// 失败存放法数组
this.onRejectedCallbacks = [];
let resolve = value => {
if (this.status === STATUS_PENDING) {
this.status = STATUS_FULFILLED
this.value = value
// pending->fulfilled 按照成功清单执行
this.onResolvedCallbacks.forEach(fn => fn())
}
}
let reject = reason => {
if (this.status === STATUS_PENDING) {
this.status = STATUS_REJECTED
this.reason = reason
// pending->rejected 按照异常清单执行
this.onRejectedCallbacks.forEach(fn => fn());
}
}
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled = () => {}, onRejected = () => {}) {
if (this.status === STATUS_FULFILLED) {
onFulfilled(this.value)
}
if (this.status === STATUS_REJECTED) {
onRejected(this.reason)
}
// 忙碌状态,先记录老板吩咐的内容
if (this.status === STATUS_PENDING) {
// onFulfilled传入到成功数组
this.onResolvedCallbacks.push(() => onFulfilled(this.value))
// onRejected传入到失败数组
this.onRejectedCallbacks.push(() => onRejected(this.reason))
}
}
catch(fn){
return this.then(null,fn)
}
}
new myPromise((resolve, reject) => {
console.log('老板曰: 一秒做完手上的事来一下我办公室,做不完滚蛋')
setTimeout(() => {
if (false) { // 臣妾做不到啊
resolve('做完了手上的事,去老板办公室')
} else {
reject('做不完,滚蛋')
}
}, 1000)
}).then(res => {
console.log(`1s 后:${res}`)
}).catch(error => {
console.log(`1s 后:${error}`)
})
防抖 debounce
const input1 = document.getElementById('input1')
// 防抖
function debounce(fn, delay = 500) {
// timer 是闭包中的
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
input1.addEventListener('keyup', debounce(function (e) {
console.log(e.target)
console.log(input1.value)
}, 600))
节流 throttle
const div1 = document.getElementById('div1')
// 节流
function throttle(fn, delay = 100) {
let timer = null
return function () {
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
div1.addEventListener('drag', throttle(function (e) {
console.log(e.offsetX, e.offsetY)
}))
数组 flat
function flat(arr) {
// 验证 arr 中,是否还有深层数组
const isDeep = arr.some(v => v instanceof Array)
if (!isDeep) return arr
const res = Array.prototype.concat.apply([], arr)
return flat(res)
}
const arr = [1, 2, [3, 4, [10, 20, [100, 200]]], 5]
console.log(flat(arr))
数组去重
// 传统方式
function unique(arr) {
const res = []
arr.forEach(item => {
if (res.indexOf(item) === -1) {
res.push(item)
}
})
return res
}
// 使用 Set
function unique(arr) {
return [...new Set(arr)]
}
console.log(unique([1, 2, 2, 3, 4, 4]))
柯里化实现 add(1)(2)(3)
需要实现一个工具专门生成柯里化函数。主要思路是要判断当前传入函数的参数个数 (args.length) 是否大于等于原函数所需参数个数 (fn.length) ,如果是,则执行当前函数;如果是小于,则返回一个函数
// 简易版
const add = x => y => z => x + y + z;
console.log(add(1)(2)(3));
// 加强版,需要实现
// add(1, 2, 3);
// add(1, 2)(3);
// add(1)(2, 3);
const curry = (fn, ...args) =>
// 函数的参数个数可以直接通过函数数的.length属性来访问
args.length >= fn.length // 这个判断很关键!!!
// 传入的参数大于等于原始函数fn的参数个数,则直接执行该函数
? fn(...args)
/**
* 传入的参数小于原始函数fn的参数个数时
* 则继续对当前函数进行柯里化,返回一个接受所有参数(当前参数和剩余参数) 的函数
*/
: (..._args) => curry(fn, ...args, ..._args);
function add1(x, y, z) {
return x + y + z;
}
const add = curry(add1);
console.log(add(1, 2, 3));
console.log(add(1)(2)(3));
console.log(add(1, 2)(3));
console.log(add(1)(2, 3));
大数相加
let a = "9007199254740991";
let b = "1234567899999999999";
function add(a ,b){
//取两个数字的最大长度
let maxLength = Math.max(a.length, b.length);
//用0去补齐长度
a = a.padStart(maxLength , 0);//"0009007199254740991"
b = b.padStart(maxLength , 0);//"1234567899999999999"
//定义加法过程中需要用到的变量
let t = 0;
let f = 0; //"进位"
let sum = "";
for(let i=maxLength-1 ; i>=0 ; i--){
t = parseInt(a[i]) + parseInt(b[i]) + f;
f = Math.floor(t/10);
sum = t%10 + sum;
}
if(f == 1){
sum = "1" + sum;
}
return sum;
}
add(a,b)
千位分隔符
方法一:
- 将数字转换为字符数组(需考虑小数点)
- 倒序字符数组,进行遍历,每三位加一个逗号
遍历完成后,再将字符数组倒序回来,再合并成字符串即可 ```javascript function numFormat(num) { num = num.toString().split(‘.’) // 分隔小数点 const arr = num[0].split(‘’).reverse() // 转换为字符数组,并倒序 const res = []
for (let i = 0; i < arr.length; i += 1) {
if (i % 3 === 0 && i !== 0) {
res.push(',')
}
res.push(arr[i])
} res.reverse() // 再次倒序恢复正常顺序 if (num[1]) {
return `${res.join('')}.${num[1]}`
} else {
return res.join('')
} }
const a = 1234567894532; const b = 673439.4542; console.log(numFormat(a)); // “1,234,567,894,532” console.log(numFormat(b)); // “673,439.4542”
方法二:
- 使用JS自带的函数 [toLocaleString](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString)
```javascript
const a = 1234567894532;
const b = 673439.4542;
console.log(a.toLocaleString()); // "1,234,567,894,532"
console.log(b.toLocaleString()); // "673,439.454" (小数部分四舍五入了)
方法三:
- 使用 正则表达式 和 replace 函数
- replace 语法:
str.replace(regexp|substr, newSubStr|function)
```javascript function numFormat(num){ const res = num.toString().replace(/\d+/, function(n) { // 先提取整数部分
}) return res; }return n.replace(/(\d)(?=(\d{3})+$)/g, function($1) {
return $1+",";
});
const a = 1234567894532; const b = 673439.4542; console.log(numFormat(a)); // “1,234,567,894,532” console.log(numFormat(b)); // “673,439.4542” ```