面试官:你了解Axios的原理吗?有看过它的源码吗?

news2024/11/6 7:27:12

 🎬 岸边的风:个人主页

 🔥 个人专栏 :《 VUE 》 《 javaScript 》

⛺️ 生活的理想,就是为了理想的生活 !

在这里插入图片描述

目录

一、axios的使用

二、实现一个简易版axios

三、源码分析

小结


 

一、axios的使用

关于axios的基本使用,上篇文章已经有所涉及,这里再稍微回顾下:

发送请求

import axios from 'axios';

axios(config) // 直接传入配置
axios(url[, config]) // 传入url和配置
axios[method](url[, option]) // 直接调用请求方式方法,传入url和配置
axios[method](url[, data[, option]]) // 直接调用请求方式方法,传入data、url和配置
axios.request(option) // 调用 request 方法

const axiosInstance = axios.create(config)
// axiosInstance 也具有以上 axios 的能力

axios.all([axiosInstance1, axiosInstance2]).then(axios.spread(response1, response2))
// 调用 all 和传入 spread 回调
请求拦截器

axios.interceptors.request.use(function (config) {
    // 这里写发送请求前处理的代码
    return config;
}, function (error) {
    // 这里写发送请求错误相关的代码
    return Promise.reject(error);
});
响应拦截器

axios.interceptors.response.use(function (response) {
    // 这里写得到响应数据后处理的代码
    return response;
}, function (error) {
    // 这里写得到错误响应处理的代码
    return Promise.reject(error);
});

取消请求

// 方式一
const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('xxxx', {
  cancelToken: source.token
})
// 取消请求 (请求原因是可选的)
source.cancel('主动取消请求');

// 方式二
const CancelToken = axios.CancelToken;
let cancel;

axios.get('xxxx', {
  cancelToken: new CancelToken(function executor(c) {
    cancel = c;
  })
});
cancel('主动取消请求');

二、实现一个简易版axios

构建一个Axios构造函数,核心代码为request

class Axios {
    constructor() {

    }

    request(config) {
        return new Promise(resolve => {
            const {url = '', method = 'get', data = {}} = config;
            // 发送ajax请求
            const xhr = new XMLHttpRequest();
            xhr.open(method, url, true);
            xhr.onload = function() {
                console.log(xhr.responseText)
                resolve(xhr.responseText);
            }
            xhr.send(data);
        })
    }
}

导出axios实例

// 最终导出axios的方法,即实例的request方法
function CreateAxiosFn() {
    let axios = new Axios();
    let req = axios.request.bind(axios);
    return req;
}

// 得到最后的全局变量axios
let axios = CreateAxiosFn();

上述就已经能够实现axios({ })这种方式的请求

下面是来实现下axios.method()这种形式的请求

// 定义get,post...方法,挂在到Axios原型上
const methodsArr = ['get', 'delete', 'head', 'options', 'put', 'patch', 'post'];
methodsArr.forEach(met => {
    Axios.prototype[met] = function() {
        console.log('执行'+met+'方法');
        // 处理单个方法
        if (['get', 'delete', 'head', 'options'].includes(met)) { // 2个参数(url[, config])
            return this.request({
                method: met,
                url: arguments[0],
                ...arguments[1] || {}
            })
        } else { // 3个参数(url[,data[,config]])
            return this.request({
                method: met,
                url: arguments[0],
                data: arguments[1] || {},
                ...arguments[2] || {}
            })
        }

    }
})

Axios.prototype上的方法搬运到request

首先实现个工具类,实现将b方法混入到a,并且修改this指向

const utils = {
  extend(a,b, context) {
    for(let key in b) {
      if (b.hasOwnProperty(key)) {
        if (typeof b[key] === 'function') {
          a[key] = b[key].bind(context);
        } else {
          a[key] = b[key]
        }
      }
      
    }
  }
}

修改导出的方法

function CreateAxiosFn() {
  let axios = new Axios();
  
  let req = axios.request.bind(axios);
  // 增加代码
  utils.extend(req, Axios.prototype, axios)
  
  return req;
}

构建拦截器的构造函数

class InterceptorsManage {
  constructor() {
    this.handlers = [];
  }

