js基础

655 阅读4分钟

ajax 封装解析

  1. 封装方案
    • 选择回调函数 将来使用的时候需要按照回调函数的语法使用 但是容易出现回调地狱
    • 选择 promise 的形式 按照 Promise 的形式来使用 后续可以利用 async/await 语法进一步简化代码
  2. 决定参数
    • 请求地址(url): 必填
    • 请求方式(method): 选填, 默认 GET
    • 是否异步(async): 选填, 默认 true 异步
    • 携带的参数(data): 选填, 默认 '' 空字符
  3. 决定返回值
    • 需要
    • 返回一个 promise 对象
      • 因为需要依赖返回值来决定使用 then 还是 catch
  4. 参数书写顺序
function ajax(url, method = "GET", async = true, data = "") {}
ajax("qwert", "GET", true, "name=123");
function ajax(options) {}

ajax 封装参数验证

function ajax(options = {}) {
    // 1.1 验证参数---url 必传
    if (options.url == undefined) throw new Error("url 为必填项, 请填写后再试");

    // 1.2 验证参数---method 可以不传, 可以为 GET, 可以是 POST, 但不能是其他的
    if (
        !(options.method === undefined || /^(GET|POST)$/i.test(options.method))
    ) {
        throw new Error("mehtod 目前仅支持 GET 或 POST");
    }
    // 1.3 验证参数---async 可以不传, 可以为 true, 可以是 false, 但不能是其他的
    if (!(options.async === undefined || typeof options.async === "boolean")) {
        throw new Error("async 目前仅支持 布尔值(Boolean)");
    }
    // 1.4 验证参数---data 可以不传, 可以为一个字符串, 可以是一个对象
    if (
        !(
            options.data === undefined ||
            typeof options.data === "string" ||
            options.data.constructor === Object
        )
    ) {
        throw new Error("data 目前仅支持 字符串(String) 或 对象(Object)");
    }

    // console.log('这里执行说明参数的 url 已经传递了')
}

ajax({
    url: "qwer",
    // method: 'POST',
    // async: false,
    data: "name=QF666&age=18",
});

ajax 封装默认值

function objToStr(obj) {
    let str = "";

    for (let k in obj) {
        str += `${k}=${obj[k]}&`;
    }

    return str.slice(0, str.length - 1);
}

function ajax(options = {}) {
    // 1.1 验证参数---url 必传
    // 1.2 验证参数---method 可以不传, 可以为 GET, 可以是 POST, 但不能是其他的
    // 1.3 验证参数---async 可以不传, 可以为 true, 可以是 false, 但不能是其他的
    // 1.4 验证参数---data 可以不传, 可以为一个字符串, 可以是一个对象

    // 2.1 处理默认值
    const _options = {
        url: options.url,
        // 代码执行到这里, method 要么是 undefined 要么是 GET或者POST
        method: options.method || "GET",
        // 代码执行到这里, async 要么是 undefined 要么是 true 或者 false
        // async: typeof options.async === 'boolean' ? options.async : true,
        // ?? 空值运算符, 只有在 左侧为 undefined 或者 null 时才会执行右边的
        async: options.async ?? true,
        // 代码执行到这里, data 要么是 undefined 要么是 '' 或者 {}
        data: options.data || "",
    };
    // 2.2 如果当前 data 是对象, 需要转换为 查询字符串
    if (typeof _options.data !== "string") {
        _options.data = objToStr(_options.data);
    }
    // 2.3 如果当前 data 有值, 且当前是 GET 方式, 那么可以提前 把 url 后拼接上 data
    if (_options.data && /^(GET)$/i.test(_options.method)) {
        _options.url += "?" + _options.data;
    }

    console.log("原始参数: ", options);
    console.log("默认参数: ", _options);

    // console.log('这里执行说明参数的 url 已经传递了')
}

ajax({
    url: "qwer",
    method: "POST",
    // async: false,
    // data: "name=QF666&age=18",
    data: {
        name: "QF666",
        age: 18,
        abc: 123,
    },
});

ajax 封装发送请求

function objToStr(obj) {
    let str = "";

    for (let k in obj) {
        str += `${k}=${obj[k]}&`;
    }

    return str.slice(0, str.length - 1);
}

