美文网首页
axios源码解析(一)工具函数

axios源码解析(一)工具函数

作者: 前端开发爱好者 | 来源:发表于2019-10-09 22:27 被阅读0次

    写在开头

    前置知识内容axios的使用,JavaScript

    本文对应的版本为0.18.X
    下载源码

    框架结构

    工具函数(helper/*.js)

    1.实现bind函数

    框架中模拟实现了JavaScript原生的bind函数,作用相同。bind函数位于lib/helpers/bind.js,通过node模块会引入到util.js

    function bind (fn,thisArg){
        return function wrap(){
            var args=new Array(argumnets.length);
            for (var i=0;i<args.length;i++){
                arg[i]=arguments[i]
            }
            return fn.apply(thisArg,arg);
        }
    }
    

    在实现中通过遍历将函数的类数组对象arguments转为了数组。

    2. 构建url的buildURL函数

    函数主要用于将查询参数拼接到url地址后面,函数接受三个参数url,参数params,用于序列化params的函数paramsSerializer

    function encode(val) {
      return encodeURIComponent(val).
        replace(/%40/gi, '@').
        replace(/%3A/gi, ':').
        replace(/%24/g, '$').
        replace(/%2C/gi, ',').
        replace(/%20/g, '+').
        replace(/%5B/gi, '[').
        replace(/%5D/gi, ']');
    }
    
    function buildURL(url, params, paramsSerializer) {
      if (!params) {
        return url;
      }
    
      var serializedParams;
      if (paramsSerializer) {
        serializedParams = paramsSerializer(params);
      } else if (utils.isURLSearchParams(params)) {
        serializedParams = params.toString();
      } else {
        var parts = [];
    
        utils.forEach(params, function serialize(val, key) {
          if (val === null || typeof val === 'undefined') {
            return;
          }
    
          if (utils.isArray(val)) {
            key = key + '[]';
          } else {
            val = [val];
          }
    
          utils.forEach(val, function parseValue(v) {
            if (utils.isDate(v)) {
              v = v.toISOString();
            } else if (utils.isObject(v)) {
              v = JSON.stringify(v);
            }
            parts.push(encode(key) + '=' + encode(v));
          });
        });
    
        serializedParams = parts.join('&');
      }
    
      if (serializedParams) {
        var hashmarkIndex = url.indexOf('#');
        if (hashmarkIndex !== -1) {
          url = url.slice(0, hashmarkIndex);
        }
    
        url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams;
      }
    
      return url;
    }
    
    

    函数首先会使用序列化函数对params进行序列化,如果没有传递参数。那么会检查params参数的类型是否是URLSearchParams,如果是则调用其toString方法获得参数。如果都不满足,那么会遍历params对象并将key,value进行encode,转为数组,然后将数组转为字符串。然后对serializedParams进行判断,去掉哈希,最后在和url拼接为地址,将地址返回。

    3.combineURLs.js

    function combineURLs(baseURL, relativeURL) {
      return relativeURL
        ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
        : baseURL;
    };
    
    

    函数接受两个参数,baseUrl和relativeURL,返回值为去掉baseURL的最后一个'/'和releativeURL最前面的'/'的拼接。

    4.用于操作cookie的cookies.js

    module.exports = (
      utils.isStandardBrowserEnv() ?
    
      // Standard browser envs support document.cookie
        (function standardBrowserEnv() {
          return {
            write: function write(name, value, expires, path, domain, secure) {
              var cookie = [];
              cookie.push(name + '=' + encodeURIComponent(value));
    
              if (utils.isNumber(expires)) {
                cookie.push('expires=' + new Date(expires).toGMTString());
              }
    
              if (utils.isString(path)) {
                cookie.push('path=' + path);
              }
    
              if (utils.isString(domain)) {
                cookie.push('domain=' + domain);
              }
    
              if (secure === true) {
                cookie.push('secure');
              }
    
              document.cookie = cookie.join('; ');
            },
    
            read: function read(name) {
              var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)'));
              return (match ? decodeURIComponent(match[3]) : null);
            },
    
            remove: function remove(name) {
              this.write(name, '', Date.now() - 86400000);
            }
          };
        })() :
    
      // Non standard browser env (web workers, react-native) lack needed support.
        (function nonStandardBrowserEnv() {
          return {
            write: function write() {},
            read: function read() { return null; },
            remove: function remove() {}
          };
        })()
    );
    

    函数封装了对于cookie的三个操作,write,read,remove。具体实现上首先判断了环境,如果不是浏览器环境,直接返回空的函数。是浏览器环境的话,借助于数组实现了write函数,借助于正则实现了read,通过将cookie的有效期设置为前一天来清除cookie。

    5.deprecatedMethod.js

    module.exports = function deprecatedMethod(method, instead, docs) {
      try {
        console.warn(
          'DEPRECATED method `' + method + '`.' +
          (instead ? ' Use `' + instead + '` instead.' : '') +
          ' This method will be removed in a future release.');
    
        if (docs) {
          console.warn('For more information about usage see ' + docs);
        }
      } catch (e) { /* Ignore */ }
    };
    
    

    当方法被弃用时,打印警告信息

    6.isAbsoluteURL

    module.exports = function isAbsoluteURL(url) {
      // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
      // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
      // by any combination of letters, digits, plus, period, or hyphen.
      return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url);
    };
    

    7.isURLSameOrigin

    module.exports = (
      utils.isStandardBrowserEnv() ?
    
      // Standard browser envs have full support of the APIs needed to test
      // whether the request URL is of the same origin as current location.
        (function standardBrowserEnv() {
        // 判断是不是IE
          var msie = /(msie|trident)/i.test(navigator.userAgent);
          var urlParsingNode = document.createElement('a');
          var originURL;
    
          /**
        * Parse a URL to discover it's components
        *
        * @param {String} url The URL to be parsed
        * @returns {Object}
        */
          function resolveURL(url) {
            var href = url;
    
            if (msie) {
            // IE needs attribute set twice to normalize properties
              urlParsingNode.setAttribute('href', href);
              href = urlParsingNode.href;
            }
    
            urlParsingNode.setAttribute('href', href);
    
            // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils
            return {
              href: urlParsingNode.href,
              protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '',
              host: urlParsingNode.host,
              search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '',
              hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '',
              hostname: urlParsingNode.hostname,
              port: urlParsingNode.port,
              pathname: (urlParsingNode.pathname.charAt(0) === '/') ?
                urlParsingNode.pathname :
                '/' + urlParsingNode.pathname
            };
          }
    
          originURL = resolveURL(window.location.href);
    
          /**
        * Determine if a URL shares the same origin as the current location
        *
        * @param {String} requestURL The URL to test
        * @returns {boolean} True if URL shares the same origin, otherwise false
        */
          return function isURLSameOrigin(requestURL) {
            var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL;
            return (parsed.protocol === originURL.protocol &&
                parsed.host === originURL.host);
          };
        })() :
    
      // Non standard browser envs (web workers, react-native) lack needed support.
        (function nonStandardBrowserEnv() {
          return function isURLSameOrigin() {
            return true;
          };
        })()
    );
    

    判断是否同源

    前提:需要了解浏览器的navigator基本属性。

    函数首先判断了当前的运行环境,非浏览器环境不存在跨域问题,直接返回false。

    浏览器环境首先创建了一个函数用于解析绝对的url地址resolveURl。然后分别解析window.location.href和传入的参数,比较协议和域名是否相同。

    resolveURL中借助于a标签对url进行解析,解析过程中处理了IE浏览器需要设置两次href的问题。

    引申思考怎么对url进行解析,方式有哪些。

    8.normalizeHeaderName.js

    function normalizeHeaderName(headers, normalizedName) {
      utils.forEach(headers, function processHeader(value, name) {
        if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) {
          headers[normalizedName] = value;
          delete headers[name];
        }
      });
    };
    

    对对象属性名的进行格式化,删除,新建符合大小写规范的属性。

    9.parseHeaders.js

    // Headers whose duplicates are ignored by node
    // c.f. https://nodejs.org/api/http.html#http_message_headers
    var ignoreDuplicateOf = [
      'age', 'authorization', 'content-length', 'content-type', 'etag',
      'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since',
      'last-modified', 'location', 'max-forwards', 'proxy-authorization',
      'referer', 'retry-after', 'user-agent'
    ];
    
    /**
     * Parse headers into an object
     *
     * ```
     * Date: Wed, 27 Aug 2014 08:58:49 GMT
     * Content-Type: application/json
     * Connection: keep-alive
     * Transfer-Encoding: chunked
     * ```
     *
     * @param {String} headers Headers needing to be parsed
     * @returns {Object} Headers parsed into an object
     */
    module.exports = function parseHeaders(headers) {
      var parsed = {};
      var key;
      var val;
      var i;
    
      if (!headers) { return parsed; }
    
      utils.forEach(headers.split('\n'), function parser(line) {
        i = line.indexOf(':');
        key = utils.trim(line.substr(0, i)).toLowerCase();
        val = utils.trim(line.substr(i + 1));
    
        if (key) {
          if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) {
            return;
          }
          if (key === 'set-cookie') {
            parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]);
          } else {
            parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
          }
        }
      });
    
      return parsed;
    };
    
    

    将响应的head转为字符串

    10.spread.js
    函数的作用是将参数展开。因为axios.all().then(callback)中callback函数接受的是一个数组,为了方便使用,将其转化为了一各个的参数。

    function spread(callback) {
      return function wrap(arr) {
        return callback.apply(null, arr);
      };
    };
    

    工具函数(util.js)

    util主要有四个主要的函数实现了forEach,merge,deepmerge,exten

    基本类型判断

    var toString = Object.prototype.toString;
    
    function isArray(val) {
      return toString.call(val) === '[object Array]';
    }
    
    function isArrayBuffer(val) {
      return toString.call(val) === '[object ArrayBuffer]';
    }
    
    function isFormData(val) {
      return (typeof FormData !== 'undefined') && (val instanceof FormData);
    }
    
    function isArrayBufferView(val) {
      var result;
      if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) {
        result = ArrayBuffer.isView(val);
      } else {
        result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer);
      }
      return result;
    }
    
    function isString(val) {
      return typeof val === 'string';
    }
    
    function isNumber(val) {
      return typeof val === 'number';
    }
    
    function isUndefined(val) {
      return typeof val === 'undefined';
    }
    
    function isObject(val) {
      return val !== null && typeof val === 'object';
    }
    
    function isDate(val) {
      return toString.call(val) === '[object Date]';
    }
    
    function isFile(val) {
      return toString.call(val) === '[object File]';
    }
    
    
    function isBlob(val) {
      return toString.call(val) === '[object Blob]';
    }
    
    function isFunction(val) {
      return toString.call(val) === '[object Function]';
    }
    
    function isStream(val) {
      return isObject(val) && isFunction(val.pipe);
    }
    
    function isURLSearchParams(val) {
      return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams;
    }
    

    实现了一下trime方法

    function trim(str) {
      return str.replace(/^\s*/, '').replace(/\s*$/, '');
    }
    
    /**
     * Determine if we're running in a standard browser environment
     *
     * This allows axios to run in a web worker, and react-native.
     * Both environments support XMLHttpRequest, but not fully standard globals.
     *
     * web workers:
     *  typeof window -> undefined
     *  typeof document -> undefined
     *
     * react-native:
     *  navigator.product -> 'ReactNative'
     * nativescript
     *  navigator.product -> 'NativeScript' or 'NS'
     */
    function isStandardBrowserEnv() {
      if (typeof navigator !== 'undefined' &&
          (navigator.product === 'ReactNative' ||
              navigator.product === 'NativeScript' ||
              navigator.product === 'NS')) {
        return false;
      }
      return (
          typeof window !== 'undefined' &&
          typeof document !== 'undefined'
      );
    }
    
    /**
     * Iterate over an Array or an Object invoking a function for each item.
     *
     * If `obj` is an Array callback will be called passing
     * the value, index, and complete array for each item.
     *
     * If 'obj' is an Object callback will be called passing
     * the value, key, and complete object for each property.
     *
     * @param {Object|Array} obj The object to iterate
     * @param {Function} fn The callback to invoke for each item
     */
    function forEach(obj, fn) {
      // Don't bother if no value provided
      if (obj === null || typeof obj === 'undefined') {
        return;
      }
    
      // Force an array if not already something iterable
      if (typeof obj !== 'object') {
        /*eslint no-param-reassign:0*/
        obj = [obj];
      }
    
      if (isArray(obj)) {
        // Iterate over array values
        for (var i = 0, l = obj.length; i < l; i++) {
          fn.call(null, obj[i], i, obj);
        }
      } else {
        // Iterate over object keys
        for (var key in obj) {
          if (Object.prototype.hasOwnProperty.call(obj, key)) {
            fn.call(null, obj[key], key, obj);
          }
        }
      }
    }
    
    /**
     * Accepts varargs expecting each argument to be an object, then
     * immutably merges the properties of each object and returns result.
     *
     * When multiple objects contain the same key the later object in
     * the arguments list will take precedence.
     *
     * Example:
     *
     * ```js
     * var result = merge({foo: 123}, {foo: 456});
     * console.log(result.foo); // outputs 456
     * ```
     *
     * @param {Object} obj1 Object to merge
     * @returns {Object} Result of all merge properties
     */
    function merge(/* obj1, obj2, obj3, ... */) {
      var result = {};
    
      function assignValue(val, key) {
        if (typeof result[key] === 'object' && typeof val === 'object') {
          result[key] = merge(result[key], val);
        } else {
          result[key] = val;
        }
      }
    
      for (var i = 0, l = arguments.length; i < l; i++) {
        forEach(arguments[i], assignValue);
      }
      return result;
    }
    
    
    /**
     * Function equal to merge with the difference being that no reference
     * to original objects is kept.
     *
     * @see merge
     * @param {Object} obj1 Object to merge
     * @returns {Object} Result of all merge properties
     */
    function deepMerge(/* obj1, obj2, obj3, ... */) {
      var result = {};
    
      function assignValue(val, key) {
        if (typeof result[key] === 'object' && typeof val === 'object') {
          result[key] = deepMerge(result[key], val);
        } else if (typeof val === 'object') {
          result[key] = deepMerge({}, val);
        } else {
          result[key] = val;
        }
      }
    
      for (var i = 0, l = arguments.length; i < l; i++) {
        forEach(arguments[i], assignValue);
      }
      return result;
    }
    
    /**
     * Extends object a by mutably adding to it the properties of object b.
     *
     * @param {Object} a The object to be extended
     * @param {Object} b The object to copy properties from
     * @param {Object} thisArg The object to bind function to
     * @return {Object} The resulting value of object a
     */
    function extend(a, b, thisArg) {
      forEach(b, function assignValue(val, key) {
        if (thisArg && typeof val === 'function') {
          a[key] = bind(val, thisArg);
        } else {
          a[key] = val;
        }
      });
      return a;
    }
    

    扩展阅读:

    https://www.jianshu.com/p/22b49e6ad819 封装不会获取重复的数据。

    相关文章

      网友评论

          本文标题:axios源码解析(一)工具函数

          本文链接:https://www.haomeiwen.com/subject/tnynpctx.html