  use(fullfield, rejected) {
    this.handlers.push({
      fullfield,
      rejected
    })
  }
}

实现axios.interceptors.response.useaxios.interceptors.request.use

class Axios {
    constructor() {
        // 新增代码
        this.interceptors = {
            request: new InterceptorsManage,
            response: new InterceptorsManage
        }
    }

    request(config) {
 		...
    }
}

执行语句axios.interceptors.response.useaxios.interceptors.request.use的时候,实现获取axios实例上的interceptors对象,然后再获取responserequest拦截器,再执行对应的拦截器的use方法

Axios上的方法和属性搬到request过去

function CreateAxiosFn() {
  let axios = new Axios();
  
  let req = axios.request.bind(axios);
  // 混入方法, 处理axios的request方法,使之拥有get,post...方法
  utils.extend(req, Axios.prototype, axios)
  // 新增代码
  utils.extend(req, axios)
  return req;
}

现在request也有了interceptors对象,在发送请求的时候,会先获取request拦截器的handlers的方法来执行

首先将执行ajax的请求封装成一个方法

request(config) {
    this.sendAjax(config)
}
sendAjax(config){
    return new Promise(resolve => {
        const {url = '', method = 'get', data = {}} = config;
        // 发送ajax请求
        console.log(config);
        const xhr = new XMLHttpRequest();
        xhr.open(method, url, true);
        xhr.onload = function() {
            console.log(xhr.responseText)
            resolve(xhr.responseText);
        };
        xhr.send(data);
    })
}

获得handlers中的回调

request(config) {
    // 拦截器和请求组装队列
    let chain = [this.sendAjax.bind(this), undefined] // 成对出现的,失败回调暂时不处理

    // 请求拦截
    this.interceptors.request.handlers.forEach(interceptor => {
        chain.unshift(interceptor.fullfield, interceptor.rejected)
    })

    // 响应拦截
    this.interceptors.response.handlers.forEach(interceptor => {
        chain.push(interceptor.fullfield, interceptor.rejected)
    })

    // 执行队列,每次执行一对,并给promise赋最新的值
    let promise = Promise.resolve(config);
    while(chain.length > 0) {
        promise = promise.then(chain.shift(), chain.shift())
    }
    return promise;
}

chains大概是['fulfilled1','reject1','fulfilled2','reject2','this.sendAjax','undefined','fulfilled2','reject2','fulfilled1','reject1']这种形式

这样就能够成功实现一个简易版axios

三、源码分析

首先看看目录结构

axios发送请求有很多实现的方法,实现入口文件为axios.js

function createInstance(defaultConfig) {
  var context = new Axios(defaultConfig);

  // instance指向了request方法,且上下文指向context,所以可以直接以 instance(option) 方式调用 
  // Axios.prototype.request 内对第一个参数的数据类型判断,使我们能够以 instance(url, option) 方式调用
  var instance = bind(Axios.prototype.request, context);

  // 把Axios.prototype上的方法扩展到instance对象上,
  // 并指定上下文为context,这样执行Axios原型链上的方法时,this会指向context
  utils.extend(instance, Axios.prototype, context);

  // Copy context to instance
  // 把context对象上的自身属性和方法扩展到instance上
  // 注:因为extend内部使用的forEach方法对对象做for in 遍历时,只遍历对象本身的属性,而不会遍历原型链上的属性
  // 这样,instance 就有了  defaults、interceptors 属性。
  utils.extend(instance, context);
  return instance;
}

// Create the default instance to be exported 创建一个由默认配置生成的axios实例
var axios = createInstance(defaults);

// Factory for creating new instances 扩展axios.create工厂函数,内部也是 createInstance
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Expose all/spread
axios.all = function all(promises) {
  return Promise.all(promises);
};

axios.spread = function spread(callback) {
  return function wrap(arr) {
    return callback.apply(null, arr);
  };
};
module.exports = axios;

主要核心是 Axios.prototype.request,各种请求方式的调用实现都是在 request 内部实现的, 简单看下 request 的逻辑