function ajax(options = {}) {
    // 1.1 验证参数---url 必传
    // 1.2 验证参数---method 可以不传, 可以为 GET, 可以是 POST, 但不能是其他的
    // 1.3 验证参数---async 可以不传, 可以为 true, 可以是 false, 但不能是其他的
    // 1.4 验证参数---data 可以不传, 可以为一个字符串, 可以是一个对象
    // 2.1 处理默认值
    // 2.2 如果当前 data 是对象, 需要转换为 查询字符串
    // 2.3 如果当前 data 有值, 且当前是 GET 方式, 那么可以提前 把 url 后拼接上 data

    // 3. 封装 ajax 请求
    const p = new Promise((resolve, reject) => {
        // 3.1 创建 ajax
        const xhr = new XMLHttpRequest();

        // 3.2 配置 ajax 请求信息
        xhr.open(_options.method, _options.url, _options.async);

        // 3.3 配置接收响应的事件
        xhr.onload = function () {
            resolve(xhr.responseText);
        };

        // 3.4 发送本次请求
        xhr.send();
    });
    return p;
}

ajax({
    url: "http://localhost:8888/test/third",
    // method: "POST",
    async: false,
    data: "name=QF666&age=18",
    // data: {
    //     name: "QF666",
    //     age: 18,
    //     abc: 123,
    // },
}).then((res) => {
    console.log(res);
});

ajax 封装请求头信息

function ajax(options = {}) {
    // code...
    // 1.5 验证参数---headers 可以不传, 可以为一个对象
    if (
        !(
            options.headers === undefined ||
            options.headers.constructor === Object
        )
    ) {
        throw new Error("herder 目前仅支持对象(Object)");
    }

    // 2.1 处理默认值
    const _options = {
        // code...
        // 代码执行到这里, headers 要么是 undefined 要么是 {}
        headers: {
            "content-type": "application/x-www-form-urlencoded",
            ...options.headers,
        },
    };

    // 3. 封装 ajax 请求
    const p = new Promise((resolve, reject) => {
        // 3.1 创建 ajax
        // 3.2 配置 ajax 请求信息
        // 3.3 配置接收响应的事件

        // 如果当前的请求方式为 POST, 那么需要配置上对应的 请求头
        if (/^(POST)$/i.test(_options.method)) {
            xhr.setRequestHeader(
                "content-type",
                _options.headers["content-type"]
            );
        }
        // 如果 authorization 内有值, 需要带上 authorization
        if (_options.headers.authorization) {
            xhr.setRequestHeader(
                "authorization",
                _options.headers.authorization
            );
        }
        // 3.4 发送本次请求
        /^(POST)$/i.test(_options.method)
            ? xhr.send(_options.data)
            : xhr.send();
    });
    return p;
}

/**
 *  思考: 请求头的设置
 *      + 当你是 post 的时候, 需要设置 content-type 字段
 *      + 有的时候还需要设置 authorization 字段
 *      + 有的时候还需要设置 abcd 字段
 *
 *  例子:
 *      1. 登录
 *          + POST 方式
 *              => 需要 content-type
 *              => 不需要 authorization
 *      2. 获取商品列表
 *          + GET 方式
 *              => 不需要 content-type
 *              => 不需要 authorization
 *      3. 获取购物车列表
 *          + GET 方式
 *              => 不需要设置 content-type
 *              => 需要 authorization
 *      4. 修改密码
 *          + POST 方式
 *              => 需要 content-type
 *              => 需要 authorization
 */

ajax({
    url: "http://localhost:8888/test/third",
    // method: "POST",
    // async: false,
    // data: "name=QF666&age=18",
    data: {
        name: "QF666",
        age: 18,
        abc: 123,
    },
    headers: { authorization: "123" },
}).then((res) => {
    console.log(res);
});

ajax 封装解析参数

function ajax(options = {}) {
    // 1.6 验证参数---dataType 可以不传, 可以为 'string' 可以为 'json'
    if (
        !(
            options.dataType === undefined ||
            /^(string|json)$/i.test(options.dataType)
        )
    ) {
        throw new Error("dataType 目前仅支持 'string' 或者 'json' ");
    }

    // 2.1 处理默认值
    const _options = {
        // 代码执行到这里, dataType 要么是 undefined 要么是 string 要么是 json
        dataType: options.dataType || "string",
    };

    // 3. 封装 ajax 请求
    const p = new Promise((resolve, reject) => {
        // 3.3 配置接收响应的事件
        xhr.onload = function () {
            if (_options.dataType === "string") {
                resolve({
                    code: 1,
                    info: xhr.responseText,
                    msg: "成功",
                });
                return;
            }

            try {
                const res = JSON.parse(xhr.responseText);
                resolve({
                    code: 1,
                    info: res,
                    msg: "成功",
                });
            } catch (error) {
                resolve({
                    code: 0,
                    info: error,
                    msg: "失败",
                });
            }
        };
    });
    return p;
}

