JavaScript 自定义工具函数库

文章目录

  • 自定义工具函数库
    • 函数相关
      • call()&apply()&bind()
        • call(Fn,obj,.....args)
        • apply(Fn,obj,args)
        • bind(Fn,obj,...args)
      • 函数节流与函数防抖
        • 相关理解
        • 实现
    • 数组相关
      • 数组去重
      • 数组合并和切片
      • 数组扁平化
      • 数组分块
      • 删除数组中部分元素
      • 得到数组的部分元素
    • 对象
      • 自定义new
      • 自定义instanceof
      • 对象的合并
    • 数据拷贝
      • 浅拷贝
        • ES6 的扩展运算符
        • ES5的浅拷贝
        • Array.prototype.concat()/Array.prototype.slice(startindex,endindex) 仅针对数组
      • 深拷贝
        • 实现一: JSON.parse(JSON.stringify())
        • 实现二: 递归拷贝
        • 实现三:Map + 递归
        • 实现四:优化遍历性能
    • 字符串
      • 字符串翻转
      • 检测字符串是否为回文
    • DOM事件监听、委托
      • 事件冒泡与事件委托
      • 实现
    • 事件总线
    • 消息订阅与发布
    • 手写ajax请求函数 -- 面试:有没有封装过axios?
      • API相关
      • 使用方法
      • axios函数封装
      • axios方法的封装
    • promise自定义

自定义工具函数库

函数相关

call()&apply()&bind()

call(Fn,obj,…args)

语法:call(Fn,obj,…args)
功能: 执行Fn,使this为obj,并将后面的参数传给fn,等同于函数对象的call方法

实现:
1.给obj添加临时方法Fn
2.调用Fn,传入参数,获取返回值
3.删除临时方法
其他说明
1.如果这个函数处于非严格模式下,指定null或undefined时会自定替换为指向全局对象

function call(Fn,obj,...arg) {
    if(obj === undefined || obj ===null)obj=globalThis;
    obj.tmp = Fn;
    let result = obj.tmp(...arg);
    delete obj.tmp;
    return result;
}
apply(Fn,obj,args)

与call的差别是,传入的数据参数用数组包起来了

function apply(Fn,obj,args) {
    if(obj === undefined || obj ===null)obj=globalThis;
    obj.tmp = Fn;
    let result = obj.tmp(...args);
    delete obj.tmp;
    return result;
}
bind(Fn,obj,…args)

bind返回一个新函数,并不执行,其余与call函数一致

function bind(Fn,obj,...args) {
    //返回新函数 args2调用返回函数时的传参
    return (...args2) => {
    //调用原来函数,参数列表由args和atgs2依次组成
      return call(Fn,obj,...args,...args2)
    }
}

函数节流与函数防抖

相关理解

目的:控制事件处理函数的执行频率

事件频繁触发可能造成的问题?

  • 一些浏览器事件:window.onresize、window.mousemove等,触发的频率非常高,会造成界面卡顿
  • 如果向后台发送请求,频繁触发,对服务器造成不必要的压力

函数节流(throttle):控制事件执行的时间间隔

理解:

  • 在函数需要频繁触发时: 函数执行一次后,只有大于设定的执行周期后才会执行第二次
  • 适合多次事件按时间做平均分配触发
    场景:
  • 窗口调整(resize)
  • 页面滚动(scroll)
  • DOM 元素的拖拽功能实现(mousemove)
  • 抢购疯狂点击(click)

函数防抖(debounce)

触发事件后不会立即执行,需要等待wait时间,如果在等待的过程中再一次触发了事件,计时器重新开始计时wait时间,直到达到wait时间后执行最后一次的回调

理解:

  • 在函数需要频繁触发时: 在规定时间内,只让最后一次生效,前面的不生效。
  • 适合多次事件一次响应的情况
    场景:
  • 输入框实时搜索联想(keyup/input)
    JavaScript 自定义工具函数库_第1张图片