Axios.prototype.request = function request(config) {
  // Allow for axios('example/url'[, config]) a la fetch API
  // 判断 config 参数是否是 字符串,如果是则认为第一个参数是 URL,第二个参数是真正的config
  if (typeof config === 'string') {
    config = arguments[1] || {};
    // 把 url 放置到 config 对象中,便于之后的 mergeConfig
    config.url = arguments[0];
  } else {
    // 如果 config 参数是否是 字符串,则整体都当做config
    config = config || {};
  }
  // 合并默认配置和传入的配置
  config = mergeConfig(this.defaults, config);
  // 设置请求方法
  config.method = config.method ? config.method.toLowerCase() : 'get';
  /*
    something... 此部分会在后续拦截器单独讲述
  */
};

// 在 Axios 原型上挂载 'delete', 'get', 'head', 'options' 且不传参的请求方法,实现内部也是 request
utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) {
  Axios.prototype[method] = function(url, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url
    }));
  };
});

// 在 Axios 原型上挂载 'post', 'put', 'patch' 且传参的请求方法,实现内部同样也是 request
utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) {
  Axios.prototype[method] = function(url, data, config) {
    return this.request(utils.merge(config || {}, {
      method: method,
      url: url,
      data: data
    }));
  };
});
request入口参数为config,可以说config贯彻了axios的一生

axios 中的 config 主要分布在这几个地方:

默认配置 defaults.js
config.method默认为 get
调用 createInstance 方法创建 axios 实例,传入的config
直接或间接调用 request 方法,传入的 config
// axios.js
// 创建一个由默认配置生成的axios实例
var axios = createInstance(defaults);

// 扩展axios.create工厂函数,内部也是 createInstance
axios.create = function create(instanceConfig) {
  return createInstance(mergeConfig(axios.defaults, instanceConfig));
};

// Axios.js
// 合并默认配置和传入的配置
config = mergeConfig(this.defaults, config);
// 设置请求方法
config.method = config.method ? config.method.toLowerCase() : 'get';

从源码中,可以看到优先级:默认配置对象default < method:get < Axios的实例属性this.default < request参数

下面重点看看request方法

Axios.prototype.request = function request(config) {
  /*
    先是 mergeConfig ... 等,不再阐述
  */
  // Hook up interceptors middleware 创建拦截器链. dispatchRequest 是重中之重,后续重点
  var chain = [dispatchRequest, undefined];

  // push各个拦截器方法 注意:interceptor.fulfilled 或 interceptor.rejected 是可能为undefined
  this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
    // 请求拦截器逆序 注意此处的 forEach 是自定义的拦截器的forEach方法
    chain.unshift(interceptor.fulfilled, interceptor.rejected);
  });

  this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
    // 响应拦截器顺序 注意此处的 forEach 是自定义的拦截器的forEach方法
    chain.push(interceptor.fulfilled, interceptor.rejected);
  });

  // 初始化一个promise对象,状态为resolved,接收到的参数为已经处理合并过的config对象
  var promise = Promise.resolve(config);

  // 循环拦截器的链
  while (chain.length) {
    promise = promise.then(chain.shift(), chain.shift()); // 每一次向外弹出拦截器
  }
  // 返回 promise
  return promise;
};
拦截器interceptors是在构建axios实例化的属性

function Axios(instanceConfig) {
  this.defaults = instanceConfig;
  this.interceptors = {
    request: new InterceptorManager(), // 请求拦截
    response: new InterceptorManager() // 响应拦截
  };
}
InterceptorManager构造函数

// 拦截器的初始化 其实就是一组钩子函数
function InterceptorManager() {
  this.handlers = [];
}

// 调用拦截器实例的use时就是往钩子函数中push方法
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  this.handlers.push({
    fulfilled: fulfilled,
    rejected: rejected
  });
  return this.handlers.length - 1;
};

// 拦截器是可以取消的,根据use的时候返回的ID,把某一个拦截器方法置为null
// 不能用 splice 或者 slice 的原因是 删除之后 id 就会变化,导致之后的顺序或者是操作不可控
InterceptorManager.prototype.eject = function eject(id) {
  if (this.handlers[id]) {
    this.handlers[id] = null;
  }
};

// 这就是在 Axios的request方法中 中循环拦截器的方法 forEach 循环执行钩子函数
InterceptorManager.prototype.forEach = function forEach(fn) {
  utils.forEach(this.handlers, function forEachHandler(h) {
    if (h !== null) {
      fn(h);
    }
  });
}