ajax 封装基准地址

function outer(url) {
    let baseUrl = url;

    function ajax(options = {}) {
        // 2.1 处理默认值
        const _options = {
            url: baseUrl + options.url,
        };
    }

    return ajax;
}

const res = outer("http://localhost:8888");

res({
    url: "/test/first",
}).then((res) => {
    console.log(res);
});

res({
    url: "/test/second",
    dataType: "json",
}).then((res) => {
    console.log(res);
});

设计模式-单例模式

  • 设计模式:
    • 为了实现某一类功能给出的简洁而优化的解决方案
  • 设计模式 - 单例模式
    • 核心意义: 一个构造函数(类) 一生只有一个实例
  • 思路:
    • 实例化一个类的时候
    • 需要先判断, 如果当前类以前被实例化过, 不需要再次实例化了
    • 而是直接拿到原先的实例化对象
    • 如果没有被实例化过, 那么新实例化一个实例对象
  • 解决:
    • 单独找一个变量, 初始化为 null
    • 每一次创建实例的时候, 都判断一下该变量的值
      • 如果为 null, 那么创建一个实例, 赋值给该变量
      • 如果不为 null, 那么我们不再创建实例, 直接给出该变量的值
class Dialog {
    constructor() {
        console.log("在 页面中 创建了一个 弹出层");
    }
}

let instance = null;
function newDialog() {
    if (instance == null) {
        instance = new Dialog();
    }
    return instance;
}

// 第一次调用, 创建一个 实例化对象然后给到 instance 中, 并返回 instance
let d1 = newDialog();

// 第二次调用, 直接返回 instance
let d2 = newDialog();

console.log(d1);
console.log(d2);
console.log(d1 === d2);

单例模式变形

  • 单例模式问题

    1. 向外部写了一个全局变量 instance
    2. 多次调用时, 无法传递不一样的参数
    3. 构造函数的类名与创建实例的函数名是两个东西
  • 解决:

    1. 利用闭包
    let newDialog = (function fn() {
        let instance = null;
        return function () {
            if (instance == null) {
                instance = new Dialog();
            }
            return instance;
        };
    })();
    
    1. 创建一个 init 方法
    class Dialog {
        constructor() {
            console.log("在 页面中 创建了一个 弹出层, 文本为: ");
            this.title = "";
        }
        setTitle(newTitle) {
            this.title = newTitle;
            console.log("将 title 属性 重新修改为了: ", this.title);
        }
    }
    let newDialog = (function outer() {
        let instance = null;
        return function inner(str) {
            // 没有接收到不同参数的原因在于, 这个 if 只有第一次才会执行, 后续永远不会执行
            if (instance == null) {
                instance = new Dialog();
            }
            // 但是, 这个位置却可以多次执行, 所以我们可以在这里, 给这个 类传递不同的参数
            instance.setTitle(str);
            return instance;
        };
    })();
    
    1. 将类放在闭包函数内, 然后将函数名修改为类名
    let Dialog = (function outer() {
        // 因为 Dialog 这个 类, 永远就使用一次, 所以函数局部 更合适
        class Dialog {
            constructor() {
                console.log("在 页面中 创建了一个 弹出层, 文本为: ");
                this.title = "";
            }
            setTitle(newTitle) {
                this.title = newTitle;
                console.log("将 title 属性 重新修改为了: ", this.title);
            }
        }
    
        let instance = null;
        return function inner(str) {
            if (instance == null) {
                instance = new Dialog();
            }
            instance.setTitle(str);
            return instance;
        };
    })();
    

设计模式-策略模式

  • 设计模式 - 策略模式
    • 核心: 减少 if...else...
    • 作用: 把多种形式的内容罗列出来
  • 案例:
    • 已知一个购物总价 1376
    • 需要根据折扣计算最终价格
    • 折扣种类: 8 折, 7 折, 300-30, 500-50
  • 当前案例核心:
    • 用一个数据结构, 把各种折扣记录下来
    • 值存储的就是该折扣和总价的计算方式