实现
  • throttle() 节流
    • 语法: throttle(callback, wait)
    • 功能: 创建一个节流函数,在 wait 毫秒内最多执行 callback 一次
    • 返回值,返回值为callback函数,该函数参数为event对象
//使用形式
window.addEventListener('scroll',throttle(()=>{},500))

/*
1.定义上一次时间,判断执行的时间和上一次时间的时间间隔
2.满足条件,执行回调函数
*/  
function throttle(callback,wait){
    let pre = 0;
    //返回值是一个函数
    return function(event){
         //节流函数/真正的事件回调函数
        const current = Date.now(); //当前时间
         //事件触发后,节流函数也会被返回,只是满足一定的条件再调用
        if (current - pre > wait) { //只有离上一次调用callback的时间差大于wait
         //callback()是window调用的,所以callback函数里的this是window,这里要修改指向事件源
        callback.call(this,event); 
         //记录此次调用的时间
        pre = current;
        }
    }
}
  • debounce()防抖
    • debounce(callback,wait)
    • 功能:创建一个防抖动函数,该函数会从上一次被调用后,延迟 wait 毫秒后调用 callback
//使用形式
window.addEventListener('scroll',debounce(()=>{},500))

function debounce(callback,time){
    let timeId = null;
	return function(event){//防抖一般用于事件绑定,所以需要接受event
        if(timeId!== null){//说明已经开启了一个定时器,所以需要清空上一次的
            clearTimeout(timeId);
        }
		/*
        启动定时器,定时器执行后里面的回调是异步执行的
        setTimeout返回值是立即返回的
		*/
        timeId = setTimeout(()=>{
            console.log('执行时的timeId:'+timeId);    
			callback.call(this,event);
            //执行成功之后,重置timeId,所以这里可以起作用
            timeId = null;
		},time)   
	}
}

数组相关

  • map(): 返回一个由回调函数的返回值组成的新数组
  • reduce(): 从左到右为每个数组元素执行一次回调函数,并把上次回调函数的返回值放在一个暂存器中传给下次回调函数,并返回最后一次回调函数的返回值
  • filter(): 将所有在过滤函数中返回 true 的数组元素放进一个新数组中并返回
  • find(): 找到第一个满足测试函数的元素并返回那个元素的值,如果找不到,则返回 undefined。
  • findIndex(): 找到第一个满足测试函数的元素并返回那个元素的索引,如果找不到,则返回 -1。
  • every(): 如果数组中的每个元素都满足测试函数,则返回 true,否则返回 false。
  • some(): 如果数组中至少有一个元素满足测试函数,则返回 true,否则返回 false。
/*
实现map
*/
function map(arr,callback) {
    let newArr = [];
    for(let i=0;i<arr.length;i++){
        newArr.push(callback(arr[i],i));
    }
    return newArr;
}

/*
实现reduce
*/
function reduce(arr,callback,initValue) {
    let result = initValue;//每次运行的结果,最开始是初始化
    for(let i=0;i<arr.length;i++){
        result=callback(result,arr[i],i);
    }
    return result;
}

/* 
实现filter()
*/
function filter(array, callback) {

    const arr = []
    for (let index = 0; index < array.length; index++) {
      if (callback(array[index], index)) {
        arr.push(array[index])
      }
    }
    return arr
  }
  
/* 
实现find()
*/
function find (array, callback) {
    for (let index = 0; index < array.length; index++) {
      if (callback(array[index], index)) {
        return array[index]
      }
    }
    return undefined
}

/* 
实现findIndex()
*/
function findIndex (array, callback) {
    for (let index = 0; index < array.length; index++) {
      if (callback(array[index], index)) {
        return index
      }
    }
  return -1
}

 /* 
 实现every()
 */
function every (array, callback) {
    for (let index = 0; index < array.length; index++) {
      if (!callback(array[index], index)) { // 只有一个结果为false, 直接返回false
        return false
      }
    }
    return true
}