请求拦截器方法是被 unshift到拦截器中,响应拦截器是被push到拦截器中的。最终它们会拼接上一个叫dispatchRequest的方法被后续的 promise 顺序执行

var utils = require('./../utils');
var transformData = require('./transformData');
var isCancel = require('../cancel/isCancel');
var defaults = require('../defaults');
var isAbsoluteURL = require('./../helpers/isAbsoluteURL');
var combineURLs = require('./../helpers/combineURLs');

// 判断请求是否已被取消,如果已经被取消,抛出已取消
function throwIfCancellationRequested(config) {
  if (config.cancelToken) {
    config.cancelToken.throwIfRequested();
  }
}

module.exports = function dispatchRequest(config) {
  throwIfCancellationRequested(config);

  // 如果包含baseUrl, 并且不是config.url绝对路径,组合baseUrl以及config.url
  if (config.baseURL && !isAbsoluteURL(config.url)) {
    // 组合baseURL与url形成完整的请求路径
    config.url = combineURLs(config.baseURL, config.url);
  }

  config.headers = config.headers || {};

  // 使用/lib/defaults.js中的transformRequest方法,对config.headers和config.data进行格式化
  // 比如将headers中的Accept,Content-Type统一处理成大写
  // 比如如果请求正文是一个Object会格式化为JSON字符串,并添加application/json;charset=utf-8的Content-Type
  // 等一系列操作
  config.data = transformData(
    config.data,
    config.headers,
    config.transformRequest
  );

  // 合并不同配置的headers,config.headers的配置优先级更高
  config.headers = utils.merge(
    config.headers.common || {},
    config.headers[config.method] || {},
    config.headers || {}
  );

  // 删除headers中的method属性
  utils.forEach(
    ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'],
    function cleanHeaderConfig(method) {
      delete config.headers[method];
    }
  );

  // 如果config配置了adapter,使用config中配置adapter的替代默认的请求方法
  var adapter = config.adapter || defaults.adapter;

  // 使用adapter方法发起请求(adapter根据浏览器环境或者Node环境会有不同)
  return adapter(config).then(
    // 请求正确返回的回调
    function onAdapterResolution(response) {
      // 判断是否以及取消了请求,如果取消了请求抛出以取消
      throwIfCancellationRequested(config);

      // 使用/lib/defaults.js中的transformResponse方法,对服务器返回的数据进行格式化
      // 例如,使用JSON.parse对响应正文进行解析
      response.data = transformData(
        response.data,
        response.headers,
        config.transformResponse
      );

      return response;
    },
    // 请求失败的回调
    function onAdapterRejection(reason) {
      if (!isCancel(reason)) {
        throwIfCancellationRequested(config);

        if (reason && reason.response) {
          reason.response.data = transformData(
            reason.response.data,
            reason.response.headers,
            config.transformResponse
          );
        }
      }
      return Promise.reject(reason);
    }
  );
};

再来看看axios是如何实现取消请求的,实现文件在CancelToken.js

function CancelToken(executor) {
  if (typeof executor !== 'function') {
    throw new TypeError('executor must be a function.');
  }
  // 在 CancelToken 上定义一个 pending 状态的 promise ,将 resolve 回调赋值给外部变量 resolvePromise
  var resolvePromise;
  this.promise = new Promise(function promiseExecutor(resolve) {
    resolvePromise = resolve;
  });

  var token = this;
  // 立即执行 传入的 executor函数,将真实的 cancel 方法通过参数传递出去。
  // 一旦调用就执行 resolvePromise 即前面的 promise 的 resolve,就更改promise的状态为 resolve。
  // 那么xhr中定义的 CancelToken.promise.then方法就会执行, 从而xhr内部会取消请求
  executor(function cancel(message) {
    // 判断请求是否已经取消过,避免多次执行
    if (token.reason) {
      return;
    }
    token.reason = new Cancel(message);
    resolvePromise(token.reason);
  });
}