const calcPrice = (function () {
    const calcList = {
        "80%": (total) => (total * 0.8).toFixed(2),
        "70%": (total) => (total * 0.7).toFixed(2),
    };
    // 任何引用数据类型, 都可以当作 对象去使用
    function inner(type, total) {
        return calcList[type](total);
    }
    inner.add = function (type, fn) {
        calcList[type] = fn;
    };
    inner.sub = function (type) {
        delete calcList[type];
    };
    inner.getList = function () {
        return calcList;
    };
    return inner;
})();
const res = calcPrice("80%", 1000);
console.log(res);

const res1 = calcPrice("70%", 1000);
console.log(res1);

calcPrice.add("300-30", (total) => total - Math.floor(total / 300) * 30);

const res2 = calcPrice("300-30", 1000);
console.log(res2);

calcPrice.sub("70%");
console.log(calcPrice.getList());

发布订阅模式

  • 设计模式-发布订阅
    • 首先明确 这个模式和一个叫做观察者模式类似但不是一个东西
    • 目前一批开发人员认为这两个是一个东西, 另外一批认为是两个东西
  • 案例: 买一本书
    • 以前
      1. 去到书店, 问店员有没有 JS 从入门到入土
      2. 没有, 回去
      3. 一会再回去问, 有没有
      4. 没有, 回去
      5. 一会再回去问, 有没有
      6. ...
      7. 一会再回去问, 有没有
      8. 有了, 购买
    • 现在
      1. 去到书店, 问店员有没有 JS 从入门到入土
      2. 没有, 给店员留下一个联系方式
      3. 直到店员给你打电话, 你触发技能(去买回来)
  • 在 JS 内 发布订阅 使用最多的地方 (addEventListener)
    • xxx.addEventListener('click', fn)
  • 思考: 以什么数据格式来记录
店员 ===
    {
        a: [fn1, fn2],
        b: [fn3, fn4],
    };
class Observer {
    constructor(name) {
        this.name = name; // 这步模拟店员名字, 可以不要
        // 店员的记录手册
        this.messages = {};
    }
    add(type, fn) {
        if (this.messages[type] === undefined) this.messages[type] = [];

        this.messages[type].push(fn);
    }
    remove(type, fn) {
        if (this.messages[type] === undefined) return;

        // if (fn === undefined) return this.messages[type] = []
        if (fn === undefined) return delete this.messages[type];

        this.messages[type] = this.messages[type].filter((item) => item !== fn);
    }
    trigger(type) {
        // console.log(type)
        // console.log(this.messages[type])
        this.messages[type].forEach((item) => item());
    }
}

const ob = new Observer("小李");

const A1 = () => {
    console.log("我是 张三, 我要买这本书");
};
const A2 = () => {
    console.log("我是 李四, 我要买这本书");
};
const B1 = () => {
    console.log("我是 王五, 我要买这本书");
};

ob.add("A1", A1);
ob.add("A1", A2);
ob.add("B1", B1);

// ob.remove('A1', A1)
// ob.remove('A1')
ob.remove("C1");

ob.trigger("A1");

console.log(ob);

认识 前后端交互

  • 认识前后端交互
    • 前端 和 后端的 通讯方式
    • 主要使用的技术栈就是 ajax (async javascript and xml)
  • 注意:
    • 前后端交互只能交互 字符串
    • 并且有自己的固定步骤
  • 前后端交互基本步骤
    1. 创建 ajax 对象
      • 语法: const xhr = new XMLHttpRequest()
    2. 配置本次请求的信息
      • 语法: xhr.open(请求方式, 请求地址, 是否异步)
        • 请求方式: 按照接口文档书写
        • 请求地址: 按照接口文档书写
        • 是否有异步: 自己决定, 默认是异步(true)
    3. 把本次请求发送出去
      • 语法: xhr.send()
    4. 配置一个请求完成事件
      • 语法: xhr.onload = function () {}
      • 使用 xhr.responseText 来获取服务端给出的相应信息
// 1. 创建 ajax 对象
const xhr = new XMLHttpRequest();
// 2. 配置 ajax 对象
xhr.open("GET", "http://localhost:8888/test/first", true);
// 3. 发送请求
xhr.send();
// 4. 接收响应
xhr.onload = function () {
    console.log("请求回来了~~~~~~~~");
    console.log(xhr.responseText);
};