/* 
实现some()
*/
function some (array, callback) {
    for (let index = 0; index < array.length; index++) {
      if (callback(array[index], index)) { // 只有一个结果为true, 直接返回true
        return true
      }
    }
    return false
}

数组去重

方法1: 利用forEach()和indexOf()
说明: 本质是双重遍历, 效率差些

方法2: 利用forEach() + 对象容器
说明: 只需一重遍历, 效率高些

方法3: 利用ES6语法: from + Set 或者 … + Set
说明: 编码简洁

  • src/array/unique.js
/*
方法1: 利用forEach()和indexOf()
  说明: 本质是双重遍历, 效率差些
*/
export function unique1 (array) {
  const arr = []
  array.forEach(item => {
    if (arr.indexOf(item)===-1) {
      arr.push(item)
    }
  })
  return arr
}

/*
方法2: 利用forEach() + 对象容器
  说明: 只需一重遍历, 效率高些
*/
export function unique2 (array) {
  const arr = []
  const obj = {}
  array.forEach(item => {
    if (!obj.hasOwnProperty(item)) {
      obj[item] = true
      arr.push(item)
    }
  })
  return arr
}

/*
方法3: 利用ES6语法
    1). from + Set
    2). ... + Set
    说明: 编码简洁
*/
export function unique3 (array) {
  // return Array.from(new Set(array))
  return [...new Set(array)]
}

数组合并和切片

concat(): 合并

语法: var new_array = concat(array, value1[, value2[, …[, valueN]]])
功能: 将n个数组或值与当前数组合并生成一个新数组, 原始数组不会被改变
slice(): 切片

语法: var new_array = slice(array, [begin[, end]])
功能: 返回一个由 begin 和 end 决定的原数组的浅拷贝, 原始数组不会被改变

/* 
语法: var new_array = concat(old_array, value1[, value2[, ...[, valueN]]]) 
功能: 将n个数组或值与当前数组合并生成一个新数组
*/
function cancat(arr,...args) {
    const result = [...arrr];
    args.forEach(item=>{
        if(Array.isArray(item))result.push(...item);
        else result.push(item);
    })
    return result;
}
/* 
  语法: var new_array = slice(oldArr, [begin[, end]])
  功能: 返回一个由 begin 和 end 决定的原数组的浅拷贝, 原始数组不会被改变
*/
function slice (array, begin, end) {
  // 如果当前数组是[], 直接返回[]
  if (array.length === 0) {
    return []
  }

  // 如果begin超过最大下标, 直接返回[]
  begin = begin || 0
  if (begin >= array.length) {
    return []
  }

  // 如果end不大于begin, 直接返回[]
  end = end || array.length
  if (end > array.length) {
    end = array.length
  }
  if (end <= begin) {
    return []
  }

  // 取出下标在[begin, end)区间的元素, 并保存到最终的数组中
  const arr = []
  for (let index = begin; index < end; index++) {
    arr.push(array[index])
  }

  return arr
}

数组扁平化

将多维数组转换为一维数组
核心就是有数组则继续遍历

  • some + conca+扩展运算符
  • forEach+递归
function flatten(arr) {
while(arr.some(item=>Array.isArray(item))) {
        arr = [].concat(...arr); //...arr展开后,元素直接连接,一维数组会展开之后连接
    }
    return arr;
}


function flatten(arr) {
    let result = [];
    arr.forEach(item=>{
        //递归返回的是一个一维数组,所以使用concat进行连接,concat连接返回新数组
        if(Array.isArray(item))result = result.concat(flatten1(item));
        else  result = result.concat(item);
    })
    return result;
}

数组分块

语法:chunk(array,size)
功能:将数组拆分成多个size长度的区块,每个区块组成小数组,整体组成一个二维数组
如[1,3,5,6,7,8]调用chunk(arr,4)->[[1,3,5,6],[7,8]]