CancelToken.source = function source() {
  // source 方法就是返回了一个 CancelToken 实例,与直接使用 new CancelToken 是一样的操作
  var cancel;
  var token = new CancelToken(function executor(c) {
    cancel = c;
  });
  // 返回创建的 CancelToken 实例以及取消方法
  return {
    token: token,
    cancel: cancel
  };
};

实际上取消请求的操作是在 xhr.js 中也有响应的配合的

if (config.cancelToken) {
    config.cancelToken.promise.then(function onCanceled(cancel) {
        if (!request) {
            return;
        }
        // 取消请求
        request.abort();
        reject(cancel);
    });
}

巧妙的地方在 CancelToken中 executor 函数,通过resolve函数的传递与执行,控制promise的状态

小结

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1024993.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

暨南大学旅游管理《乡村振兴战略下传统村落文化旅游设计》校友许少辉—2023学生开学季辉少许

暨南大学旅游管理《乡村振兴战略下传统村落文化旅游设计》校友许少辉——2023学生开学季辉少许

软文发布是推动企业发展的无形动力 | 媒介启航

同样是销售产品&#xff0c;为什么以故事的形式呈现要比直接讲产品功效更易让消费者认同和销售呢&#xff1f;这就是软文的魅力&#xff0c;它极好的运用了人作为情感动物的喜好。因此&#xff0c;各大小企业纷纷做软文&#xff0c;比如溯源啊、初心啊、情怀啊、创业经历啊等等…

Java集成微信支付实现企业付款到零钱和商家转账到零钱的功能

Java集成微信支付实现企业付款到零钱和商家转账到零钱的功能 文章目录 [toc] 1.企业付款到零钱和商家转账到零钱的区别1.1 申请要求不同1.2 API接口不同1.3 用户收款限制1.4 商户付款额度1.5 派发方式不同1.6 打款方式不同 2.集成实现2.1 v2版本集成2.2 依赖2.3 配置2.3.1 naco…

java多线程学习笔记一

一、线程的概述 1.1 线程的相关概念 1.1.1 进程&#xff08;Process&#xff09; 进程&#xff08;Process&#xff09;是计算机的程序关于某数据集合上的一次运行活动&#xff0c;是操作系统进行资源分配与调度的基本单位。 可以把进程简单的理解为操作系统中正在有运行的一…

插入排序代码及时间空间复杂度

插入排序&#xff08;Insertion Sort&#xff09;是一种简单的排序算法&#xff0c;它将一个数组分成已排序和未排序两部分&#xff0c;然后逐步将未排序部分的元素插入已排序部分的正确位置。以下是插入排序的代码示例以及时间和空间复杂度分析&#xff0c;希望对大家有所帮助…

Markdown 文档标题序号重排版(WebUI Tool)

Markdown 标题重编号 1. 项目背景 在日常写 Markdown 时&#xff0c;我们可能会遇到这样的情况&#xff1a; 文档的迁移与整合&#xff1a;在迁移或整合文档时&#xff0c;可能会让原本的标题编号混乱文档的重构&#xff1a;在重构文档时&#xff0c;例如仅仅修改了一处标题&…

malloc与free

目录 前提须知&#xff1a; malloc&#xff1a; 大意&#xff1a; 头文件&#xff1a; 申请空间&#xff1a; 判断是否申请成功&#xff1a; 使用空间&#xff1a; 结果&#xff1a; 整体代码&#xff1a; malloc申请的空间怎么回收呢? 注意事项&#xff1a; free:…

uniapp级联菜单地点区域使用label值,web端el-cascader绑定的value

效果图 一、uniapp uniapp级联菜单地点区域使用label值 1.ui使用 <uni-forms-item label="地址" name="userArea" required><view class="" style="height: 100%;display: flex;align-items: center;">

神经网络 03(参数初始化)

一、参数初始化 对于某一个神经元来说&#xff0c;需要初始化的参数有两类&#xff1a;一类是权重W&#xff0c;还有一类是偏置b&#xff0c;偏置b初始化为0即可。而权重W的初始化比较重要&#xff0c;我们着重来介绍常见的初始化方式。 &#xff08;1&#xff09;随机初始化 …

zabbix监控告警邮箱提醒,钉钉提醒