ajax 的异步问题

  1. 创建 ajax 对象(同步代码) const xhr = new XMLHttpRequest()
  2. 配置请求信息(同步代码) xhr.open(请求方式, 请求地址, 是否异步)
  3. 发送本次请求(同步发送, 异步接收) xhr.send()
  4. 注册完成事件(同步注册事件) xhr.onload = function () {}
  • 异步 1 2 3 4
    1. 创建一个 ajax 对象
    2. 配置请求信息
    3. 发出去
    4. 注册一个接收事件
    5. 收回来
    6. 触发接收事件, 打印信息
  • 异步 1 2 4 3
    1. 创建 ajax 对象
    2. 配置请求信息
    3. 注册一个接收事件
    4. 发送请求
    5. 收回来
    6. 触发接收事件, 打印信息
  • 同步 1 2 3 4
    1. 创建一个 ajax 对象
    2. 配置请求信息
    3. 发送请求
    4. 注册一个接收事件
      • 因为 注册事件之前, 本次请求已经完成
      • 所以再也不会有完成行为发生了
  • 同步 1 2 4 3
    1. 创建 ajax 对象
    2. 配置请求信息
    3. 注册一个接收事件
    4. 发出去, 等待收回来
      • 过了一会: 收回来了
      • 因为已经注册过事件, 所以可以触发事件
    5. 打印响应信息

ajax 的状态码

  • 用一个数字表明 当前 ajax 运行到那一步了
  • 语法: xhr.readyState
    • 0: 创建成功
    • 1: 当前 ajax 配置成功
    • 2: 当前 ajax 发送成功(响应已经回到浏览器了)
    • 3: 表示浏览器当前正在解析本次响应, 但可能还没完成
    • 4: 表示浏览器当前已经完成解析本次响应, 可以正常使用 responseText
const xhr = new XMLHttpRequest();
xhr.open("GET", "地址", true);
xhr.onreadystatechange = function () {
    if (xhr.readyState === 2) console.log(xhr.responseText);
    if (xhr.readyState === 3) console.log(xhr.responseText);
    if (xhr.readyState === 4) console.log(xhr.responseText);
};
xhr.send();

http 传输协议(了解)

  • http(s) 协议规定了, 只能由前端主动发起, 并且, 在传输过程中, 只能传递 字符串
  1. 建立连接
    • 浏览器和服务器建立连接
  2. 发送请求
    • 要求前端必须以 请求报文的形式发送
    • 报文由浏览器组装, 我们只需要提供对应的信息即可(请求方式, 请求地址, 传输协议)
  3. 接收响应
    • 要求后端必须以 响应报文的形式返回
    • 内部有一个东西叫做 响应状态码, 关于响应的简单描述
  4. 断开连接
    • 浏览器和服务器断开连接
  • 响应状态码
    • 100~199 表明连接继续
    • 200~299 表明各种成功
    • 300~399 表明重定向
    • 400~499 表明客户端错误
    • 500~599 表明服务端错误

请求方式

不同的方式, 携带参数的位置不同

  • 常见的请求方式
    • GET: 偏向获取的语义
    • DELETE: 偏向获取的语义(删除)
    • POST: 偏向提交的语义
    • PUT: 偏向提交的语义(修改账号密码)
    • ...
  • 最常见的两个请求方式
    • GET
    • PUST
  • 请求方式的区别
    1. 携带参数的位置
      • GET: 直接在地址后面写(参数与地址之间用问好间隔)
      • PUST: 在请求体内书写(其实就是 xhr.send() 的小括号内)
    2. 携带参数的大小
      • GET: 2kb 左右
      • PUST: 原则上不限制大小, 但是服务器可以做出限制
    3. 携带参数的格式
      • GET: 只能携带查询字符串格式
      • PUST: 原则上不限制格式, 但是需要在请求报文的 content-type 做配置说明
    4. 安全性(相对)
      • GET: 明文发送, 相对不安全
      • PUST: 暗文发送, 相对安全

测试请求

// 测试请求 2
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:8888/test/second");
xhr.send();
xhr.onload = function () {
    let res = JSON.parse(xhr.responseText);
    console.log(res);
};

/**
 *  测试请求 3
 *      请求方式 GET, 需要携带参数
 *      GET 方式直接在 地址栏后面携带参数
 *      GET 方式只能携带查询字符串格式 'key=value&key2=value2'
 *
 *  注意: 参数与地址之间使用 问号间隔
 */