function chunk(array,size){
    let result = [];
    let tmp = [];
    size = size||1;
    if(array.length<=size ||size===1){
        return array;
    }
    array.forEach((item) => {
        //先压入,因为为满在压入,那么最后不足size的元素将不会入栈
        if(tmp.length===0)result.push(tmp);
        tmp.push(item);
        if(tmp.length===size) tmp = [];
    });
    return result;
}

删除数组中部分元素

pull(array, …values):
删除原数组中与value相同的元素, 返回所有删除元素的数组
说明: 原数组发生了改变
如: pull([1,3,5,3,7], 2, 7, 3, 7) —> 原数组变为[1, 5], 返回值为[3,3,7]

pullAll(array, values):
功能与pull一致, 只是参数变为数组
如: pullAll([1,3,5,3,7], [2, 7, 3, 7]) —> 数组1变为[1, 5], 返回值为[3,3,7]

/**
 * 删除数组元素
 * @param {Array} arr 
 * @param  {...any} args 
 */
function pull(arr,...args) {
    const result = [];
   for(let index=0;index<arr.length;++index){
      if(args.includes(arr[index])){
          result.push(arr[index]);
          arr.splice(index,1);
          index--;//splice删除一般需要小标自减
      }
   } 
    return result;
}
function pullAll(arr,values) {
    return pull(arr,...values);
}

得到数组的部分元素

drop(array, count)
得到当前数组过滤掉左边count个后剩余元素组成的数组
说明: 不改变当前数组, count默认是1
如: drop([1,3,5,7], 2) —> [5, 7]

dropRight(array, count)
得到当前数组过滤掉右边count个后剩余元素组成的数组
说明: 不改变当前数组, count默认是1
如: dropRight([1,3,5,7], 2) —> [1, 3]

/* 
1. drop(array, count): 
   得到数组过滤掉左边count个后剩余元素组成的数组
   说明: 不改变当前数组, count默认是1
   如: drop([1,3,5,7], 2) ===> [5, 7]
2. dropRight(array, count): 
   得到数组过滤掉右边count个后剩余元素组成的数组
   说明: 不改变数组, count默认是1
   如: dropRight([1,3,5,7], 2) ===> [1, 3]
*/

export function drop (array, count=1) {
  if (array.length === 0 || count >= array.length) {
    return []
  }

  return array.filter((item, index) => index>=count)
}

export function dropRight (array, count=1) {
  if (array.length === 0 || count >= array.length) {
    return []
  }

  return array.filter((item, index) => index < array.length-count)
}

对象

自定义new

new的原理
1.立即创建一个新对象
2.对象的隐式原型指向构造函数的显式原型prototype
3.将函数中的this,设置成新建的对象,这样在构造器中用this引用新建的对象
4.执行构造函数的对象
5.构造函数有返回值且是对象?返回构造函数返回值:1创建的新对象

语法: newInstance(Fn, …args)
功能: 创建Fn构造函数的实例对象

function newInstance(Fn,...arg) {
    const obj = {};
    obj.__proto__ = Fn.prototype;
    let result = Fn.call(obj,...arg);
    return result instanceof Object ? result:obj;
}

自定义instanceof

语法: myInstanceOf(obj, Type)
功能: 判断obj是否是Type类型的实例
实现: Type的原型对象是否是obj的原型链上的某个对象, 如果是返回tru, 否则返回false

A instanceof B原理: 沿着A 的隐式原型链去找是否有B的原型

/*
判断obj是否是Type类型的实例
*/
function myInstanceOf(obj,Type) {
    let protoObj = obj.__proto__;
    while(protoObj){
        if(protoObj === Type.prototype)return true;
        protoObj = protoObj.__proto__;
    }
    return false;
}

对象的合并

