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>### 并行```javascriptconst 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 = selectorselector = null}elem.addEventListener(type, event => {const target = event.targetif (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 = thisconst result = context.fn(...args)delete context.fnreturn result}
apply
Function.prototype.myApply = function (context = window, args) {context.fn = thisconst result = !args ? context.fn() : context.fn(...args)delete context.fnreturn result}
bind
Function.prototype.myBind = function (context, ...args) {const self = thisreturn 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 resultif (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 === obj2if (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.lengthfor (let i = 0; i < length; i++) {this[i] = result[i]}this.length = lengththis.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 = 0promises.forEach((promise, index) => {promise.then((res) => {result[index] = rescount++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_PENDINGthis.value = ''this.reason = ''// 成功存放的数组this.onResolvedCallbacks = [];// 失败存放法数组this.onRejectedCallbacks = [];let resolve = value => {if (this.status === STATUS_PENDING) {this.status = STATUS_FULFILLEDthis.value = value// pending->fulfilled 按照成功清单执行this.onResolvedCallbacks.forEach(fn => fn())}}let reject = reason => {if (this.status === STATUS_PENDING) {this.status = STATUS_REJECTEDthis.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 = nullreturn 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 = nullreturn 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 arrconst 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}// 使用 Setfunction 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)```javascriptconst 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” ```