const xhr = new XMLHttpRequest();
xhr.open("GET", "http://localhost:8888/test/third?name=QF666&age=18");
xhr.send();
xhr.onload = function () {
    let res = JSON.parse(xhr.responseText);
    console.log(res);
};

/**
 *  测试请求 4
 *      请求方式 POST, 需要携带参数
 *      POST 方式在请求体携带参数(其实就是 send小括号内部)
 *      POST 方式在携带参数的时候, 需要设置请求头内部的 content-type    通过 xhr.setRequestHeader
 *
 *      查询字符串: application/x-www-form-urlencoded
 *      JSON 字符串: application/json
 */
const xhr = new XMLHttpRequest();
xhr.open("POST", "http://localhost:8888/test/fourth");
xhr.setRequestHeader("content-type", "application/x-www-form-urlencoded");
xhr.send("name=QF666&age=18");
xhr.onload = function () {
    let res = JSON.parse(xhr.responseText);
    console.log(res);
};

分页

// 更改渲染函数
function myFn(list) {
    var str = "";
    list.forEach(function (item) {
        str += `...`;
    });
    oUl.innerHTML = str;

    totalBox.innerHTML = currentNum + "/" + totalNum;

    // ...;
}

// 在数据请求成功的时候, 调用渲染函数
function getList() {
    const xhr = new XMLHttpRequest();
    xhr.open(
        "GET",
        `http://localhost:8888/goods/list?current=${currentNum}&pagesize=${pageSize}`
    );
    xhr.onload = function () {
        const data = JSON.parse(xhr.responseText);
        totalNum = data.total;
        myFn(data.list);
    };
    xhr.send();
}
getList();

认识 SPA

  • SPA: single page application
  • 所有的内容, 都在一个 HTML 中渲染
  • 大部分用于: 移动端; PC 后台管理
  • 切换依赖于:
    1. 依赖 hash 的改变(锚点)
    2. 依赖历史记录(history)
  • 开发过程: 组件化开发(框架内基本都是组件化开发)
    • 组件: 一段完整的 HTML+CSS+JS 结构
    1. 面向过程开发 => 所有代码一步一步写
    2. 面向对象开发 => 将面向过程的一个封装
    3. 函数式开发 => 所有东西都封装在函数中
    4. 模块化开发 => 所有的 JS 功能都拆分为模块
    5. 组件化开发 => 将一个 HTML+CSS+JS 合并在一起做为一个组件, 每一个组件就是一个独立的功能
  • 前端路由:
    • 在页面内同一个位置, 根据不同的 hash 值显示不同的页面结构
<div class="box">
    <div class="top"> 顶部通栏 </div>
    <div class="bottom">
        <div class="slide">
            <a href="#/pageA">pageA</a>
            <a href="#/pageB">pageB</a>
            <a href="#/pageC">pageC</a>
            <a href="#/pageD">pageD</a>
        </div>
        <div class="content router-view">内容区</div>
    </div>
</div>;
const templateA = `<div>templateA</div>`;
const templateB = `<div>templateB</div>`;
const templateC = `<div>templateC</div>`;
const templateD = `<div>templateD</div>`;

const rw = document.querySelector(".router-view");

window.onhashchange = function () {
    const { hash } = window.location;

    if (hash === "#/pageA") {
        rw.innerHTML = templateA;
    } else if (hash === "#/pageB") {
        rw.innerHTML = templateB;
    } else if (hash === "#/pageC") {
        rw.innerHTML = templateC;
    } else if (hash === "#/pageD") {
        rw.innerHTML = templateD;
    }
};

SPA 应用

  1. 路由重定向

    // router.js
    {
        // 如果当前的 路由是 /, 那么就重定向到 /pageA
        name: "/",
        redirect: "/pageA",
    }
    // index.js
    if (info.redirect) return (window.location.hash = info.redirect);
    
  2. 路由懒加载

    • 因为上述引入的方式会导致 页面首次加载时直接全部加载
    • 所以我们应该更改引入方式为 异步引入模块
    • import(地址) 按照 promise 的语法封装的函数
    • 返回值不是我们默认导出的内容, 而是一个 promise 对象
    // router.js
    {
        name: "/pageA",
        compoment: () => import("./components/templateA.js"),
    }
    // index.js
    const res = info.compoment();
    if (res === undefined) return;
    res.then((res) => res.default());