语法: object mergeObject(…objs)
功能: 合并多个对象, 返回一个合并后对象(不改变原对象)
例子:
{ a: [{ x: 2 }, { y: 4 }], b: 1}
{ a: { z: 3}, b: [2, 3], c: ‘foo’}
合并后: { a: [ { x: 2 }, { y: 4 }, { z: 3 } ], b: [ 1, 2, 3 ], c: ‘foo’ }


/*
1.新建一个对象resultObj
2.先把第一个对象放进去
3.在放第二个对象,如果此对象的键在resultObj里面有,则合并。没有则直接写入
*/
function mergerObject(...objs) {
    const resultObj = {};
    objs.forEach(obj=>{
        //获取当前对象的所有属性;
        Object.keys(obj).forEach(key=>{
             // 如果resultObjt还没有key值属性
             if(resultObj.hasOwnproperty(key)){
                 //如果有合并
                 resultObj[key] = [].concat(resultObj[key],obj[key]);
             }else{
                 //如果没有则直接写入
                resultObj[key] = obj[key];
             }
        })
    })
    return resultObj;
}

数据拷贝

深浅拷贝只是针对引用数据类型
复制之后的副本进行修改会不会影响到原来的

浅拷贝:修改拷贝以后的数据会影响原数据,拷贝的引用。使得原数据不安全。(只拷贝一层)
深拷贝:修改拷贝以后的数据不会影响原数据,拷贝的时候生成新数据。

浅拷贝

ES6 的扩展运算符
function clone1(target) {
    //深浅拷贝针对对象
    if(typeof target ==='object' && target !==null){
        if(Array.isArray(target)){
            return [...target];
        }else{
            return {...target}
        }
    }else{
        return target;
    }
}

扩展运算符只能对一层进行深拷贝,如果拷贝的层数超过了一层的话,那么就会进行浅拷贝。

ES5的浅拷贝
function clone2(target) {
    if(typeof target ==='object' && target !==null){
        //创建一个容器
        const result = Array.isArray(target)?[]:{};
        //for in 既可以遍历数组也可以遍历对象,但是他会遍历原型上的方法
        for (let key in target) {
            if (target.hasOwnProperty(key)) {
                result[key] = target[key];  
            }
        }
        return result;
    }
    else{
        return target;
    }
}
Array.prototype.concat()/Array.prototype.slice(startindex,endindex) 仅针对数组
let arr= [1,3,{name:'ran'}]
let arr2 = arr.slice()
arr2[1]=55
arr2[2].name = 'ke'
console.log(arr); // 输出[1,3,{name:'ke'}]
   
let arr= [1,3,{name:'ran'}]
let arr2 = arr.concat()
// arr2[1]=55  //输出[1,3,{name:'ran'}]
arr2[3].name = 'ke'
console.log(arr);
//输出 [1,3,{name:'ke'}]

深拷贝

实现一: JSON.parse(JSON.stringify())

问题1: 函数属性会丢失,不能克隆方法
问题2: 循环引用会出错

//循环引用:b中引用了c,c中又有b
obj = {
b:['a','f'],
c:{h:20}
}
obj.b.push(obj.c);
obj.c.j = obj.b;
b:['a','f',{h:20,j:[]}],
c:{h:20,j:['a','f',[]]}

JSON.stringify():将JavaScript对象转换为JSON字符串
JSON.parse():可以将JSON字符串转为一个对象。

function deepClone1(target) {
    //通过数组创建JSON格式的字符串
    let str = JSON.stringify(target);
    //将JSON格式的字符串转换为JS数据
    let data = JSON.parse(str);
    return data;
}
实现二: 递归拷贝

判断是否是引用类型,引用类型循环遍历内元素进行复制

解决问题1: 函数属性还没丢失

function deepClone2(target) {
    if(typeof target ==='object' && target !==null){
        const result = Array.isArray(target)?[]:{};
        for (let key in target) {//检测是否本身的元素
            if (target.hasOwnProperty(key)) {
                result[key] = deepClone2(key);
            }
        }
        return result;
    }else{
        return target;
    }
}
实现三:Map + 递归