一、注册网易邮箱及其配置邮箱 1、开启POP3/SMTP/IMAP 二、service端配置邮件服务 1.安装 mailx dos2unix yum install -y mailx dos2unix mailx&#xff1a;邮件服务 mos2unix&#xff1a;用于转换文本文件格式的实用工具 查看mailx版本 2.配置mailx配置文件 编辑&#xf…

控制理论::带零点的二阶系统时域响应分析

一、系统描述 二、系统分析(分类讨论分析) 1、类型一&#xff08;极点为实数&#xff08;阻尼比>1&#xff09;&#xff0c;零点为负实数&#xff08;τ-1/b<0&#xff09;&#xff09; 1.1 定性分析 1.2 定量分析 有零点二阶系统的动态性能分析 - 豆丁网 (docin.com)

Python WEB框架FastAPI (二)

Python WEB框架FastAPI &#xff08;二&#xff09; 最近一直在使用fastapi&#xff0c;随着使用的深入发现我对于它的了解还是太少了&#xff0c;以至于踩了一些坑。所以在这里记录一下&#xff0c;愿看到的小伙伴不迷路。 路径传参并发问题 一、路径传参 这是对上一个传参…

TTS | 利用Fastspeech训练LJSpeech语音数据集后英文文本生成语音及代码详解

FastSpeech 基于 Transformer 的前馈网络&#xff0c;用于并行生成 TTS 梅尔谱图。 FastSpeech 模型与自回归 Transformer TTS 相比&#xff0c;梅尔谱图生成速度加快了 270 倍&#xff0c;端到端语音合成速度加快了 38 倍。 项目实现 docker cp LJSpeech-1.1.tar.bz2 torch_…

Kibana 安装部署 - Centos7

Kibana 安装部署 - Centos7 本文介绍一下在Centos7上部署和配置Kibana1、下载 直接去官网地址进行下载即可。【注意】&#xff1a; 一定要下载和你的ES版本一致的Kibana。比如我的ES的版本是 7.9.1&#xff0c;所以我下载的kibana就是7.9.1。下载地址 &#xff1a; https://w…

活动报名|如何使用70万预算从头训练千亿语言大模型

王业全 北京智源人工智能研究院认知模型团队负责人&#xff0c;清华大学博士&#xff0c;中国中文信息学会情感计算专委会委员&#xff0c;2022年被评为AI 2000全球最具影响力人工智能学者&#xff08;自然语言处理领域&#xff09;。主要从事语言大模型、自然语言处理方面的研…

Laravel框架 - 中间件篇

什么是中间件&#xff1f; 在 Laravel 框架中&#xff0c;中间件是一种用于处理 HTTP 请求的组件。它允许你在请求进入 路由 处理 之前 或 之后 执行一些代码逻辑。 中间件的优势和功能 处理身份验证&#xff1a;验证用户是否已经登录或者检查用户是否有权限访问特定的路由 记…

国外发达国家码农是真混得好么?

来看看花旗工作十多年的码农怎么说吧! 美国最大的论坛 Reddit&#xff0c;之前有一个热帖&#xff1a; 一个程序员说自己喝醉了&#xff0c;软件工程师已经当了10年&#xff0c;心里有 好多话想说&#xff0c;“我可能会后悔今天说了这些话。”他洋洋洒洒写了 一大堆&#xff…

Knife4jInsight ,Knife4j 的商业化产品之路

Knife4jInsight &#xff0c;Knife4j 的商业化产品之路 写在前面产品定位产品名称技术架构功能架构产品定价最后 Knife4jInsight &#xff0c;Knife4j 的商业化产品之路 在之前发布的《Knife4j新产品的想法》一文中&#xff0c;我提到想给Knife4j的生态做一些扩展&#xff0c…

使用Python和XPath解析动态JSON数据

JSON动态数据在Python中扮演着重要的角色&#xff0c;为开发者提供了处理实时和灵活数据的能力。Python作为一种强大的编程语言&#xff0c;提供了丰富的工具和库来处理动态JSON数据使得解析和处理动态JSON数据变得简单和高效。例如&#xff0c;使用内置的json模块&#xff0c;…

SQL Server Management Studio 打开非常慢

一、去掉启动画面(感觉没用) 目标增加参数 /nosplash 二、打开控制面板 设置 Internet选项 基本上5秒就可以打开了