实现二不能解决问题的原因是:死循环?

解决问题2: 循环引用正常

思路:保证对象只克隆一次,就不会套娃了。使用Map存储已经克隆之后的对象。

//加入Map保证克隆过的不再克隆
function deepClone2(target,map=new Map()) {
    if(typeof target ==='object' && target !==null){
        //先判断是否已经克隆过了
        let cloneLet = map.get(target);
        if(cache)return cache;
        const result = Array.isArray(target)?[]:{};
        //key为原对象,result为克隆的对象,如果克隆过则直接取克隆之后的对象就可以了
        map.set(target,result);
        for (let key in target) {//检测是否本身的元素
            if (target.hasOwnProperty(key)) {
                result[key] = deepClone3(key,map);
            }
        }
        return result;
    }else{
        return target;
    }
}
实现四:优化遍历性能

for-in效率比较低,因为还需要遍历原型上的方法和属性

数组: while | for | forEach() 优于 for-in | keys()&forEach()
对象: for-in 与 keys()&forEach() 差不多

//map存放已经拷贝过的对象,key为需要拷贝的对象,value为拷贝后的对象
function deepClone3(target,map=new Map()){
	//1.判断是否是引用类型
	if(typeof target === 'object' && target !==null ){
		if(map.has(target))return map.get(target); //说明已经拷贝过了
		let isArr = Array.isArray(target);
		let res = isArr?[]:{};
		map.set(target,res)
		if(isArr){//拷贝的是数组
			target.forEach((item,index) => {
				res[index] = deepClone(item,map);
			});	
		}else{//拷贝的是对象
			Object.keys(target).forEach(key=>{
				 res[key]=deepClone(target[key],map);
			})
		} 
		return res; //返回的是一个数组或对象
	}else{
		return target;
	}

字符串

字符串翻转

字符串倒序
语法: reverseString(str)
功能: 生成一个倒序的字符串

/*
1.先转换成数组 split
2.将数组翻转
3.转化回字符串 join
*/
function reverseString(str) {
    let arr = str.split('');
    //let arr = [...str]
    arr.reverse();
    return arr.join('');
}

检测字符串是否为回文

字符串是否是回文
语法: palindrome(str)
功能: 如果给定的字符串是回文,则返回 true ;否则返回 false

/*
使用双指针,一个从头开始,一个从尾开始,遇见不一样的就返回false
可以先将字符串翻转,判断反转后是否等于翻转前
*/
function palindrome(str) {
    return reverseString(str) === str;
}

DOM事件监听、委托

事件冒泡与事件委托

  • 事件冒泡的流程

    • 基于DOM树形结构
    • 事件在目标元素上处理后, 会由内向外(上)逐层传递
    • 应用场景: 事件代理/委托/委派
      在这里插入图片描述
  • 事件委托/代理

    • 将多个子元素的同类事件监听委托给(绑定在)共同的一个父组件上
    • 好处:
      • 减少内存占用(事件监听的回调变少)
      • 动态添加的内部元素也能响应

实现

语法:addEventListener(element, type, fn, selector)
参数
element:父级元素选择器
type:绑定事件的类型,如click
fn:事件处理程序
selector:需要绑定事件的元素

说明:如果selector没有,直接给element绑定事件,如果selector有,将selector对应的多个元素的事件委托绑定给父元素element

function addEventListener1(element, type, fn, selector) {
    //判断el的类型,获取到el元素
    if(typeof element === 'string')element=document.querySelector(element);
  
    // 如果没有指定selector, 普通的事件绑定
    if (!selector) {
      element.addEventListener(type, fn)
    } else {// 否则是代委托的事件绑定,点击之后先判断是不是需要触发的子元素
      element.addEventListener(type, function (event) {
        // 得到真正发生事件的目标元素
        const target = event.target
        // 如果与选择器匹配则触发,不是则不触发
        console.log(target);
        if (target.matches(selector)) {
          // 调用处理事件的回调fn, 并指定this为目标元素, 参数为event
          fn.call(target, event)
        }
      })
    }
  }

事件总线

eventBus: 包含所有功能的事件总线对象
eventBus.on(eventName, listener): 绑定事件监听
eventBus.emit(eventName, data): 分发事件
eventBus.off(eventName): 解绑指定事件名的事件监听, 如果没有指定解绑所有

//为一个事件绑定多个回调
 eventBus.on('add', (data) => {
   console.log('add', data)
})
eventBus.on('add', (data) => {
   console.log('add2', data)
})
//触发事件
eventBus.emit('add', 123)

//实现
  const eventBus = {
    //保存类型与回调的容器
    callback:{}
  };
  //绑定事件
  eventBus.on = function (type,callback) {
    if(this.callback[type]){
      this.callback[type].push(callback);
    }else{
      this.callback[type] = [callback];
    }
  }
  //触发事件
  eventBus.emit = function (type,data) {
    if(this.callback[type] && this.callback[type].length>0){
      this.callback[type].forEach(callback => {
        callback(data);
      });
    }
  }
  //解绑事件
  eventBus.off = function (eventName) {
    if(eventName){
      //清楚对应事件名
      delete this.callback[eventName];
    }else{//全部清除
      this.callback = {};
    }
  }

消息订阅与发布

使用描述

// 订阅pay频道,当支付成功pay的消息触发,商家和骑手接到消息
PubSub.subscribe('pay', (data) => {
  console.log("商家接到了订单,准备开始制作", data)
})
PubSub.subscribe('pay', (data) => {
  console.log("骑手接到了订单,准备开始去取餐", data)
})

//发布消息,上述的两个回调函数都会被触发
PubSub.publish('pay',{
	title:'鱼香肉丝盖饭'
})

与事件总线的区别
可以取消单个订阅,如上图的骑手,因此单个订阅应该有唯一的编号用于取消订阅
事件总线只能取消pay整个事件

PubSub: 包含所有功能的订阅/发布消息的管理者
PubSub.subscribe(msg, subscriber): 订阅消息: 指定消息名和订阅者回调函数
PubSub.publish(msg, data):同步发布消息: 指定消息名和数据
PubSub.publishSync(msg, data): 同步发布消息: 指定消息名和数据

const PubSub = {
    //订阅的唯一编号
    id:0,
    /*
    频道于回调保存容器,
    需要单个取消,同时执行,所以用对象保存pay:{token1:xx,token2:yy}
    */
    callbacks:{

    }
}

/*
订阅频道
*/
PubSub.subscribe = function (channel,callback) {
    let token = "token_" + this.id++;
    //先判断callbacks是不是已经有channel
    if(this.callbacks[channel]){
        this.callbacks[channel][token] = callback;
    }else{//如果没有使用上面this.callbacks[channel]就会找不到了undefined[token]
        this.callbacks[channel] ={
            [token]:callback
        }
    }
   return token;
}
/*
发布消息
*/
PubSub.publish = function (channel,data) {
        if(this.callbacks[channel]){
            Object.values(this.callbacks[channel]).forEach(callback => {
                callback(data)
            })
        }
}

/*
取消订阅
1.没有传值,取消所有
2.传入token字符串,取消对应的token订阅
3.msgName字符串,取消对应的频道
*/
PubSub.unsubscribe = function (flag) {
    if(flag===undefined){
        this.PubSub = {};
    }else if(typeof flag === 'string'){
        if(flag.indexOf('token_'===0)){//订阅id
           let callbackObj= Object.values(this.callbacks).find(obj =>obj.hasOwnProperty(flag));
            if(callbackObj){
                delete callbackObj[flag];
                this.id--;
            }
        }else{//频道的名称
            delete this.callback[flag];
        }
    }   
}

手写ajax请求函数 – 面试:有没有封装过axios?

API相关

语法:

  • axios(options)
    • 参数配置对象:url, method, params与data
    • 返回值为:promise对象
  • axios.get(url, options)
  • axios.post(url, data, options)
  • axios.put(url, data, options)
  • axios.delete(url, options)

功能:使用xhr发送ajax请求的工具函数,与axios库功能类似

使用方法

//使用方式1 配置参数
axios({
      url: 'http://localhost:3000/posts',
      method: 'GET',
      params: {
        id: 1,
        xxx: 'abc'
      }
}).then(
      response => {
        console.log(response)
      },
      error => {
        alert(error.message)
      }
)
//使用方式2 简介语法
axios.get('http://localhost:3000/posts',{
	params:{
		 id: 1,
		  xxx: 'abc'
	}
}).then(
      response => {
        console.log(response)
      },
      error => {
        alert(error.message)
      }
)

axios函数封装

思路
1.先处理参数,有默认值的设置默认参数
2.执行异步ajax请求,如果请求成功了调用resolve(),如果请求失败了调用reject()
2.1 创建xhr对象new XMLHttpRequest()
2.2 初始化请求xmr.open()
2.3 发送请求xmr.send()发送请求。参数为字符串,可以是json对象字符串,也可以是urlencoded格式字符串

function axios({
    url,
    method="GET", //设置默认值
    parmas={},//默认空对象
    data={}//设置默认值
  }) {
    //返回一个Promise对象
    return new Promise((resolve, reject) =>  {
        //处理query参数,拼接到url
        let queryString='';
        Obeject.keys(params).forEach(key=>{
			queryString+=`${key}=${parmas[key]}&`
		})
        if (queryString){
            //去掉最后的&
           queryString = queryString.substring(0,queryString.length-1)
           //拼接
           url += "?"+queryString
        }
        //处理method为大写,传进来的可能是小写
        method = method.toUpperCase()
    //1.执行异步ajax请求
        //创建xhr对象
        const request = new XMLHttpRequest();
        //初始化请求(异步)
        request.open(method,url,true)
        //绑定状态改变的监听,send是异步的,所以绑定监听写在send后面也可以
        request.onreadystatechange = function () {
            //如果请求没有完成,不需要做处理
            if (request.readyState!==4){
                return
            }
             //请求完成了可以获取状态码了
             //如果响应状态码在[200,300)之间代表成功,否则失败
    		 const {status,statusText} = request;
    		      if (status>=200&&status<=299){
        		 //2.1如果请求成功了,调用resolve()
         		 //准备结果response对象
         				const response = {
             				//服务器返回的是JSON数据需要转换成对象
             				//响应json数据自动解析为js的对象/数组
             				data:JSON.parse(request.response),
             				status,
             				statusText,
        	 			}
            			resolve(response)
    	 }else {
         	//2.2如果请求失败,调用reject()
         	reject(new Error(`request error status is ${status}`))
    	 }
        }
        switch (method){
            case "GET"||"DELETE" :
                //get的参数通过url传
                request.send();
                break;
            case "POST"||"PUT":
                //发送JSON请求体的数据
                request.setRequestHeader("Content-Type","application/json;charset=utf");
                request.send(JSON.stringify(data));
                break;
        }
    })
}

axios方法的封装

/* 发送特定请求的静态方法 */
axios.get = function (url, options) {
  return axios(Object.assign(options, {url, method: 'GET'}))
}
axios.delete = function (url, options) {
  return axios(Object.assign(options, {url, method: 'DELETE'}))
}
axios.post = function (url, data, options) {
  return axios(Object.assign(options, {url, data, method: 'POST'}))
}
axios.put = function (url, data, options) {
  return axios(Object.assign(options, {url, data, method: 'PUT'}))
}

promise自定义

笔记:https://editor.csdn.net/md/?articleId=124798768

你可能感兴趣的:(JavaScript,javascript)