[{"data":1,"prerenderedAt":8555},["ShallowReactive",2],{"sidebar-posts":3,"page-/husky":8489},[4,114,197,276,406,525,742,858,882,2123,2181,2289,2574,2614,2804,2920,3203,3252,3396,3473,3506,3726,4474,5216,5339,5405,5634,6787,6846,6931,6958,7990],{"id":5,"title":6,"body":7,"description":22,"extension":104,"meta":105,"navigation":110,"path":111,"seo":112,"stem":108,"__hash__":113},"content/husky.md","基于Husky的Git提交规范实践",{"type":8,"value":9,"toc":94},"minimark",[10,15,26,30,36,39,43,46,49,57,61,64,72,76,82,85,91],[11,12,14],"h2",{"id":13},"安装-husky及相关依赖现代写法","安装 Husky及相关依赖（现代写法）",[16,17,23],"pre",{"className":18,"code":20,"language":21,"meta":22},[19],"language-bash","npm install --save-dev @commitlint/cli @commitlint/config-conventional husky lint-staged\n","bash","",[24,25,20],"code",{"__ignoreMap":22},[27,28,29],"p",{},"初始化：",[16,31,34],{"className":32,"code":33,"language":21,"meta":22},[19],"npx husky init\n",[24,35,33],{"__ignoreMap":22},[27,37,38],{},"生成： .husky/ 目录。",[11,40,42],{"id":41},"配置-commitlint","配置 commitlint",[27,44,45],{},"创建 Commitlint 配置文件 ，指定校验规则，让它遵循 Conventional Commits 规范：",[27,47,48],{},"在项目根目录创建 commitlint.config.js 文件；\n写入以下配置（固定格式，可直接复制）：",[16,50,55],{"className":51,"code":53,"language":54,"meta":22},[52],"language-js","// commitlint.config.js\nmodule.exports = {\n    // 继承官方的Conventional Commits规范\n    extends: ['@commitlint/config-conventional'],\n    // 自定义校验规则（可选，根据团队需求调整）\n    rules: {\n        // type的枚举值，和Conventional Commits规范一致，可新增自定义type\n        'type-enum': [\n            2, // 2表示错误级别（0=禁用，1=警告，2=错误）\n            'always', // always表示必须满足\n            [\n                'feat',      // ✨ 新功能\n                'fix',       // 🐛 修复 Bug\n                'docs',      // 📝 文档修改\n                'style',     // 💄 样式/格式调整（不改逻辑）\n                'refactor',  // ♻️ 重构代码\n                'perf',      // ⚡ 性能优化\n                'test',      // ✅ 测试相关\n                'build',     // 📦 打包构建\n                'ci',        // 👷 CI/CD配置\n                'chore',     // 🔧 杂项维护\n                'revert'     // ⏪ 回滚提交\n            ]\n        ],\n        // type不能为空\n        'type-empty': [2, 'never'],\n        // description不能为空\n        'subject-empty': [2, 'never'],\n        // description首字母小写\n        'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],\n        // body和description之间要有空行\n        'body-leading-blank': [1, 'always'],\n        // footer和body之间要有空行\n        'footer-leading-blank': [1, 'always']\n    }\n};\n\n","js",[24,56,53],{"__ignoreMap":22},[11,58,60],{"id":59},"配置-lint-staged","配置 lint-staged",[27,62,63],{},"package.json：",[16,65,70],{"className":66,"code":68,"language":69,"meta":22},[67],"language-json","{\n  \"lint-staged\": {\n    \"*.{js,ts,vue}\": [\n      \"eslint --fix\",\n      \"prettier --write\"\n    ]\n  }\n}\n","json",[24,71,68],{"__ignoreMap":22},[73,74,75],"h3",{"id":75},"提交前校验",[16,77,80],{"className":78,"code":79,"language":21,"meta":22},[19],"npx lint-staged\n",[24,81,79],{"__ignoreMap":22},[73,83,84],{"id":84},"提交信息校验",[16,86,89],{"className":87,"code":88,"language":21,"meta":22},[19],"npx --no-install commitlint --edit \"$1\"\n",[24,90,88],{"__ignoreMap":22},[27,92,93],{},"说明：\n@commitlint/config-conventional：内置了 Conventional Commits 规范的校验规则，无需自己编写；\n如果项目不需要 Prettier，可删除 prettier --write 命令，只保留 eslint --fix；\n如果只需要校验Commit信息，不需要代码校验，可跳过此步骤，删除 pre-commit 钩子即可。",{"title":22,"searchDepth":95,"depth":95,"links":96},2,[97,98,99],{"id":13,"depth":95,"text":14},{"id":41,"depth":95,"text":42},{"id":59,"depth":95,"text":60,"children":100},[101,103],{"id":75,"depth":102,"text":75},3,{"id":84,"depth":102,"text":84},"md",{"date":106,"tags":107},"2026-04-25 13:33:12",[108,109],"husky","git",true,"/husky",{"title":6,"description":22},"N0fnYJPafGiI_FyJA0RZIreIZe4bD7QZQFAQ_vj1o0I",{"id":115,"title":116,"body":117,"description":121,"extension":104,"meta":186,"navigation":110,"path":193,"seo":194,"stem":195,"__hash__":196},"content/request.md","基于TypeScript的Axios封装实践方案",{"type":8,"value":118,"toc":184},[119,122,127,135,139,145,149,155,159,165,168,174,178],[27,120,121],{},"在项目中，合理封装请求可以提升代码复用性、统一错误处理并增强类型安全,本文将介绍一个完整的TypeScript封装方案，包含uniapp端和pc端请求拦截、响应拦截、错误处理和模块化API管理。",[123,124,126],"h1",{"id":125},"uniapp端请求封装ts版","uniapp端请求封装（ts版）",[16,128,133],{"className":129,"code":131,"language":132,"meta":22},[130],"language-ts","// request.ts\nconst base_url = 'https://xx.com'\n\n// ===== 类型定义 =====\ninterface ApiResponse\u003CT = any> {\n    code: number\n    msg: string\n    data: T\n}\n\ninterface RequestOptions\u003CT = any> {\n    url?: string\n    method?: 'GET' | 'POST'\n    data?: any\n    header?: Record\u003Cstring, any>\n    loading?: boolean\n    showError?: boolean\n    showSuccess?: boolean   // ✅ 新增\n    responseType?: 'text' | 'arraybuffer'\n    timeout?: number\n}\n\n// ===== loading 控制 =====\nlet loadingCount = 0\n\nfunction showLoading(title = '加载中...') {\n    if (loadingCount === 0) {\n        uni.showLoading({ title, mask: true })\n    }\n    loadingCount++\n}\n\nfunction hideLoading() {\n    if (loadingCount > 0) {\n        loadingCount--\n    }\n    if (loadingCount === 0) {\n        uni.hideLoading()\n    }\n}\n\n// ===== 核心请求函数 =====\nexport function request\u003CT = any>(options: RequestOptions\u003CT> = {}): Promise\u003CApiResponse\u003CT>> {\n    const {\n        url = '',\n        method = 'GET',\n        data = {},\n        header = {},\n        loading = false,\n        showError = true,\n        showSuccess = false, \n        responseType = 'text',\n        timeout = 15000\n    } = options\n\n    // token 注入\n    const requestHeader: Record\u003Cstring, any> = { ...header }\n    const token = uni.getStorageSync('token')\n    if (token) {\n        requestHeader.Authorization = `Bearer ${token}`\n    }\n\n    if (loading) {\n        showLoading()\n    }\n\n    return new Promise((resolve, reject) => {\n        uni.request({\n            url: base_url + url,\n            method,\n            data,\n            header: requestHeader,\n            timeout,\n            responseType,\n\n            success: (res) => {\n                const { statusCode } = res\n                const resData = res.data as ApiResponse\u003CT>\n\n                // HTTP 错误\n                if (statusCode !== 200) {\n                    handleHttpError(statusCode || 0, showError)\n                    reject(res)\n                    return\n                }\n\n                // 业务错误\n                if (resData.code !== 1) {\n                    if (showError) {\n                        uni.showToast({\n                            title: resData.msg || '请求失败',\n                            icon: 'none'\n                        })\n                    }\n\n                    if (resData.code === 401) {\n                        handleLogout()\n                    }\n\n                    reject(resData)\n                    return\n                }\n\n                //  成功提示\n                if (showSuccess) {\n                    uni.showToast({\n                        title: resData.msg || '操作成功',\n                        icon: 'none'\n                    })\n                }\n                // 只返回 data 字段\n                resolve(resData.data)\n            },\n\n            fail: (err) => {\n                if (showError) {\n                    uni.showToast({\n                        title: '网络异常，请稍后再试',\n                        icon: 'none'\n                    })\n                }\n                reject(err)\n            },\n\n            complete: () => {\n                if (loading) {\n                    hideLoading()\n                }\n            }\n        })\n    })\n}\n\n// ===== HTTP 错误处理 =====\nfunction handleHttpError(statusCode: number, showError: boolean) {\n    const map: Record\u003Cnumber, string> = {\n        400: '请求错误',\n        401: '未授权，请重新登录',\n        403: '拒绝访问',\n        404: '接口不存在',\n        500: '服务器错误'\n    }\n\n    if (showError) {\n        uni.showToast({\n            title: map[statusCode] || '网络错误',\n            icon: 'none'\n        })\n    }\n\n    if (statusCode === 401) {\n        handleLogout()\n    }\n}\n\n// ===== 退出登录 =====\nfunction handleLogout() {\n    uni.removeStorageSync('token')\n    uni.reLaunch({\n        url: '/pages/login/login'\n    })\n}\n","ts",[24,134,131],{"__ignoreMap":22},[123,136,138],{"id":137},"使用方法","使用方法：",[16,140,143],{"className":141,"code":142,"language":132,"meta":22},[130],"import { request } from '@/utils/request'\nconst res = await request\u003CUserInfo>({\n  url: '/user/info',\n  method: 'GET',\n  loading: true\n})\n",[24,144,142],{"__ignoreMap":22},[123,146,148],{"id":147},"uniapp端请求封装js版","uniapp端请求封装（js版）",[16,150,153],{"className":151,"code":152,"language":54,"meta":22},[52],"const base_url = 'https://xx.com'\n\n// ===== loading 控制 =====\nlet loadingCount = 0\n\nfunction showLoading(title = '加载中...') {\n    if (loadingCount === 0) {\n        uni.showLoading({ title, mask: true })\n    }\n    loadingCount++\n}\n\nfunction hideLoading() {\n    if (loadingCount > 0) {\n        loadingCount--\n    }\n    if (loadingCount === 0) {\n        uni.hideLoading()\n    }\n}\n\n// ===== 核心请求函数 =====\nexport function request(options = {}) {\n    const {\n        url = '',\n        method = 'GET',\n        data = {},\n        header = {},\n        loading = false,\n        showError = true,\n        showSuccess = false,\n        responseType = 'text',\n        timeout = 15000\n    } = options\n\n    // token 注入\n    const requestHeader = { ...header }\n    const token = uni.getStorageSync('token')\n    if (token) {\n        requestHeader.Authorization = `Bearer ${token}`\n    }\n\n    if (loading) {\n        showLoading()\n    }\n\n    return new Promise((resolve, reject) => {\n        uni.request({\n            url: base_url + url,\n            method,\n            data,\n            header: requestHeader,\n            timeout,\n            responseType,\n\n            success: (res) => {\n                const statusCode = res.statusCode\n                const resData = res.data || {}\n\n                // HTTP 错误\n                if (statusCode !== 200) {\n                    handleHttpError(statusCode || 0, showError)\n                    reject(res)\n                    return\n                }\n\n                // 业务错误\n                if (resData.code !== 1) {\n                    if (showError) {\n                        uni.showToast({\n                            title: resData.msg || '请求失败',\n                            icon: 'none'\n                        })\n                    }\n\n                    if (resData.code === 401) {\n                        handleLogout()\n                    }\n\n                    reject(resData)\n                    return\n                }\n\n                // 成功提示\n                if (showSuccess) {\n                    uni.showToast({\n                        title: resData.msg || '操作成功',\n                        icon: 'none'\n                    })\n                }\n\n                // 只返回 data 字段\n                resolve(resData.data)\n            },\n\n            fail: (err) => {\n                if (showError) {\n                    uni.showToast({\n                        title: '网络异常，请稍后再试',\n                        icon: 'none'\n                    })\n                }\n                reject(err)\n            },\n\n            complete: () => {\n                if (loading) {\n                    hideLoading()\n                }\n            }\n        })\n    })\n}\n\n// ===== HTTP 错误处理 =====\nfunction handleHttpError(statusCode, showError) {\n    const map = {\n        400: '请求错误',\n        401: '未授权，请重新登录',\n        403: '拒绝访问',\n        404: '接口不存在',\n        500: '服务器错误'\n    }\n\n    if (showError) {\n        uni.showToast({\n            title: map[statusCode] || '网络错误',\n            icon: 'none'\n        })\n    }\n\n    if (statusCode === 401) {\n        handleLogout()\n    }\n}\n\n// ===== 退出登录 =====\nfunction handleLogout() {\n    uni.removeStorageSync('token')\n    uni.reLaunch({\n        url: '/pages/login/login'\n    })\n}\n",[24,154,152],{"__ignoreMap":22},[123,156,158],{"id":157},"pc端请求封装ts版","pc端请求封装（ts版）",[16,160,163],{"className":161,"code":162,"language":132,"meta":22},[130],"import axios, {\n    type AxiosRequestConfig,\n    type AxiosResponse,\n    type AxiosInstance,\n    type InternalAxiosRequestConfig\n} from 'axios'\nimport { ElLoading, ElMessage, type LoadingInstance } from 'element-plus'\nimport router from '@/router'\n\n/**\n * Axios 实例\n * baseURL 优先使用环境变量，没有则退回 /api\n */\nconst service: AxiosInstance = axios.create({\n    baseURL: import.meta.env.VITE_API_BASE_URL || '/api',\n    timeout: 15000,\n    headers: {\n        'Content-Type': 'application/json;charset=UTF-8'\n    }\n})\n\n/**\n * 后端统一返回结构\n */\nexport interface ApiResponse\u003CT = unknown> {\n    data: T\n    msg: string\n    code: number\n}\n\n/**\n * 扩展后的请求配置\n * 仍兼容 AxiosRequestConfig\n */\nexport interface RequestConfig extends AxiosRequestConfig {\n    showSuccessMessage?: boolean\n    showErrorMessage?: boolean\n    showLoading?: boolean\n}\n\n/**\n * 自定义element-plus行为（集中管理）\n */\nconst requestConfig = {\n    showSuccessMessage: false,\n    showErrorMessage: true,\n    showLoading: false\n}\n\n// -------- Loading 计数，避免多个请求同时闪烁 --------\nlet loadingInstance: LoadingInstance | null = null\nlet loadingCount = 0\n\nfunction showLoading(title = '加载中...'): void {\n    if (loadingCount === 0) {\n        loadingInstance = ElLoading.service({\n            lock: true,\n            text: title,\n            background: 'rgba(0, 0, 0, 0.7)'\n        })\n    }\n    loadingCount += 1\n}\n\nfunction hideLoading(): void {\n    if (loadingCount > 0) {\n        loadingCount -= 1\n    }\n    if (loadingCount === 0) {\n        loadingInstance?.close()\n        loadingInstance = null\n    }\n}\n\n// -------- 请求拦截器 --------\nservice.interceptors.request.use(\n    (config: InternalAxiosRequestConfig) => {\n        const reqConfig = config as RequestConfig\n\n        // 根据 requestConfig 控制 loading\n        if (reqConfig.showLoading ?? requestConfig.showLoading) {\n            showLoading()\n        }\n\n        // 统一拼装请求头\n        const token = localStorage.getItem('token')\n        if (token) {\n            config.headers.set('Authorization', `Bearer ${token}`)\n        }\n\n        return config\n    },\n    (error) => Promise.reject(error)\n)\n\n// -------- 响应拦截器 --------\nservice.interceptors.response.use(\n    (response: AxiosResponse\u003CApiResponse>) => {\n        const config = response.config as RequestConfig\n        if (config.showLoading ?? requestConfig.showLoading) {\n            hideLoading()\n        }\n\n        const { code, msg } = response.data\n        const showError = config.showErrorMessage ?? requestConfig.showErrorMessage\n\n        if (code === 1) {\n            if (config.showSuccessMessage ?? requestConfig.showSuccessMessage) {\n                ElMessage.success(msg || '操作成功')\n            }\n            return response\n        }\n\n        if (showError) {\n            ElMessage.error(msg || '请求失败')\n        }\n        if (code === 401) {\n            handleLogout()\n        }\n        return Promise.reject(msg || '请求失败')\n    },\n    (error) => {\n        const config = (error?.config || {}) as RequestConfig\n        if (config.showLoading ?? requestConfig.showLoading) {\n            hideLoading()\n        }\n\n        const showError = config.showErrorMessage ?? requestConfig.showErrorMessage\n        if (error?.response?.status) {\n            handleHttpError(error.response.status, showError)\n        } else if (showError) {\n            ElMessage.error('网络异常，请稍后再试')\n        }\n\n        return Promise.reject(error)\n    }\n)\n\n/**\n * 核心请求函数\n * 约定：后端 code === 1 才视为成功\n */\nexport async function request\u003CT = unknown>(config: RequestConfig): Promise\u003CT> {\n    const response = await service.request\u003CApiResponse\u003CT>, AxiosResponse\u003CApiResponse\u003CT>>>(config)\n    //成功时只返回 data 字段，简化业务层调用\n    return response.data.data as T\n}\n\n// -------- HTTP 状态码统一处理 --------\nfunction handleHttpError(status: number, showError: boolean): void {\n    const map: Record\u003Cnumber, string> = {\n        400: '请求错误',\n        401: '未授权，请重新登录',\n        403: '拒绝访问',\n        404: '接口不存在',\n        500: '服务器错误'\n    }\n\n    if (showError) {\n        ElMessage.error(map[status] || '网络错误')\n    }\n\n    if (status === 401) {\n        handleLogout()\n    }\n}\n\n// -------- 退出登录 --------\nfunction handleLogout(): void {\n    localStorage.removeItem('token')\n    router.replace({ name: 'login' })\n}\n\n",[24,164,162],{"__ignoreMap":22},[123,166,137],{"id":167},"使用方法-1",[16,169,172],{"className":170,"code":171,"language":132,"meta":22},[130],"import { request } from '@/utils/request'\n\nexport function getProfile() {\n  return request\u003C{ name: string; uid: number }>({\n    url: '/api/user/profile',\n    method: 'get'\n  })\n}\n\nexport function updateProfile(data: { name: string }) {\n  return request({\n    url: '/api/user/profile',\n    method: 'post',\n    data,\n    showLoading: true,\n    showSuccessMessage: true\n  })\n}\n",[24,173,171],{"__ignoreMap":22},[123,175,177],{"id":176},"pc端请求封装js版","pc端请求封装(js版)",[16,179,182],{"className":180,"code":181,"language":54,"meta":22},[52],"import axios from 'axios'\nimport { ElLoading, ElMessage } from 'element-plus'\nimport router from '@/router'\n/**\n * Axios 实例\n */\nconst service = axios.create({\n    baseURL: (import.meta.env && import.meta.env.VITE_API_BASE_URL) || '/api',\n    timeout: 15000,\n    headers: {\n        'Content-Type': 'application/json;charset=UTF-8'\n    }\n})\n\n/**\n * 全局默认配置\n */\nconst defaultConfig = {\n    showSuccessMessage: false,\n    showErrorMessage: true,\n    showLoading: false\n}\n\n// -------- Loading 控制 --------\nlet loadingInstance = null\nlet loadingCount = 0\n\nfunction showLoading(title = '加载中...') {\n    if (loadingCount === 0) {\n        loadingInstance = ElLoading.service({\n            lock: true,\n            text: title,\n            background: 'rgba(0, 0, 0, 0.7)'\n        })\n    }\n    loadingCount++\n}\n\nfunction hideLoading() {\n    if (loadingCount > 0) {\n        loadingCount--\n    }\n    if (loadingCount === 0 && loadingInstance) {\n        loadingInstance.close()\n        loadingInstance = null\n    }\n}\n\n// -------- 请求拦截器 --------\nservice.interceptors.request.use(\n    (config) => {\n        const custom = config\n\n        // ⚠️ 修复逻辑：优先用单次配置，否则用全局配置\n        if ((custom.showLoading ?? defaultConfig.showLoading)) {\n            showLoading()\n        }\n\n        // token\n        const token = localStorage.getItem('token')\n        if (token) {\n            config.headers['Authorization'] = `Bearer ${token}`\n        }\n\n        return config\n    },\n    (error) => Promise.reject(error)\n)\n\n// -------- 响应拦截器 --------\nservice.interceptors.response.use(\n    (response) => {\n        const config = response.config || {}\n\n        if ((config.showLoading ?? defaultConfig.showLoading)) {\n            hideLoading()\n        }\n\n        const { code, msg } = response.data || {}\n        const showError = config.showErrorMessage ?? defaultConfig.showErrorMessage\n\n        if (code === 1) {\n            if (config.showSuccessMessage ?? defaultConfig.showSuccessMessage) {\n                ElMessage.success(msg || '操作成功')\n            }\n            return response\n        }\n\n        if (showError) {\n            ElMessage.error(msg || '请求失败')\n        }\n\n        if (code === 401) {\n            handleLogout()\n        }\n\n        return Promise.reject(msg || '请求失败')\n    },\n    (error) => {\n        const config = error.config || {}\n\n        if ((config.showLoading ?? defaultConfig.showLoading)) {\n            hideLoading()\n        }\n\n        const showError = config.showErrorMessage ?? defaultConfig.showErrorMessage\n\n        if (error.response && error.response.status) {\n            handleHttpError(error.response.status, showError)\n        } else if (showError) {\n            ElMessage.error('网络异常，请稍后再试')\n        }\n\n        return Promise.reject(error)\n    }\n)\n\n/**\n * 核心请求函数\n */\nexport function request(config) {\n    return service.request(config).then((res) => {\n        //成功时只返回 data 字段，简化业务层调用\n        return res.data.data\n    })\n}\n\n// -------- HTTP 错误处理 --------\nfunction handleHttpError(status, showError) {\n    const map = {\n        400: '请求错误',\n        401: '未授权，请重新登录',\n        403: '拒绝访问',\n        404: '接口不存在',\n        500: '服务器错误'\n    }\n\n    if (showError) {\n        ElMessage.error(map[status] || '网络错误')\n    }\n\n    if (status === 401) {\n        handleLogout()\n    }\n}\n\n// -------- 退出登录 --------\nfunction handleLogout() {\n    localStorage.removeItem('token')\n    router.replace({ name: 'login' })\n}\n",[24,183,181],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":185},[],{"date":187,"tags":188},"2026-03-21 22:24:24",[189,190,191,192],"typescript","封装","请求","axios","/request",{"title":116,"description":121},"request","VTXgAhQZbm_PAJwu2ZKyZLCLTzvtFhvH-6sy3WboqfU",{"id":198,"title":199,"body":200,"description":22,"extension":104,"meta":266,"navigation":110,"path":273,"seo":274,"stem":270,"__hash__":275},"content/rtsp.md","ffmpeg实现前端播放rtsp",{"type":8,"value":201,"toc":259},[202,206,210,214,217,223,226,234,237,241,247,251],[73,203,205],{"id":204},"html5-是不是就真的没办法播放-flv-等格式视频了呢不是解决方案是-msemedia-source-extensions就是说html5-不仅可以直接播放上面支持的-mp4m3u8webmogg-格式还可以支持由-js-处理过后的视频流这样我们就可以用-js-把一些不支持的视频流格式转化为支持的格式如-h264-的-mp4b-站开源的-flvjs-就是这个技术的一个典型实现b-站的-pc-html5-播放器就是用-mse-技术将-flv-源用-js-实时转码成-html5-支持的视频流编码格式其实就一个文件头的差异这里文件头改成容器感谢评论区谦谦的指教是容器的差异容器不只是文件头提供给-html5-播放器播放","HTML5 是不是就真的没办法播放 FLV 等格式视频了呢？不是。解决方案是 MSE，Media Source Extensions，就是说，HTML5 不仅可以直接播放上面支持的 mp4、m3u8、webm、ogg 格式，还可以支持由 JS 处理过后的视频流，这样我们就可以用 JS 把一些不支持的视频流格式，转化为支持的格式（如 H.264 的 mp4）。B 站开源的 flv.js 就是这个技术的一个典型实现。B 站的 PC HTML5 播放器，就是用 MSE 技术，将 FLV 源用 JS 实时转码成 HTML5 支持的视频流编码格式（其实就一个文件头的差异（这里文件头改成容器。感谢评论区谦谦的指教，是容器的差异，容器不只是文件头）），提供给 HTML5 播放器播放。",[73,207,209],{"id":208},"原理前端本不支持rtsp播放这里是通过nodejs驱动ffmpeg进行后台转码flv并通过前端mse技术渲染到video标签当中","原理：前端本不支持rtsp播放，这里是通过Node.js驱动ffmpeg进行后台转码FLV，并通过前端MSE技术渲染到video标签当中",[11,211,213],{"id":212},"核心指令ffmpeg把rtsp转rmtp","核心指令：ffmpeg把rtsp转rmtp",[27,215,216],{},"（ffmpeg默认推流方式采用UDP方式，若需要使用TCP协议，则需要修改-rtsp_transport tcp，-c copy表示不经转码,直接进行流复制，在以前的ffmpeg里，“-c copy”是以“-vcodec copy -acodec copy”这种形式表示的。现在的ffmpeg不存在这个问题了，而且这两种都可以用）",[16,218,221],{"className":219,"code":220,"language":21,"meta":22},[19],"ffmpeg -rtsp_transport tcp -i your_rtsp_url -c copy -f flv rtcmp://127.0.0.1:1935/live/\n",[24,222,220],{"__ignoreMap":22},[27,224,225],{},"转rmtp需要nginx的nginx-rtmp-module模块，nginx.conf配置如下",[16,227,232],{"className":228,"code":230,"language":231,"meta":22},[229],"language-nginx","rtmp {\n    server {\n        listen 1935;#监听端口,若被占用,可以更改\n        chunk_size 4096;#上传flv文件块儿的大小\n        application live { #创建一个叫live的应用\n             live on;#开启live的应用\n             allow publish 127.0.0.1;\n             allow play all;\n        }\n    }\n}\n","nginx",[24,233,230],{"__ignoreMap":22},[27,235,236],{},"首先确保你已安装了ffmpeg以及websocket-stream、fluent-ffmpeg模块",[11,238,240],{"id":239},"后端nodejs","后端:nodejs",[16,242,245],{"className":243,"code":244,"language":54,"meta":22},[52],"const WebSocket =require( 'ws')\nconst webSocketStream =require( 'websocket-stream/stream')\nconst ffmpeg =require( 'fluent-ffmpeg')\n\n// 建立WebSocket服务\nconst wss = new WebSocket.Server({ port: 9999, perMessageDeflate: false })\n\n// 监听连接\nwss.on('connection', handleConnection)\n\n// 连接时触发事件\nfunction handleConnection (ws, req) {\nconsole.log('一个客户端连接进来啦')\n  // 获取前端请求的流地址（前端websocket连接时后面带上流地址）\n  const url = req.url.slice(1)\n  // 传入连接的ws客户端 实例化一个流\n  const stream = webSocketStream(ws, { binary: true })\n  // 通过ffmpeg命令 对实时流进行格式转换 输出flv格式\n  const ffmpegCommand = ffmpeg(url)\n    .addInputOption('-rtsp_transport', 'tcp')\n    .on('start', function () { console.log('Stream started.') })\n    .on('codecData', function () { console.log('Stream codecData.') })\n    .on('error', function (err) {\n      console.log('An error occured: ', err.message)\n      stream.end()\n    })\n    .on('end', function () {\n      console.log('Stream end!')\n      stream.end()\n    })\n    .outputFormat('flv').videoCodec('copy').noAudio()\n\n  stream.on('close', function () {\n    ffmpegCommand.kill('SIGKILL')\n  })\n\n  try {\n    // 执行命令 传输到实例流中返回给客户端\n    ffmpegCommand.pipe(stream)\n  } catch (error) {\n    console.log(error)\n  }\n}\n",[24,246,244],{"__ignoreMap":22},[11,248,250],{"id":249},"前端vue3","前端:vue3",[16,252,257],{"className":253,"code":255,"language":256,"meta":22},[254],"language-html","\u003Ctemplate>\n    \u003Cdiv class=\"streamer\">\n        \u003Cvideo :id=\"props.videoId\" autoplay muted controls width=\"100%\" height=\"100%\">\u003C/video>\n    \u003C/div>\n\u003C/template>\n\n\u003Cscript setup>\n    import flvjs from '@/static/js/flv.min'\n    import {defineProps,onMounted,onBeforeUnmount,ref} from 'vue'\n    let flvPlayer=ref(null)\n    const props = defineProps({\n        url:{\n            type:String,\n            required:true\n        },\n        videoId: {\n            type: String,\n            default: 'player'\n        },\n    })\n    onMounted(()=>{\n        console.log(props.videoId)\n        const videoElement = document.getElementById(props.videoId)\n        flvPlayer.value = flvjs.createPlayer({\n            isLive: true,\n            type: 'flv',\n            url: 'ws://localhost:9999/'+props.url,\n            enableWorker: true,//启用分离线程\n            enableStashBuffer: false,//关闭IO隐藏缓冲区\n            stashInitialSize: 128, // 减少首桢显示等待时长\n            autoCleanupSourceBuffer: true, //自动清除缓存\n        })\n        flvPlayer.value.attachMediaElement(videoElement)\n        flvPlayer.value.load()//加载\n        setTimeout(()=>{\n            flvPlayer.value.play();\n        } ,1000);\n    })\n    onBeforeUnmount(()=>{\n        if(flvPlayer.value){\n            flvPlayer.value.destroy()\n        }\n    })\n\u003C/script>\n","html",[24,258,255],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":260},[261,262,263,264,265],{"id":204,"depth":102,"text":205},{"id":208,"depth":102,"text":209},{"id":212,"depth":95,"text":213},{"id":239,"depth":95,"text":240},{"id":249,"depth":95,"text":250},{"date":267,"tags":268},"2024-02-19 14:39:04",[269,270,271,272],"ffmpeg","rtsp","vue","nodejs","/rtsp",{"title":199,"description":22},"T3Ji_0sZTZkwSJV8zv6z8ZRHGZ3Iyj_aI_GK37hSPuw",{"id":277,"title":278,"body":279,"description":286,"extension":104,"meta":398,"navigation":110,"path":403,"seo":404,"stem":401,"__hash__":405},"content/redis.md","linux系统安装redis教程",{"type":8,"value":280,"toc":393},[281,284,287,293,296,302,305,310,313,316,322,325,331,334,340,343,349,352,355,361,364,370,373,376,382,385],[123,282,283],{"id":283},"安装redis",[27,285,286],{},"1、检查是否有redis yum 源",[16,288,291],{"className":289,"code":290,"language":21,"meta":22},[19],"yum install redis\n",[24,292,290],{"__ignoreMap":22},[27,294,295],{},"2、下载fedora的epel仓库",[16,297,300],{"className":298,"code":299,"language":21,"meta":22},[19],"yum install epel-release\n",[24,301,299],{"__ignoreMap":22},[27,303,304],{},"3、安装redis数据库",[16,306,308],{"className":307,"code":290,"language":21,"meta":22},[19],[24,309,290],{"__ignoreMap":22},[27,311,312],{},"4、安装完毕后，使用下面的命令启动redis服务",[11,314,315],{"id":315},"启动redis",[16,317,320],{"className":318,"code":319,"language":21,"meta":22},[19],"service redis start\n",[24,321,319],{"__ignoreMap":22},[11,323,324],{"id":324},"停止redis",[16,326,329],{"className":327,"code":328,"language":21,"meta":22},[19],"service redis stop\n",[24,330,328],{"__ignoreMap":22},[27,332,333],{},"##3 查看redis运行状态",[16,335,338],{"className":336,"code":337,"language":21,"meta":22},[19],"service redis status\n",[24,339,337],{"__ignoreMap":22},[11,341,342],{"id":342},"查看redis进程",[16,344,347],{"className":345,"code":346,"language":21,"meta":22},[19],"ps -ef | grep redis\n",[24,348,346],{"__ignoreMap":22},[123,350,351],{"id":351},"安装php-redis",[27,353,354],{},"最简单的：",[16,356,359],{"className":357,"code":358,"language":21,"meta":22},[19],"yum install php-redis\n",[24,360,358],{"__ignoreMap":22},[27,362,363],{},"如果这条不行，试试手动安装",[16,365,368],{"className":366,"code":367,"language":21,"meta":22},[19],"wget https://github.com/phpredis/phpredis/archive/refs/tags/5.3.7.tar.gz\ntar -zxvf phpredis-5.3.7.tar.gz\ncd phpredis-5.3.7\n/www/server/php/73/bin/phpize\n./configure --with-php-config=/www/server/php/73/bin/php-config\nmake && make install\n",[24,369,367],{"__ignoreMap":22},[27,371,372],{},"在php.ini中加入extension=redis.so",[27,374,375],{},"查看redis扩展是否安装成功",[16,377,380],{"className":378,"code":379,"language":21,"meta":22},[19],"php -m | grep redis\n",[24,381,379],{"__ignoreMap":22},[27,383,384],{},"测试:",[16,386,391],{"className":387,"code":389,"language":390,"meta":22},[388],"language-php","\u003C?php\n    //连接本地的 Redis 服务\n   $redis = new Redis();\n   $redis->connect('127.0.0.1', 6379);\n   $redis->auth('123456');\n   echo \"Connection to server sucessfully\";\n   //查看服务是否运行\n   echo \"Server is running: \" . $redis->ping();\n","php",[24,392,389],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":394},[395,396,397],{"id":315,"depth":95,"text":315},{"id":324,"depth":95,"text":324},{"id":342,"depth":95,"text":342},{"date":399,"tags":400},"2023-04-12 19:30:49",[401,402],"redis","linux","/redis",{"title":278,"description":286},"-ekNrrng4pbiLHW8G9Atfmhk96y5kfu0hQqJSmy0rW0",{"id":407,"title":408,"body":409,"description":22,"extension":104,"meta":517,"navigation":110,"path":522,"seo":523,"stem":520,"__hash__":524},"content/webhook.md","git 代码实时同步更新到宝塔",{"type":8,"value":410,"toc":508},[411,415,421,425,431,434,440,443,446,452,455,462,465,471,474,480,483,487,493,502],[11,412,414],{"id":413},"执行脚本代码健全版","执行脚本代码（健全版）",[16,416,419],{"className":417,"code":418,"language":21,"meta":22},[19],"#!/bin/bash\n\n# ===== 日志开始 =====\necho \"\"\necho \"==============================\"\necho \"时间: $(date '+%Y-%m-%d %H:%M:%S')\"\necho \"开始执行 webhook\"\necho \"当前用户: $(whoami)\"\necho \"当前目录: $(pwd)\"\necho \"PATH: $PATH\"\necho \"==============================\"\n\n# 遇到错误直接退出\nset -e\n\n# git项目路径\ngitPath=\"/www/wwwroot/项目名称\"\ngitHttp=\"https://gitee.com/项目作者/项目名称.git\"\n#gitHttp=\"git@gitee.com:项目作者/项目名称.git\"\necho \"项目路径: $gitPath\"\n\n# ===== 检查 git =====\necho \"git版本:\"\ngit --version || echo \"git 不存在！\"\n\n# ===== 检查目录 =====\nif [ -d \"$gitPath\" ]; then\n    echo \"目录存在\"\nelse\n    echo \"目录不存在，创建\"\n    mkdir -p $gitPath\nfi\n\ncd $gitPath\n\necho \"进入目录后: $(pwd)\"\nls -al\n\n# ===== 修复 safe.directory（关键）=====\ngit config --global --add safe.directory $gitPath || true\n\n# ===== 如果没有 .git =====\nif [ ! -d \".git\" ]; then\n    echo \"未检测到 .git，开始 clone\"\n\n    rm -rf *\n    \n    git clone $gitHttp . || {\n        echo \"❌ git clone 失败\"\n        exit 1\n    }\nelse\n    echo \"检测到 .git\"\nfi\n\n# ===== 检查远程 =====\necho \"远程仓库信息:\"\ngit remote -v\n\n# ===== 当前分支 =====\nbranch=$(git symbolic-ref --short HEAD || echo \"master\")\necho \"当前分支: $branch\"\n\n# ===== 强制同步 =====\necho \"开始 reset\"\ngit fetch --all\n\ngit reset --hard origin/$branch || {\n    echo \"❌ reset 失败\"\n    exit 1\n}\n\necho \"开始 pull\"\ngit pull origin $branch || {\n    echo \"❌ pull 失败\"\n    exit 1\n}\n\n# ===== 文件确认 =====\necho \"拉取后文件:\"\nls -al | head -20\n\n# ===== Node 处理 =====\nif [ -f \"package.json\" ]; then\n    echo \"检测到 Node 项目\"\n\n    which npm || echo \"❌ npm 不存在\"\n\n    npm install --production || echo \"❌ npm install 失败\"\nfi\n\n# ===== PHP 依赖 =====\nif [ -f \"composer.json\" ]; then\n    echo \"检测到 PHP 项目，安装依赖\"\n\n    which composer || echo \"⚠️ composer 未安装\"\n\n    composer install --no-dev --optimize-autoloader || echo \"❌ composer install 失败\"\nfi\n\n# ===== 权限 =====\necho \"设置权限\"\nchown -R www:www $gitPath\n\necho \"执行完成\"\necho \"==============================\"\n",[24,420,418],{"__ignoreMap":22},[11,422,424],{"id":423},"指定php版本","指定php版本：",[16,426,429],{"className":427,"code":428,"language":21,"meta":22},[19],"PHP_BIN=\"/www/server/php/73/bin/php\"\nCOMPOSER=\"/usr/bin/composer\"\n\n$PHP_BIN $COMPOSER install --no-dev --optimize-autoloader\n",[24,430,428],{"__ignoreMap":22},[11,432,433],{"id":433},"指定node版本",[16,435,438],{"className":436,"code":437,"language":21,"meta":22},[19],"NODE_BIN=\"/www/server/nodejs/v18.19.0/bin/node\"\nNPM_BIN=\"/www/server/nodejs/v18.19.0/bin/npm\"\n\n$NPM_BIN install --production\n$NPM_BIN run build\n",[24,439,437],{"__ignoreMap":22},[11,441,442],{"id":442},"身份验证",[27,444,445],{},"如果遇到无法同步代码的情况，还有一个操作。\n在宝塔终端里，配置git身份",[16,447,450],{"className":448,"code":449,"language":21,"meta":22},[19],"git config --global user.name \"gitee用户名\"\ngit config --global user.email \"gitee注册邮箱\"\n",[24,451,449],{"__ignoreMap":22},[11,453,454],{"id":454},"私有仓库处理方法",[456,457,458],"ul",{},[459,460,461],"li",{},"githttp记得改成ssh方式",[27,463,464],{},"生成公钥，输入下面命令后回车三下",[16,466,469],{"className":467,"code":468,"language":21,"meta":22},[19],"ssh-keygen -t rsa\n",[24,470,468],{"__ignoreMap":22},[27,472,473],{},"查看公钥，按顺序输入下面代码，最后一个是获取密钥，cat 后面是公钥文件名",[16,475,478],{"className":476,"code":477,"language":21,"meta":22},[19],"cd ~/.ssh\nls\ncat id_rsa.pub\n",[24,479,477],{"__ignoreMap":22},[27,481,482],{},"仓库就简单的默认仓库就好，要记好仓库名。\n进入仓库管理的添加公钥功能，将上面复制的密钥copy进底下后，标题会自动生成，直接点击添加即可。",[73,484,486],{"id":485},"测试","测试：",[16,488,491],{"className":489,"code":490,"language":21,"meta":22},[19],"$ ssh -T git@gitee.com\nHi Anonymous (DeployKey)! You've successfully authenticated, but GITEE.COM does not provide shell access.\nNote: Perhaps the current use is DeployKey.\nNote: DeployKey only supports pull/fetch operations\n",[24,492,490],{"__ignoreMap":22},[27,494,495,496,501],{},"如果卡在fetch / reset 卡住或失败\n改成：gitHttp=\"",[497,498,500],"a",{"href":499},"mailto:git@gitee.com","git@gitee.com",":项目作者/项目名称.git\"\n执行一次：",[16,503,506],{"className":504,"code":505,"language":21,"meta":22},[19],"cd /www/wwwroot/项目名称\ngit remote set-url origin git@gitee.com:项目作者/项目名称.git\n",[24,507,505],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":509},[510,511,512,513,514],{"id":413,"depth":95,"text":414},{"id":423,"depth":95,"text":424},{"id":433,"depth":95,"text":433},{"id":442,"depth":95,"text":442},{"id":454,"depth":95,"text":454,"children":515},[516],{"id":485,"depth":102,"text":486},{"date":518,"tags":519},"2023-01-05 21:24:09",[520,109,521],"webhook","宝塔","/webhook",{"title":408,"description":22},"7c87jS5VlbpQheooCU1sLgUlMkjfYunLxf4gPFImQro",{"id":526,"title":527,"body":528,"description":532,"extension":104,"meta":736,"navigation":110,"path":739,"seo":740,"stem":738,"__hash__":741},"content/chatgpt.md","快速注册并使用ChatGpt接口",{"type":8,"value":529,"toc":730},[530,533,536,539,542,545,548,556,562,565,571,574,580,583,592,599,605,608,615,621,624,627,630,639,644,647,650,653,657,663,667,673,677,683,687,692,695,700,705,710,715,720,725],[27,531,532],{},"最近大火的ChatGPT项目强烈的吸引了我的注意力。于是乎就尝试的注册了一个账号，然后你会发现，能让你玩一天。什么取代搜索引擎，取代程序员都不是危言耸听的话。",[27,534,535],{},"开始之前先简单了解一下什么是ChatGPT：OpenAI的一个人工智能项目，说是能直接生成代码、自动修复bug、在线问诊、模仿名人写作甚至代替各大搜索引擎的一个人工智能项目。ChatGPT 是 OpenAI 训练的对话式大规模语言模型，以对话的方式进行交互。它和之前的另一款模型 InstructGPT 属于同级模型，代表“GPT 3.5”代。",[27,537,538],{},"既然是OpenAI的项目，那肯定是注册一个OpenAI的账户。注册账户是免费的，但是注册账户时国内的电话号码是不能用的，所以，需要购买一个国外的电话号码，用来接受短信。简单明了，所需材料如下：",[27,540,541],{},"一个邮箱（国内可行）\n一个国外电话号码（用来接受验证短信）\n科学上网（目前国内地址注册会提示该国家不支持，所以要学会科学上网）",[123,543,544],{"id":544},"第一步通过邮箱注册",[27,546,547],{},"点击OpenAI登录界面，点击Sign up进入到注册表单页进行邮箱注册。按照注册流程一步一步进行即可。",[27,549,550,555],{},[551,552],"img",{"alt":553,"src":554},"c1","/images/ph.png","\n登录页面",[27,557,558,561],{},[551,559],{"alt":560,"src":554},"c2","\n注册信息页",[27,563,564],{},"注册信息填写完毕后，会给你发送一份邮箱，让你确认邮箱地址。然后点击确认即可。然后会跳到一个填写姓名的 页面，自己随意填写即可。",[27,566,567,570],{},[551,568],{"alt":569,"src":554},"c3","\n个人详情页",[27,572,573],{},"填写完成后，最重要的一步来了，需要你填写电话号码进行短信验证。电话列表里面虽然有中国选项，但是当你填写国内号码是你会发现提示你：‘OpenAI’s services are not available in your country.’，对，不支持国内号码，所以这个时候你就需要购买一个国外的电话号码。",[27,575,576,579],{},[551,577],{"alt":578,"src":554},"c4","\n电话号码填写",[123,581,582],{"id":582},"第二步购买国外虚拟号码",[27,584,585,586,591],{},"登录注册\n",[497,587,588],{"href":588,"rel":589},"https://sms-activate.org/cn",[590],"nofollow"," 网站，里面有各种需要的号码。目前找到最便宜的是印度的号码，只需要0.85元（截止到2022年12月8日）。购买国外点好号码流程如下：",[27,593,594,595,598],{},"登录注册上面的网站地址\n点击右上角余额进行充值（支持支付宝支付，但是最低1美元起）\n点击左侧的搜索框，输入OpenAI，点击搜索到的内容\n选择一个你想要购买的国家，点击列表右边的购物车即可（我购买的是India的，最便宜）\n然后页面中就会显示分配给你的电话号码，在进行接下来的操作\n",[551,596],{"alt":597,"src":554},"c5","\n搜索支持OpenAI的号码",[27,600,601,604],{},[551,602],{"alt":603,"src":554},"c6","\n点击购物车购买",[123,606,607],{"id":607},"第三步复制号码进行验证",[27,609,610,611,614],{},"复制上文加入到购物车的虚拟电话号码。其中前两位代表的是区号，后面几位才是实际意义上的电话号码。复制号码到你在OpenAI的验证号码页面，然后进行验证。点击‘Send Code’后应该就会在你的\n",[497,612,588],{"href":588,"rel":613},[590]," 页面收到验证码，然后输入OpenAI注册验证即可（如果没有收到验证码，在二十分钟之内虚拟号码是可以免费更换的，我是一次就通过了，很快几秒钟就收到）。",[27,616,617,620],{},[551,618],{"alt":619,"src":554},"c7","\n验证码填写",[27,622,623],{},"就这样，一切顺利，一分钟之内就可以注册完成。然后就可以体验ChatGPT的强大功能了。当你尝试后也许会落寞，因为它的确很轻大，强大到可能真的要替换部分人工工作。甚至搜索引擎都可以代替了，而且还没有广告。",[123,625,626],{"id":626},"第四步获取令牌",[27,628,629],{},"要获取会话令牌：",[27,631,632,633,638],{},"1.转到",[497,634,637],{"href":635,"rel":636},"https://chat.openai.com/chat%E5%B9%B6%E7%99%BB%E5%BD%95%E6%88%96%E6%B3%A8%E5%86%8C%E3%80%82",[590],"https://chat.openai.com/chat并登录或注册。","\n2.打开开发工具。\n3.打开Application> Cookies。 聊天 GPT cookie\n4.复制值__Secure-next-auth.session-token并将其保存到您的环境中。",[27,640,641],{},[551,642],{"alt":643,"src":554},"c8",[123,645,646],{"id":646},"第五步调试运行",[27,648,649],{},"这里，我选择的是NodeJs作为服务端,Express作为MVC框架",[27,651,652],{},"Node版本要大于16，低版本可能不支持await import语法",[11,654,656],{"id":655},"_1安装核心库","1.安装核心库：",[16,658,661],{"className":659,"code":660,"language":21,"meta":22},[19],"npm install chatgpt\n",[24,662,660],{"__ignoreMap":22},[11,664,666],{"id":665},"_2封装一个js文件chatgptjs","2.封装一个js文件(chatgpt.js)",[16,668,671],{"className":669,"code":670,"language":54,"meta":22},[52],"let chatApi = null;\n\nasync function init() {\n    const { ChatGPTAPI } = await import('chatgpt')\n    // sessionToken is required; see below for details\n    const api = new ChatGPTAPI({\n        sessionToken: '你的token'\n    })\n\n    // ensure the API is properly authenticated\n    await api.ensureAuth()\n\n    chatApi = api;\n}\n\nasync function sendMsg(text){\n    // send a message and wait for the response\n    if(!chatApi){\n        return;\n    }\n    const response = await chatApi.sendMessage(text)\n    return response;\n}\n\ninit().then( async () => {\n    console.log(\"初始化ChatGpt成功\")\n})\n\n\nexports.sendMsg = sendMsg;\n",[24,672,670],{"__ignoreMap":22},[11,674,676],{"id":675},"_3在运行的地方引入文件","3.在运行的地方引入文件",[16,678,681],{"className":679,"code":680,"language":54,"meta":22},[52],"const chatApi = require(\"../static/js/chatgpt.js\");\nconst res= await chatApi.sendMsg('What is OpenAI?')\nconsole.log(res)\n",[24,682,680],{"__ignoreMap":22},[11,684,686],{"id":685},"调试输出","调试输出：",[27,688,689],{},[551,690],{"alt":691,"src":554},"c9",[123,693,694],{"id":694},"实战效果",[27,696,697],{},[551,698],{"alt":699,"src":554},"c10",[27,701,702],{},[551,703],{"alt":704,"src":554},"c11",[27,706,707],{},[551,708],{"alt":709,"src":554},"c12",[27,711,712],{},[551,713],{"alt":714,"src":554},"c13",[27,716,717],{},[551,718],{"alt":719,"src":554},"c14",[27,721,722],{},[551,723],{"alt":724,"src":554},"c15",[27,726,727],{},[551,728],{"alt":729,"src":554},"c16",{"title":22,"searchDepth":95,"depth":95,"links":731},[732,733,734,735],{"id":655,"depth":95,"text":656},{"id":665,"depth":95,"text":666},{"id":675,"depth":95,"text":676},{"id":685,"depth":95,"text":686},{"date":737,"tags":738},"2022-12-10 14:42:25","chatgpt","/chatgpt",{"title":527,"description":532},"kfPKzJDo5pCDGna8TEf2K0rE5xHHCRhBF2PlVEZhvgw",{"id":743,"title":744,"body":745,"description":749,"extension":104,"meta":851,"navigation":110,"path":854,"seo":855,"stem":856,"__hash__":857},"content/colab.md","云端部署NovelAi",{"type":8,"value":746,"toc":849},[747,750,753,756,759,766,769,777,783,789,795,798,801,806,809,814,817,820,826,831,837,840,843,846],[27,748,749],{},"NovelAi不用再说了吧，当下最火的AI绘图网站，很多人只是跟着视频学到怎么部署到本地，但是，作为程序员来说光部署到本地是无法满足需求的。",[27,751,752],{},"目前有WEBUI和NAIFU两种版本，由于NAIFU更耗时间，这里选择WEBUI版本",[27,754,755],{},"目前GPU服务器价格居高(阿里的最低配500多r每月)，这里使用Google Colab作为方案。",[27,757,758],{},"Colab是Google的一项可以免费使用GPU资源的云服务，因为是Google的服务，所以首先你需要能够访问谷歌。如果不能，点这里，最后那个，每天免费一小时，先用着。其次你需要有一个谷歌账号。谷歌账号的注册过程不多说，如果注册接收验证码时出现“此电话号码无法用于进行验证”的情况，可点击浏览器设置中的语言选项，添加英语（美国），并移除中文（简体）后重开浏览器再重新注册，如果还是不能请自行搜索其他方法。",[27,760,761,762],{},"地址：",[497,763,764],{"href":764,"rel":765},"https://colab.research.google.com/",[590],[27,767,768],{},"Colab笔记代码如下",[16,770,775],{"className":771,"code":773,"language":774,"meta":22},[772],"language-python","import os\nfrom google.colab import drive\ndrive.mount('/content/drive')\n","python",[24,776,773],{"__ignoreMap":22},[16,778,781],{"className":779,"code":780,"language":21,"meta":22},[19],"!git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui\n%cd /content/drive/MyDrive/stable-diffusion-webui\n",[24,782,780],{"__ignoreMap":22},[16,784,787],{"className":785,"code":786,"language":21,"meta":22},[19],"!mkdir -p /content/drive/MyDrive/stable-diffusion-webui/models/Stable-diffusion /content/drive/MyDrive/stable-diffusion-webui/models/hypernetworks\n%cd /content/drive/MyDrive/stable-diffusion-webui/models/Stable-diffusion/\n\n# 4G animefull-final-pruned (backup)\n!curl -Lo model.ckpt https://cloudflare-ipfs.com/ipfs/bafybeicpamreyp2bsocyk3hpxr7ixb2g2rnrequub3j2ahrkdxbvfbvjc4/model.ckpt\n\n# Install VAE Weights (optional)\n!curl -Lo model.vae.pt https://cloudflare-ipfs.com/ipfs/bafybeiccldswdd3wvg57jhclcq53lvsc6gizasiblwayvhlv6eq4wow7wu/animevae.pt \n\n# Install hypernetwork （optional)\n!curl -L https://cloudflare-ipfs.com/ipfs/bafybeiduanx2b3mcvxlwr66igcwnpfmk3nc3qgxlpwh6oq6m6pxii3f77e/_modules.tar | tar x -C /content/drive/MyDrive/stable-diffusion-webui/models/hypernetworks\n",[24,788,786],{"__ignoreMap":22},[16,790,793],{"className":791,"code":792,"language":21,"meta":22},[19],"%cd /content/drive/MyDrive/stable-diffusion-webui\n!COMMANDLINE_ARGS=\"--share --gradio-debug --medvram\" REQS_FILE=\"requirements.txt\" python launch.py\n",[24,794,792],{"__ignoreMap":22},[27,796,797],{},"依次运行笔记下的所有代码",[27,799,800],{},"最终输出结果：",[27,802,803],{},[551,804],{"alt":805,"src":554},"a2",[27,807,808],{},"最后那个地址就是公共访问地址，输入到浏览器即可访问",[27,810,811],{},[551,812],{"alt":813,"src":554},"a3",[27,815,816],{},"注意如果使用的是免费Colab，请不要批量生成，这会导致配额快速流失",[27,818,819],{},"配置api路由：\n修改：stable-diffusion-webui\\modules的ui.py",[16,821,824],{"className":822,"code":823,"language":774,"meta":22},[772],"submit.click(**txt2img_args,api_name=\"txt2img\")\nsubmit.click(**img2img_args,api_name=\"img2img\")\n",[24,825,823],{"__ignoreMap":22},[27,827,828],{},[551,829],{"alt":830,"src":554},"a1",[27,832,833,834],{},"在网站最下方有viewapi，点击\n",[551,835],{"alt":836,"src":554},"a4",[27,838,839],{},"goolecolab每天都会清理gpu并且有限额，所以云端部署持续时间最多只有一天，一天过后就又要重新部署，每次重新启动都要先连接云盘再执行",[27,841,842],{},"Colab 之所以能够免费提供资源，部分原因在于它的用量限额是时有变化的动态限额，并且它不会保证资源供应或无限供应资源。也就是说，总体用量限额、空闲超时时长、虚拟机生命周期上限、可用 GPU 类型以及其他因素都会不时变化。Colab 不会公布这些限额，原因之一是它们不仅可能、而且有时确实会快速变化。",[27,844,845],{},"您可以在此处购买我们的付费方案来缓解 Colab 的用量限额。对于这些方案，资源可用性同样可能会随时间而动态变化。",[27,847,848],{},"您可以通过 GCP Marketplace 购买有保证的资源，以便在 Colab 中使用。",{"title":22,"searchDepth":95,"depth":95,"links":850},[],{"date":852,"tags":853},"2022-11-09 22:40:10","novelai, colab","/colab",{"title":744,"description":749},"colab","moaesa1dEFA7ST50cGVFc0Wh3Ud9yx2LNMiCvMzOUnw",{"id":859,"title":860,"body":861,"description":865,"extension":104,"meta":874,"navigation":110,"path":878,"seo":879,"stem":880,"__hash__":881},"content/taobao.md","淘宝模拟登录及滑块验证",{"type":8,"value":862,"toc":872},[863,866],[27,864,865],{},"webdriver前面已经提到过，必须安装之后才能运行",[16,867,870],{"className":868,"code":869,"language":774,"meta":22},[772],"import time\nfrom selenium import webdriver\nfrom selenium.webdriver.common.action_chains import ActionChains\nfrom selenium.webdriver.common.by import By\n\nprint('使用selenium模拟登陆')\n# 使用selenium模拟登陆，获取并返回cookie\nusername = 'xxx'\npassword = 'xxx'\noptions = webdriver.ChromeOptions()  # 声明浏览器的对象\noptions.add_experimental_option('excludeSwitches', ['enable-automation'])\noptions.add_argument('--disable-blink-features')\noptions.add_argument('--disable-blink-features=AutomationControlled')  # 去除浏览器selenium监控\noptions.add_argument('--headless')  # 浏览器不提供可视化页面\noptions.add_argument('--disable-gpu')  # 禁用GPU加速\nbrowser = webdriver.Chrome(options=options)\nbrowser.get('https://login.taobao.com/')\ntime.sleep(1)\nbrowser.find_element(By.XPATH, '//*[@id=\"fm-login-id\"]').send_keys(username)\ntime.sleep(1)\nbrowser.find_element(By.XPATH, '//*[@id=\"fm-login-password\"]').send_keys(password)\ntime.sleep(1)\nbrowser.find_element(By.XPATH, '//*[@type=\"submit\"]').click()\ntime.sleep(4)\n# 检查是否出现了滑动验证码\ntry:\n    slider = browser.find_element(By.XPATH,  '//*[@id=\"nc_1_n1z\"]')\n    ActionChains(browser).click_and_hold(slider).perform()  # 按住滑块\n    ActionChains(browser).move_by_offset(300, 0).perform()  # 移动到最右边\n    ActionChains(browser).pause(0.5).release().perform()  # 松开鼠标\nexcept:\n    pass\n# cookies_dict = {cookie['name']: cookie['value'] for cookie in driver.get_cookies()}\n# driver.quit()\n# print(cookies_dict)\n",[24,871,869],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":873},[],{"date":875,"tags":876},"2022-10-31 00:48:31",[877],"selenium","/taobao",{"title":860,"description":865},"taobao","wk3SvtEMqXnvq2ANvZ-h3Irr5AhBbrONl_bMiCxNUzk",{"id":883,"title":884,"body":885,"description":892,"extension":104,"meta":2117,"navigation":110,"path":2120,"seo":2121,"stem":2119,"__hash__":2122},"content/vue3.md","从vue2到vue3，一文带你看完vue3的新特性",{"type":8,"value":886,"toc":2072},[887,890,893,896,902,906,909,912,915,918,923,926,929,932,935,940,945,948,951,954,957,960,963,966,969,973,977,981,984,987,991,994,999,1002,1008,1011,1014,1017,1020,1023,1026,1029,1032,1035,1038,1041,1044,1047,1050,1056,1059,1062,1068,1071,1077,1080,1083,1089,1093,1096,1099,1105,1108,1111,1114,1117,1123,1126,1129,1135,1138,1141,1145,1149,1152,1156,1159,1165,1170,1173,1177,1180,1183,1186,1190,1193,1199,1202,1205,1211,1214,1220,1223,1228,1231,1235,1238,1244,1248,1251,1257,1261,1265,1268,1274,1277,1280,1283,1289,1292,1296,1299,1302,1305,1308,1314,1317,1321,1324,1327,1330,1333,1336,1339,1342,1346,1350,1353,1356,1362,1366,1369,1377,1380,1384,1390,1394,1400,1404,1407,1413,1417,1420,1426,1429,1433,1436,1442,1445,1451,1454,1457,1461,1465,1468,1471,1475,1478,1484,1487,1493,1496,1502,1505,1511,1514,1520,1523,1529,1532,1538,1541,1545,1548,1554,1557,1563,1567,1571,1574,1577,1580,1586,1589,1595,1599,1602,1605,1607,1613,1615,1621,1625,1628,1631,1637,1640,1646,1649,1655,1659,1662,1668,1670,1676,1680,1683,1686,1689,1695,1698,1704,1707,1711,1714,1719,1722,1725,1730,1733,1736,1740,1744,1748,1751,1757,1760,1766,1769,1772,1778,1781,1787,1790,1793,1799,1802,1805,1811,1814,1818,1821,1827,1833,1836,1842,1846,1849,1855,1858,1864,1867,1870,1873,1877,1881,1884,1890,1894,1897,1903,1907,1910,1913,1919,1923,1927,1930,1936,1940,1943,1949,1952,1958,1961,1967,1970,1973,1977,1980,1983,1986,1992,1995,2001,2004,2007,2012,2015,2019,2023,2026,2029,2032,2038,2041,2044,2052,2055,2061,2064,2069],[123,888,889],{"id":889},"前言",[27,891,892],{},"这篇文章的主要意义是：针对想要从vue2快速迁移到vue3的朋友，快速理解vue3的新特性。",[27,894,895],{},"为了让大家更好的理解，我会从设计者的角度来讲为什么vue3要这样设计api。",[27,897,898,899],{},"vue3新特性思维导图：\n",[551,900],{"alt":22,"src":901},"/images/v1.jpg",[123,903,905],{"id":904},"第一部分vue2的痛点代码糅杂","第一部分、vue2的痛点——代码糅杂",[27,907,908],{},"从vue2到vue3，最大的改动就是组合式API——setup。",[27,910,911],{},"为什么要这样改？",[27,913,914],{},"在vue2项目中，当一个组件的代码量超过300行时，业务逻辑分散的问题很容易出现。比如下面这张图，大家可以直观的看到代码交叉在一起，错综复杂。",[27,916,917],{},"新增或者修改一个需求，就需要分别在data，methods，computed里修改 ，滚动条反复上下移动",[27,919,920],{},[551,921],{"alt":22,"src":922},"/images/v2.jpg",[27,924,925],{},"尤其对于那些一开始没有编写这些组件的人来说，这会导致组件难以阅读和理解。",[27,927,928],{},"同时，如果想把一段业务代码从中抽离出来，要花不少的时间——你要不停找和业务相关的变量、方法，有一些地方复制漏了还容易出bug（不说了，都是血与泪o(╥﹏╥)o）",[27,930,931],{},"特别是大项目的时候，随随便便就是几百个组件，我们需要共享和重用代码，但是却vue2的语法导致很难抽离业务代码复用。",[27,933,934],{},"那我们就想啊， 如果可以按照逻辑进行分割，将上面这张图变成下边这张图，是不是就清晰很多了呢, 这样的代码可读性和可维护性都更高：",[27,936,937],{},[551,938],{"alt":22,"src":939},"/images/v3.jpg",[27,941,942],{},[551,943],{"alt":22,"src":944},"/images/v4.jpg",[27,946,947],{},"针对这个问题，虽然vue2.x 版本给出的解决方案—— Mixin, 但是使用 Mixin 也会遇到让人苦恼的问题：",[27,949,950],{},"命名冲突问题\n不清楚暴露出来的变量的作用\n逻辑重用到其他 component 经常遇到问题\n好，问题来了，如果你是设计者，你如何解决代码的共享和复用的问题？？",[27,952,953],{},"我打个分割线，你可以思考几分钟。",[27,955,956],{},"好了，公布答案：在语法上，允许把业务逻辑写在单独的js文件里，就可以解决了。",[27,958,959],{},"而vue3也是这样做的，Vue3直接抄了一手React1.68版本推出的「hook」，「hook」的含义就是上面提到的——需要大量复用的业务函数，「hook」的作用类似于vue2中的mixin技术，但是功能更强。",[27,961,962],{},"说简单点——「hook」就是具有高度复用性的业务js文件。",[27,964,965],{},"那么问题来了，如果要允许把业务逻辑写在单独的js文件里，你就不能像原来一样，变量必须写在vue文件的data里，方法必须写在vue文件的methods里，否则就没办法把代码抽离出去。",[27,967,968],{},"因此，我们就需要一套新的语法，「hook」的实现就是这套新的语法——Vue3的组合式API。",[123,970,972],{"id":971},"第二部分vue3的组合式apicomposition-api","第二部分、Vue3的组合式API（Composition API）",[11,974,976],{"id":975},"一setup","一、setup",[73,978,980],{"id":979},"_1setup的定义","1.setup的定义",[27,982,983],{},"setup 是 Vue3.x 新增的一个选项， 他是组件内使用组合式API的入口， 所有的组合API函数都在此使用, 只在初始化时执行一次。",[27,985,986],{},"setup的意义：允许你把vue2中的data的变量、method的方法、compute、watch等等API，直接写在JS里。",[73,988,990],{"id":989},"_2setup的语法","2.setup的语法",[27,992,993],{},"setup有两种语法，分别是vue3.0的setup函数版，和vue3.2以上的setup语法糖版，我们一个个来看。",[995,996,998],"h4",{"id":997},"_1写法1vue30setup函数","1）写法1：vue3.0，setup函数",[27,1000,1001],{},"为方便大家理解，我把vue2.0的写法也贴上去了，setup写法看下方代码：",[16,1003,1006],{"className":1004,"code":1005,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Ch2>{{count}}\u003C/h2>\n  \u003Chr>\n  \u003Cbutton @click=\"update\">更新\u003C/button>\n\u003C/template>\n \n\u003Cscript>\nimport {\n  ref\n} from 'vue'\nexport default {\n \n  /* 在Vue3中依然可以使用data和methods配置, 但建议使用其新语法实现 */\n  // data () {\n  //   return {\n  //     count: 0\n  //   }\n  // },\n  // methods: {\n  //   update () {\n  //     this.count++\n  //   }\n  // }\n \n  /* 使用vue3的composition API */\n  setup () {\n \n    // 定义响应式数据 ref对象 后面会讲\n    const count = ref(1)\n    console.log(count)\n \n    // 更新响应式数据的函数\n    function update () {\n      // alert('update')\n      count.value = count.value + 1\n    }\n    \n    // 暴露给 template\n    return {\n      count,\n      update\n    }\n  }\n}\n\u003C/script>\n",[24,1007,1005],{"__ignoreMap":22},[27,1009,1010],{},"setup函数的注意点：",[27,1012,1013],{},"①声明变量需要ref、reactive",[27,1015,1016],{},"作为vue2.0data的语法的代替，vue3提供了新的响应式变量声明方法——ref、reactive，这个后面会详细讲。",[27,1018,1019],{},"②暴露变量必须 return",[27,1021,1022],{},"setup方法一般都返回一个对象: 为模板提供数据, 也就是模板中可以直接使用此对象中的所有属性/方法。",[27,1024,1025],{},"所以，最后要把注册的对象和方法 return 给模板，这个不要忘了。如果你发现模板的变量没有正确更新，可以检查一下这个属性有没有return。",[27,1027,1028],{},"③method绑定函数的语法变化",[27,1030,1031],{},"原来在method绑定函数的语法，到vue3中变为了，在setup内声明函数，然后return即可。",[27,1033,1034],{},"④setup方法的参数",[27,1036,1037],{},"setup中不能访问 Vue2 中最常用的this对象（后面会讲），这就带来了一个问题：",[27,1039,1040],{},"在vue2语法中，组件的this.props、子组件的this.$refs.xxx，原来这些依赖this对象的api就要调整。",[27,1042,1043],{},"vue3把这些依赖this对象的api，放在了setup 参数里，使用setup时，它接受两个参数：",[27,1045,1046],{},"参数1：props:",[27,1048,1049],{},"组件传入的属性，setup 函数中的 props 是响应式的，当传入新的 prop 时，它将被更新。",[16,1051,1054],{"className":1052,"code":1053,"language":54,"meta":22},[52],"export default {\n  props: {\n    title: String\n  },\n  setup(props) {\n    console.log(props.title)\n  }\n}\n",[24,1055,1053],{"__ignoreMap":22},[27,1057,1058],{},"注意：因为 props 是响应式的，你不能使用 ES6 解构，它会消除 prop 的响应性。",[27,1060,1061],{},"如果需要解构 prop，可以在 setup 函数中使用 toRefs 函数来完成此操作：",[16,1063,1066],{"className":1064,"code":1065,"language":54,"meta":22},[52],"import { toRefs } from 'vue'\n \nsetup(props) {\n  const { title } = toRefs(props)\n \n  console.log(title.value)\n}\n",[24,1067,1065],{"__ignoreMap":22},[27,1069,1070],{},"如果 title 是可选的 prop，则传入的 props 中可能没有 title 。在这种情况下，toRefs 将不会为 title 创建一个 ref 。你需要使用 toRef （比toRefs少了个s）替代它：",[16,1072,1075],{"className":1073,"code":1074,"language":54,"meta":22},[52],"import { toRef } from 'vue'\nsetup(props) {\n  const title = toRef(props, 'title')\n  console.log(title.value)\n}\n",[24,1076,1074],{"__ignoreMap":22},[27,1078,1079],{},"参数2：context",[27,1081,1082],{},"接下来说一下第二个参数context，我们前面说了setup中不能访问 Vue2 中最常用的this对象，所以context中就提供了this中最常用的三个属性：attrs、slot 和emit，分别对应 Vue2.x 中的 $attr属性、slot插槽 和$emit发射事件。并且这几个属性都是自动同步最新的值，所以我们每次使用拿到的都是最新值。",[16,1084,1087],{"className":1085,"code":1086,"language":54,"meta":22},[52],"export default {\n  setup(props, context) {\n    // Attribute (非响应式对象，等同于 $attrs)\n    console.log(context.attrs)\n \n    // 插槽 (非响应式对象，等同于 $slots)\n    console.log(context.slots)\n \n    // 触发事件 (方法，等同于 $emit)\n    console.log(context.emit)\n \n    // 暴露公共 property (函数)\n    console.log(context.expose)\n  }\n}\n",[24,1088,1086],{"__ignoreMap":22},[995,1090,1092],{"id":1091},"_2写法2vue32以上setup语法糖","2）写法2：vue3.2以上，setup语法糖",[27,1094,1095],{},"Vue3.2 中 只需要在 script 标签上加上 setup 属性，组件在编译的过程中代码运行的上下文是在 setup() 函数中，无需return，template可直接使用。",[27,1097,1098],{},"要使用这个语法，需要将 setup attribute 添加到 script 代码块上：",[16,1100,1103],{"className":1101,"code":1102,"language":256,"meta":22},[254],"\u003Cscript setup>\nconsole.log('hello script setup')\n\u003C/script>\n",[24,1104,1102],{"__ignoreMap":22},[27,1106,1107],{},"里面的代码会被编译成组件 setup() 函数的内容。这意味着与普通的 script 只在组件被首次引入的时候执行一次不同，script setup 中的代码会在每次组件实例被创建的时候执行。",[27,1109,1110],{},"setup语法糖的细节：",[27,1112,1113],{},"①顶层的绑定会被暴露给模板",[27,1115,1116],{},"当使用 script setup 的时候，任何在 script setup 声明的顶层的绑定 (包括变量，函数声明，以及 import 引入的内容) 都能在模板中直接使用：",[16,1118,1121],{"className":1119,"code":1120,"language":256,"meta":22},[254],"\u003Cscript setup>\n// 变量\nconst msg = 'Hello!'\n \n// 函数\nfunction log() {\n  console.log(msg)\n}\n\u003C/script>\n \n\u003Ctemplate>\n  \u003Cdiv @click=\"log\">{{ msg }}\u003C/div>\n\u003C/template>\n",[24,1122,1120],{"__ignoreMap":22},[27,1124,1125],{},"②引入的组件自动注册",[27,1127,1128],{},"import 导入的内容也会以同样的方式暴露。意味着可以在模板表达式中直接使用导入的 helper 函数，并不需要通过 methods 选项来暴露它：",[16,1130,1133],{"className":1131,"code":1132,"language":256,"meta":22},[254],"\u003Cscript setup>\nimport { capitalize } from './helpers'\n\u003C/script>\n \n\u003Ctemplate>\n  \u003Cdiv>{{ capitalize('hello') }}\u003C/div>\n\u003C/template>\n",[24,1134,1132],{"__ignoreMap":22},[27,1136,1137],{},"③获取props、emit从函数参数改成了引入API",[27,1139,1140],{},"之前setup内获取props、emit是通过setup函数参数传递的，由于移除了setup方法，这些参数的获取换成了APIdefineProps 和 defineEmits，具体使用会在后面props、emit修改中讲到。",[73,1142,1144],{"id":1143},"_3setup注意点","3.setup注意点：",[995,1146,1148],{"id":1147},"_1setup中没有this","1）setup中没有this",[27,1150,1151],{},"在 setup() 内部，this 不是该活跃实例的引用，因为 setup() 是在解析其它组件选项之前被调用的，所以 setup() 内部的 this 的行为与其它选项中的 this 完全不同。这使得 setup() 在和其它选项式 API 一起使用时可能会导致混淆。",[995,1153,1155],{"id":1154},"_2setup的执行时机","2）setup的执行时机",[27,1157,1158],{},"我在学习过程中看到很多文章都说 setup 是在 beforeCreate和created之间， 这个结论是错误的。实践是检验真理的唯一标准， 于是自己去检验了一下：",[16,1160,1163],{"className":1161,"code":1162,"language":54,"meta":22},[52],"export default defineComponent({\n  beforeCreate() {\n    console.log(\"----beforeCreate----\");\n  },\n  created() {\n    console.log(\"----created----\");\n  },\n  setup() {\n    console.log(\"----setup----\");\n  },\n});\n",[24,1164,1162],{"__ignoreMap":22},[27,1166,1167],{},[551,1168],{"alt":22,"src":1169},"/images/v5.jpg",[27,1171,1172],{},"setup 执行时机是在 beforeCreate 之前执行，详细的可以看后面生命周期讲解。",[11,1174,1176],{"id":1175},"二响应式对象的绑定reactiveref","二、响应式对象的绑定——reactive、ref",[27,1178,1179],{},"接下来要讲vue3组合式API中2个最重要的响应式API——reactive和ref，这两兄弟的作用是什么呢？",[27,1181,1182],{},"实际上就是vue2中的data，定义出响应式的对象。要知道，在setup函数里，如果用var随便定义一个变量，然后return给模板，是无法实现模板驱动视图的，即你修改了变量的值，视图不会跟着变，所以就需要一个生产响应式对象的工厂函数——也就是ref，vue称为这个对象为ref对象。",[27,1184,1185],{},"下面一个个的来看：",[73,1187,1189],{"id":1188},"_1ref","1.ref",[27,1191,1192],{},"作用: 定义一个数据的响应式\n语法: const xxx = ref(initValue):\n创建一个包含响应式数据的引用(reference)对象\njs中操作数据: xxx.value\n模板中操作数据: 不需要.value\n一般用来定义一个基本类型的响应式数据\n如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象。",[16,1194,1197],{"className":1195,"code":1196,"language":54,"meta":22},[52],"const count = ref(0)\nconsole.log(count.value) // 0\n \ncount.value++\nconsole.log(count.value) // 1\n",[24,1198,1196],{"__ignoreMap":22},[27,1200,1201],{},"值得注意的是， ref 不仅可以实现响应式，还可以用于模板的DOM元素。",[27,1203,1204],{},"语法：",[16,1206,1209],{"className":1207,"code":1208,"language":54,"meta":22},[52],"//HTML：\u003Cinput type=\"text\" ref=\"inputRef\">\n//\"inputRef\"是template中，元素设置的ref值\nconst inputRef = ref\u003CHTMLElement|null>(null)\n",[24,1210,1208],{"__ignoreMap":22},[27,1212,1213],{},"我们用一段代码来演示一下：",[16,1215,1218],{"className":1216,"code":1217,"language":256,"meta":22},[254],"\u003Ctemplate>\n    \u003Cp ref=\"elemRef\">今天是周一\u003C/p>\n\u003C/template>\n \n\u003Cscript>\nimport { ref, onMounted } from 'vue'\n \nexport default {\n    name: 'RefTemplate',\n    setup(){\n        const elemRef = ref(null)\n \n        onMounted(() => {\n            console.log('ref template', elemRef.value.innerHTML, elemRef.value)\n        })\n \n        return{\n            elemRef\n        }\n    }\n}\n\u003C/script>\n",[24,1219,1217],{"__ignoreMap":22},[27,1221,1222],{},"复制代码\n此时浏览器的显示效果如下所示：",[27,1224,1225],{},[551,1226],{"alt":22,"src":1227},"/images/v6.jpg",[27,1229,1230],{},"我们通过在模板中绑定一个 ref ，之后在生命周期中调用，最后浏览器显示出该 DOM 元素。所以说， ref 也可以用来渲染模板中的DOM元素。",[73,1232,1234],{"id":1233},"_2reactive","2.reactive",[27,1236,1237],{},"作用: 定义对象的响应式\nconst proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象\n不能用于基本数据类型，例如字符串、数字、boolean 等，方法参数只能json或对象。\n响应式转换是“深层的”：会影响对象内部所有嵌套的属性。\n内部基于 ES6 的 Proxy 实现，通过代理对象（proxy）操作源对象内部数据都是响应式的。\nreactive 注册的对象本身是响应式的，但reactive 对象取出的所有属性值都是非响应式的，这个时候就需要用到toRefs函数，后面会讲相应的案例。",[16,1239,1242],{"className":1240,"code":1241,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Ch2>name: {{state.name}}\u003C/h2>\n  \u003Ch2>age: {{state.age}}\u003C/h2>\n  \u003Ch2>wife: {{state.wife}}\u003C/h2>\n  \u003Chr>\n  \u003Cbutton @click=\"update\">更新\u003C/button>\n\u003C/template>\n \n\u003Cscript>\n/* \nreactive: \n    作用: 定义多个数据的响应式\n    const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象\n    响应式转换是“深层的”：会影响对象内部所有嵌套的属性\n    内部基于 ES6 的 Proxy 实现，通过代理对象操作源对象内部数据都是响应式的\n*/\nimport {\n  reactive,\n} from 'vue'\nexport default {\n  setup () {\n    /* \n    定义响应式数据对象\n    */\n    const state = reactive({\n      name: 'tom',\n      age: 25,\n      wife: {\n        name: 'marry',\n        age: 22\n      },\n    })\n    console.log(state, state.wife)\n \n    const update = () => {\n      state.name += '--'\n      state.age += 1\n      state.wife.name += '++'\n      state.wife.age += 2\n    }\n \n    return {\n      state,\n      update,\n    }\n  }\n}\n\u003C/script>\n",[24,1243,1241],{"__ignoreMap":22},[73,1245,1247],{"id":1246},"_3reactive与ref的使用细节","3.reactive与ref的使用细节",[27,1249,1250],{},"ref用来处理基本类型数据, reactive用来处理对象(递归深度响应式)，不懂基本类型数据和引用数据类型的区别戳这里看原理\n如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象",[16,1252,1255],{"className":1253,"code":1254,"language":256,"meta":22},[254],"\u003Cscript>\n  import { reactive, ref, toRefs } from 'vue'\n  export default {\n    /* 在Vue3中的reactive, ref,就相当于这里data定义变量的作用 */\n    // data() {\n    //   return {\n    //     name: \"\",\n    //     state: {\n    //       name: 'Jerry',\n    //     },\n    //   }\n    // }\n    setup () {\n      // ref声明响应式数据，用于声明基本数据类型\n      const name = ref('Jerry')\n      // 修改\n      name.value = 'Tom'\n    \n      // reactive声明响应式数据，用于声明引用数据类型\n      const state = reactive({\n        name: 'Jerry',\n        sex: '男'\n      })\n      // 修改\n      state.name = 'Tom'\n \n      \n      return {\n          name,\n          state,\n        }\n  }\n}\n\u003C/script>\n",[24,1256,1254],{"__ignoreMap":22},[73,1258,1260],{"id":1259},"_4toref和torefs将对象的一个或多个属性转换为ref","4.toRef和toRefs：将对象的一个或多个属性转换为ref",[995,1262,1264],{"id":1263},"_1toref","1）toRef",[27,1266,1267],{},"作用：可以用来为源响应式对象上的一个 property 新创建一个 ref。然后，ref 可以被传递，它会保持对其源 property 的响应式连接。",[16,1269,1272],{"className":1270,"code":1271,"language":54,"meta":22},[52],"const state = reactive({\n  foo: 1,\n  bar: 2\n})\n \nconst fooRef = toRef(state, 'foo')\n \nfooRef.value++\nconsole.log(state.foo) // 2\n \nstate.foo++\nconsole.log(fooRef.value) // 3\n",[24,1273,1271],{"__ignoreMap":22},[27,1275,1276],{},"应用：当你要将 prop 的 ref 传递给复合函数时，toRef 很有用：",[27,1278,1279],{},"问题：props作为参数接受时，是非响应式的。",[27,1281,1282],{},"解决：利用 toRef方法需要把它变成响应式的。",[16,1284,1287],{"className":1285,"code":1286,"language":54,"meta":22},[52],"export default {\n  setup(props) {\n    useSomeFeature(toRef(props, 'foo'))\n  }\n}\n",[24,1288,1286],{"__ignoreMap":22},[27,1290,1291],{},"即使源 property 不存在，toRef 也会返回一个可用的 ref。这使得它在使用可选 prop 时特别有用，可选 prop 并不会被 toRefs 处理。",[995,1293,1295],{"id":1294},"_2torefs","2）toRefs",[27,1297,1298],{},"作用：将响应式对象转换为普通对象，其中结果对象的每个 property 都是指向原始对象相应 property 的 ref。",[27,1300,1301],{},"应用：当从合成函数返回响应式对象时，toRefs 非常有用，这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用",[27,1303,1304],{},"问题：reactive 对象取出的所有属性值都是非响应式的（reactive 注册的对象本身是响应式的，只是他的属性不是响应式，别搞混了）",[27,1306,1307],{},"解决：利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性",[16,1309,1312],{"className":1310,"code":1311,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Ch2>App\u003C/h2>\n  \u003Ch3>foo: {{foo}}\u003C/h3>\n  \u003Ch3>bar: {{bar}}\u003C/h3>\n  \u003Ch3>foo2: {{foo2}}\u003C/h3>\n  \u003Ch3>bar2: {{bar2}}\u003C/h3>\n \n \n\u003C/template>\n \n\u003Cscript lang=\"ts\">\nimport { reactive, toRefs } from 'vue'\n/*\ntoRefs:\n  将响应式对象中所有属性包装为ref对象, 并返回包含这些ref对象的普通对象\n  应用: 当从合成函数返回响应式对象时，toRefs 非常有用，\n        这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用\n*/\nexport default {\n \n  setup () {\n \n    const state = reactive({\n      foo: 'a',\n      bar: 'b',\n    })\n \n    const stateAsRefs = toRefs(state)\n \n    setTimeout(() => {\n      state.foo += '++'\n      state.bar += '++'\n    }, 2000);\n \n    const {foo2, bar2} = useReatureX()\n \n    return {\n      // ...state,\n      ...stateAsRefs,\n      foo2, \n      bar2\n    }\n  },\n}\n \nfunction useReatureX() {\n  const state = reactive({\n    foo2: 'a',\n    bar2: 'b',\n  })\n \n  setTimeout(() => {\n    state.foo2 += '++'\n    state.bar2 += '++'\n  }, 2000);\n \n  return toRefs(state)\n}\n \n\u003C/script>\n",[24,1313,1311],{"__ignoreMap":22},[27,1315,1316],{},"注意：toRefs 只会为源对象中包含的 property 生成 ref。如果要为特定的 property 创建 ref，则应当使用 toRef",[73,1318,1320],{"id":1319},"_5vue2x-与-vue3x-响应式重要","5.vue2.x 与 vue3.x 响应式（重要）",[27,1322,1323],{},"其实在 Vue3.x 还没有发布 bate 的时候， 很火的一个话题就是Vue3.x 将使用 Proxy 取代 Vue2.x 版本的 Object.defineProperty。没有无缘无故的爱，也没有无缘无故的恨。为何要将Object.defineProperty换掉呢，咋们可以简单聊一下。我刚上手 Vue2.x 的时候就经常遇到一个问题，数据更新了啊，为何页面不更新呢？什么时候用$set更新，什么时候用$forceUpdate强制更新，你是否也一度陷入困境。后来的学习过程中开始接触源码，才知道一切的根源都是 Object.defineProperty。对这块想要深入了解的小伙伴可以看这篇文章 为什么 Vue3.0 不再使用 defineProperty 实现数据监听？",[27,1325,1326],{},"要详细解释又是一篇文章，这里就简单对比一下Object.defineProperty 与 Proxy",[27,1328,1329],{},".Object.defineProperty只能劫持对象的属性， 而 Proxy 是直接代理对象",[27,1331,1332],{},"由于Object.defineProperty只能劫持对象属性，需要遍历对象的每一个属性，如果属性值也是对象，就需要递归进行深度遍历。但是 Proxy 直接代理对象， 不需要遍历操作",[27,1334,1335],{},"2.Object.defineProperty对新增属性需要手动进行Observe",[27,1337,1338],{},"因为Object.defineProperty劫持的是对象的属性，所以新增属性时，需要重新遍历对象， 对其新增属性再次使用Object.defineProperty进行劫持。也就是 Vue2.x 中给数组和对象新增属性时，需要使用$set才能保证新增的属性也是响应式的, $set内部也是通过调用Object.defineProperty去处理的。",[27,1340,1341],{},"想了解更详细的内容戳这里。",[11,1343,1345],{"id":1344},"三计算属性与监听","三、计算属性与监听",[73,1347,1349],{"id":1348},"_1computed","1.computed",[27,1351,1352],{},"与vue2.0的computed功能一致，只是使用方式变为了API引入，有两种形态：",[27,1354,1355],{},"只有getter\n有getter和setter",[16,1357,1360],{"className":1358,"code":1359,"language":256,"meta":22},[254],"\u003Cscript setup>\nimport {\n  reactive,\n  ref,\n  computed,\n} from 'vue'\n \n  const user = reactive({\n      firstName: 'A',\n      lastName: 'B'\n    })\n \n    // 只有getter的计算属性\n    const fullName1 = computed(() => {\n      console.log('fullName1')\n      return user.firstName + '-' + user.lastName\n    })\n \n    // 有getter与setter的计算属性\n    const fullName2 = computed({\n      get () {\n        console.log('fullName2 get')\n        return user.firstName + '-' + user.lastName\n      },\n \n      set (value: string) {\n        console.log('fullName2 set')\n        const names = value.split('-')\n        user.firstName = names[0]\n        user.lastName = names[1]\n      }\n    })\n \n\u003C/script>\n",[24,1361,1359],{"__ignoreMap":22},[73,1363,1365],{"id":1364},"_2watch-与-watcheffect-的用法","2.watch 与 watchEffect 的用法",[27,1367,1368],{},"watch 函数用来侦听特定的数据源，并在回调函数中执行副作用。默认情况是惰性的，也就是说仅在侦听的源数据变更时才执行回调。",[27,1370,1371,1372,1376],{},"watch(source, callback, ",[1373,1374,1375],"span",{},"options",")\n参数说明：",[27,1378,1379],{},"source: 可以支持 string,Object,Function,Array; 用于指定要侦听的响应式变量\ncallback: 执行的回调函数\noptions：支持 deep、immediate 和 flush 选项。\n接下来我会分别介绍这个三个参数都是如何使用的， 如果你对 watch 的使用不明白的请往下看：",[995,1381,1383],{"id":1382},"_1侦听-reactive-定义的数据","1）侦听 reactive 定义的数据",[16,1385,1388],{"className":1386,"code":1387,"language":54,"meta":22},[52],"import { defineComponent, ref, reactive, toRefs, watch } from \"vue\";\nexport default defineComponent({\n  setup() {\n    const state = reactive({ nickname: \"xiaofan\", age: 20 });\n \n    setTimeout(() => {\n      state.age++;\n    }, 1000);\n \n    // 修改age值时会触发 watch的回调\n    watch(\n      () => state.age,\n      (curAge, preAge) => {\n        console.log(\"新值:\", curAge, \"老值:\", preAge);\n      }\n    );\n \n    return {\n      ...toRefs(state),\n    };\n  },\n});\n",[24,1389,1387],{"__ignoreMap":22},[995,1391,1393],{"id":1392},"_2侦听-ref-定义的数据","2）侦听 ref 定义的数据",[16,1395,1398],{"className":1396,"code":1397,"language":54,"meta":22},[52],"const year = ref(0);\n \nsetTimeout(() => {\n  year.value++;\n}, 1000);\n \nwatch(year, (newVal, oldVal) => {\n  console.log(\"新值:\", newVal, \"老值:\", oldVal);\n});\n",[24,1399,1397],{"__ignoreMap":22},[995,1401,1403],{"id":1402},"_3侦听多个数据","3）侦听多个数据",[27,1405,1406],{},"上面两个例子中，我们分别使用了两个 watch, 当我们需要侦听多个数据源时， 可以进行合并， 同时侦听多个数据：",[16,1408,1411],{"className":1409,"code":1410,"language":54,"meta":22},[52],"watch([() => state.age, year], ([curAge, newVal], [preAge, oldVal]) => {\nconsole.log(\"新值:\", curAge, \"老值:\", preAge); console.log(\"新值:\", newVal,\n\"老值:\", oldVal); });\n",[24,1412,1410],{"__ignoreMap":22},[995,1414,1416],{"id":1415},"_4侦听复杂的嵌套对象","4）侦听复杂的嵌套对象",[27,1418,1419],{},"我们实际开发中，复杂数据随处可见， 比如：",[16,1421,1424],{"className":1422,"code":1423,"language":54,"meta":22},[52],"const state = reactive({\n  room: {\n    id: 100,\n    attrs: {\n      size: \"140平方米\",\n      type: \"三室两厅\",\n    },\n  },\n});\nwatch(\n  () => state.room,\n  (newType, oldType) => {\n    console.log(\"新值:\", newType, \"老值:\", oldType);\n  },\n  { deep: true }\n);\n",[24,1425,1423],{"__ignoreMap":22},[27,1427,1428],{},"如果不使用第三个参数deep:true， 是无法监听到数据变化的。前面我们提到，默认情况下，watch 是惰性的, 那什么情况下不是惰性的， 可以立即执行回调函数呢？其实使用也很简单， 给第三个参数中设置immediate: true即可。关于flush配置，还在学习，后期会补充",[995,1430,1432],{"id":1431},"_5stop-停止监听","5）stop 停止监听",[27,1434,1435],{},"我们在组件中创建的watch监听，会在组件被销毁时自动停止。如果在组件销毁之前我们想要停止掉某个监听， 可以调用watch()函数的返回值，操作如下：",[16,1437,1440],{"className":1438,"code":1439,"language":54,"meta":22},[52],"const stopWatchRoom = watch(() => state.room, (newType, oldType) => {\n    console.log(\"新值:\", newType, \"老值:\", oldType);\n}, {deep:true});\n \nsetTimeout(()=>{\n    // 停止监听\n    stopWatchRoom()\n}, 3000)\n",[24,1441,1439],{"__ignoreMap":22},[27,1443,1444],{},"还有一个监听函数watchEffect, 在我看来watch已经能满足监听的需求，为什么还要有watchEffect呢？虽然我没有 get 到它的必要性，但是还是要介绍一下watchEffect，首先看看它的使用和watch究竟有何不同。",[16,1446,1449],{"className":1447,"code":1448,"language":54,"meta":22},[52],"import { defineComponent, ref, reactive, toRefs, watchEffect } from \"vue\";\nexport default defineComponent({\n  setup() {\n    const state = reactive({ nickname: \"xiaofan\", age: 20 });\n    let year = ref(0)\n \n    setInterval(() =>{\n        state.age++\n        year.value++\n    },1000)\n \n    watchEffect(() => {\n        console.log(state);\n        console.log(year);\n      }\n    );\n \n    return {\n        ...toRefs(state)\n    }\n  },\n});\n",[24,1450,1448],{"__ignoreMap":22},[27,1452,1453],{},"执行结果首先打印一次state和year值；然后每隔一秒，打印state和year值。从上面的代码可以看出， 并没有像watch一样需要先传入依赖，watchEffect会自动收集依赖, 只要指定一个回调函数。在组件初始化时， 会先执行一次来收集依赖， 然后当收集到的依赖中数据发生变化时， 就会再次执行回调函数。所以总结对比如下：",[27,1455,1456],{},"watchEffect 不需要手动传入依赖\nwatchEffect 会先执行一次用来自动收集依赖\nwatchEffect 无法获取到变化前的值， 只能获取变化后的值\n上面介绍了 Vue3 Composition API的部分内容, 还有很多非常好用的 API, 建议直接查看官网 composition-api。其实我们也能进行自定义封装。",[11,1458,1460],{"id":1459},"四v-model","四、v-model",[73,1462,1464],{"id":1463},"_1v-model-变更","1.v-model 变更",[27,1466,1467],{},"在使用 Vue 3 之前就了解到 v-model 发生了很大的变化， 使用过了之后才真正的 get 到这些变化， 我们先纵观一下发生了哪些变化， 然后再针对的说一下如何使用：",[27,1469,1470],{},"变更：在自定义组件上使用v-model时， 属性以及事件的默认名称变了\n变更：v-bind的.sync修饰符在 Vue 3 中又被去掉了, 合并到了v-model里\n新增：同一组件可以同时设置多个 v-model\n新增：开发者可以自定义 v-model修饰符",[73,1472,1474],{"id":1473},"_2v-model-的update事件","2.v-model 的update事件",[27,1476,1477],{},"在 Vue2 中， 在组件上使用 v-model其实就相当于传递了value属性， 并触发了input事件：",[16,1479,1482],{"className":1480,"code":1481,"language":256,"meta":22},[254],"\u003C!-- Vue 2 -->\n\u003Csearch-input v-model=\"searchValue\">\u003Csearch-input>\n \n\u003C!-- 相当于 -->\n\u003Csearch-input :value=\"searchValue\" @input=\"searchValue=$event\">\u003Csearch-input>\n",[24,1483,1481],{"__ignoreMap":22},[27,1485,1486],{},"这时v-model只能绑定在组件的value属性上，那我们就不开心了， 我们就像给自己的组件用一个别的属性，并且我们不想通过触发input来更新值，在.sync出来之前，Vue 2 中这样实现：",[16,1488,1491],{"className":1489,"code":1490,"language":54,"meta":22},[52],"// 子组件：searchInput.vue\nexport default {\n    model:{\n        prop: 'search',\n        event:'change'\n    }\n}\n",[24,1492,1490],{"__ignoreMap":22},[27,1494,1495],{},"修改后， searchInput 组件使用v-model就相当于这样：",[16,1497,1500],{"className":1498,"code":1499,"language":256,"meta":22},[254],"\u003Csearch-input v-model=\"searchValue\">\u003Csearch-input>\n\u003C!-- 相当于 -->\n\u003Csearch-input :search=\"searchValue\" @change=\"searchValue=$event\">\u003Csearch-input>\n",[24,1501,1499],{"__ignoreMap":22},[27,1503,1504],{},"但是在实际开发中，有些场景我们可能需要对一个 prop 进行 “双向绑定”， 这里以最常见的 modal 为例子：modal 挺合适属性双向绑定的，外部可以控制组件的visible显示或者隐藏，组件内部关闭可以控制 visible属性隐藏，同时 visible 属性同步传输到外部。组件内部， 当我们关闭modal时, 在子组件中以 update:PropName 模式触发事件：",[16,1506,1509],{"className":1507,"code":1508,"language":54,"meta":22},[52],"this.$emit('update:visible', false)\n",[24,1510,1508],{"__ignoreMap":22},[27,1512,1513],{},"然后在父组件中可以监听这个事件进行数据更新：",[16,1515,1518],{"className":1516,"code":1517,"language":256,"meta":22},[254],"\u003Cmodal :visible=\"isVisible\" @update:visible=\"isVisible = $event\">\u003C/modal>\n",[24,1519,1517],{"__ignoreMap":22},[27,1521,1522],{},"此时我们也可以使用v-bind.sync来简化实现：",[16,1524,1527],{"className":1525,"code":1526,"language":256,"meta":22},[254],"\u003Cmodal :visible.sync=\"isVisible\">\u003C/modal>\n",[24,1528,1526],{"__ignoreMap":22},[27,1530,1531],{},"上面回顾了 Vue2 中v-model实现以及组件属性的双向绑定，那么在 Vue 3 中应该怎样实现的呢？在 Vue3 中, 在自定义组件上使用v-model, 相当于传递一个modelValue 属性， 同时触发一个update:modelValue事件：",[16,1533,1536],{"className":1534,"code":1535,"language":256,"meta":22},[254],"\u003Cmodal v-model=\"isVisible\">\u003C/modal>\n\u003C!-- 相当于 -->\n\u003Cmodal :modelValue=\"isVisible\" @update:modelValue=\"isVisible = $event\">\u003C/modal>\n如果要绑定属性名， 只需要给v-model传递一个参数就行, 同时可以绑定多个v-model：\n\n\u003Cmodal v-model:visible=\"isVisible\" v-model:content=\"content\">\u003C/modal>\n \n\u003C!-- 相当于 -->\n\u003Cmodal\n    :visible=\"isVisible\"\n    :content=\"content\"\n    @update:visible=\"isVisible\"\n    @update:content=\"content\"\n/>\n",[24,1537,1535],{"__ignoreMap":22},[27,1539,1540],{},"不知道你有没有发现，这个写法完全没有.sync什么事儿了， 所以啊，Vue 3 中又抛弃了.sync写法， 统一使用v-model。",[73,1542,1544],{"id":1543},"_3v-model-参数","3.v-model 参数",[27,1546,1547],{},"默认情况下，组件上的 v-model 使用 modelValue 作为 prop 和 update:modelValue 作为事件。我们可以通过向 v-model 传递参数来修改这些名称：",[16,1549,1552],{"className":1550,"code":1551,"language":256,"meta":22},[254],"\u003Cmy-component v-model:title=\"bookTitle\">\u003C/my-component>\n",[24,1553,1551],{"__ignoreMap":22},[27,1555,1556],{},"在本例中，子组件将需要一个 title prop 并发出 update:title 事件来进行同步：",[16,1558,1561],{"className":1559,"code":1560,"language":54,"meta":22},[52],"app.component('my-component', {\n  props: {\n    title: String\n  },\n  emits: ['update:title'],\n  template: `\n    \u003Cinput\n      type=\"text\"\n      :value=\"title\"\n      @input=\"$emit('update:title', $event.target.value)\">\n  `\n})\n",[24,1562,1560],{"__ignoreMap":22},[11,1564,1566],{"id":1565},"五组件通信","五、组件通信",[73,1568,1570],{"id":1569},"_1父传子props","1.父传子：props",[27,1572,1573],{},"如果你用的是vue3.0，那么直接翻回看上面的setup函数获取props的方法。",[27,1575,1576],{},"如果用的是vue3.2以上，使用的是setup语法糖，那么获取props的方式是defineProps()方法，无需引入，直接看代码吧：",[27,1578,1579],{},"子组件",[16,1581,1584],{"className":1582,"code":1583,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Cspan>{{props.name}}\u003C/span>\n  // 可省略【props.】\n  \u003Cspan>{{name}}\u003C/span>\n\u003C/template>\n \n\u003Cscript setup>\n  // import { defineProps } from 'vue'\n  // defineProps在\u003Cscript setup>中自动可用，无需导入\n  // 需在.eslintrc.js文件中【globals】下配置【defineProps: true】\n \n  // 声明props\n  const props = defineProps({\n    name: {\n      type: String,\n      default: ''\n    }\n  })  \n\u003C/script>\n",[24,1585,1583],{"__ignoreMap":22},[27,1587,1588],{},"父组件",[16,1590,1593],{"className":1591,"code":1592,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Cchild name='Jerry'/>  \n\u003C/template>\n \n\u003Cscript setup>\n  // 引入子组件(组件自动注册)\n  import child from './child.vue'\n\u003C/script>\n",[24,1594,1592],{"__ignoreMap":22},[73,1596,1598],{"id":1597},"_2子传父emit","2.子传父：emit",[27,1600,1601],{},"emit的也是一样，vue3.0和vue3.2获取方法不一样，vue3.0直接看上文setup函数参数部分。",[27,1603,1604],{},"在vue3.2以上版本的setup语法糖下，获取emit使用的是defineEmits方法，使用方法如下：",[27,1606,1579],{},[16,1608,1611],{"className":1609,"code":1610,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Cspan>{{props.name}}\u003C/span>\n  // 可省略【props.】\n  \u003Cspan>{{name}}\u003C/span>\n  \u003Cbutton @click='changeName'>更名\u003C/button>\n\u003C/template>\n \n\u003Cscript setup>\n\n  // import { defineEmits, defineProps } from 'vue'\n  // defineEmits和defineProps在\u003Cscript setup>中自动可用，无需导入\n  // 需在.eslintrc.js文件中【globals】下配置【defineEmits: true】、【defineProps: true】\n    \n  // 声明props\n  const props = defineProps({\n    name: {\n      type: String,\n      default: ''\n    }\n  }) \n  // 声明事件\n  const emit = defineEmits(['updateName'])\n  \n  const changeName = () => {\n    // 执行\n    emit('updateName', 'Tom')\n  }\n\u003C/script>\n",[24,1612,1610],{"__ignoreMap":22},[27,1614,1588],{},[16,1616,1619],{"className":1617,"code":1618,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Cchild :name='state.name' @updateName='updateName'/>  \n\u003C/template>\n \n\u003Cscript setup>\n  import { reactive } from 'vue'\n  // 引入子组件\n  import child from './child.vue'\n \n  const state = reactive({\n    name: 'Jerry'\n  })\n  \n  // 接收子组件触发的方法\n  const updateName = (name) => {\n    state.name = name\n  }\n\u003C/script>\n",[24,1620,1618],{"__ignoreMap":22},[73,1622,1624],{"id":1623},"_3祖孙通信provide-与-inject","3.祖孙通信：provide 与 inject",[27,1626,1627],{},"provide和inject提供依赖注入，功能类似 2.x 的provide/inject，是为了弥补props和emit只能子父通信的缺陷，实现跨层级组件(祖孙)间通信。",[27,1629,1630],{},"父组件：",[16,1632,1635],{"className":1633,"code":1634,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Ch1>父组件\u003C/h1>\n  \u003Cp>当前颜色: {{color}}\u003C/p>\n  \u003Cbutton @click=\"color='red'\">红\u003C/button>\n  \u003Cbutton @click=\"color='yellow'\">黄\u003C/button>\n  \u003Cbutton @click=\"color='blue'\">蓝\u003C/button>\n  \n  \u003Chr>\n  \u003CSon />\n\u003C/template>\n \n\u003Cscript lang=\"ts\">\nimport { provide, ref } from 'vue'\n/* \n- provide` 和 `inject` 提供依赖注入，功能类似 2.x 的 `provide/inject\n- 实现跨层级组件(祖孙)间通信\n*/\n \nimport Son from './Son.vue'\nexport default {\n  name: 'ProvideInject',\n  components: {\n    Son\n  },\n  setup() {\n    \n    const color = ref('red')\n \n    provide('color', color)\n \n    return {\n      color\n    }\n  }\n}\n\u003C/script>\n",[24,1636,1634],{"__ignoreMap":22},[27,1638,1639],{},"子组件：",[16,1641,1644],{"className":1642,"code":1643,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Cdiv>\n    \u003Ch2>子组件\u003C/h2>\n    \u003Chr>\n    \u003CGrandSon />\n  \u003C/div>\n\u003C/template>\n \n\u003Cscript lang=\"ts\">\nimport GrandSon from './GrandSon.vue'\nexport default {\n  components: {\n    GrandSon\n  },\n}\n\u003C/script>\n",[24,1645,1643],{"__ignoreMap":22},[27,1647,1648],{},"孙子组件：",[16,1650,1653],{"className":1651,"code":1652,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Ch3 :style=\"{color}\">孙子组件: {{color}}\u003C/h3>\n  \n\u003C/template>\n \n\u003Cscript lang=\"ts\">\nimport { inject } from 'vue'\nexport default {\n  setup() {\n    const color = inject('color')\n \n    return {\n      color\n    }\n  }\n}\n\u003C/script>\n",[24,1654,1652],{"__ignoreMap":22},[73,1656,1658],{"id":1657},"_4子组件ref变量和defineexpose","4.子组件ref变量和defineExpose",[27,1660,1661],{},"在标准组件写法里，子组件的数据都是默认隐式暴露给父组件的，但在 script-setup 模式下，所有数据只是默认 return 给 template 使用，不会暴露到组件外，所以父组件是无法直接通过挂载 ref 变量获取子组件的数据。\n如果要调用子组件的数据，需要先在子组件显示的暴露出来，才能够正确的拿到，这个操作，就是由 defineExpose 来完成。\n子组件",[16,1663,1666],{"className":1664,"code":1665,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Cspan>{{state.name}}\u003C/span>\n\u003C/template>\n \n\u003Cscript setup>\n  import { reactive, toRefs } from 'vue'\n  // defineExpose无需引入\n  // import { defineExpose, reactive, toRefs } from 'vue'\n \n  // 声明state\n  const state = reactive({\n    name: 'Jerry'\n  }) \n    \n  // 将方法、变量暴露给父组件使用，父组件才可通过ref API拿到子组件暴露的数据\n  defineExpose({\n    // 解构state\n    ...toRefs(state),\n    // 声明方法\n    changeName () {\n      state.name = 'Tom'\n    }\n  })\n\u003C/script>\n",[24,1667,1665],{"__ignoreMap":22},[27,1669,1588],{},[16,1671,1674],{"className":1672,"code":1673,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Cchild ref='childRef'/>  \n\u003C/template>\n \n\u003Cscript setup>\n  import { ref, nextTick } from 'vue'\n  // 引入子组件\n  import child from './child.vue'\n \n  // 子组件ref\n  const childRef = ref('childRef')\n  \n  // nextTick\n  nextTick(() => {\n    // 获取子组件name\n    console.log(childRef.value.name)\n    // 执行子组件方法\n    childRef.value.changeName()\n  })\n\u003C/script>\n",[24,1675,1673],{"__ignoreMap":22},[11,1677,1679],{"id":1678},"五自定义可复用的功能函数hooks","五、自定义可复用的功能函数：Hooks",[27,1681,1682],{},"hook不是api，表示的是可复用的业务代码，是一种代码思想。当我们维护大型项目时，针对组件中大量重复的业务逻辑，就需要把这些代码重构为hook，以方便复用。",[27,1684,1685],{},"hook的使用思路如下：",[27,1687,1688],{},"Composition API 指抽离逻辑代码到一个函数；\n函数的命名约定为 useXxxx 格式（React hooks也是）；\n在 setup 中引用 useXxx 函数。\n为了方便大家理解，我们写了一个实现加减的例子， 这里可以将其封装成一个 hook, 我们约定这些「自定义 Hook」以 use 作为前缀，和普通的函数加以区分。useCount.ts 实现：",[16,1690,1693],{"className":1691,"code":1692,"language":54,"meta":22},[52],"import { ref, Ref, computed } from \"vue\";\n \ntype CountResultProps = {\n  count: Ref\u003Cnumber>;\n  multiple: Ref\u003Cnumber>;\n  increase: (delta?: number) => void;\n  decrease: (delta?: number) => void;\n};\n \nexport default function useCount(initValue = 1): CountResultProps {\n  const count = ref(initValue);\n \n  const increase = (delta?: number): void => {\n    if (typeof delta !== \"undefined\") {\n      count.value += delta;\n    } else {\n      count.value += 1;\n    }\n  };\n  const multiple = computed(() => count.value * 2);\n \n  const decrease = (delta?: number): void => {\n    if (typeof delta !== \"undefined\") {\n      count.value -= delta;\n    } else {\n      count.value -= 1;\n    }\n  };\n \n  return {\n    count,\n    multiple,\n    increase,\n    decrease,\n  };\n}\n",[24,1694,1692],{"__ignoreMap":22},[27,1696,1697],{},"接下来看一下在组件中使用useCount这个 hook:",[16,1699,1702],{"className":1700,"code":1701,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Cp>count: {{ count }}\u003C/p>\n  \u003Cp>倍数： {{ multiple }}\u003C/p>\n  \u003Cdiv>\n    \u003Cbutton @click=\"increase()\">加1\u003C/button>\n    \u003Cbutton @click=\"decrease()\">减一\u003C/button>\n  \u003C/div>\n\u003C/template>\n \n\u003Cscript lang=\"ts\">\nimport useCount from \"../hooks/useCount\";\n setup() {\n    const { count, multiple, increase, decrease } = useCount(10);\n        return {\n            count,\n            multiple,\n            increase,\n            decrease,\n        };\n    },\n\u003C/script>\n",[24,1703,1701],{"__ignoreMap":22},[27,1705,1706],{},"如果是用Vue2.x 实现，业务代码就会分散在data,method,computed等， 如果刚接手项目，实在无法快速将data字段和method关联起来，而 Vue3 的方式可以很明确的看出，将 count 相关的逻辑聚合在一起， 看起来舒服多了， 而且useCount还可以扩展更多的功能。",[11,1708,1710],{"id":1709},"六生命周期","六、生命周期",[27,1712,1713],{},"我们可以直接看生命周期图来认识都有哪些生命周期钩子 (图片是根据官网翻译后绘制的)：",[27,1715,1716],{},[551,1717],{"alt":22,"src":1718},"/images/v7.jpg",[27,1720,1721],{},"从图中我们可以看到 Vue3.0 新增了setup，这个在前面我们也详细说了， 然后是将 Vue2.x 中的beforeDestroy名称变更成beforeUnmount; destroyed 表更为 unmounted，作者说这么变更纯粹是为了更加语义化，因为一个组件是一个mount和unmount的过程。其他 Vue2 中的生命周期仍然保留。",[27,1723,1724],{},"生命周期图中并没包含全部的生命周期钩子， 还有其他的几个， 全部生命周期钩子如图所示：",[27,1726,1727],{},[551,1728],{"alt":22,"src":1729},"/images/v8.jpg",[27,1731,1732],{},"我们可以看到有三点变化：",[27,1734,1735],{},"beforeCreate和created被setup替换了（但是 Vue3 中你仍然可以使用， 因为 Vue3 是向下兼容的， 也就是你实际使用的是 vue2 的）。\n钩子命名都增加了on; 通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。\nVue3.x 还新增用于调试的钩子函数onRenderTriggered和onRenderTricked",[123,1737,1739],{"id":1738},"第三部分其他旧语法变更","第三部分、其他旧语法变更",[11,1741,1743],{"id":1742},"一插槽slot","一、插槽slot",[73,1745,1747],{"id":1746},"_1具名插槽","1.具名插槽",[27,1749,1750],{},"在 Vue2.x 中， 具名插槽的写法：",[16,1752,1755],{"className":1753,"code":1754,"language":256,"meta":22},[254],"\u003C!--  子组件中：-->\n\u003Cslot name=\"title\">\u003C/slot>\n",[24,1756,1754],{"__ignoreMap":22},[27,1758,1759],{},"在父组件中使用：",[16,1761,1764],{"className":1762,"code":1763,"language":256,"meta":22},[254],"\u003Ctemplate slot=\"title\">\n    \u003Ch1>歌曲：成都\u003C/h1>\n\u003Ctemplate>\n",[24,1765,1763],{"__ignoreMap":22},[27,1767,1768],{},"对于具名插槽，vue3的使用只是语法更改为了： v-slot指令，而且只能用于template 元素 (只有一种例外情况)。",[27,1770,1771],{},"举个例子，有时我们需要多个插槽。例如对于一个带有如下模板的 base-layout 组件：",[16,1773,1776],{"className":1774,"code":1775,"language":256,"meta":22},[254],"\u003Cdiv class=\"container\">\n  \u003Cheader>\n    \u003C!-- 我们希望把页头放这里 -->\n  \u003C/header>\n  \u003Cmain>\n    \u003C!-- 我们希望把主要内容放这里 -->\n  \u003C/main>\n  \u003Cfooter>\n    \u003C!-- 我们希望把页脚放这里 -->\n  \u003C/footer>\n\u003C/div>\n",[24,1777,1775],{"__ignoreMap":22},[27,1779,1780],{},"对于这样的情况，slot 元素有一个特殊的 attribute：name。通过它可以为不同的插槽分配独立的 ID，也就能够以此来决定内容应该渲染到什么地方：",[16,1782,1785],{"className":1783,"code":1784,"language":256,"meta":22},[254],"\u003Cdiv class=\"container\">\n  \u003Cheader>\n    \u003Cslot name=\"header\">\u003C/slot>\n  \u003C/header>\n  \u003Cmain>\n    \u003Cslot>\u003C/slot>\n  \u003C/main>\n  \u003Cfooter>\n    \u003Cslot name=\"footer\">\u003C/slot>\n  \u003C/footer>\n\u003C/div>\n",[24,1786,1784],{"__ignoreMap":22},[27,1788,1789],{},"一个不带 name 的 slot 出口会带有隐含的名字“default”。",[27,1791,1792],{},"在向具名插槽提供内容的时候，我们可以在一个 template 元素上使用 v-slot 指令，并以 v-slot 的参数的形式提供其名称：",[16,1794,1797],{"className":1795,"code":1796,"language":256,"meta":22},[254],"\u003Cbase-layout>\n  \u003Ctemplate v-slot:header>\n    \u003Ch1>Here might be a page title\u003C/h1>\n  \u003C/template>\n \n  \u003Ctemplate v-slot:default>\n    \u003Cp>A paragraph for the main content.\u003C/p>\n    \u003Cp>And another one.\u003C/p>\n  \u003C/template>\n \n  \u003Ctemplate v-slot:footer>\n    \u003Cp>Here's some contact info\u003C/p>\n  \u003C/template>\n\u003C/base-layout>\n",[24,1798,1796],{"__ignoreMap":22},[27,1800,1801],{},"现在 template 元素中的所有内容都将会被传入相应的插槽。",[27,1803,1804],{},"渲染的 HTML 将会是：",[16,1806,1809],{"className":1807,"code":1808,"language":256,"meta":22},[254],"\u003Cdiv class=\"container\">\n  \u003Cheader>\n    \u003Ch1>Here might be a page title\u003C/h1>\n  \u003C/header>\n  \u003Cmain>\n    \u003Cp>A paragraph for the main content.\u003C/p>\n    \u003Cp>And another one.\u003C/p>\n  \u003C/main>\n  \u003Cfooter>\n    \u003Cp>Here's some contact info\u003C/p>\n  \u003C/footer>\n\u003C/div>\n",[24,1810,1808],{"__ignoreMap":22},[27,1812,1813],{},"再次强调，v-slot 只能添加在 template 上 (只有一种例外情况)。",[995,1815,1817],{"id":1816},"_2作用域插槽","2.作用域插槽",[27,1819,1820],{},"在vue2中，如果我们要在 slot 上面绑定数据，可以使用作用域插槽，实现如下：",[16,1822,1825],{"className":1823,"code":1824,"language":256,"meta":22},[254],"// 子组件\n\u003Cslot name=\"content\" :data=\"data\">\u003C/slot>\n\u003Cscript>\nexport default {\n    data(){\n        return{\n            data:[\"走过来人来人往\",\"不喜欢也得欣赏\",\"陪伴是最长情的告白\"]\n        }\n    }\n}\n\u003C/script>\n",[24,1826,1824],{"__ignoreMap":22},[16,1828,1831],{"className":1829,"code":1830,"language":256,"meta":22},[254],"\u003C!-- 父组件中使用 -->\n\u003Ctemplate slot=\"content\" slot-scope=\"scoped\">\n    \u003Cdiv v-for=\"item in scoped.data\">{{item}}\u003C/div>\n\u003Ctemplate>\n",[24,1832,1830],{"__ignoreMap":22},[27,1834,1835],{},"在 Vue2.x 中具名插槽和作用域插槽分别使用slot和slot-scope来实现， 在 Vue3.0 中将slot和slot-scope进行了合并同意使用。Vue3.0 中v-slot：",[16,1837,1840],{"className":1838,"code":1839,"language":256,"meta":22},[254],"\u003C!-- 父组件中使用 -->\n \u003Ctemplate v-slot:content=\"scoped\">\n   \u003Cdiv v-for=\"item in scoped.data\">{{item}}\u003C/div>\n\u003C/template>\n \n\u003C!-- 也可以简写成： -->\n\u003Ctemplate #content=\"{data}\">\n    \u003Cdiv v-for=\"item in data\">{{item}}\u003C/div>\n\u003C/template>\n",[24,1841,1839],{"__ignoreMap":22},[73,1843,1845],{"id":1844},"二nexttick","二、nextTick",[27,1847,1848],{},"setup内无法使用this，vue2的这种写法已经不能使用：",[16,1850,1853],{"className":1851,"code":1852,"language":54,"meta":22},[52],"this.nextTick(()=>{\n    ...\n})\n",[24,1854,1852],{"__ignoreMap":22},[27,1856,1857],{},"所以在vue3中，nextTickAPI 需要通过 ES Module的引用方式进行具名引用：",[16,1859,1862],{"className":1860,"code":1861,"language":256,"meta":22},[254],"\u003Cscript setup>\n  import { nextTick } from 'vue'\n    \n  nextTick(() => {\n    // ...\n  })\n\u003C/script>\n",[24,1863,1861],{"__ignoreMap":22},[27,1865,1866],{},"其他受影响的 API",[27,1868,1869],{},"和nextTick一样，需要this对象的API都要用具名导入了，这是一个比较大的变化，以下 API 均有影响：",[27,1871,1872],{},"Vue.nextTick\nVue.observable（用 Vue.reactive 替换）\nVue.version\nVue.compile（仅限完整版本时可用）\nVue.set（仅在 2.x 兼容版本中可用）\nVue.delete（与上同）",[11,1874,1876],{"id":1875},"三路由","三、路由",[73,1878,1880],{"id":1879},"_1路由useroute和userouter","1.路由useRoute和useRouter",[27,1882,1883],{},"在setup中，调用路由的方法为：useRoute, useRouter，代码如下：",[16,1885,1888],{"className":1886,"code":1887,"language":256,"meta":22},[254],"\u003Cscript setup>\n  import { useRoute, useRouter } from 'vue-router'\n    \n  // 必须先声明调用\n  const route = useRoute()\n  const router = useRouter()\n    \n  // 路由信息\n  console.log(route.query)\n \n  // 路由跳转\n  router.push('/newPage')\n\u003C/script>\n",[24,1889,1887],{"__ignoreMap":22},[73,1891,1893],{"id":1892},"_2路由导航守卫","2.路由导航守卫",[27,1895,1896],{},"调用路由守卫是这两个API：onBeforeRouteLeave, onBeforeRouteUpdate",[16,1898,1901],{"className":1899,"code":1900,"language":256,"meta":22},[254],"\u003Cscript setup>\n  import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'\n    \n  // 添加一个导航守卫，在当前组件将要离开时触发。\n  onBeforeRouteLeave((to, from, next) => {\n    next()\n  })\n \n  // 添加一个导航守卫，在当前组件更新时触发。\n  // 在当前路由改变，但是该组件被复用时调用。\n  onBeforeRouteUpdate((to, from, next) => {\n    next()\n  })\n\u003C/script>\n",[24,1902,1900],{"__ignoreMap":22},[11,1904,1906],{"id":1905},"四vuex","四、Vuex",[73,1908,1909],{"id":1909},"store",[27,1911,1912],{},"Vue3 中的Vuex不再提供辅助函数写法，而是使用useStore方法：",[16,1914,1917],{"className":1915,"code":1916,"language":256,"meta":22},[254],"\u003Cscript setup>\n  import { useStore } from 'vuex'\n  import { key } from '../store/index'\n \n  // 必须先声明调用\n  const store = useStore(key)\n    \n  // 获取Vuex的state\n  store.state.xxx\n \n  // 触发mutations的方法\n  store.commit('fnName')\n \n  // 触发actions的方法\n  store.dispatch('fnName')\n \n  // 获取Getters\n  store.getters.xxx\n\u003C/script>\n",[24,1918,1916],{"__ignoreMap":22},[123,1920,1922],{"id":1921},"第四部分vue3新组件","第四部分、vue3新组件",[11,1924,1926],{"id":1925},"一fragment片断","一、Fragment（片断）",[27,1928,1929],{},"在Vue2中: 组件必须有一个根标签\n在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中\n好处: 减少标签层级, 减小内存占用",[16,1931,1934],{"className":1932,"code":1933,"language":256,"meta":22},[254],"\u003Ctemplate>\n    \u003Ch2>aaaa\u003C/h2>\n    \u003Ch2>aaaa\u003C/h2>\n\u003C/template>\n",[24,1935,1933],{"__ignoreMap":22},[11,1937,1939],{"id":1938},"二suspense异步组件","二、Suspense（异步组件）",[27,1941,1942],{},"Suspense是 Vue3.x 中新增的特性， 那它有什么用呢？别急，我们通过 Vue2.x 中的一些场景来认识它的作用。Vue2.x 中应该经常遇到这样的场景：",[16,1944,1947],{"className":1945,"code":1946,"language":256,"meta":22},[254],"\u003Ctemplate>\n\u003Cdiv>\n    \u003Cdiv v-if=\"!loading\">\n        ...\n    \u003C/div>\n    \u003Cdiv v-if=\"loading\">\n        加载中...\n    \u003C/div>\n\u003C/div>\n\u003C/template>\n",[24,1948,1946],{"__ignoreMap":22},[27,1950,1951],{},"在前后端交互获取数据时， 是一个异步过程，一般我们都会提供一个加载中的动画，当数据返回时配合v-if来控制数据显示。如果你使用过vue-async-manager这个插件来完成上面的需求， 你对Suspense可能不会陌生，Vue3.x 感觉就是参考了vue-async-manager.Vue3.x 新出的内置组件Suspense, 它提供两个template slot, 刚开始会渲染一个 fallback 状态下的内容， 直到到达某个条件后才会渲染 default 状态的正式内容， 通过使用Suspense组件进行展示异步渲染就更加的简单。:::warning 如果使用 Suspense, 要返回一个 promise :::Suspense 组件的使用：",[16,1953,1956],{"className":1954,"code":1955,"language":256,"meta":22},[254],"  \u003CSuspense>\n        \u003Ctemplate #default>\n            \u003Casync-component>\u003C/async-component>\n        \u003C/template>\n        \u003Ctemplate #fallback>\n            \u003Cdiv>\n                Loading...\n            \u003C/div>\n        \u003C/template>\n  \u003C/Suspense>\n",[24,1957,1955],{"__ignoreMap":22},[27,1959,1960],{},"asyncComponent.vue:",[16,1962,1965],{"className":1963,"code":1964,"language":256,"meta":22},[254],"\u003Ctemplate>\n\u003Cdiv>\n    \u003Ch4>这个是一个异步加载数据\u003C/h4>\n    \u003Cp>用户名：{{user.nickname}}\u003C/p>\n    \u003Cp>年龄：{{user.age}}\u003C/p>\n\u003C/div>\n\u003C/template>\n \n\u003Cscript>\nimport { defineComponent } from \"vue\"\nimport axios from \"axios\"\nexport default defineComponent({\n    setup(){\n        const rawData = await axios.get(\"http://xxx.xinp.cn/user\")\n        return {\n            user: rawData.data\n        }\n    }\n})\n\u003C/script>\n",[24,1966,1964],{"__ignoreMap":22},[27,1968,1969],{},"从上面代码来看，Suspense 只是一个带插槽的组件，只是它的插槽指定了default 和 fallback 两种状态。",[27,1971,1972],{},"PS：Suspense 是一个试验性的新特性，其 API 可能随时会发生变动。生产环境请勿使用。",[123,1974,1976],{"id":1975},"三teleport传送门","三、Teleport（传送门）",[27,1978,1979],{},"Teleport 是 Vue3.x 新推出的功能， 没听过这个词的小伙伴可能会感到陌生；翻译过来是传送的意思，可能还是觉得不知所以，没事下边我就给大家形象的描述一下。",[27,1981,1982],{},"1.Teleport 是什么呢？\nTeleport 就像是哆啦 A 梦中的「任意门」，任意门的作用就是可以将人瞬间传送到另一个地方。有了这个认识，我们再来看一下为什么需要用到 Teleport 的特性呢，看一个小例子：在子组件Header中使用到Dialog组件，我们实际开发中经常会在类似的情形下使用到 Dialog ，此时Dialog就被渲染到一层层子组件内部，处理嵌套组件的定位、z-index和样式都变得困难。Dialog从用户感知的层面，应该是一个独立的组件，从 dom 结构应该完全剥离 Vue 顶层组件挂载的 DOM；同时还可以使用到 Vue 组件内的状态（data或者props）的值。简单来说就是,即希望继续在组件内部使用Dialog, 又希望渲染的 DOM 结构不嵌套在组件的 DOM 中。此时就需要 Teleport 上场，我们可以用包裹Dialog, 此时就建立了一个传送门，可以将Dialog渲染的内容传送到任何指定的地方。接下来就举个小例子，看看 Teleport 的使用方式",[27,1984,1985],{},"2.Teleport 的使用\n我们希望 Dialog 渲染的 dom 和顶层组件是兄弟节点关系, 在index.html文件中定义一个供挂载的元素:",[16,1987,1990],{"className":1988,"code":1989,"language":256,"meta":22},[254],"\u003Cbody>\n  \u003Cdiv id=\"app\">\u003C/div>\n  \u003Cdiv id=\"dialog\">\u003C/div>\n\u003C/body>\n定义一个Dialog组件Dialog.vue, 留意 to 属性， 与上面的id选择器一致：\n\n\u003Ctemplate>\n  \u003Cteleport to=\"#dialog\">\n    \u003Cdiv class=\"dialog\">\n      \u003Cdiv class=\"dialog_wrapper\">\n        \u003Cdiv class=\"dialog_header\" v-if=\"title\">\n          \u003Cslot name=\"header\">\n            \u003Cspan>{{ title }}\u003C/span>\n          \u003C/slot>\n        \u003C/div>\n      \u003C/div>\n      \u003Cdiv class=\"dialog_content\">\n        \u003Cslot>\u003C/slot>\n      \u003C/div>\n      \u003Cdiv class=\"dialog_footer\">\n        \u003Cslot name=\"footer\">\u003C/slot>\n      \u003C/div>\n    \u003C/div>\n  \u003C/teleport>\n\u003C/template>\n",[24,1991,1989],{"__ignoreMap":22},[27,1993,1994],{},"最后在一个子组件Header.vue中使用Dialog组件, 这里主要演示 Teleport 的使用，不相关的代码就省略了。header组件",[16,1996,1999],{"className":1997,"code":1998,"language":256,"meta":22},[254],"\u003Cdiv class=\"header\">\n    ...\n    \u003Cnavbar />\n    \u003CDialog v-if=\"dialogVisible\">\u003C/Dialog>\n\u003C/div>\n",[24,2000,1998],{"__ignoreMap":22},[27,2002,2003],{},"Dom 渲染效果如下：图片. png可以看到，我们使用 teleport 组件，通过 to 属性，指定该组件渲染的位置与",[27,2005,2006],{},"同级，也就是在 body 下，但是 Dialog 的状态 dialogVisible 又是完全由内部 Vue 组件控制.",[27,2008,2009],{},[551,2010],{"alt":22,"src":2011},"/images/v9.jpg",[27,2013,2014],{},"想了解更多？戳此看官方文档。",[123,2016,2018],{"id":2017},"第五部分其他新增特性","第五部分、其他新增特性",[11,2020,2022],{"id":2021},"一css变量注入","一、css变量注入",[27,2024,2025],{},"vue3更新了一个新特性， 新增了CSS变量注入，简单点说：就是允许你在CSS中引入JS变量。",[27,2027,2028],{},"那么怎样才能在 vue3 的style中使用script里声明的变量呢？",[27,2030,2031],{},"首先创建一个组件，组件型式长这样：",[16,2033,2036],{"className":2034,"code":2035,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Ch1>{{ color }}\u003C/h1>\n\u003C/template>\n \n\u003Cscript>\nexport default {\n  data () {\n    return {\n      color: 'red'\n    }\n  }\n}\n\u003C/script>\n \n\u003Cstyle vars=\"{ color }\">\nh1 {\n  color: var(--color);\n}\n\u003C/style>\n",[24,2037,2035],{"__ignoreMap":22},[27,2039,2040],{},"复制代码\n首先要在style标签中写个vars=\"{}\"，再在大括号里写上你在data中声明过的值。",[27,2042,2043],{},"如果有多个变量的话，变量之间需要使用逗号进行分隔：",[16,2045,2050],{"className":2046,"code":2048,"language":2049,"meta":22},[2047],"language-h","\u003Ctemplate>\n  \u003Ch1>Vue\u003C/h1>\n\u003C/template>\n \n\u003Cscript>\nexport default {\n  data () {\n    return {\n      border: '1px solid black',\n      color: 'red'\n    }\n  }\n}\n\u003C/script>\n \n\u003Cstyle vars=\"{ border, color }\" scoped>\nh1 {\n  color: var(--color);\n  border: var(--border);\n}\n\u003C/style>\n","h",[24,2051,2048],{"__ignoreMap":22},[27,2053,2054],{},"复制代码\n再来试一下这个变量是不是响应式的，动态改变script标签中的this.opacity值会不会引起视图的变化呢？来试一下：",[16,2056,2059],{"className":2057,"code":2058,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Ch1>Vue\u003C/h1>\n\u003C/template>\n \n\u003Cscript>\nexport default {\n  data () {\n    return {\n      opacity: 0\n    }\n  },\n  mounted () {\n    setInterval(_ => {\n      this.opacity >= 1 && (this.opacity = 0)\n      this.opacity += 0.2\n    }, 300)\n  }\n}\n\u003C/script>\n \n\u003Cstyle vars=\"{ opacity }\">\nh1 {\n  color: rgb(65, 184, 131);\n  opacity: var(--opacity);\n}\n\u003C/style>\n",[24,2060,2058],{"__ignoreMap":22},[27,2062,2063],{},"运行结果：",[27,2065,2066],{},[551,2067],{"alt":22,"src":2068},"/images/v10.jpg",[27,2070,2071],{},"可以看到每300毫秒我们就改变一下 this.opacity 的值，它会映射到 CSS 变量上去，this.opacity 变了，--opacity 的值就会随之变化，视图也会随着数据的更新而相应的更新，这个特性简直太棒了！",{"title":22,"searchDepth":95,"depth":95,"links":2073},[2074,2079,2086,2090,2095,2101,2102,2103,2107,2111,2114,2115,2116],{"id":975,"depth":95,"text":976,"children":2075},[2076,2077,2078],{"id":979,"depth":102,"text":980},{"id":989,"depth":102,"text":990},{"id":1143,"depth":102,"text":1144},{"id":1175,"depth":95,"text":1176,"children":2080},[2081,2082,2083,2084,2085],{"id":1188,"depth":102,"text":1189},{"id":1233,"depth":102,"text":1234},{"id":1246,"depth":102,"text":1247},{"id":1259,"depth":102,"text":1260},{"id":1319,"depth":102,"text":1320},{"id":1344,"depth":95,"text":1345,"children":2087},[2088,2089],{"id":1348,"depth":102,"text":1349},{"id":1364,"depth":102,"text":1365},{"id":1459,"depth":95,"text":1460,"children":2091},[2092,2093,2094],{"id":1463,"depth":102,"text":1464},{"id":1473,"depth":102,"text":1474},{"id":1543,"depth":102,"text":1544},{"id":1565,"depth":95,"text":1566,"children":2096},[2097,2098,2099,2100],{"id":1569,"depth":102,"text":1570},{"id":1597,"depth":102,"text":1598},{"id":1623,"depth":102,"text":1624},{"id":1657,"depth":102,"text":1658},{"id":1678,"depth":95,"text":1679},{"id":1709,"depth":95,"text":1710},{"id":1742,"depth":95,"text":1743,"children":2104},[2105,2106],{"id":1746,"depth":102,"text":1747},{"id":1844,"depth":102,"text":1845},{"id":1875,"depth":95,"text":1876,"children":2108},[2109,2110],{"id":1879,"depth":102,"text":1880},{"id":1892,"depth":102,"text":1893},{"id":1905,"depth":95,"text":1906,"children":2112},[2113],{"id":1909,"depth":102,"text":1909},{"id":1925,"depth":95,"text":1926},{"id":1938,"depth":95,"text":1939},{"id":2021,"depth":95,"text":2022},{"date":2118,"tags":2119},"2022-07-17 10:00:40","vue3","/vue3",{"title":884,"description":892},"fhTOPQgFvxBMCa1jn9afXNQSXDLpg-mMPhxpctGhRCw",{"id":2124,"title":2125,"body":2126,"description":2130,"extension":104,"meta":2172,"navigation":110,"path":2177,"seo":2178,"stem":2179,"__hash__":2180},"content/jssdk.md","微信JS-SDK和WeixinJSBridge的区别",{"type":8,"value":2127,"toc":2170},[2128,2131,2137,2140,2143,2149,2152,2155,2161,2164],[27,2129,2130],{},"官方解释\n使用 WeixinJSBridge 预览图片",[16,2132,2135],{"className":2133,"code":2134,"language":54,"meta":22},[52],"WeixinJSBridge.invoke('imagePreview', {\n    current: 'http://inews.gtimg.com/newsapp_bt/0/1693121381/641',\n    urls: [ // 所有图片的URL列表，数组格式\n        'https://img1.gtimg.com/10/1048/104857/10485731_980x1200_0.jpg',\n        'https://img1.gtimg.com/10/1048/104857/10485726_980x1200_0.jpg',\n        'https://img1.gtimg.com/10/1048/104857/10485729_980x1200_0.jpg'\n    ]\n}, function(res) {\n    console.log(res.err_msg)\n})\n",[24,2136,2134],{"__ignoreMap":22},[27,2138,2139],{},"实际上，微信官方是没有对外暴露过如此调用的，此类 API 最初是提供给腾讯内部一些业务使用，很多外部开发者发现了之后，依葫芦画瓢地使用了，逐渐成为微信中网页的事实标准。2015年初，微信发布了一整套网页开发工具包，称之为 JS-SDK，开放了拍摄、录音、语音识别、二维码、地图、支付、分享、卡券等几十个API。给所有的 Web 开发者打开了一扇全新的窗户，让所有开发者都可以使用到微信的原生能力，去完成一些之前做不到或者难以做到的事情。",[27,2141,2142],{},"同样是调用原生的浏览图片，使用 JS-SDK 调用图片预览组件",[16,2144,2147],{"className":2145,"code":2146,"language":54,"meta":22},[52],"wx.previewImage({\n  current: 'https://img1.gtimg.com/10/1048/104857/10485726_980x1200_0.jpg',\n  urls: [ // 所有图片的URL列表，数组格式\n    'https://img1.gtimg.com/10/1048/104857/10485731_980x1200_0.jpg',\n    'https://img1.gtimg.com/10/1048/104857/10485726_980x1200_0.jpg',\n    'https://img1.gtimg.com/10/1048/104857/10485729_980x1200_0.jpg'\n  ],\n  success: function(res) {\n    console.log(res)\n  }\n})\n",[24,2148,2146],{"__ignoreMap":22},[27,2150,2151],{},"JS-SDK是对之前的 WeixinJSBridge 的一个包装，以及新能力的释放",[27,2153,2154],{},"其他\n使用 WeixinJSBridge 拉起微信支付",[16,2156,2159],{"className":2157,"code":2158,"language":54,"meta":22},[52],"onBridgeReady(field) {\n  WeixinJSBridge.invoke('getBrandWCPayRequest', {\n    \"appId\": field.appId,     //公众号ID，由商户传入\n    \"timeStamp\": field.timeStamp.toString(),     //时间戳，自1970年以来的秒数\n    \"nonceStr\": field.nonceStr,      //随机串\n    \"package\": field.package,\n    \"signType\": field.signType,     //微信签名方式：\n    \"paySign\": field.paySign, //微信签名\n  },\n  function (res) {\n    if (res.err_msg == \"get_brand_wcpay_request:ok\") {\n       //支付成功\n    } else if (res.err_msg == \"get_brand_wcpay_request:cancel\") {\n       //支付取消\n    } else if (res.err_msg == \"get_brand_wcpay_request:fail\") {\n       //支付失败\n    }\n    // 使用以上方式判断前端返回,微信团队郑重提示：\n    //res.err_msg将在用户支付成功后返回ok，但并不保证它绝对可靠。\n    console.log(res);\n    this.$router.push({path: '/order/waiting', query: {pay_no: field.pay_no}})\n  });\n},\n\nif (typeof WeixinJSBridge == \"undefined\") {\n  if (document.addEventListener) {\n    document.addEventListener('WeixinJSBridgeReady', this.onBridgeReady(field), false);\n  } else if (document.attachEvent) {\n    document.attachEvent('WeixinJSBridgeReady', this.onBridgeReady(field));\n    document.attachEvent('onWeixinJSBridgeReady', this.onBridgeReady(field));\n  }\n} else {\n  this.onBridgeReady(field);\n}\n",[24,2160,2158],{"__ignoreMap":22},[27,2162,2163],{},"使用 JS-SDK 拉起微信支付",[16,2165,2168],{"className":2166,"code":2167,"language":54,"meta":22},[52],"wx.chooseWXPay({\n  timestamp: field.timeStamp, // 支付签名时间戳，注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符\n  nonceStr: field.nonceStr, // 支付签名随机串，不长于 32 位\n  package: field.package, // 统一支付接口返回的prepay_id参数值，提交格式如：prepay_id=\\*\\*\\*）\n  signType: field.signType, // 签名方式，默认为'SHA1'，使用新版支付需传入'MD5'\n  paySign: field.paySign, // 支付签名\n  success: function (res) {\n    // 支付成功后的回调函数\n    console.log(res);\n    this.$router.push({path: '/order/waiting', query: {pay_no: field.pay_no}})\n  },\n  // 支付取消回调函数\n  cancel: function (res) {\n    console.log(res)\n    alert('用户取消支付~')\n  },\n  fail: function (res) {\n    console.log(res);\n    this.$router.push({path: '/order/waiting', query: {pay_no: field.pay_no}})\n  }\n});\n",[24,2169,2167],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":2171},[],{"date":2173,"tags":2174},"2022-06-15 22:46:40",[2175,2176],"JSSDK","微信支付","/jssdk",{"title":2125,"description":2130},"jssdk","T-qZTbrL3cSxZ7a-GDFInmBkZjn805Z-xmjTK5ig0AU",{"id":2182,"title":2183,"body":2184,"description":2188,"extension":104,"meta":2282,"navigation":110,"path":2285,"seo":2286,"stem":2287,"__hash__":2288},"content/cors.md","手把手教你解决web前端跨域问题",{"type":8,"value":2185,"toc":2278},[2186,2189,2192,2200,2203,2206,2209,2212,2218,2221,2227,2230,2233,2236,2242,2245,2248,2254,2257,2263,2266,2269,2272],[27,2187,2188],{},"一、什么是跨域\n出于浏览器的同源策略限制。同源策略是一种约定，它是浏览器最核心也最基本的安全功能，如果缺少了同源策略，则浏览器的正常功能可能都会受到影响。知识点：跨域只会出现在浏览器上，小程序和APP开发不会有跨域问题",[27,2190,2191],{},"二、什么情况下会出现跨域\n说人话就是域名不同的时候会出现跨域。下面以 百度 域名为例，在域名的：协议、主机名、域名、端口 任何一个与当前访问的站点域名不同时就会出现跨域",[16,2193,2198],{"className":2194,"code":2196,"language":2197,"meta":22},[2195],"language-text","https://www.baidu.com:443\n","text",[24,2199,2196],{"__ignoreMap":22},[27,2201,2202],{},"切记：本文章只讲述 前端解决跨域的办法，并且前端解决跨域问题只在本地开发时有效，项目发布线上需要前端服务器配置请求代理比如 nginx的反向代理 或 后端允许跨域请求",[27,2204,2205],{},"解决跨域的方法就是“欺骗”浏览器 或 删除浏览器限制",[27,2207,2208],{},"三、uni-app 项目 解决跨域办法\n方法一：使用 HBuilderX 内置浏览器，内置浏览器是删除了浏览器的跨域限制的。",[27,2210,2211],{},"方法二：在项目根目录 manifest.json 文件中添加 h5 配置，点我 查看 uni-app 文档说明。",[16,2213,2216],{"className":2214,"code":2215,"language":69,"meta":22},[67],"\"h5\": {\n    \"devServer\":{\n        \"proxy\":{\n            \"^/api\":{\n                \"target\":\"http://127.0.0.1:8081\",\n                \"ws\": true,\n                \"changeOrigin\": true\n            }\n        }\n    }\n}\n",[24,2217,2215],{"__ignoreMap":22},[27,2219,2220],{},"页面请求代码示例",[16,2222,2225],{"className":2223,"code":2224,"language":54,"meta":22},[52],"uni.request({\n    url:'/api/1.json',    // url 要与 proxy 匹配，不能写成 xx.com/api.1.json\n    method:'GET',\n    success: (res) => {\n        console.log(res);\n    }\n})\n",[24,2226,2224],{"__ignoreMap":22},[27,2228,2229],{},"四、Vue.js 项目 解决跨域办法(只在开发环境有效)",[11,2231,2232],{"id":2232},"webpack写法",[27,2234,2235],{},"在项目根目录 vue.config.js 文件中添加如下配置",[16,2237,2240],{"className":2238,"code":2239,"language":54,"meta":22},[52],"/* 开发环境配置 */\nmodule.exports = {\n    devServer:{\n        /* 代理目录 */\n        proxy:{\n            '^/api': {\n                target: 'http://127.0.0.1:8081',\n                changeOrigin: true,\n                pathRewrite: {\n                    '^/api': ''\n                }\n            }\n        }\n    }\n}\n",[24,2241,2239],{"__ignoreMap":22},[11,2243,2244],{"id":2244},"vite写法",[27,2246,2247],{},"在项目根目录 vite.config.js 文件中添加如下配置",[16,2249,2252],{"className":2250,"code":2251,"language":54,"meta":22},[52],"import { defineConfig } from 'vite'\nimport vue from '@vitejs/plugin-vue'\n\nexport default defineConfig({\n  plugins: [vue()],\n  server: {\n    proxy: {\n      '/api': {\n        target: 'http://127.0.0.1:8081',\n        changeOrigin: true,\n        rewrite: (path) => path.replace(/^\\/api/, '')\n      }\n    }\n  }\n})\n",[24,2253,2251],{"__ignoreMap":22},[27,2255,2256],{},"如果后端接口本身也带/api，那么配置时就不需要rewrite/pathRewrite\n页面请求代码，以 axios 库为例",[16,2258,2261],{"className":2259,"code":2260,"language":54,"meta":22},[52],"request.get('/api/1.json',{\n    params\n}).then(res=>{\n    console.log(res)\n})\n",[24,2262,2260],{"__ignoreMap":22},[27,2264,2265],{},"五、终极解决办法，删除浏览器跨域限制\n以 chrome 浏览器为例",[27,2267,2268],{},"【设置步骤】",[27,2270,2271],{},"新建一个 Chrome 的桌面快捷方式\n在快捷方式上右键，点击【属性】，打开【属性】面板\n在【属性】面板的【目标】中（chrome.exe 后面）添加启动参数\n// 完整复制如下参数，注意空格",[16,2273,2276],{"className":2274,"code":2275,"language":21,"meta":22},[19],"--args --disable-web-security --user-data-dir=C:\\MyChromeDevUserData\n",[24,2277,2275],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":2279},[2280,2281],{"id":2232,"depth":95,"text":2232},{"id":2244,"depth":95,"text":2244},{"date":2283,"tags":2284},"2022-04-06 23:03:36","跨域,前端,浏览器,cors","/cors",{"title":2183,"description":2188},"cors","rAPhtYOOHvaOtMM1crX4e2wYUpG9kaY9qXSXUAl0cws",{"id":2290,"title":2291,"body":2292,"description":22,"extension":104,"meta":2564,"navigation":110,"path":2570,"seo":2571,"stem":2572,"__hash__":2573},"content/anim.md","AIML框架学习",{"type":8,"value":2293,"toc":2556},[2294,2298,2301,2305,2311,2314,2320,2323,2326,2334,2338,2341,2347,2350,2353,2359,2366,2372,2375,2378,2384,2387,2390,2396,2399,2405,2411,2414,2420,2426,2429,2435,2441,2444,2450,2456,2459,2465,2471,2474,2477,2483,2486,2489,2495,2498,2501,2508,2511,2517,2520,2523,2526,2532,2535,2538,2541,2547,2550],[11,2295,2297],{"id":2296},"什么是aiml","什么是AIML？",[27,2299,2300],{},"AIML是Richard Wallace开发的。 他开发了一个叫A.L.I.C.E（Artificial Linguistics Internet Computer Entity）的机器人并且赢了几个人工智能的奖项。 有趣的是， 其中一个图灵测试是让一个人在文本界面跟一个机器人聊几分钟，看看人们是否认为它是个人类。 AIML是一种定义了匹配模式和决定响应的规则的一种XML。",[11,2302,2304],{"id":2303},"安装python-aiml库","安装Python aiml库",[16,2306,2309],{"className":2307,"code":2308,"language":21,"meta":22},[19],"Python 2：pip install aiml\nPython 3：pip install python-aiml\n",[24,2310,2308],{"__ignoreMap":22},[27,2312,2313],{},"主文件",[16,2315,2318],{"className":2316,"code":2317,"language":774,"meta":22},[772],"# -*- coding: utf-8 -*-\nimport aiml\nimport os\nmybot_path = './mybot'\n#切换到语料库所在工作目录\nos.chdir(mybot_path)\nmybot = aiml.Kernel()\nmybot.learn(\"std-startup.xml\")\nmybot.respond('load aiml b')\nwhile True:\n    print(mybot.respond(input(\"Enter your message >> \")))\n",[24,2319,2317],{"__ignoreMap":22},[11,2321,2322],{"id":2322},"创建标准启动文件",[27,2324,2325],{},"标准的做法是，创建一个名为std-startup.xml的启动文件，作为加载AIML文件的主入口点。在这个例子中，我们将创建一个基础的文件，它匹配一个模式，并且返回一个相应。我们想要匹配模式load aiml b，然后让它加载我们的aiml大脑作为响应。我们将在一步内创建basic_chat.aiml文件。",[16,2327,2332],{"className":2328,"code":2330,"language":2331,"meta":22},[2329],"language-xml","\u003Caiml version=\"1.0.1\" encoding=\"UTF-8\">\n    \u003C!-- std-startup.xml -->\n    \u003C!-- Category是一个自动的AIML单元 -->\n    \u003Ccategory>\n        \u003C!-- Pattern用来匹配用户输入 -->\n        \u003C!-- 如果用户输入 \"LOAD AIML B\" -->\n        \u003Cpattern>LOAD AIML B\u003C/pattern>\n        \u003C!-- Template是模式的响应 -->\n        \u003C!-- 这里学习一个aiml文件 -->\n        \u003Ctemplate>\n            \u003Clearn>basic_chat.aiml\u003C/learn>\n            \u003C!-- 你可以在这里添加更多的aiml文件 -->\n            \u003C!--\u003Clearn>more_aiml.aiml\u003C/learn>-->\n        \u003C/template>\n    \u003C/category>\n\u003C/aiml>\n","xml",[24,2333,2330],{"__ignoreMap":22},[11,2335,2337],{"id":2336},"创建一个aiml文件","创建一个AIML文件",[27,2339,2340],{},"在上面，我们创建的AIML文件只能处理一个模式:load aiml b。当我们向机器人输入那个命令时，它将会尝试加载basic_chat.aiml。除非我们真的创建了它，否则无效。下面是你可以写进basic_chat.aiml的内容。我们将匹配两个基本的模式和响应。",[16,2342,2345],{"className":2343,"code":2344,"language":2331,"meta":22},[2329],"\u003Caiml version=\"1.0.1\" encoding=\"UTF-8\">\n\u003C!-- basic_chat.aiml -->\n    \u003Ccategory>\n        \u003Cpattern>HELLO\u003C/pattern>\n        \u003Ctemplate>\n            Well, hello!\n        \u003C/template>\n    \u003C/category>\n    \u003Ccategory>\n        \u003Cpattern>WHAT ARE YOU\u003C/pattern>\n        \u003Ctemplate>\n            I'm a bot, silly!\n        \u003C/template>\n    \u003C/category>\n\u003C/aiml>\n",[24,2346,2344],{"__ignoreMap":22},[11,2348,2349],{"id":2349},"标签",[27,2351,2352],{},"1、基本标签",[16,2354,2357],{"className":2355,"code":2356,"language":2197},[2195],"\u003Caiml>：定义在AIML文件的开头和结尾。 它包含在版本和编码属性的版本和编码信息。\n\n\u003Ccategory>：定义unit of knowledge中Alicebot的知识库。每个类别包含在一个句子的形式，用户输入可以是一个断言，问题和感叹号等用户的输入可以包含通配符的字符，如*和_。\n用\u003Ccategory>标签必须有\u003Cpattern>和\u003Ctemplate>标记。\n\n\u003Cpattern>：定义图案什么用户可输入匹配到Alicebot。\n\n\u003Ctemplate> ：定义一个Alicebot的用户的输入的响应。\u003Ctemplate>标签可以保存数据，可以调用另一个程序，能够给条件答案或委托给其他类别。\n",[24,2358,2356],{"__ignoreMap":22},[27,2360,2361,2362],{},"2、\u003C * >：用于匹配通配符*character(s)在",[2363,2364,2365],"pattern",{},"标签。",[16,2367,2370],{"className":2368,"code":2369,"language":2331,"meta":22},[2329],"\u003Ccategory>\n \n        \u003Cpattern> A * is a *.\u003C/pattern>\n \n        \u003Ctemplate>When a \u003Cstar index=\"1\"/> is not a \u003Cstar index=\"2\"/>?\u003C/template>\n \n\u003C/category>\n",[24,2371,2369],{"__ignoreMap":22},[27,2373,2374],{},"3、\u003Crandom> ：用于获取随机响应。",[27,2376,2377],{},"4、\u003Cli> ：用来表示多个响应。",[16,2379,2382],{"className":2380,"code":2381,"language":2331,"meta":22},[2329],"\u003Caiml version=\"1.0.1\" encoding=\"UTF-8\">\n    \u003Ccategory>\n    \u003Cpattern>HI\u003C/pattern>\n        \u003Ctemplate>\n                \u003Crandom>\n                   \u003Cli> Hello! \u003C/li>\n                   \u003Cli> Hi! Nice to meet you! \u003C/li>\n                \u003C/random>\n        \u003C/template> \n    \u003C/category>\n\u003C/aiml>\n",[24,2383,2381],{"__ignoreMap":22},[27,2385,2386],{},"5、\u003Cset> ：使用在AIML变量设定值。",[27,2388,2389],{},"6、\u003Cget>：用于获取存储在AIML变量的值。",[16,2391,2394],{"className":2392,"code":2393,"language":2331,"meta":22},[2329],"\u003Caiml version=\"1.0.1\" encoding=\"UTF-8\">\n    \u003Ccategory>\u003Cpattern>I am*\u003C/pattern>\n            \u003Ctemplate>Hello \u003Cset name=\"username\">\u003Cstar/>! \u003C/set>\u003C/template> \n    \u003C/category> \n    \u003Ccategory> \u003Cpattern>GoodNight\u003C/pattern>\n            \u003Ctemplate>Hi \u003Cget name=\"username\"/> Thanks for the conversation!\u003C/template> \n    \u003C/category> \n\u003C/aiml>\n",[24,2395,2393],{"__ignoreMap":22},[27,2397,2398],{},"7、\u003Cthat> ：在AIML用于应对基于上下文。表示先前机器人说的话。",[16,2400,2403],{"className":2401,"code":2402,"language":2331,"meta":22},[2329],"\u003Caiml version=\"1.0.1\" encoding=\"UTF-8\">\n    \u003Ccategory>\u003Cpattern>WHAT ABOUTMOVIES\u003C/pattern>\n        \u003Ctemplate>Do you like comedymovies\u003C/template> \n    \u003C/category>\n    \u003Ccategory>\u003Cpattern>YES\u003C/pattern>\n        \u003Cthat>Do you like comedymovies\u003C/that>\n        \u003Ctemplate>Nice, I like comedy moviestoo.\u003C/template>\n    \u003C/category>\n    \u003Ccategory>\u003Cpattern>NO\u003C/pattern>\n         \u003Cthat>Do you like comedymovies\u003C/that>\n         \u003Ctemplate>Ok! But I like comedymovies.\u003C/template>\n    \u003C/category>  \n\u003C/aiml>\n",[24,2404,2402],{"__ignoreMap":22},[16,2406,2409],{"className":2407,"code":2408,"language":2197},[2195],"Human: What about movies?\n\nRobot: Do you like comedy movies?\n\nHuman: No\n\nRobot: Ok! But I like comedy movies.\n",[24,2410,2408],{"__ignoreMap":22},[27,2412,2413],{},"8、\u003Ctopic> :用于AIML存储上下文，这样以后可以谈话基于这一背景下进行。",[16,2415,2418],{"className":2416,"code":2417,"language":2331,"meta":22},[2329],"\u003Caiml version=\"1.0.1\" encoding=\"UTF-8\">\n    \u003Ccategory>\n        \u003Cpattern>LET DISCUSSMOVIES\u003C/pattern>\n        \u003Ctemplate>Yes \u003Cset name=\"topic\">movies\u003C/set>\u003C/template> \n    \u003C/category>\n    \u003Ctopic name=\"movies\">\n        \u003Ccategory>\n            \u003Cpattern>*\u003C/pattern>\n            \u003Ctemplate>Watching good movierefreshes our minds.\u003C/template>\n        \u003C/category>\n        \u003Ccategory>\n            \u003Cpattern> I LIKE WATCHING COMEDY!\u003C/pattern>\n            \u003Ctemplate>I like comedy moviestoo.\u003C/template>\n        \u003C/category>\n    \u003C/topic>\n\u003C/aiml>\n",[24,2419,2417],{"__ignoreMap":22},[16,2421,2424],{"className":2422,"code":2423,"language":2197},[2195],"Human: let discuss movies\n\nRobot: Yes movies\n\nHuman: Comedy movies are nice to watch\n\nRobot: Watching good movie refreshes ourminds.\n\nHuman: I like watching comedy\n\nRobot: I too like watching comedy.\n",[24,2425,2423],{"__ignoreMap":22},[27,2427,2428],{},"9、\u003Cthink>：用于AIML存储变量不通知用户。",[16,2430,2433],{"className":2431,"code":2432,"language":2331,"meta":22},[2329],"\u003Caiml version=\"1.0.1\" encoding=\"UTF-8\">\n    \u003Ccategory>\u003Cpattern>My name is*\u003C/pattern>\n            \u003Ctemplate>Hello!\u003Cthink>\u003Cset name=\"username\"> \u003Cstar/>\u003C/set>\u003C/think>\u003C/template> \n    \u003C/category> \n    \n    \u003Ccategory>\u003Cpattern>Byeee\u003C/pattern>\n            \u003Ctemplate>Hi \u003Cget name=\"username\"/> Thanks for the conversation!\u003C/template> \n    \u003C/category> \n \n \u003C/aiml>\n",[24,2434,2432],{"__ignoreMap":22},[16,2436,2439],{"className":2437,"code":2438,"language":2197},[2195],"Human: My name is Mahesh\n\nRobot: Hello!\n\nHuman: Byeee\n\nRobot: Hi Mahesh Thanks for theconversation!\n",[24,2440,2438],{"__ignoreMap":22},[27,2442,2443],{},"10、\u003Ccondition>：类似的切换在编程语言语句。 它有助于ALICE来匹配输入作出响应。",[16,2445,2448],{"className":2446,"code":2447,"language":2331,"meta":22},[2329],"\u003Caiml version=\"1.0.1\" encoding=\"UTF-8\">\n    \u003Ccategory>\u003Cpattern> HOW ARE YOUFEELING TODAY \u003C/pattern>\n        \u003Ctemplate>\u003Cthink>\u003Cset name=\"state\"> happy\u003C/set>\u003C/think>\n        \u003Ccondition name=\"state\" value=\"happy\">I am happy!\u003C/condition>\n        \u003Ccondition name=\"state\" value=\"sad\">I am sad!\u003C/condition>\u003C/template>\n        \u003C/category>\n\u003C/aiml>\n",[24,2449,2447],{"__ignoreMap":22},[16,2451,2454],{"className":2452,"code":2453,"language":2197},[2195],"Human: How are you feeling today\n\nRobot: I am happy!\n",[24,2455,2453],{"__ignoreMap":22},[27,2457,2458],{},"11、\u003Csrai>：多用途标签，用来调用其它/匹配的类别。（符号缩减、分而治之、同义词分辨率、关键词检测）",[16,2460,2463],{"className":2461,"code":2462,"language":2331,"meta":22},[2329],"\u003Caiml version=\"1.0.1\"encoding=\"UTF-8\"?>\n    \u003Ccategory>\u003Cpattern> WHO ISALBERT EINSTEIN \u003C/pattern>\n            \u003Ctemplate>Albert Einstein was agerman physicist.\u003C/template>\n    \u003C/category>\n    \u003Ccategory>\u003Cpattern> WHO ISIsaac NEWTON \u003C/pattern>\n            \u003Ctemplate>Isaac Newton was a englishphysicist and mathematician.\u003C/template>\n    \u003C/category>\n    \u003Ccategory>\u003Cpattern>DO YOU KNOWWHO * IS\u003C/pattern>\n            \u003Ctemplate>\u003Csrai>WHO IS\u003Cstar/>\u003C/srai>\u003C/template>\n    \u003C/category>\n \n    \u003Ccategory>\u003Cpattern>BYE\u003C/pattern>\n        \u003Ctemplate>Good Bye!\u003C/template>\n    \u003C/category>\n \n    \u003Ccategory>\u003Cpattern>BYE*\u003C/pattern>\n        \u003Ctemplate>\u003Csrai>BYE\u003C/srai>\u003C/template>\n    \u003C/category>\n    \u003Ccategory>\u003Cpattern>FACTORY\u003C/pattern>\n            \u003Ctemplate>DevelopmentCenter!\u003C/template>\n    \u003C/category>\n \n    \u003Ccategory>\u003Cpattern>INDUSTRY\u003C/pattern>\n            \u003Ctemplate>\u003Csrai>FACTORY\u003C/srai>\u003C/template>\n    \u003C/category>\n \n    \u003Ccategory>\u003Cpattern>SCHOOL\u003C/pattern>\n            \u003Ctemplate>School is an importantinstitution in a child's life.\u003C/template>\n    \u003C/category> \n    \u003Ccategory>\u003Cpattern>_SCHOOL\u003C/pattern>\n            \u003Ctemplate>\u003Csrai>SCHOOL\u003C/srai>\u003C/template>\n    \u003C/category>\n    \u003Ccategory>\u003Cpattern>_SCHOOL\u003C/pattern>\n            \u003Ctemplate>\u003Csrai>SCHOOL\u003C/srai>\u003C/template>\n    \u003C/category>\n    \u003Ccategory>\u003Cpattern>SCHOOL*\u003C/pattern>\n            \u003Ctemplate>\u003Csrai>SCHOOL\u003C/srai>\u003C/template>\n    \u003C/category>\n    \u003Ccategory>\u003Cpattern>_ SCHOOL*\u003C/pattern>\n            \u003Ctemplate>\u003Csrai>SCHOOL\u003C/srai>\u003C/template>\n    \u003C/category>\n\u003C/aiml>\n",[24,2464,2462],{"__ignoreMap":22},[16,2466,2469],{"className":2467,"code":2468,"language":2197},[2195],"Human: I love going to school daily.\n\nRobot: School is an important institutionin a child's life.\n\nHuman: I like my school.\n\nRobot: School is an important institutionin a child's life.\n",[24,2470,2468],{"__ignoreMap":22},[27,2472,2473],{},"12、\u003Cupcase>xiaoxie\u003C/upcase>元素用来把xiaoxie转换成大写形式：XIAOXIE",[27,2475,2476],{},"13、\u003Csystem>\u003Csystem>元素表示调用系统函数，例如：",[16,2478,2481],{"className":2479,"code":2480,"language":2331,"meta":22},[2329],"\u003Csystem>date\u003C/system>\n",[24,2482,2480],{"__ignoreMap":22},[27,2484,2485],{},"表示取系统当前日期。",[27,2487,2488],{},"14、\u003Csentence>元素用来格式化句子，比如：",[16,2490,2493],{"className":2491,"code":2492,"language":2331,"meta":22},[2329],"\u003Csentence>this is some kind ofsentence test.\u003C/sentence>\n",[24,2494,2492],{"__ignoreMap":22},[27,2496,2497],{},"可以格式化成：This is some kind of sentence test.即把句子首字母大写。",[27,2499,2500],{},"15、\u003Cpattern>表示匹配模式，里面的内容必须大写，可以有星号* 或下划线_，但必须空格隔开，星号表示匹配所有，任意情况；下划线的意义跟星号一样，除了不能匹配字典里面Z后面的字母。",[27,2502,2503,2504],{},"16、\u003Clowcase>\u003C/lowcase>表示把中间的内容变成小写，对应的是",[2505,2506,2507],"upcase",{},"把内容变成大写\n中文无效。\n17、\u003Clearen filename=”xxx.aiml”>元素表示让机器人学习某个aiml文件。",[27,2509,2510],{},"18、\u003Cif>元素，判断元素，有以下形式：",[16,2512,2515],{"className":2513,"code":2514,"language":2331,"meta":22},[2329],"    \u003Cif name=\"topic\" value=\"cars\">\u003C/if> \n    \u003Cif name=\"topic\" contains=\"cars\">\u003C/if> \n    \u003Cif name=\"topic\" exists=\"true\">\u003C/if>\n",[24,2516,2514],{"__ignoreMap":22},[27,2518,2519],{},"19、\u003Cformal>元素，用来格式化输出，例如：\u003Cformal>jon baer\u003C/formal>那么回复将被格式化成首字母大写输出：Jon Baer，对中文无效。",[27,2521,2522],{},"20、\u003CNUM/>元素，匹配数字",[27,2524,2525],{},"21、约束匹配模式",[16,2527,2530],{"className":2528,"code":2529,"language":2331,"meta":22},[2329],"\u003Ccategory>\n    \u003Cpattern that=\"你好\">你好啊\u003C/pattern>\n        \u003Ctemplate>\n            \u003Crandom>\n                \u003Cli>你好，我们刚刚说过一遍了。\u003C/li>\n                \u003Cli>你好，客气啥！\u003C/li>\n            \u003C/random>\n        \u003C/template>\n\u003C/category>\n",[24,2531,2529],{"__ignoreMap":22},[27,2533,2534],{},"上述示例中，并不会直接命中”你好啊“，而是在上一次是匹配的”你好“，当又匹配了”你好啊“才会命中上述category，即上次和当前次的连续匹配",[11,2536,2537],{"id":2537},"会话与断言",[27,2539,2540],{},"通过指定一个会话，AIML可以为不同的人剪裁不同的会话。例如，如果某个人告诉机器人，他的名字是Alice，而另一个人告诉机器人他的名字是Bob，机器人可以区分不同的人。为了指定你所使用的会话，将其作为第二个参数传给respond()",[16,2542,2545],{"className":2543,"code":2544,"language":774,"meta":22},[772],"sessionId = 12345\nmybot.respond(raw_input(\">>>\"), sessionId)\n",[24,2546,2544],{"__ignoreMap":22},[27,2548,2549],{},"这对于为每一个客户端定制个性化的对话是很有帮助的。你将必须以某种形式生成自己的会话ID，并且跟踪它。注意，保存brain文件不会保存所有的会话值。",[16,2551,2554],{"className":2552,"code":2553,"language":774,"meta":22},[772],"sessionId = 12345\n# 会话信息作为字典获取. 包含输入输出历史，\n# 以及任何已知断言\nsessionData = mybot.getSessionData(sessionId)\n# 每一个会话ID需要时一个唯一值。\n# 断言名是机器人在与你的会话中了解到的某些/某个名字 \n# 机器人可能知道，你是\"Billy\"，而你的狗的名字是\"Brandy\"\nmybot.setPredicate(\"dog\", \"Brandy\", sessionId)\nclients_dogs_name = mybot.getPredicate(\"dog\", sessionId)\nmybot.setBotPredicate(\"hometown\", \"127.0.0.1\")\nbot_hometown = mybot.getBotPredicate(\"hometown\")\n",[24,2555,2553],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":2557},[2558,2559,2560,2561,2562,2563],{"id":2296,"depth":95,"text":2297},{"id":2303,"depth":95,"text":2304},{"id":2322,"depth":95,"text":2322},{"id":2336,"depth":95,"text":2337},{"id":2349,"depth":95,"text":2349},{"id":2537,"depth":95,"text":2537},{"date":2565,"tags":2566},"2022-03-09 22:26:21",[2567,2568,774,2569],"AIML","人工智能","自然语言","/anim",{"title":2291,"description":22},"anim","ZIvRpWaSe9njtZGGKHYdebMoJvwDWO7sPBbJPB8QrPc",{"id":2575,"title":2576,"body":2577,"description":22,"extension":104,"meta":2605,"navigation":110,"path":2610,"seo":2611,"stem":2612,"__hash__":2613},"content/pay.md","EasyWechat开发微信支付代码实例",{"type":8,"value":2578,"toc":2603},[2579,2585,2588,2594,2597],[16,2580,2583],{"className":2581,"code":2582,"language":390,"meta":22},[388],"$tradeType = \"支付场景，APP/JSAPI\";\n$accountPayment = [\n    'APP' => [\n        'app_id' => 'app_id',\n        'mch_id' => 'mch_id',\n        'key' => 'XXXXXXXXXXXXXXXXXXXXXXX',\n        'cert_path' => app_path('resources/cert/apiclient_cert.pem'),\n        'key_path' => app_path('resources/cert/apiclient_key.pem'),\n        'notify_url' => route('weChatNotifyUrl'),\n    ],\n    'JSAPI' => [\n        'app_id' => 'app_id',\n        'mch_id' => 'mch_id',\n        'key' => 'XXXXXXXXXXXXXXXXXXXXXXX',\n        'cert_path' => app_path('resources/cert2/apiclient_cert.pem'),\n        'key_path' => app_path('resources/cert2/apiclient_key.pem'),\n        'notify_url' => route('weChatNotifyUrl'),\n    ],\n];\n \n$app = Factory::payment($accountPayment[$tradeType]);\n \n$order_info = [\n    'body' => $title,\n    'out_trade_no' => $order_number,\n    'total_fee' => $amount,\n    'trade_type' => $tradeType, // 交易类型 JSAPI | NATIVE |APP | WAP\n];\n \n$result = $app->order->unify($order_info);\nif ($tradeType == 'JSAPI') { // 微信内H5/小程序支付\n    $jssdk = $app->jssdk;\n    $config = $jssdk->bridgeConfig($result['prepay_id'], false);\n} else { // APP支付\n    $config = $app->jssdk->appConfig($result['prepay_id']);\n}\n",[24,2584,2582],{"__ignoreMap":22},[27,2586,2587],{},"微信内H5/小程序支付$config信息",[16,2589,2592],{"className":2590,"code":2591,"language":54,"meta":22},[52],"config: {\n    appId: \"wxfd3ca09bb10e3a63\",\n    timeStamp: \"1527846324\",\n    nonceStr: \"5b1115b465d95\",\n    package: \"prepay_id=wx01174524358388e762240d961371110515\",\n    signType: \"MD5\",\n    paySign: \"96B18F3E55A9F09123C007074C62849C\"\n}\n",[24,2593,2591],{"__ignoreMap":22},[27,2595,2596],{},"APP支付$config信息",[16,2598,2601],{"className":2599,"code":2600,"language":54,"meta":22},[52],"config: {\n   appid: \"wxaba67d5bcd2c0a6c\",\n   partnerid: \"1494244042\",\n   prepayid: \"wx0117351453138316e25af7282351919901\",\n   noncestr: \"5b11135296ddf\",\n   timestamp: 1527845714,\n   package: \"Sign=WXPay\",\n   sign: \"93A42757183D28CBCB7BF8A408C8675F\"\n}\n",[24,2602,2600],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":2604},[],{"date":2606,"tags":2607},"2021-12-09 23:57:59",[390,2176,2608,2609],"小程序","APP","/pay",{"title":2576,"description":22},"pay","4bvaZh2eRBubmFaJejX0xG3a-_zxLyJgkvHuPAQ4YHo",{"id":2615,"title":2616,"body":2617,"description":22,"extension":104,"meta":2796,"navigation":110,"path":2800,"seo":2801,"stem":2802,"__hash__":2803},"content/flask-1.md","flask 获取GET和POST请求参数（全）",{"type":8,"value":2618,"toc":2785},[2619,2623,2626,2629,2632,2635,2638,2641,2645,2648,2652,2658,2661,2665,2668,2674,2677,2683,2686,2692,2696,2699,2702,2705,2708,2711,2714,2717,2720,2726,2729,2732,2738,2741,2744,2750,2753,2760,2766,2769,2775,2779],[11,2620,2622],{"id":2621},"_1简要说明","1、简要说明",[27,2624,2625],{},"近日，在使用flask框架获取前端的请求时获取参数时，遇到了几个问题；之前的项目也有使用这部分，当时程序没有问题就没再深究，直到遇到了问题。果然，遇到问题才会成长！^_^",[27,2627,2628],{},"因此，对GET和POST两种请求方式的参数获取方式进行梳理。",[27,2630,2631],{},"request对象是从客户端向服务器发出请求，包括用户提交的信息以及客户端的一些信息。客户端可通过HTML表单或在网页地址后面提供参数的方法提交数据，然后通过request对象的相关方法来获取这些数据。",[27,2633,2634],{},"request请求总体分为两类：",[27,2636,2637],{},"get请求\nGET把参数包含在URL中，访问时会在地址栏直接显示参数不安全，且参数大小比较小",[27,2639,2640],{},"post请求\n参数通过request body传递",[11,2642,2644],{"id":2643},"_2常见的方式","2、常见的方式",[27,2646,2647],{},"在最初使用时，上网一搜，得到的结果大致如下：",[73,2649,2651],{"id":2650},"flask获取参数方式","flask获取参数方式：",[16,2653,2656],{"className":2654,"code":2655,"language":774,"meta":22},[772],"request.form.get(\"key\", type=str, default=None) # 获取表单数据\nrequest.args.get(\"key\") # 获取get请求参数\nrequest.values.get(\"key\") # 获取所有参数\n",[24,2657,2655],{"__ignoreMap":22},[27,2659,2660],{},"上述是三种方式，可以满足基本的使用，但是并不是万能的！",[11,2662,2664],{"id":2663},"_3get请求方式获取参数","3、GET请求方式获取参数",[27,2666,2667],{},"当采用GET请求方式时，参数直接显示在请求连接中，可以使用两种获取参数的方式：",[16,2669,2672],{"className":2670,"code":2671,"language":774,"meta":22},[772],"request.args.get('key')\nrequest.values.get('key')\n",[24,2673,2671],{"__ignoreMap":22},[27,2675,2676],{},"在route装饰器语句中，通过methods指定请求方式，如下：",[16,2678,2681],{"className":2679,"code":2680,"language":774,"meta":22},[772],"@app.route(\"/\", methods=[\"GET\"])\n",[24,2682,2680],{"__ignoreMap":22},[27,2684,2685],{},"获取参数",[16,2687,2690],{"className":2688,"code":2689,"language":774,"meta":22},[772],"if request.method == \"GET\":\n    comment = request.args.get(\"content\")\n    comment = request.values.get(\"content\")\n",[24,2691,2689],{"__ignoreMap":22},[73,2693,2695],{"id":2694},"_4post请求方式获取参数","4、POST请求方式获取参数",[27,2697,2698],{},"客户端在发送post请求时，数据可以使用不同的Content-Type 来发送。",[27,2700,2701],{},"比如：",[27,2703,2704],{},"以 application/json 的方式 ，请求body体的内容就是{\"a\": \"b\", \"c\": \"d\"}\n以 application/x-www-form-urlencoded 的方式，则body体的内容就是 a=b&c=d\n在Postman软件中，可以方便的查看参数是以什么形式发送的，对应的Content-Type是什么。",[27,2706,2707],{},"Body中选择“raw”，则对应的Headers中的“Content-Type”是“application/json”，参数形式是{\"content\":\"很好\"}",[27,2709,2710],{},"Body中选择“x-www-form-urlencoded”，则对应的Headers中的“Content-Type”是“application/x-www-form-urlencoded”，参数形式是Key-Value形式。",[27,2712,2713],{},"Body中选择“form-data”， 则对应的Headers中的“Content-Type”是“multipart/form-data”，参数形式是Key-Value。",[27,2715,2716],{},"具体位置如下图：",[27,2718,2719],{},"POST请求不同Content-Type的处理方式\nContent-Type为 application/json，获取json参数",[16,2721,2724],{"className":2722,"code":2723,"language":774,"meta":22},[772],"request.get_json()['content']\n# 或者\nrequest.json.get('centent')\n",[24,2725,2723],{"__ignoreMap":22},[27,2727,2728],{},"获取的是序列化后的参数，一般情况下满足使用，不需要json.loads()来序列化。\n打印出结果就是json串，如{'name':'lucy', 'age':22}",[27,2730,2731],{},"Content-Type为 application/json，获取json原始参数",[16,2733,2736],{"className":2734,"code":2735,"language":774,"meta":22},[772],"request.get_data()\n",[24,2737,2735],{"__ignoreMap":22},[27,2739,2740],{},"request.get_data()获取的原始参数，接受的是type是'bytes’的对象，如：b{'name':'lucy', 'age':22}",[27,2742,2743],{},"Content-Type为application/x-www-form-urlencoded",[16,2745,2748],{"className":2746,"code":2747,"language":774,"meta":22},[772],"request.values.get('key')\n",[24,2749,2747],{"__ignoreMap":22},[27,2751,2752],{},"Content-Type为multipart/form-data ，获取表单参数",[27,2754,2755,2756,2759],{},"可以使用request.form.get('content') 或者 request.form",[1373,2757,2758],{},"'content'","来获取参数",[16,2761,2764],{"className":2762,"code":2763,"language":774,"meta":22},[772],"request.form.get('key')\n# 或者\nrequest.form['key']\n",[24,2765,2763],{"__ignoreMap":22},[73,2767,2768],{"id":2768},"代码示例",[16,2770,2773],{"className":2771,"code":2772,"language":774,"meta":22},[772],"if request.method == \"POST\":\n     if request.content_type.startswith('application/json'):            \n         # comment = request.get_json()[\"content\"]\n         comment = request.json.get('content')\n     elif request.content_type.startswith('multipart/form-data'):\n         comment = request.form.get('content')\n     else:\n         comment = request.values.get(\"content\")\n",[24,2774,2772],{"__ignoreMap":22},[73,2776,2778],{"id":2777},"_5完整示例","5、完整示例",[16,2780,2783],{"className":2781,"code":2782,"language":774,"meta":22},[772],"@app.route(\"/\", methods=[\"GET\", \"POST\"])\ndef process():\n    if request.method == \"GET\":\n        comment = request.args.get(\"content\")\n        # comment = request.values.get(\"content\")\n    if request.method == \"POST\":\n        if request.content_type.startswith('application/json'):            \n            # comment = request.get_json()[\"content\"]\n            comment = request.json.get('content')\n        elif request.content_type.startswith('multipart/form-data'):\n            comment = request.form.get('content')\n        else:\n            comment = request.values.get(\"content\")\n    logger.debug('%s'%comment)\n",[24,2784,2782],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":2786},[2787,2788,2791],{"id":2621,"depth":95,"text":2622},{"id":2643,"depth":95,"text":2644,"children":2789},[2790],{"id":2650,"depth":102,"text":2651},{"id":2663,"depth":95,"text":2664,"children":2792},[2793,2794,2795],{"id":2694,"depth":102,"text":2695},{"id":2768,"depth":102,"text":2768},{"id":2777,"depth":102,"text":2778},{"date":2797,"tags":2798},"2021-09-04 20:33:12",[2799,774],"flask","/flask-1",{"title":2616,"description":22},"flask-1","O5_JU6V4q2sWyVlyQgM7C3kWrr2tZ5mn-zLh4TXbFwg",{"id":2805,"title":2806,"body":2807,"description":2811,"extension":104,"meta":2913,"navigation":110,"path":2917,"seo":2918,"stem":2799,"__hash__":2919},"content/flask.md","CentOS 7下宝塔部署Django项目、Flask项目",{"type":8,"value":2808,"toc":2911},[2809,2812,2815,2818,2824,2827,2830,2833,2836,2839,2842,2845,2848,2851,2854,2857,2883,2887,2890,2893,2896,2899,2902,2905,2908],[27,2810,2811],{},"python是我从很早就接触，但没有怎么学的一门语言，人工智能、机器学习、爬虫等功能是它的专长，最近发现一些很有用的功能用php、node都无法实现，于是想去研究怎么搭建python web框架的项目",[27,2813,2814],{},"首先要写代码那比少不了一个好的IDE，这里不要说肯定要用PC(Pycharm),它支持python所有框架的一键搭建,语法提示非常到位,对新手非常友好，下面我们开始学习怎么在宝塔部署django框架",[27,2816,2817],{},"在上传项目文件的时候，我们需要先配置好项目所有的环境依赖包，有个命令必须要知道，就是",[16,2819,2822],{"className":2820,"code":2821,"language":21,"meta":22},[19],"pip freeze>requirements.txt\n",[24,2823,2821],{"__ignoreMap":22},[27,2825,2826],{},"相当于是node中的package.json或者php中的composer.json吧(根据以往经验)",[27,2828,2829],{},"这样我们创建依赖文件的时候，能减少引入一些不必要的包。",[27,2831,2832],{},"遇到的坑:",[27,2834,2835],{},"1.name 'os' is not defined",[27,2837,2838],{},"解决方法：在settings.py文件里from pathlib import Path 后加 import os",[27,2840,2841],{},"2.django.core.exceptions.ImproperlyConfigured: SQLite 3.8.3 or later is required (found 3.7.17)",[27,2843,2844],{},"方法一：给Django降级",[27,2846,2847],{},"方法二：升级SQLite",[27,2849,2850],{},"方法三：打开site-packages/django/db/backends/sqlite3/base.py\n下翻找到 check_sqlite_version 这个函数，把 if Database.sqlite_version_info \u003C (3, 8, 3): 注掉，改成\nif Database.sqlite_version_info \u003C (3, 6, 3):",[27,2852,2853],{},"1.先要上传django文件夹的全部到/www/wwwroot/ 上的文件夹中，通过WebHook上传git代码，之后打开python项目管理器",[27,2855,2856],{},"2.现在我们开始创建 python 项目。打开首页的 python 项目管理器，点击 添加项目。填充数据：",[456,2858,2859,2862,2865,2868,2871,2874,2877,2880],{},[459,2860,2861],{},"项目名称： 自定义就行",[459,2863,2864],{},"路径： 定位到 manage.py 文件所在的路径",[459,2866,2867],{},"Python 版本： 选择你项目对应的 python 版本",[459,2869,2870],{},"框架： 选择 Django",[459,2872,2873],{},"启动方式： uswgi",[459,2875,2876],{},"启动文件/文件夹： 根目录就行",[459,2878,2879],{},"端口： 自定义就行，django默认8000，flask默认5000",[459,2881,2882],{},"勾选上 是否安装模块依赖 与 开机启动\n点击确定，然后等待创建。",[27,2884,2885],{},[551,2886],{"alt":22,"src":554},[27,2888,2889],{},"3.映射域名",[27,2891,2892],{},"4.放行端口",[27,2894,2895],{},"5.域名解析",[27,2897,2898],{},"部署成功，但是发现uswgi方式下内存占用很大，200MB+,卧槽,node才30MB,python 200MB？",[27,2900,2901],{},"最后我对比了下Django和Flask，发现Flask是个轻量级框架，非常适合搭建API，于是放弃django,开始搭建flask，参数和之前大体一样，不同的是：启动文件为app.py,框架为flask,端口为5000，生成依赖后上传到服务器，开始部署，发现内存占用还是200MB+,",[27,2903,2904],{},"因为用的是uswgi，起初我用gunicorn的启动方式，一直报错:",[27,2906,2907],{},"ModuleNotFoundError: No module named 'typing' 或者 ModuleNotFoundError: No module named 'py'",[27,2909,2910],{},"后来我升级了python的版本(3.8.5),换了一种叫做gunicorn的启动方式，发现占用优化了很多，在70MB左右",{"title":22,"searchDepth":95,"depth":95,"links":2912},[],{"date":2914,"tags":2915},"2021-08-08 19:10:24",[2916,774],"部署","/flask",{"title":2806,"description":2811},"ZjZ5azQHiidysXHyiAH6I8jUz8dr5wX39-73lwmzsjQ",{"id":2921,"title":2922,"body":2923,"description":2927,"extension":104,"meta":3194,"navigation":110,"path":3200,"seo":3201,"stem":3198,"__hash__":3202},"content/buffer.md","聊聊JS的二进制家族：Blob、ArrayBuffer和Buffer",{"type":8,"value":2924,"toc":3192},[2925,2928,2931,2934,2939,2942,2945,2948,2951,2957,2960,2966,2969,2972,2975,2978,2981,2987,2990,2993,2998,3001,3004,3010,3012,3017,3020,3023,3029,3032,3038,3041,3044,3049,3052,3055,3058,3064,3066,3071,3074,3077,3082,3085,3090,3093,3096,3099,3105,3107,3112,3115,3118,3124,3127,3133,3135,3140,3143,3149,3152,3158,3161,3166,3169,3175,3181,3183,3189],[27,2926,2927],{},"前端较少涉及对二进制数据的处理，但即便如此，我们偶尔总能在角落里看见它们的身影，今天我们就来聊一聊JS的二进制家族：Blob、ArrayBuffer和Buffer（Buffer由Node.js提供）",[27,2929,2930],{},"概述\nBlob: 前端的一个专门用于支持文件操作的二进制对象\nArrayBuffer：前端的一个通用的二进制缓冲区，类似数组，但在API和特性上却有诸多不同\nBuffer：Node.js提供的一个二进制缓冲区，常用来处理I/O操作\n这篇文章的内容主要就聊一聊这一家子的人际关系展开。",[27,2932,2933],{},"复杂的人际关系网.jpg",[27,2935,2936],{},[551,2937],{"alt":22,"src":2938},"images/buf1.jpg",[27,2940,2941],{},"下面就来一一介绍",[27,2943,2944],{},"Blob\n我们首先来介绍Blob，Blob是用来支持文件操作的。简单的说：在JS中，有两个构造函数 File 和 Blob, 而File继承了所有Blob的属性。",[27,2946,2947],{},"所以在我们看来，File对象可以看作一种特殊的Blob对象。",[27,2949,2950],{},"在前端工程中，我们在哪些操作中可以获得File对象呢？ 请看：",[27,2952,2953,2956],{},[551,2954],{"alt":22,"src":2955},"images/buf2.jpg","\n（备注：目前 File API规范的状态为Working Draft）",[27,2958,2959],{},"我们上面说了，File对象是一种特殊的Blob对象，那么它自然就可以直接调用Blob对象的方法。让我们看一看Blob具体有哪些方法，以及能够用它们实现哪些功能",[27,2961,2962,2965],{},[551,2963],{"alt":22,"src":2964},"images/buf3.jpg","\nBlob实战\n通过window.URL.createObjectURL方法可以把一个blob转化为一个Blob URL，并且用做文件下载或者图片显示的链接。",[27,2967,2968],{},"Blob URL所实现的下载或者显示等功能，仅仅可以在单个浏览器内部进行。而不能在服务器上进行存储，亦或者说它没有在服务器端存储的意义。",[27,2970,2971],{},"下面是一个Blob的例子，可以看到它很短",[27,2973,2974],{},"blob:d3958f5c-0777-0845-9dcf-2cb28783acaf\n和冗长的Base64格式的Data URL相比，Blob URL的长度显然不能够存储足够的信息，这也就意味着它只是类似于一个浏览器内部的“引用“。从这个角度看，Blob URL是一个浏览器自行制定的一个伪协议",[27,2976,2977],{},"Blob实现下载文件",[27,2979,2980],{},"我们可以通过window.URL.createObjectURL，接收一个Blob（File）对象，将其转化为Blob URL,然后赋给 a.download属性，然后在页面上点击这个链接就可以实现下载了",[16,2982,2985],{"className":2983,"code":2984,"language":256,"meta":22},[254],"\u003C!-- html部分 -->\n\u003Ca id=\"h\">点此进行下载\u003C/a>\n\u003C!-- js部分 -->\n\u003Cscript>\n  var blob = new Blob([\"Hello World\"]);\n  var url = window.URL.createObjectURL(blob);\n  var a = document.getElementById(\"h\");\n  a.download = \"helloworld.txt\";\n  a.href = url;\n\u003C/script> \n",[24,2986,2984],{"__ignoreMap":22},[27,2988,2989],{},"(备注：download属性不兼容IE, 对IE可通过window.navigator.msSaveBlob方法或其他进行优化)",[27,2991,2992],{},"运行结果",[27,2994,2995],{},[551,2996],{"alt":22,"src":2997},"images/buf4.jpg",[27,2999,3000],{},"Blob实现图片本地显示",[27,3002,3003],{},"window.URL.createObjectURL生成的Blob URL还可以赋给img.src，从而实现图片的显示",[16,3005,3008],{"className":3006,"code":3007,"language":256,"meta":22},[254],"\u003C!-- html部分 -->\n\u003Cinput type=\"file\" id='f' />\n\u003Cimg id='img' style=\"width: 200px;height:200px;\" />\n\u003C!-- js部分 -->\n\u003Cscript>\n  document.getElementById('f').addEventListener('change', function (e) {\n    var file = this.files[0];\n    const img = document.getElementById('img');\n    const url = window.URL.createObjectURL(file);\n    img.src = url;\n    img.onload = function () {\n        // 释放一个之前通过调用 URL.createObjectURL创建的 URL 对象\n        window.URL.revokeObjectURL(url);\n    }\n  }, false);\n\u003C/script>\n",[24,3009,3007],{"__ignoreMap":22},[27,3011,2992],{},[27,3013,3014],{},[551,3015],{"alt":22,"src":3016},"images/buf5.jpg",[27,3018,3019],{},"Blob实现文件分片上传",[27,3021,3022],{},"通过Blob.slice(start,end)可以分割大Blob为多个小Blob\nxhr.send是可以直接发送Blob对象的\n前端",[16,3024,3027],{"className":3025,"code":3026,"language":256,"meta":22},[254],"\u003C!-- html部分 -->\n\u003Cinput type=\"file\" id='f' />\n\u003C!-- js部分 -->\n\u003Cscript>\nfunction upload(blob) {\n    var xhr = new XMLHttpRequest();\n    xhr.open('POST', '/ajax', true);\n    xhr.setRequestHeader('Content-Type', 'text/plain')\n    xhr.send(blob);\n}\n\ndocument.getElementById('f').addEventListener('change', function (e) {\n    var blob = this.files[0];\n    const CHUNK_SIZE = 20; .\n    const SIZE = blob.size;\n    var start = 0;\n    var end = CHUNK_SIZE;\n    while (start \u003C SIZE) {\n        upload(blob.slice(start, end));\n        start = end;\n        end = start + CHUNK_SIZE;\n    }\n}, false);\n\u003C/script>\n",[24,3028,3026],{"__ignoreMap":22},[27,3030,3031],{},"Node端(Koa)",[16,3033,3036],{"className":3034,"code":3035,"language":54,"meta":22},[52],"app.use(async (ctx, next) => {\n    await next();\n    if (ctx.path === '/ajax') {\n        const req = ctx.req;\n        const body = await parse(req);\n        ctx.status = 200;\n        console.log(body);\n        console.log('---------------');\n    }\n});\n",[24,3037,3035],{"__ignoreMap":22},[27,3039,3040],{},"文件内容",[27,3042,3043],{},"According to the Zhanjiang commerce bureau, the actual amount of foreign capital\nutilized in Zhanjiang from January to October this year was\n运行结果",[27,3045,3046],{},[551,3047],{"alt":22,"src":3048},"images/buf6.jpg",[27,3050,3051],{},"本地读取文件内容",[27,3053,3054],{},"如果想要读取Blob或者文件对象并转化为其他格式的数据，可以借助FileReader对象的API进行操作",[27,3056,3057],{},"FileReader.readAsText(Blob)：将Blob转化为文本字符串\nFileReader.readAsArrayBuffer(Blob)： 将Blob转为ArrayBuffer格式数据\nFileReader.readAsDataURL(): 将Blob转化为Base64格式的Data URL\n下面我们尝试把一个文件的内容通过字符串的方式读取出来",[16,3059,3062],{"className":3060,"code":3061,"language":256,"meta":22},[254],"\u003Cinput type=\"file\" id='f' />\n\u003Cscript>\n  document.getElementById('f').addEventListener('change', function (e) {\n    var file = this.files[0];\n    const reader = new FileReader();\n    reader.onload = function () {\n        const content = reader.result;\n        console.log(content);\n    }\n    reader.readAsText(file);\n  }, false);\n\u003C/script>\n",[24,3063,3061],{"__ignoreMap":22},[27,3065,2992],{},[27,3067,3068],{},[551,3069],{"alt":22,"src":3070},"images/buf7.jpg",[27,3072,3073],{},"上面介绍了Blob的用法，我们不难发现，Blob是针对文件的，或者可以说它就是一个文件对象，同时呢我们发现Blob欠缺对二进制数据的细节操作能力，比如如果如果要具体修改某一部分的二进制数据，Blob显然就不够用了，而这种细粒度的功能则可以由下面介绍的ArrayBuffer来完成。",[27,3075,3076],{},"ArrayBuffer\n让我们用一张图看下ArrayBuffer的大体的功能",[27,3078,3079],{},[551,3080],{"alt":22,"src":3081},"images/buf8.jpg",[27,3083,3084],{},"同时要说明，ArrayBuffer跟JS的原生数组有很大的区别，如图所示",[27,3086,3087],{},[551,3088],{"alt":22,"src":3089},"images/buf9.jpg",[27,3091,3092],{},"下面一一进行细节的介绍",[27,3094,3095],{},"ArrayBuffer实战",[27,3097,3098],{},"通过ArrayBuffer的格式读取本地数据",[16,3100,3103],{"className":3101,"code":3102,"language":54,"meta":22},[52],"document.getElementById('f').addEventListener('change', function (e) {\n  const file = this.files[0];\n  const fileReader = new FileReader();\n  fileReader.onload = function () {\n    const result = fileReader.result;\n    console.log(result)\n  }\n  fileReader.readAsArrayBuffer(file);\n}, false);\n",[24,3104,3102],{"__ignoreMap":22},[27,3106,2992],{},[27,3108,3109],{},[551,3110],{"alt":22,"src":3111},"images/buf10.jpg",[27,3113,3114],{},"通过ArrayBuffer的格式读取Ajax请求数据",[27,3116,3117],{},"通过xhr.responseType = \"arraybuffer\" 指定响应的数据类型\n在onload回调里打印xhr.response\n前端",[16,3119,3122],{"className":3120,"code":3121,"language":54,"meta":22},[52],"const xhr = new XMLHttpRequest();\nxhr.open(\"GET\", \"ajax\", true);\nxhr.responseType = \"arraybuffer\";\nxhr.onload = function () {\n    console.log(xhr.response)\n}\nxhr.send();\n",[24,3123,3121],{"__ignoreMap":22},[27,3125,3126],{},"Node端",[16,3128,3131],{"className":3129,"code":3130,"language":54,"meta":22},[52],"const app = new Koa();\napp.use(async (ctx) => {\n  if (pathname = '/ajax') {\n    ctx.body = 'hello world';\n    ctx.status = 200;\n  }\n}).listen(3000)\n",[24,3132,3130],{"__ignoreMap":22},[27,3134,2992],{},[27,3136,3137],{},[551,3138],{"alt":22,"src":3139},"images/buf11.jpg",[27,3141,3142],{},"通过TypeArray对ArrayBuffer进行写操作",[16,3144,3147],{"className":3145,"code":3146,"language":54,"meta":22},[52],"const typedArray1 = new Int8Array(8);\ntypedArray1[0] = 32;\n\nconst typedArray2 = new Int8Array(typedArray1);\ntypedArray2[1] = 42;\n\nconsole.log(typedArray1);\n//  output: Int8Array [32, 0, 0, 0, 0, 0, 0, 0]\n\nconsole.log(typedArray2);\n//  output: Int8Array [32, 42, 0, 0, 0, 0, 0, 0]\n",[24,3148,3146],{"__ignoreMap":22},[27,3150,3151],{},"通过DataView对ArrayBuffer进行写操作",[16,3153,3156],{"className":3154,"code":3155,"language":54,"meta":22},[52],"const buffer = new ArrayBuffer(16);\nconst view = new DataView(buffer);\nview.setInt8(2, 42);\nconsole.log(view.getInt8(2));\n// 输出: 42\n",[24,3157,3155],{"__ignoreMap":22},[27,3159,3160],{},"Buffer\nBuffer是Node.js提供的对象，前端没有。 它一般应用于IO操作，例如接收前端请求数据时候，可以通过以下的Buffer的API对接收到的前端数据进行整合",[27,3162,3163],{},[551,3164],{"alt":22,"src":3165},"images/buf12.jpg",[27,3167,3168],{},"Buffer实战\n例子如下",[16,3170,3173],{"className":3171,"code":3172,"language":54,"meta":22},[52],"// Node端（Koa）\nconst app = new Koa();\napp.use(async (ctx, next) => {\n    if (ctx.path === '/ajax') {\n        const chunks = [];\n        const req = ctx.req;\n        req.on('data', buf => {\n            chunks.push(buf);\n        })\n        req.on('end', () => {\n            let buffer = Buffer.concat(chunks);\n            console.log(buffer.toString())\n        })\n    }\n});\napp.listen(3000)\n",[24,3174,3172],{"__ignoreMap":22},[16,3176,3179],{"className":3177,"code":3178,"language":54,"meta":22},[52],"// 前端\nconst xhr = new XMLHttpRequest();\nxhr.open(\"POST\", \"ajax\", true);\nxhr.setRequestHeader('Content-Type', 'text/plain')\nxhr.send(\"asdasdsadfsdfsadasdas\");\n",[24,3180,3178],{"__ignoreMap":22},[27,3182,2992],{},[16,3184,3187],{"className":3185,"code":3186,"language":2197,"meta":22},[2195],"// Node端输出\nasdasdsadfsdfsadasdas\n",[24,3188,3186],{"__ignoreMap":22},[27,3190,3191],{},"本文完",{"title":22,"searchDepth":95,"depth":95,"links":3193},[],{"date":3195,"tags":3196},"2021-08-05 21:39:14",[3197,3198,54,3199],"arraybuffer","buffer","blob","/buffer",{"title":2922,"description":2927},"Oi3r3KtNC7pgAPr4HLeFMDM1h80eq3DLibAPKcb8F6s",{"id":3204,"title":3205,"body":3206,"description":3210,"extension":104,"meta":3242,"navigation":110,"path":3248,"seo":3249,"stem":3250,"__hash__":3251},"content/jqerylist.md","PHP爬虫实战之获取STEAM游戏信息",{"type":8,"value":3207,"toc":3239},[3208,3211,3214,3217,3223,3227],[27,3209,3210],{},"本篇介绍php里最好用的工具——QueryList",[27,3212,3213],{},"QueryList是一套用于内容采集的PHP工具，它使用更加现代化的开发思想，语法简洁、优雅，可扩展性强。相比传统的使用晦涩的正则表达式来做采集，QueryList使用了更加强大而优雅的CSS选择器来做采集，大大降低了PHP做采集的门槛，同时也让采集代码易读易维护，让你从此告别晦涩难懂且不易维护的正则表达式.",[27,3215,3216],{},"之前用的正则的方法，比较复杂，容易出问题，所以我还是推荐这款库，只要懂一点Jquery的选择器规则就可以轻松上手",[16,3218,3221],{"className":3219,"code":3220,"language":390,"meta":22},[388],"    public function getlist(){\n        //以热销为例，获取热销列表\n        $url=\"https://store.steampowered.com/search/?filter=topsellers&l=schinese\";\n        $rules = [\n            //第一个参数为选择器，第二个参数为属性，第三个参数为排除，第四个参数为回调函数\n            'image' => ['div.col.search_capsule > img','src'],\n            'title'=> ['div.col.search_name.ellipsis > span.title','text'],\n            'appid'=>['','data-ds-appid'],\n            'bundleid'=>['','data-ds-bundleid'],\n            'time'=>['div.col.search_released.responsive_secondrow','text'],\n            'ori_price'=>['div.col.search_price.responsive_secondrow > span > strike','text'],\n            'now_price'=>['div.col.search_price.responsive_secondrow','text','-span'],\n            'discount'=>['div.col.search_discount.responsive_secondrow > span','text'],\n        ];\n        $range = '#search_resultsRows a';//切片选择器\n        $rt = QueryList::get($url)->rules($rules)->range($range)->query()->getData();\n        return success('success', $rt);\n    }\n    //搜索游戏\n    public function search(){\n        $keyword=input('keyword');\n        $url=\"https://store.steampowered.com/search/?term=$keyword&l=schinese\";\n        $rules = [\n            //第一个参数为选择器，第二个参数为属性，第三个参数为排除，第四个参数为回调函数\n            'image' => ['div.col.search_capsule > img','src'],\n            'title'=> ['div.col.search_name.ellipsis > span.title','text'],\n            'appid'=>['','data-ds-appid'],\n            'bundleid'=>['','data-ds-bundleid'],\n            'time'=>['div.col.search_released.responsive_secondrow','text'],\n            'ori_price'=>['div.col.search_price.responsive_secondrow > span > strike','text'],\n            'now_price'=>['div.col.search_price.responsive_secondrow','text','-span'],\n            'discount'=>['div.col.search_discount.responsive_secondrow > span','text'],\n        ];\n        $range = '#search_resultsRows a';//切片选择器\n        $rt = QueryList::get($url)->rules($rules)->range($range)->query()->getData();\n        return success('success', $rt);\n    }\n    public function getdetail(){\n        $appid=input('appid');\n        $url=\"https://store.steampowered.com/app/$appid/_/\";\n        $ql=QueryList::get($url,['l'=>'schinese'],['headers' => ['Cookie'=> 'wants_mature_content=1;birthtime=786211201']]);\n        $rules = [\n            //第一个参数为选择器，第二个参数为属性(attrs可获取多个元素属性)，第三个参数为排除，第四个参数为回调函数\n            'name' => ['div#appHubAppName','text'],\n            'description'=> ['div.game_description_snippet','text'],\n            'summary'=>['span.nonresponsive_hidden','text','',function($item){\n                return str_replace([\"\\t\",\"\\n\",\"\\r\"],['','',''],$item);\n            }],\n            'date'=>['div.release_date >div.date','text'],\n            'tag'=>['a.app_tag','text','',function($item){\n                return str_replace([\"\\t\",\"\\n\",\"\\r\"],['','',','],$item);\n            }],\n            'image'=>['img.game_header_image_full','src'],\n            'preview'=>['a.highlight_screenshot_link','attrs(href)','',function($item){\n                return str_replace(['https://steamcommunity.com/linkfilter/?url=','1920x1080'],['','600x338'],$item);\n            }],\n            'videos'=>['div.highlight_player_item.highlight_movie','attrs(data-poster)']\n        ];\n        $rt = $ql->rules($rules)->query()->getData();\n        return success('success',$rt);\n    }\n",[24,3222,3220],{"__ignoreMap":22},[73,3224,3226],{"id":3225},"前端小程序渲染效果展示","前端(小程序)渲染效果展示",[27,3228,3229,3230,3234,3235],{},"游戏列表\n",[551,3231],{"alt":3232,"src":3233},"fe1","images/fe1.png","\n游戏详情\n",[551,3236],{"alt":3237,"src":3238},"fe2","images/fe2.png",{"title":22,"searchDepth":95,"depth":95,"links":3240},[3241],{"id":3225,"depth":102,"text":3226},{"date":3243,"tags":3244},"2021-07-09 10:26:41",[3245,3246,3247],"PHP","QueryList","爬虫","/jqerylist",{"title":3205,"description":3210},"jqerylist","9HPPmdni55h51Pf6rjFrgDHvMEWxM739zG6798MgOqA",{"id":3253,"title":3254,"body":3255,"description":22,"extension":104,"meta":3385,"navigation":110,"path":3392,"seo":3393,"stem":3394,"__hash__":3395},"content/workerman.md","GatewayWorker快速搭建IM框架",{"type":8,"value":3256,"toc":3377},[3257,3261,3264,3267,3270,3273,3276,3282,3285,3291,3294,3300,3303,3309,3312,3320,3323,3326,3329,3335,3338,3344,3347,3350,3356,3360,3363,3366,3371],[73,3258,3260],{"id":3259},"今天来分享下个人搭建im的心得","今天来分享下个人搭建IM的心得",[27,3262,3263],{},"GatewayWorker基于Workerman开发的一个项目框架，用于快速开发TCP长连接应用，例如app推送服务端、即时IM服务端、游戏服务端、物联网、智能家居等等",[27,3265,3266],{},"GatewayWorker使用经典的Gateway和Worker进程模型。Gateway进程负责维持客户端连接，并转发客户端的数据给BusinessWorker进程处理，BusinessWorker进程负责处理实际的业务逻辑（默认调用Events.php处理业务），并将结果推送给对应的客户端。Gateway服务和BusinessWorker服务可以分开部署在不同的服务器上，实现分布式集群。",[27,3268,3269],{},"GatewayWorker提供非常方便的API，可以全局广播数据、可以向某个群体广播数据、也可以向某个特定客户端推送数据。配合Workerman的定时器，也可以定时推送数据。",[73,3271,3272],{"id":3272},"第一步",[27,3274,3275],{},"composer安装:",[16,3277,3280],{"className":3278,"code":3279,"language":21,"meta":22},[19],"composer require workerman/gateway-worker\n",[24,3281,3279],{"__ignoreMap":22},[27,3283,3284],{},"(可选，用于服务端主动推送消息)",[16,3286,3289],{"className":3287,"code":3288,"language":21,"meta":22},[19],"composer require workerman/gatewayclient\n",[24,3290,3288],{"__ignoreMap":22},[27,3292,3293],{},"(可选，用于连接数据库)",[16,3295,3298],{"className":3296,"code":3297,"language":21,"meta":22},[19],"composer require workerman/mysql\n",[24,3299,3297],{"__ignoreMap":22},[27,3301,3302],{},"(可选，用于连接redis)",[16,3304,3307],{"className":3305,"code":3306,"language":21,"meta":22},[19],"composer require workerman/redis\n",[24,3308,3306],{"__ignoreMap":22},[73,3310,3311],{"id":3311},"第二步",[27,3313,3314,3315,3319],{},"删掉start.php中的require_once ",[3316,3317,3318],"strong",{},"DIR"," . '/vendor/autoload.php';",[27,3321,3322],{},"原因是tp有自己的自动加载机制，貌似在集成composer自动加载机制时有BUG",[73,3324,3325],{"id":3325},"第三步",[27,3327,3328],{},"在start_gateway.php中创建wss服务，因为小程序要用websocket必须是wss：\n假设我的证书目录是统一放在tp根目录，start_gateway.php都放在application里的workerman里\n记得把原来的tcp链接要改掉！不然bug",[16,3330,3333],{"className":3331,"code":3332,"language":390,"meta":22},[388],"// 证书最好是申请的证书\n$context = array(\n    // 更多ssl选项请参考手册 http://php.net/manual/zh/context.ssl.php\n    'ssl' => array(\n        // 请使用绝对路径\n        'local_cert'                 => __DIR__.'/../../certificate/websocket/wss.pem', // 也可以是crt文件\n        'local_pk'                   => __DIR__.'/../../certificate/websocket/wss.key',\n        'verify_peer'               => false,\n        // 'allow_self_signed' => true, //如果是自签名证书需要开启此选项\n    )\n);\n// websocket协议(端口任意，只要没有被其它程序占用就行)\n$gateway = new Gateway(\"websocket://0.0.0.0:443\", $context);\n// 开启SSL，websocket+SSL 即wss\n$gateway->transport = 'ssl';\n",[24,3334,3332],{"__ignoreMap":22},[27,3336,3337],{},"基本环境已经配置好了，现在我们可以在前端先测试一下",[16,3339,3342],{"className":3340,"code":3341,"language":54,"meta":22},[52],"// 证书是会检查域名的，请使用域名连接\nws = new WebSocket(\"wss://test.com:443\");\nws.onopen = function() {\n    console.log(\"连接成功\");\n    ws.send('tom');\n    console.log(\"给服务端发送一个字符串：tom\");\n};\nws.onmessage = function(e) {\n    console.log(\"收到服务端的消息：\" + e.data);\n};\nws.onerror =function(e){\n    console.log(e);\n}\n",[24,3343,3341],{"__ignoreMap":22},[73,3345,3346],{"id":3346},"第四步",[27,3348,3349],{},"接下来开始在events.php中写我们的主要通信逻辑，本项目主要涉及离线消息推送、在线用户状态显示、群聊、私聊等功能，根据自身情况去调整",[16,3351,3354],{"className":3352,"code":3353,"language":390,"meta":22},[388],"\u003C?php\n/**\n * This file is part of workerman.\n *\n * Licensed under The MIT License\n * For full copyright and license information, please see the MIT-LICENSE.txt\n * Redistributions of files must retain the above copyright notice.\n *\n * @author walkor\u003Cwalkor@workerman.net>\n * @copyright walkor\u003Cwalkor@workerman.net>\n * @link http://www.workerman.net/\n * @license http://www.opensource.org/licenses/mit-license.php MIT License\n */\n\n/**\n * 用于检测业务代码死循环或者长时间阻塞等问题\n * 如果发现业务卡死，可以将下面declare打开（去掉//注释），并执行php start.php reload\n * 然后观察一段时间workerman.log看是否有process_timeout异常\n */\n//declare(ticks=1);\nuse GatewayWorker\\Lib\\Gateway;\nuse Workerman\\MySQL\\Connection;\nuse Workerman\\Redis\\Client;\n\n/**\n * 主逻辑\n * 主要是处理 onConnect onMessage onClose 三个方法\n * onConnect 和 onClose 如果不需要可以不用实现并删除\n */\nclass Events\n{\n    /**\n     * 新建静态成员，用来保存数据库实例\n     */\n    public static $db = null;\n    public static $redis = null;\n    /**\n     * 进程启动后初始化数据库连接\n     */\n    public static function onWorkerStart()\n    {\n        self::$db = new Connection('IP', '端口号', '账户', '密码', '数据库');\n        self::$redis = new Client('redis://127.0.0.1:6379');\n    }\n    /**\n     * 当客户端连接时触发\n     * 如果业务不需此回调可以删除onConnect\n     * \n     * @param int $client_id 连接id\n     */\n    public static function onConnect($client_id)\n    {\n\n    }\n    \n   /**\n    * 当客户端发来消息时触发\n    * @param int $client_id 连接id\n    * @param mixed $message 具体消息\n    */\n   public static function onMessage($client_id, $message)\n   {\n       //fromUid,toUid,isGroup,type为必须参数\n       $msg=json_decode($message,true);//来源消息\n       Gateway::bindUid($client_id, $msg['fromUid']);//将client_id与uid绑定\n       $guid=self::$db->select('friendguid')->from('oh_ai_groupfriend')->where(\"uid={$msg['fromUid']}\")->query();\n       foreach ($guid as $k=>$v){\n           Gateway::joinGroup($client_id,$v['friendguid']);//将client_id与群uid绑定\n       }\n       //常见事件(只处理，不给客户端发送)\n       switch ($msg['type']){\n           //离线消息(可选)\n           case 'pull':\n               self::$redis->lrange($msg['fromUid'],0,-1,function ($list) use ($msg) {\n               if(!empty($list)){\n                   GateWay::sendToUid($msg['fromUid'], json_encode(['message'=>$list,'type'=>'pull']));\n                   self::$redis->del($msg['fromUid']);//清除redis缓存\n               }\n           });\n               return false;\n           //上线\n           case 'online':\n               Gateway::sendToAll(json_encode(['list'=>array_keys(Gateway::getAllUidList()),'type'=>'online']));\n               return false;\n           //心跳\n           case 'heart':\n               GateWay::sendToUid($msg['fromUid'], json_encode(['type'=>'heart','group'=>Gateway::getClientSessionsByGroup(10000),'client'=>$client_id]));\n               return false;\n           //创建群聊\n           case 'create_group':\n               Gateway::joinGroup($client_id,$msg['toUid']);\n               return false;\n           //加入群聊\n           case 'join_group':\n               $client_id_array=Gateway::getClientIdByUid($msg['fromUid']);\n               Gateway::joinGroup($client_id_array[0],$msg['toUid']);\n               return false;\n           //解散群聊\n           case 'dismiss_group':\n               GateWay::ungroup($msg['toUid']);\n               return false;\n           //退出群聊\n           case 'leave_group':\n               Gateway::leaveGroup($client_id, $msg['toUid']);\n               return false;\n           //踢出群聊\n           case 'kick_group':\n               $client_id_array=Gateway::getClientIdByUid($msg['fromUid']);\n               Gateway::leaveGroup($client_id_array[0], $msg['toUid']);\n               return false;\n       }\n       //自己和自己发消息,不执行任何操作\n       if($msg['fromUid']==$msg['toUid']){\n           return false;\n       }\n       //处理要发送的数据\n       //私聊\n       if($msg['isGroup']==0){\n           self::sendMsg($msg['toUid'],$msg);\n       }\n       //群聊\n       else{\n           self::sendMsgGroup($msg['fromUid'],$msg['toUid'],$msg);\n       }\n       return false;\n   }\n   \n   /**\n    * 当用户断开连接时触发\n    * @param int $client_id 连接id\n    */\n   public static function onClose($client_id)\n   {\n       //向所有客户端通知下线\n       $list=array_keys(Gateway::getAllUidList());\n       GateWay::sendToAll(json_encode(['list'=>$list,'type'=>'online']));\n   }\n   /**\n    * 发送私聊消息\n    * @param int $uid 目标uid\n    * @param array $message 消息内容\n    */\n   public static function sendMsg($uid,$message){\n       //判断是否为好友\n       if($message['type']!=='read' && $message['type']!=='friend_request' && $message['type']!=='friend_response' && $message['type']!=='group_request' && $message['type']!=='group_response'){\n           $db=self::$db->select('frienduid')->from('oh_ai_friend')->where(\"uid={$message['toUid']} and frienduid={$message['fromUid']}\")->single();\n           if(empty($db)){\n               GateWay::sendToUid($message['fromUid'], json_encode(['id'=>$message['id'],'type'=>'stranger']));\n               return false;\n           }\n       }\n       //如果对方不在线\n       if(!Gateway::isUidOnline($uid)){\n           if($message['type']!=='heart' && $message['type']!=='online'){\n               self::$redis->set('ai_msg_time',date('Y-m-d',time()));\n               self::$redis->rpush($uid,json_encode($message));\n               self::$redis->get('ai_msg_time', function ($date) use ($uid) {\n                   if($date!==date('Y-m-d',time())){\n                       self::$redis->expire($uid,43200);\n                   }\n               });\n           }\n       }\n       //如果对方在线\n       else{\n           GateWay::sendToUid($uid, json_encode($message));\n       }\n       return false;\n   }\n    /**\n     * 发送群聊消息\n     * @param int $uid 发送者uid\n     * @param int $guid 目标群uid\n     * @param array $message 消息内容\n     */\n    public static function sendMsgGroup($uid, $guid, $message)\n    {\n        //判断用户是否在此群\n        $db=self::$db->select('friendguid')->from('oh_ai_groupfriend')->where(\"uid=$uid and friendguid=$guid\")->single();\n        if(empty($db)){\n            GateWay::sendToUid($message['fromUid'], json_encode(['id'=>$message['id'],'type'=>'stranger']));\n            return false;\n        }\n        $group_info=self::$db->select('is_at,ai_id')->from('oh_ai_group')->where(\"guid=$guid\")->row();\n        $client_id_array=Gateway::getClientIdByUid($uid);\n        Gateway::sendToGroup($guid,json_encode($message),$client_id_array[0]);//一定要排除自己\n        return false;\n    }\n\n",[24,3355,3353],{"__ignoreMap":22},[73,3357,3359],{"id":3358},"最后是tp框架主动推送消息的一种方法","最后是TP框架主动推送消息的一种方法：",[27,3361,3362],{},"需要安装GatewayClient",[27,3364,3365],{},"总体思路图如下",[27,3367,3368],{},[551,3369],{"alt":551,"src":3370},"images/work-with-other-mvc-framework.png",[16,3372,3375],{"className":3373,"code":3374,"language":390,"meta":22},[388],"\u003C?php\n\n\nnamespace app\\workerman\\controller;\n\nuse app\\BaseController;\nuse GatewayClient\\Gateway;\nuse think\\facade\\Db;\n\nclass RestfulApi extends BaseController\n{\n    public function __construct()\n    {\n        Gateway::$registerAddress = '127.0.0.1:1238';\n    }\n    public function sendMsg(){\n        $message=input('post.message');\n        // 向任意uid发送数据\n        Gateway::sendToUid($message['toUid'], json_encode($message));\n        return success('发送成功',$message);\n    }\n    public function sendGroupMsg(){\n        $message=input('post.message');\n        $client_id_array=Gateway::getClientIdByUid($message['fromUid']);\n        Gateway::sendToGroup($message['toUid'],json_encode($message),$client_id_array[0]);//一定要排除自己\n        return success('发送成功',$message);\n    }\n}\n",[24,3376,3374],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":3378},[3379,3380,3381,3382,3383,3384],{"id":3259,"depth":102,"text":3260},{"id":3272,"depth":102,"text":3272},{"id":3311,"depth":102,"text":3311},{"id":3325,"depth":102,"text":3325},{"id":3346,"depth":102,"text":3346},{"id":3358,"depth":102,"text":3359},{"date":3386,"tags":3387},"2021-05-09 20:36:42",[3388,3389,390,3390,3391],"IM","websocket","thinkphp","workman","/workerman",{"title":3254,"description":22},"workerman","WD5l7iGLRenzviuKgDTe0jeOi408NSZ_PiKVPOkvGk4",{"id":3397,"title":3398,"body":3399,"description":3403,"extension":104,"meta":3466,"navigation":110,"path":3470,"seo":3471,"stem":3469,"__hash__":3472},"content/watch.md","Vue中watch的用法",{"type":8,"value":3400,"toc":3464},[3401,3404,3410,3413,3419,3422,3425,3428,3431,3434,3440,3443,3449,3452,3455,3461],[27,3402,3403],{},"在vue中，使用watch来响应数据的变化。watch的用法大致有三种。下面代码是watch的一种简单的用法：",[16,3405,3408],{"className":3406,"code":3407,"language":54,"meta":22},[52],"\u003Cinput type=\"text\" v-model=\"cityName\"/>\n\nnew Vue({\n  el: '#root',\n  data: {\n    cityName: 'shanghai'\n  },\n  watch: {\n    cityName(newName, oldName) {\n      // ...\n    }\n  } \n})\n",[24,3409,3407],{"__ignoreMap":22},[27,3411,3412],{},"直接写一个监听处理函数，当每次监听到 cityName 值发生改变时，执行函数。也可以在所监听的数据后面直接加字符串形式的方法名：（这个真没用过，感觉很好用）",[16,3414,3417],{"className":3415,"code":3416,"language":54,"meta":22},[52],"watch: {\n    cityName: 'nameChange'\n    }\n } \n",[24,3418,3416],{"__ignoreMap":22},[27,3420,3421],{},"immediate和handler\n这样使用watch时有一个特点，就是当值第一次绑定的时候，不会执行监听函数，只有值发生改变才会执行。如果我们需要在最初绑定值的时候也执行函数，则就需要用到immediate属性。",[27,3423,3424],{},"比如当父组件向子组件动态传值时，子组件props首次获取到父组件传来的默认值时，也需要执行函数，此时就需要将immediate设为true。",[27,3426,3427],{},"监听的数据后面写成对象形式，包含handler方法和immediate，之前我们写的函数其实就是在写这个handler方法；",[27,3429,3430],{},"immediate表示在watch中首次绑定的时候，是否执行handler，值为true则表示在watch中声明的时候，就立即执行handler方法，值为false，则和一般使用watch一样，在数据发生变化的时候才执行handler。",[27,3432,3433],{},"复制代码",[16,3435,3438],{"className":3436,"code":3437,"language":54,"meta":22},[52],"new Vue({\n  el: '#root',\n  data: {\n    cityName: ''\n  },\n  watch: {\n    cityName: {\n    　　handler(newName, oldName) {\n      　　// ...\n    　　},\n    　　immediate: true\n    }\n  } \n})\n",[24,3439,3437],{"__ignoreMap":22},[27,3441,3442],{},"deep\n当需要监听一个对象的改变时，普通的watch方法无法监听到对象内部属性的改变，只有data中的数据才能够监听到变化，此时就需要deep属性对对象进行深度监听。",[16,3444,3447],{"className":3445,"code":3446,"language":54,"meta":22},[52],"\u003Cinput type=\"text\" v-model=\"cityName.name\"/>\n\nnew Vue({\n  el: '#root',\n  data: {\n    cityName: {id: 1, name: 'shanghai'}\n  },\n  watch: {\n    cityName: {\n      handler(newName, oldName) {\n      // ...\n    },\n    deep: true,\n    immediate: true\n    }\n  } \n})\n",[24,3448,3446],{"__ignoreMap":22},[27,3450,3451],{},"设置deep: true 则可以监听到cityName.name的变化，此时会给cityName的所有属性都加上这个监听器，当对象属性较多时，每个属性值的变化都会执行handler。如果只需要监听对象中的一个属性值，则可以做以下优化：使用字符串的形式监听对象属性：",[27,3453,3454],{},"这样只会给对象的某个特定的属性加监听器。",[16,3456,3459],{"className":3457,"code":3458,"language":54,"meta":22},[52],"watch: {\n    'cityName.name': {\n      handler(newName, oldName) {\n      // ...\n      },\n      deep: true,\n      immediate: true\n    }\n  }\n",[24,3460,3458],{"__ignoreMap":22},[27,3462,3463],{},"数组（一维、多维）的变化不需要通过深度监听，对象数组中对象的属性变化则需要deep深度监听。",{"title":22,"searchDepth":95,"depth":95,"links":3465},[],{"date":3467,"tags":3468},"2021-01-05 21:24:09",[271,3469],"watch","/watch",{"title":3398,"description":3403},"ZWCrHuGhcs9PgD1FXnhvKWiVE4Srx5FEMbMQWxFjU8U",{"id":3474,"title":3475,"body":3476,"description":3499,"extension":104,"meta":3500,"navigation":110,"path":3502,"seo":3503,"stem":3504,"__hash__":3505},"content/phpexcel.md","php导出excel表",{"type":8,"value":3477,"toc":3497},[3478,3485,3488,3491],[27,3479,3480,3481],{},"安装类库\n从GitHub上下载PHPExcel类库\n地址：",[497,3482,3483],{"href":3483,"rel":3484},"https://github.com/PHPOffice/PHPExcel",[590],[27,3486,3487],{},"解压后将Classes文件夹移动到ThinkPHP的extend目录，并将其重命名为phpexcel",[27,3489,3490],{},"在项目中需要的地方添加引用",[16,3492,3495],{"className":3493,"code":3494,"language":390,"meta":22},[388],"import('phpexcel.PHPExcel', EXTEND_PATH);\n代码实现\n\u003C?php\nnamespace app\\index\\controller;\n\nuse think\\Controller;\n\nclass Excel extends Controller\n{\n    // 将数据导出至Excel\n    public function exportExcel($data,$table_name='sheet1',$index=0)\n    {\n        // 引入类库\n        import('phpexcel.PHPExcel', EXTEND_PATH);\n\n        // 文件名和文件类型\n        $fileName = \"student\";\n        $fileType = \"xlsx\";\n\n        $obj = new \\PHPExcel();\n\n        // 以下内容是excel文件的信息描述信息\n        $obj->getProperties()->setCreator(''); //设置创建者\n        $obj->getProperties()->setLastModifiedBy(''); //设置修改者\n        $obj->getProperties()->setTitle(''); //设置标题\n        $obj->getProperties()->setSubject(''); //设置主题\n        $obj->getProperties()->setDescription(''); //设置描述\n        $obj->getProperties()->setKeywords('');//设置关键词\n        $obj->getProperties()->setCategory('');//设置类型\n        //创建表\n        if($index!==0){\n        $obj->createSheet();\n        }\n        // 设置当前sheet\n        $obj->setActiveSheetIndex($index);\n\n        // 设置当前sheet的名称\n        $obj->getActiveSheet()->setTitle($table_name);\n\n        // 列标\n        $list = ['A', 'B', 'C'];\n\n        // 填充第一行数据\n        $obj->getActiveSheet()\n            ->setCellValue($list[0] . '1', '学号')\n            ->setCellValue($list[1] . '1', '姓名')\n            ->setCellValue($list[2] . '1', '班级');\n\n        // 填充第n(n>=2, n∈N*)行数据\n        $length = count($data);\n        for ($i = 0; $i \u003C $length; $i++) {\n            $obj->getActiveSheet()->setCellValue($list[0] . ($i + 2), $data[$i]['stuNo'], \\PHPExcel_Cell_DataType::TYPE_STRING);//将其设置为文本格式\n            $obj->getActiveSheet()->setCellValue($list[1] . ($i + 2), $data[$i]['name']);\n            $obj->getActiveSheet()->setCellValue($list[2] . ($i + 2), $data[$i]['class']);\n        }\n\n        // 设置加粗和左对齐\n        foreach ($list as $col) {\n            // 设置第一行加粗\n            $obj->getActiveSheet()->getStyle($col . '1')->getFont()->setBold(true);\n            // 设置第1-n行，居中\n            for ($i = 1; $i \u003C= $length + 1; $i++) {\n                $obj->getActiveSheet()->getStyle($col . $i)->getAlignment()->setHorizontal(\\PHPExcel_Style_Alignment::HORIZONTAL_CENTER);\n            }\n        }\n\n        // 设置列宽\n        $obj->getActiveSheet()->getColumnDimension('A')->setWidth(20);\n        $obj->getActiveSheet()->getColumnDimension('B')->setWidth(20);\n        $obj->getActiveSheet()->getColumnDimension('C')->setWidth(15);\n        //设置单元格边框\n        $style_array = array(\n        'borders' => array(\n            'allborders' => array(\n                'style' => \\PHPExcel_Style_Border::BORDER_THIN\n            )\n        ));\n        $obj->getActiveSheet()->getStyle('A1:G'.(string)($length+1))->applyFromArray($style_array);\n        // 导出\n        ob_clean();\n        if ($fileType == 'xls') {\n            header('Content-Type: application/vnd.ms-excel');\n            header('Content-Disposition: attachment;filename=\"' . $fileName . '.xls');\n            header('Cache-Control: max-age=1');\n            $objWriter = new \\PHPExcel_Writer_Excel5($obj);\n            $objWriter->save('php://output');\n            exit;\n        } elseif ($fileType == 'xlsx') {\n            header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');\n            header('Content-Disposition: attachment;filename=\"' . $fileName . '.xlsx');\n            header('Cache-Control: max-age=1');\n            $objWriter = \\PHPExcel_IOFactory::createWriter($obj, 'Excel2007');\n            $objWriter->save('php://output');\n            exit;\n        }\n    }\n\n\n    // 准备数据\n        $studentList = [\n            [\n                'stuNo' => '20190101',\n                'name' => 'student01',\n                'class' => '1班'\n            ], [\n                'stuNo' => '20190102',\n                'name' => 'student02',\n                'class' => '1班'\n            ], [\n                'stuNo' => '20190103',\n                'name' => 'student03',\n                'class' => '1班'\n            ]\n        ];\n    // 导出表格\n        exportExcel($data);\n\n}\n",[24,3496,3494],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":3498},[],"安装类库\n从GitHub上下载PHPExcel类库\n地址：https://github.com/PHPOffice/PHPExcel",{"date":3501,"tags":390},"2020-10-20 19:46:20","/phpexcel",{"title":3475,"description":3499},"phpexcel","l-j7RuUabfm7aCSKnKiO-AiQnZKQYN1LpNNCQnKCu9k",{"id":3507,"title":3508,"body":3509,"description":3513,"extension":104,"meta":3717,"navigation":110,"path":3722,"seo":3723,"stem":3724,"__hash__":3725},"content/login.md","做好用户获取第一步，微信小程序OpenID和UnionID详解",{"type":8,"value":3510,"toc":3706},[3511,3514,3517,3520,3523,3526,3529,3532,3535,3538,3541,3547,3550,3553,3556,3559,3562,3565,3572,3578,3584,3587,3590,3596,3599,3605,3609,3612,3615,3620,3623,3628,3631,3634,3637,3642,3645,3649,3653,3656,3659,3663,3666,3669,3672,3675,3678,3683,3686,3689,3692,3695,3698,3701],[27,3512,3513],{},"OpenID 和 UnionID 是什么\nOpenID 是微信提供给开发者的用户唯一标识。然而当开发者拥有多个移动应用、网站应用、和公众账号（包括小程序）时，同一用户、不同应用下的 OpenID 是不一样的。",[27,3515,3516],{},"而同一个微信开放平台账号下的不同应用，用户的 UnionID 是唯一的。",[27,3518,3519],{},"因此，对有多个应用的开发者来讲，只有通过 UnionID 来区分用户的唯一性，才能实现多个应用间的账号打通。",[27,3521,3522],{},"应该使用哪个 ID 登录\n理论上讲，当产品有App、小程序、公众号等多种形式时，用 UnionID 是最好的选择，否则会因为同一用户在不同应用下的 OpenID 不一样导致产生多个账号，那处理起来就很麻烦了。",[27,3524,3525],{},"然而 UnionID 并不是那么容易获取的。微信小程序最新的限制是：",[27,3527,3528],{},"必须使用一个专用按钮控件让用户主动点击，否则无法弹出授权弹窗\n用户必须点击「允许」同意小程序获取公开信息",[27,3530,3531],{},"以上2步，每一步都会造成一定的用户流失。所以有的开发者会使用 OpenID ，以最大程度的降低用户获取的成本，从而推动注册用户数的快速增长。",[27,3533,3534],{},"拿拼多多来说，用户打开小程序之后会静默获取 OpenID 并生成账号，可以正常使用购物车、历史记录等服务。用户在「个人中心」页面点击「更新资料」时就会触发授权弹窗获取公开信息和 UnionID。",[27,3536,3537],{},"对于不同的业务场景，会有各自最适合的选择。深入研究下微信的机制，或许能有一些启发，在某些场景下可以尝试优化用户获取的路径。",[27,3539,3540],{},"小程序 OpenID 和 UnionID 获取机制\n在小程序的官方文档里有一张图解释了小程序调用wx.login接口的登录流程。不过那张图除了开发能看懂，一般人应该都看不懂什么意思。翻译成一般人都能看懂的图，小程序登录流程是这样的：",[27,3542,3543],{},[551,3544],{"alt":3545,"src":3546},"avatar","images/wxlogin.png",[27,3548,3549],{},"首先，开发者可以在小程序中静默调用登录接口，拿到一个凭证\n小程序把凭证发送到开发者的服务器上\n服务器拿着这个凭证以及小程序密钥向微信接口请求换取 OpenID\n微信接口返回 OpenID 给开发者服务器，满足特殊条件时会一并返回 UnionID\n开发者服务器创建登录态并返回给小程序，从而完成登录",[27,3551,3552],{},"总结一下：",[27,3554,3555],{},"OpenID可以无感获取。",[27,3557,3558],{},"而无感获取 UnionID 必须满足以下任一条件：",[27,3560,3561],{},"用户已经关注了同主体的公众号\n用户已经授权过同主体的其他应用获取 UnionID\n用户刚刚通过小程序完成了支付\n否则就必须让用户主动点击按钮并允许获取公开信息后，才可以获得 UnionID。\n公开信息有哪些\n最后说说微信的用户公开信息（UserInfo）究竟包含哪些信息：",[27,3563,3564],{},"微信昵称\n微信头像图片的URL，如果用户没有头像，URL会是空的。如果用户更换了头像，原有头像的URL会失效\n用户性别：未知、男性、女性\n所在国家\n所在省份\n所在城市\n国家、省份、城市所用的语言：英文、简体中文、繁体中文\n与用户信息一并返回的还有一串加密信息，转交给开发者的服务器解密之后，就可以得到用户的 OpenID 和 UnionID 了。",[27,3566,3567,3568],{},"一件比较tricky的事情是，如果只是需要在小程序中展示用户头像和昵称，可以使用 ",[3569,3570,3571],"open-data",{},"，微信在渲染小程序的时候会显示用户的头像和昵称。但是此时只是显示出来能被用户看到，开发者并不能拿到用户头像昵称的数据，所以这个时候就不要想什么分享到聊天的时候小程序卡片标题能带上用户昵称了。",[16,3573,3576],{"className":3574,"code":3575,"language":256,"meta":22},[254],"\u003Copen-data type=\"userNickName\">\u003C/open-data>\n\u003Copen-data type=\"userAvatarUrl\">\u003C/open-data>\n\u003Copen-data type=\"userGender\">\u003C/open-data>\n",[24,3577,3575],{"__ignoreMap":22},[27,3579,3580,3581],{},"通过",[3569,3582,3583],{},"除了可以不经过授权直接展示头像、昵称之外，还可以直接展示：",[27,3585,3586],{},"用户性别\n用户所在国家\n用户所在城市\n用户所在省份\n用户的语言\n群名称（必须是用户曾经分享过小程序的群）",[27,3588,3589],{},"获取用户信息。页面产生点击事件（例如 button 上 bindtap 的回调中）后才可调用，每次请求都会弹出授权窗口，用户同意后返回 userInfo。该接口用于替换 wx.getUserInfo，小程序代码如下：",[16,3591,3594],{"className":3592,"code":3593,"language":256,"meta":22},[254],"\u003Ctemplate>\n    \u003Cview>\n        \u003C!-- #ifdef MP-WEIXIN -->\n        \u003Cview>\n            \u003Cview>\n                \u003Cview class='header'>\n                    \u003C!-- 自己的小程序logo -->\n                    \u003Cimage src='/static/image/logo.png'>\u003C/image>\n                \u003C/view>\n                \u003Cview class='content'>\n                    \u003Cview>申请获取以下权限\u003C/view>\n                    \u003Ctext>获得你的公开信息(昵称，头像、地区等)\u003C/text>\n                \u003C/view>\n                \u003Cbutton class='bottom' type='primary' @click=\"GetUserInfo\">\n                    授权登录\n                \u003C/button>\n            \u003C/view>\n        \u003C/view>\n        \u003C!-- #endif -->\n    \u003C/view>\n\u003C/template>\n\n\u003Cscript>\n    import {code2session} from 'api/login' //接口js,自己写\n    export default {\n        data() {\n            return {\n                \n            }\n        },\n        methods: {\n            GetUserInfo() {\n                uni.getUserProfile({\n                    lang:'zh_CN',\n                    desc: '用户授权登陆', // 声明获取用户个人信息后的用途，后续会展示在弹窗中，请谨慎填写\n                    success: (res) => {\n                        uni.setStorageSync('userInfo', res.userInfo)    \n                        this.Login()//微信登陆\n                    }\n                })\n            },\n            Login(){\n                uni.login({\n                    provider: 'weixin',\n                    success: (loginRes) => {\n                        let code = loginRes.code;\n                        code2session({\n                            userinfo:uni.getStorageSync('userInfo'),//向服务端提交用户信息\n                            code: code\n                        }).then(res=>{\n                            uni.setStorageSync('openid',res.data.openid)\n                            uni.setStorageSync('unionid',res.data.unionid)\n                            uni.setStorageSync('uid',res.data.uid)//uid为可选\n                            uni.showModal({\n                                content:res.msg,\n                                showCancel: false,\n                                success: (res) => {\n                                    if (res.confirm) {\n                                        //回到主页\n                                        this.backHome()\n                                    }\n                                }\n                            })\n                        })\n                    }\n                });\n            },\n            backHome(){\n                uni.reLaunch({\n                    url:'/pages/index/index'//自己小程序的主页\n                })\n            },\n        }\n    }\n\u003C/script>\n\n\u003Cstyle>\n    .header {\n        margin: 90rpx 0 90rpx 50rpx;\n        border-bottom: 1px solid #ccc;\n        text-align: center;\n        width: 650rpx;\n        height: 300rpx;\n        line-height: 450rpx;\n    }\n\n    .header image {\n        width: 200rpx;\n        height: 200rpx;\n    }\n\n    .content {\n        margin-left: 50rpx;\n        margin-bottom: 90rpx;\n    }\n\n    .content text {\n        display: block;\n        color: #9d9d9d;\n        margin-top: 40rpx;\n    }\n\n    .bottom {\n        border-radius: 80rpx;\n        margin: 70rpx 50rpx;\n        font-size: 35rpx;\n    }\n\u003C/style>\n",[24,3595,3593],{"__ignoreMap":22},[27,3597,3598],{},"服务端代码参考",[16,3600,3603],{"className":3601,"code":3602,"language":390,"meta":22},[388],"\u003C?php\n\nnamespace app\\wechat\\controller;\n\nuse app\\BaseController;\nuse EasyWeChat\\Factory;\nuse think\\facade\\Config;\nuse think\\facade\\Db;\n\nclass MiniProgram extends BaseController\n{\n    protected $app;\n    protected $config=[];\n    protected $table='wx_user';\n    public function __construct()\n    {\n        $this->config= Config::get('apikey');\n        $config = [\n            // 必要配置\n            'app_id'             => $this->config['mp_appid'],\n            'secret'             => $this->config['mp_appsecret'],\n            'response_type'      => 'array'\n        ];\n        $this->app = Factory::miniProgram($config);\n    }\n    public function code2session(){\n        $info=input('userinfo');//用户信息\n        $code=input('code');\n        $res=$this->app->auth->session($code);\n        //业务逻辑\n        $query=Db::name($this->table)->where('unionid',$res['unionid'])->find();\n        //如果unionid已存在\n        if($query){\n            Db::name($this->table)->where('unionid',$res['unionid'])->update([\n                'username'=>$info['nickName'],\n                'avatar'=>$info['avatarUrl'],\n                'gender'=>$info['gender'],\n                'mp_openid'=>$res['openid'],\n                'city'=>$info['city'],\n                'session_key'=>$res['session_key'],//会话密钥，以后用\n                'province'=>$info['province'],\n                'update_time'=>date('Y-m-d H:i:s',time())\n            ]);\n        }\n        else{\n            Db::name($this->table)->insert([\n                'username'=>$info['nickName'],\n                'avatar'=>$info['avatarUrl'],\n                'gender'=>$info['gender'],\n                'mp_openid'=>$res['openid'],\n                'city'=>$info['city'],\n                'province'=>$info['province'],\n                'session_key'=>$res['session_key'],//会话密钥，以后用\n                'unionid'=> $res['unionid'],\n                'status'=>1,\n                'create_time'=>date('Y-m-d H:i:s',time())\n            ]);\n        }\n        if(isset($res['errcode'])){\n            return success('登陆失败',$res['errmsg']);\n        }\n        return success('登陆成功',$res);\n    }\n}\n",[24,3604,3602],{"__ignoreMap":22},[11,3606,3608],{"id":3607},"session_key-的作用","session_key 的作用",[27,3610,3611],{},"那么，session_key在登录的过程中或者登录完成后起什么作用呢？一起来看一下。",[27,3613,3614],{},"首先来看一下wx.getUserInfo 这个api：",[27,3616,3617],{},[551,3618],{"alt":22,"src":3619},"images/wxlogin1.png",[27,3621,3622],{},"在设置withCredentials 属性为true 的情况下，这个api 可以拿到encryptedData，iv 等敏感信息，encryptedData 需要使用session_key 进行解密，解密后可以拿到的数据如下：",[27,3624,3625],{},[551,3626],{"alt":22,"src":3627},"images/wxlogin2.png",[27,3629,3630],{},"也就是说，session_key的作用之一是将小程序前端从微信服务器获取到的encryptedData 解密出来，获取到openId 和unionId等信息。",[27,3632,3633],{},"但是在1.2登录过程中可以看到开发者服务器是能够直接拿到用户的openId信息，而且unionId 也是有其他获取途径，所以session_key 在这里的作用看起来有点鸡肋。",[27,3635,3636],{},"session_key 更重要的作用大概体现在获取用户手机方面（可能还包含其他敏感信息获取api）。",[27,3638,3639],{},[551,3640],{"alt":22,"src":3641},"images/wxlogin3.png",[27,3643,3644],{},"从文档中可以看到getPhoneNumber 返回的用户数据是加密过的，只有使用session_key才能解密，而小程序前端没有session_key，所以无法获取到用户的手机，只能传到开发者服务器进行处理。",[11,3646,3648],{"id":3647},"unionid-的作用有哪些获取途径","unionId 的作用，有哪些获取途径？",[73,3650,3652],{"id":3651},"unionid机制说明","UnionID机制说明",[27,3654,3655],{},"如果公司拥有多个移动应用、网站应用、和公众帐号（包括小程序），可通过unionid来区分用户的唯一性，因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号（包括小程序），用户的unionid是唯一的。换句话说，同一用户，对同一个微信开放平台下的不同应用，unionid是相同的。",[27,3657,3658],{},"Tip：unionid 用于识别同一主体下不同账号之间的用户。举例说明：就是公司有A订阅号，B服务号，同一个人关注A和B，会得到不同的OPENID，但是会得到相同的unionid。这样就可以识别到相同的用户，用于不同账号之间打通用户关系。",[73,3660,3662],{"id":3661},"unionid获取途径","UnionID获取途径",[27,3664,3665],{},"必须有一个微信开放平台账号绑定了至少一个微信公众账号或者网站应用或者小程序，否则UnionID返回null。绑定了开发者帐号的小程序，可以通过下面3种途径获取UnionID。",[27,3667,3668],{},"方法一：调用接口wx.getUserInfo，从解密数据中获取UnionID。注意本接口需要用户授权，请开发者妥善处理用户拒绝授权后的情况。",[27,3670,3671],{},"方法二：如果开发者帐号下存在同主体的公众号，并且该用户已经关注了该公众号。开发者可以直接通过wx.login获取到该用户UnionID，无须用户再次授权。",[27,3673,3674],{},"方法三：如果开发者帐号下存在同主体的公众号或移动应用，并且该用户已经授权登录过该公众号或移动应用。开发者也可以直接通过wx.login获取到该用户UnionID，无须用户再次授权。",[27,3676,3677],{},"code2session接口返回参数如下：",[27,3679,3680],{},[551,3681],{"alt":22,"src":3682},"images/wxlogin4.png",[11,3684,3685],{"id":3685},"在应用中如何保存用户登录态",[27,3687,3688],{},"保存用户登录态，一直以来都有两种解决方案：前端保存和后端保存。",[73,3690,3691],{"id":3691},"后端保存",[27,3693,3694],{},"写session的时候可以直接设定过期时间，定期通知小程序前端重新进行登录（wx.login）。",[73,3696,3697],{"id":3697},"前端保存",[27,3699,3700],{},"因为session_key 存在时效性问题（毕竟是用来查看敏感信息），而小程序前端可以通过wx.checkSession() 来检查session_key 是否过期。所以可以通过这个来作为保存用户登录态的机制，这也是小程序文档中推荐的方法：",[27,3702,3703],{},[551,3704],{"alt":22,"src":3705},"images/wxlogin5.png",{"title":22,"searchDepth":95,"depth":95,"links":3707},[3708,3709,3713],{"id":3607,"depth":95,"text":3608},{"id":3647,"depth":95,"text":3648,"children":3710},[3711,3712],{"id":3651,"depth":102,"text":3652},{"id":3661,"depth":102,"text":3662},{"id":3685,"depth":95,"text":3685,"children":3714},[3715,3716],{"id":3691,"depth":102,"text":3691},{"id":3697,"depth":102,"text":3697},{"date":3718,"tags":3719},"2020-10-17 19:05:50",[3720,3721],"微信小程序","unionid","/login",{"title":3508,"description":3513},"login","FWZSlNf3CxD08glTB8vU7GxrwGi7ZKqsGDs-6Ux4730",{"id":3727,"title":3728,"body":3729,"description":4465,"extension":104,"meta":4466,"navigation":110,"path":4470,"seo":4471,"stem":4472,"__hash__":4473},"content/uniapp.md","uni-app学习笔记",{"type":8,"value":3730,"toc":4463},[3731,3738,3742,3745,3753,3761,3764,3767,3770,3773,3776,3782,3785,3791,3794,3802,3805,3811,3814,3817,3820,3894,3897,3941,3944,3947,3950,3953,3956,3962,3965,3973,3976,3979,3985,3988,3991,3993,3999,4002,4005,4008,4011,4014,4080,4084,4087,4090,4093,4096,4099,4102,4105,4108,4111,4117,4123,4126,4129,4135,4138,4144,4147,4150,4156,4159,4162,4168,4171,4174,4177,4180,4183,4190,4195,4198,4204,4207,4214,4220,4223,4226,4229,4232,4235,4238,4241,4247,4250,4253,4255,4258,4264,4267,4273,4276,4279,4282,4285,4291,4294,4300,4303,4306,4312,4315,4321,4336,4343,4350,4357,4364,4371,4378,4385,4392,4399,4406,4413,4420,4423,4426,4432,4435,4441,4444,4450,4457],[27,3732,3733,3734],{},"uni-app 跨平台框架官方教程\n链接：",[497,3735,3736],{"href":3736,"rel":3737},"https://ke.qq.com/course/343370",[590],[123,3739,3741],{"id":3740},"一框架简介基础知识点","一、框架简介基础知识点",[27,3743,3744],{},"uniapp 生命周期\nonLaunch 当 uniapp 初始化完成时进行触发，全局只触发一次\nonShow 当 uniapp 启动或者从后台进入前台显示\nonHide 当 uniapp 从前台进入后台\nonUniNViewMessage 对 nvue 页面发送数据进行监听",[27,3746,3747,3748,3752],{},"uniapp 页面生命周期\n",[497,3749,3750],{"href":3750,"rel":3751},"https://uniapp.dcloud.io/frame",[590],"\n参看文档",[27,3754,3755,3756],{},"路由\nuni-app 有两种路由跳转方式：使用 navigator 组件跳转、调用 API 跳转。 1.打开新页面，页面重定向===》 调用 API uni.navigateTo 、使用组件 ",[3757,3758,3760],"navigator",{"open-type":3759},"navigateTo"," 2.页面返回 调用 API uni.navigateBack 、使用组件 \u003Cnavigator open-type=\"navigateBack\"/> 、用户按左上角返回按钮、安卓用户点击物理 back 按键",[27,3762,3763],{},"3.Tab 切换 调用 API uni.switchTab 、使用组件 \u003Cnavigator open-type=\"switchTab\"/> 、用户切换 Tab",[27,3765,3766],{},"4.重加载 调用 API uni.reLaunch 、使用组件 \u003Cnavigator open-type=\"reLaunch\"/>",[27,3768,3769],{},"Tips：",[27,3771,3772],{},"navigateTo, redirectTo 只能打开非 tabBar 页面。\nswitchTab 只能打开 tabBar 页面。\nreLaunch 可以打开任意页面。\n页面底部的 tabBar 由页面决定，即只要是定义为 tabBar 的页面，底部都有 tabBar。\n不能在 App.vue 里面进行页面跳转。\n页面样式与布局\nuni-app 支持的通用 css 单位包括 px、upx、vh。\nupx 是相对于基准宽度的单位，可以根据屏幕宽度进行自适应。uni-app 规定屏幕基准宽度 750upx。",[27,3774,3775],{},"upx2px\n因为 upx 是编译器处理的，动态绑定 upx 不生效\n可使用 uni.upx2px(Number) 转换为 px 后再赋值。\n例如",[16,3777,3780],{"className":3778,"code":3779,"language":54,"meta":22},[52],"return uni.upx2px(750 / 2) + 'px';\n",[24,3781,3779],{"__ignoreMap":22},[27,3783,3784],{},"样式导入\n使用@import 语句可以导入外联样式表，@import 后跟需要导入的外联样式表的相对路径，用;表示语句结束。",[16,3786,3789],{"className":3787,"code":3788,"language":54,"meta":22},[52],"@import \"../../common/uni.css\";\n",[24,3790,3788],{"__ignoreMap":22},[27,3792,3793],{},"内联样式\n1.style：静态的样式统一写到 class 中。",[16,3795,3800],{"className":3796,"code":3798,"language":3799,"meta":22},[3797],"language-scss","\u003Cview :style=\"{color:color}\" />\n","scss",[24,3801,3798],{"__ignoreMap":22},[27,3803,3804],{},"class：用于指定样式规则，其属性值是样式规则中类选择器名(样式类名)的集合，样式类名不需要带上.，样式类名之间用空格分隔。",[16,3806,3809],{"className":3807,"code":3808,"language":256,"meta":22},[254],"\u003Cview class=\"normal_view\" />\n",[24,3810,3808],{"__ignoreMap":22},[27,3812,3813],{},"选择器\n1 .class\n2 #id\n3 element 选择所有 view 组件",[27,3815,3816],{},"全局样式与局部样式\n定义在 App.vue 中的样式为全局样式，作用于每一个页面。在 pages 目录下 的 vue 文件中定义的样式为局部样式，只作用在对应的页面，并会覆盖 App.vue 中相同的选择器。\n注意： App.vue 中通过 @import 语句可以导入外联样式，一样作用于每一个页面。",[27,3818,3819],{},"CSS 变量",[3821,3822,3823,3844],"table",{},[3824,3825,3826],"thead",{},[3827,3828,3829,3833,3836,3839,3841],"tr",{},[3830,3831,3832],"th",{},"CSS变量",[3830,3834,3835],{},"描述",[3830,3837,3838],{},"5+App",[3830,3840,2608],{},[3830,3842,3843],{},"H5",[3845,3846,3847,3864,3879],"tbody",{},[3827,3848,3849,3853,3856,3858,3861],{},[3850,3851,3852],"td",{},"--status-bar-height",[3850,3854,3855],{},"系统状态栏高度",[3850,3857,3855],{},[3850,3859,3860],{},"25px",[3850,3862,3863],{},"0",[3827,3865,3866,3869,3872,3874,3876],{},[3850,3867,3868],{},"--window-top",[3850,3870,3871],{},"内容区域距离顶部的距离",[3850,3873,3863],{},[3850,3875,3863],{},[3850,3877,3878],{},"NavigationBar的高度",[3827,3880,3881,3884,3887,3889,3891],{},[3850,3882,3883],{},"--window-bottom",[3850,3885,3886],{},"内容区域距离底部的距离",[3850,3888,3863],{},[3850,3890,3863],{},[3850,3892,3893],{},"TabBar的高度",[27,3895,3896],{},"固定值\nuni-app 中以下组件的高度是固定的，不可修改：",[3821,3898,3899,3912],{},[3824,3900,3901],{},[3827,3902,3903,3906,3908,3910],{},[3830,3904,3905],{},"组件",[3830,3907,3835],{},[3830,3909,3838],{},[3830,3911,3843],{},[3845,3913,3914,3927],{},[3827,3915,3916,3919,3922,3925],{},[3850,3917,3918],{},"NavigationBar",[3850,3920,3921],{},"导航栏",[3850,3923,3924],{},"44px",[3850,3926,3924],{},[3827,3928,3929,3932,3935,3938],{},[3850,3930,3931],{},"TabBar",[3850,3933,3934],{},"底部选项卡",[3850,3936,3937],{},"56px",[3850,3939,3940],{},"50px",[27,3942,3943],{},"Flex 布局\n为支持跨平台，框架建议使用 Flex 布局",[27,3945,3946],{},"背景图片\nuni-app 支持使用在 css 里设置背景图片，使用方式与普通 web 项目相同，需要注意以下几点：",[27,3948,3949],{},"支持 base64 格式图片。\n支持网络路径图片。\n使用本地路径背景图片需注意：\n图片小于 40kb，uni-app 会自动将其转化为 base64 格式；",[27,3951,3952],{},"图片大于等于 40kb， 需开发者自己将其转换为 base64 格式使用，或将其挪到服务器上，从网络地址引用。",[27,3954,3955],{},"本地背景图片的引用路径仅支持以 ~@ 开头的绝对路径（不支持相对路径）。",[16,3957,3960],{"className":3958,"code":3959,"language":3799,"meta":22},[3797],".test2 {\nbackground-image: url('~@/static/logo.png');\n}\n",[24,3961,3959],{"__ignoreMap":22},[27,3963,3964],{},"字体图标\nuni-app 支持使用字体图标，使用方式与普通 web 项目相同，需要注意以下几点：",[27,3966,3967,3968,3972],{},"支持 base64 格式字体图标。\n支持网络路径字体图标。\n网络路径必须加协议头 https。\n从 ",[497,3969,3970],{"href":3970,"rel":3971},"http://www.iconfont.cn",[590]," 上拷贝的代码，默认是没加协议头的。\nuni-app 本地路径图标字体支持情况：\n字体文件小于 40kb，uni-app 会自动将其转化为 base64 格式；",[27,3974,3975],{},"字体文件大于等于 40kb， 需开发者自己转换，否则使用将不生效；",[27,3977,3978],{},"字体文件的引用路径仅支持以 ~@ 开头的绝对路径（不支持相对路径）。",[16,3980,3983],{"className":3981,"code":3982,"language":3799,"meta":22},[3797],"@font-face {\nfont-family: test1-icon;\nsrc: url('~@/static/iconfont.ttf');\n}\n",[24,3984,3982],{"__ignoreMap":22},[27,3986,3987],{},"\u003Ctemplate>和 \u003Cblock>\nuni-app 支持在 template 模板中嵌套 \u003Ctemplate> 和 \u003Cblock>，用来进行 列表渲染 和 条件渲染。",[27,3989,3990],{},"\u003Ctemplate> 和 \u003Cblock> 并不是一个组件，它们仅仅是一个包装元素，不会在页面中做任何渲染，只接受控制属性。",[27,3992,2768],{},[16,3994,3997],{"className":3995,"code":3996,"language":256,"meta":22},[254],"\u003Ctemplate>\n    \u003Cview>\n        \u003Ctemplate v-if=\"test\">\n            \u003Cview>test 为 true 时显示\u003C/view>\n        \u003C/template>\n        \u003Ctemplate v-else>\n            \u003Cview>test 为 false 时显示\u003C/view>\n        \u003C/template>\n    \u003C/view>\n\u003C/template>\n\u003Ctemplate>\n    \u003Cview>\n        \u003Cblock v-for=\"(item,index) in list\" :key=\"index\">\n            \u003Cview>{{item}} - {{index}}\u003C/view>\n        \u003C/block>\n    \u003C/view>\n\u003C/template>\n",[24,3998,3996],{"__ignoreMap":22},[27,4000,4001],{},"ES6 支持\nuni-app 在支持绝大部分 ES6 API 的同时，也支持了 ES7 的 await/async。",[27,4003,4004],{},"NPM 支持\nuni-app 支持使用 npm 安装第三方包。",[27,4006,4007],{},"TypeScript 支持\n在 uni-app 中使用 ts 开发",[27,4009,4010],{},"小程序组件支持\nuni-app 支持在 5+App 和小程序中使用小程序组件。",[27,4012,4013],{},"平台差异说明",[3821,4015,4016,4029],{},[3824,4017,4018],{},[3827,4019,4020,4023,4026],{},[3830,4021,4022],{},"平台",[3830,4024,4025],{},"支持情况",[3830,4027,4028],{},"小程序组件存放目录",[3845,4030,4031,4040,4050,4058,4069],{},[3827,4032,4033,4035,4038],{},[3850,4034,3843],{},[3850,4036,4037],{},"不支持",[3850,4039],{},[3827,4041,4042,4044,4047],{},[3850,4043,3838],{},[3850,4045,4046],{},"支持微信小程序组件",[3850,4048,4049],{},"wxcomponents",[3827,4051,4052,4054,4056],{},[3850,4053,3720],{},[3850,4055,4046],{},[3850,4057,4049],{},[3827,4059,4060,4063,4066],{},[3850,4061,4062],{},"支付宝小程序",[3850,4064,4065],{},"支持支付宝小程序组件",[3850,4067,4068],{},"mycomponents",[3827,4070,4071,4074,4077],{},[3850,4072,4073],{},"百度小程序",[3850,4075,4076],{},"支持百度小程序组件",[3850,4078,4079],{},"swancomponents",[123,4081,4083],{"id":4082},"二vue-使用注意事项","二、vue 使用注意事项",[27,4085,4086],{},"uni-app 在发布到 H5 时支持所有 vue 的语法；发布到 App 和小程序时，由于平台限制，无法实现全部 vue 语法，但 uni-app 仍是是对 vue 语法支持度最高的跨端框架。本文将详细讲解差异。",[27,4088,4089],{},"生命周期\nuni-app 完整支持 Vue 实例的生命周期，同时还新增 应用生命周期 及 页面生命周期。",[27,4091,4092],{},"详见 Vue 官方文档：生命周期钩子。",[27,4094,4095],{},"注意",[27,4097,4098],{},"不要在选项属性或回调上使用箭头函数，比如 created: () => console.log(this.a) 或 vm.$watch('a', newValue => this.myMethod())。因为箭头函数是和父级上下文绑定在一起的，this 不会是如你做预期的 Vue 实例，且 this.a 或 this.myMethod 也会是未定义的。\n建议使用 uni-app 的 onReady 代替 vue 的 mounted。\n建议使用 uni-app 的 onLoad 代替 vue 的 created。\n模板语法\nuni-app 完整支持 Vue 模板语法。",[27,4100,4101],{},"详见 Vue 官方文档：模板语法。",[27,4103,4104],{},"注意 如果使用老版的非自定义组件模式，即 manifest 中\"usingComponents\":false，部分模版语法不支持，但此模式已不再推荐使用，详见。",[27,4106,4107],{},"老版非自定义组件模式不支持情况：",[27,4109,4110],{},"不支持纯 HTML\n不支持部分复杂的 JavaScript 渲染表达式\n不支持过滤器\ndata 属性\ndata 必须声明为返回一个初始数据对象的函数；否则页面关闭时，数据不会自动销毁，再次打开该页面时，会显示上次数据。",[16,4112,4115],{"className":4113,"code":4114,"language":54,"meta":22},[52],"//正确用法，使用函数返回对象\ndata() {\nreturn {\ntitle: 'Hello'\n}\n}\n",[24,4116,4114],{"__ignoreMap":22},[16,4118,4121],{"className":4119,"code":4120,"language":54,"meta":22},[52],"//错误写法，会导致再次打开页面时，显示上次数据\ndata: {\ntitle: 'Hello'\n}\n",[24,4122,4120],{"__ignoreMap":22},[27,4124,4125],{},"全局变量\n实现全局变量的方式需要遵循 Vue 单文件模式的开发规范。详细参考：uni-app 全局变量的几种实现方式",[27,4127,4128],{},"lass 支持的语法:",[16,4130,4133],{"className":4131,"code":4132,"language":256,"meta":22},[254],"\u003Cview :class=\"{ active: isActive }\">111\u003C/view>\n\u003Cview class=\"static\" v-bind:class=\"{ active: isActive, 'text-danger': hasError }\">222\u003C/view>\n\u003Cview class=\"static\" :class=\"[activeClass, errorClass]\">333\u003C/view>\n\u003Cview class=\"static\" v-bind:class=\"[isActive ? activeClass : '', errorClass]\">444\u003C/view>\n\u003Cview class=\"static\" v-bind:class=\"[{ active: isActive }, errorClass]\">555\u003C/view>\n",[24,4134,4132],{"__ignoreMap":22},[27,4136,4137],{},"style 支持的语法:",[16,4139,4142],{"className":4140,"code":4141,"language":256,"meta":22},[254],"\u003Cview v-bind:style=\"{ color: activeColor, fontSize: fontSize + 'px' }\">666\u003C/view>\n\u003Cview v-bind:style=\"[{ color: activeColor, fontSize: fontSize + 'px' }]\">777\u003C/view>\n",[24,4143,4141],{"__ignoreMap":22},[27,4145,4146],{},"非 H5 端不支持 Vue 官方文档：Class 与 Style 绑定 中的 classObject 和 styleObject 语法。",[27,4148,4149],{},"不支持示例：",[16,4151,4154],{"className":4152,"code":4153,"language":256,"meta":22},[254],"\u003Ctemplate>\n    \u003Cview :class=\"[activeClass]\" :style=\"[baseStyles,overridingStyles]\">\u003C/view>\n\u003C/template>\n\n\u003Cscript>\n    export default {\n        data() {\n            return {\n                activeClass: {\n                    'active': true,\n                    'text-danger': false\n                },\n                baseStyles: {\n                    color: 'green',\n                    fontSize: '30px'\n                },\n                overridingStyles: {\n                    'font-weight': 'bold'\n                }\n            }\n        }\n    }\n\u003C/script>\n",[24,4155,4153],{"__ignoreMap":22},[27,4157,4158],{},"注意：以:style=\"\"这样的方式设置 px 像素值，其值为实际像素，不会被编译器转换。",[27,4160,4161],{},"此外还可以用 computed 方法生成 class 或者 style 字符串，插入到页面中，举例说明：",[16,4163,4166],{"className":4164,"code":4165,"language":256,"meta":22},[254],"\u003Ctemplate>\n    \u003C!-- 支持 -->\n    \u003Cview class=\"container\" :class=\"computedClassStr\">\u003C/view>\n    \u003Cview class=\"container\" :class=\"{active: isActive}\">\u003C/view>\n    \u003C!-- 不支持 -->\n    \u003Cview class=\"container\" :class=\"computedClassObject\">\u003C/view>\n\u003C/template>\n\u003Cscript>\n    export default {\n        data () {\n            return {\n                isActive: true\n            }\n        },\n        computed: {\n            computedClassStr () {\n                return this.isActive ? 'active' : ''\n            },\n            computedClassObject () {\n                return { active: this.isActive }\n            }\n        }\n    }\n\n\u003C/script>\n",[24,4167,4165],{"__ignoreMap":22},[27,4169,4170],{},"用在组件上",[27,4172,4173],{},"非 H5 端暂不支持在自定义组件上使用 Class 与 Style 绑定",[27,4175,4176],{},"计算属性\n完整支持 Vue 官方文档：计算属性。",[27,4178,4179],{},"条件渲染\n完整支持 Vue 官方文档：条件渲染",[27,4181,4182],{},"列表渲染\n完整 vue 列表渲染 Vue 官方文档：列表渲染",[27,4184,4185,4186,4189],{},"key\n如果列表中项目的位置会动态改变或者有新的项目添加到列表中，并且希望列表中的项目保持自己的特征和状态（如 \u003Cinput> 中的输入内容，\u003Cswitch> 的选中状态），需要使用 ",[4187,4188],"key",{}," 来指定列表中项目的唯一的标识符。",[27,4191,4192,4194],{},[4187,4193],{}," 的值以两种形式提供",[27,4196,4197],{},"使用 v-for 循环 array 中 item 的某个 property，该 property 的值需要是列表中唯一的字符串或数字，且不能动态改变。\n使用 v-for 循环中 item 本身，这时需要 item 本身是一个唯一的字符串或者数字\n当数据改变触发渲染层重新渲染的时候，会校正带有 key 的组件，框架会确保他们被重新排序，而不是重新创建，以确保使组件保持自身的状态，并且提高列表渲染时的效率。",[27,4199,4200,4201,4203],{},"如不提供 ",[4187,4202],{},"，会报一个 warning， 如果明确知道该列表是静态，或者不必关注其顺序，可以选择忽略。",[27,4205,4206],{},"注意：",[27,4208,4209,4210],{},"事件映射表中没有的原生事件也可以使用，例如 map 组件的 regionchange 事件直接在组件上写成 @regionchange,同时这个事件也非常特殊，它的 event type 有 begin 和 end 两个，导致我们无法在 handleProxy 中区分到底是什么事件，所以你在监听此类事件的时候同时监听事件名和事件类型既 \u003Cmap @regionchange=\"functionName\" @end=\"functionName\" @begin=\"functionName\">",[4211,4212,4213],"map",{},"。\n为兼容各端，事件需使用 v-on 或 @ 的方式绑定，请勿使用小程序端的 bind 和 catch 进行事件绑定。\n事件修饰符\n.stop：各平台均支持， 使用时会阻止事件冒泡，在非 H5 端同时也会阻止事件的默认行为\n.prevent 仅在 H5 平台支持\n.self：仅在 H5 平台支持\n.once：仅在 H5 平台支持\n.capture：仅在 H5 平台支持\n.passive：仅在 H5 平台支持\n若需要禁止蒙版下的页面滚动，可使用 @touchmove.stop.prevent=\"moveHandle\"，moveHandle 可以用来处理 touchmove 的事件，也可以是一个空函数。",[16,4215,4218],{"className":4216,"code":4217,"language":256,"meta":22},[254],"\u003Cview class=\"mask\" @touchmove.stop.prevent=\"moveHandle\">\u003C/view>\n",[24,4219,4217],{"__ignoreMap":22},[27,4221,4222],{},"按键修饰符：uni-app 运行在手机端，没有键盘事件，所以不支持按键修饰符。",[27,4224,4225],{},"表单控件绑定\n支持 Vue 官方文档：表单控件绑定。",[27,4227,4228],{},"建议开发过程中直接使用 uni-app：表单组件。",[27,4230,4231],{},"组件\nVue 组件\n组件是整个 Vue.js 中最复杂的部分，支持 Vue 官方文档：组件 。",[27,4233,4234],{},"有且只能使用单文件组件（.vue 组件）的形式进行支持。其他的诸如：动态组件，自定义 render，和\u003Cscript type=\"text/x-template\"> 字符串模版等非 H5 端都不支持。",[27,4236,4237],{},"uni-app 组件\nuni-app 提供了丰富的 UI 组件，比如： picker,map 等，需要注意的是原生组件上的事件绑定，需要以 vue 的事件绑定语法来绑定，如 bindchange=\"eventName\" 事件，需要写成 @change=\"eventName\"",[27,4239,4240],{},"示例",[16,4242,4245],{"className":4243,"code":4244,"language":256,"meta":22},[254],"\u003Cpicker mode=\"date\" :value=\"date\" start=\"2015-09-01\" end=\"2017-09-01\" @change=\"bindDateChange\">\n\u003Cview class=\"picker\">\n",[24,4246,4244],{"__ignoreMap":22},[27,4248,4249],{},"当前选择: date\n\n\n全局组件\nuni-app 支持配置全局组件，需在 main.js 里进行全局注册，注册后就可在所有页面里使用该组件。",[27,4251,4252],{},"注意：Vue.component 的第一个参数必须是静态的字符串。",[27,4254,4240],{},[27,4256,4257],{},"main.js 里进行全局注册",[16,4259,4262],{"className":4260,"code":4261,"language":54,"meta":22},[52],"import Vue from 'vue'\nimport pageHead from './components/page-head.vue'\nVue.component('page-head',pageHead)\n",[24,4263,4261],{"__ignoreMap":22},[27,4265,4266],{},"index.vue 里可直接使用组件",[16,4268,4271],{"className":4269,"code":4270,"language":256,"meta":22},[254],"\u003Ctemplate>\n  \u003Cview>\n    \u003Cpage-head>\u003C/page-head>\n    \u003C/view>\n\u003C/template>\n",[24,4272,4270],{"__ignoreMap":22},[27,4274,4275],{},"常见问题\n如何获取上个页面传递的数据\n如何设置全局的数据和全局的方法\n如何捕获 app 的 onError\n组件属性设置不生效解决办法\n使用nvue Weex注意事项\n使用HTML5+注意事项\n条件编译\n条件编译是里用特殊的注释作为标记，在编译时根据这些特殊的注释，将注释里面的代码编译到不同平台。",[27,4277,4278],{},"写法：以 #ifdef 或 #ifndef 加** %PLATFORM%** 开头，以 #endif 结尾。",[27,4280,4281],{},"API 的条件编译\n// #ifdef %PLATFORM%\n平台特有的 API 实现\n// #endif\n组件的条件编译\npages.json 的条件编译\nstatic 目录的条件编译 +性能优化建议\n+uni-app 跨端开发注意事项 +高效开发技巧",[27,4283,4284],{},"使用代码块直接创建组件模板\n为提升开发效率，HBuilderX 将 uni-app 常用代码封装成了以 u 开头的代码块，如在 template 标签内输入 ulist 回车，会自动生成如下代码：",[16,4286,4289],{"className":4287,"code":4288,"language":256,"meta":22},[254],"\u003Cview class=\"uni-list\">\n    \u003Cview class=\"uni-list-cell\">\n        \u003Cview class=\"uni-list-cell-navigate uni-navigate-right\" v-for=\"(item,index) in list\" :key=\"index\">\n            {{item.value}}\n        \u003C/view>\n    \u003C/view>\n\u003C/view>\n",[24,4290,4288],{"__ignoreMap":22},[27,4292,4293],{},"代码块分为Tag代码块、JS代码块，如在 script 标签内输入 uShowToast 回车，会自动生成如下代码：",[16,4295,4298],{"className":4296,"code":4297,"language":54,"meta":22},[52],"uni.showToast({\ntitle: '',\nmask: false,\nduration: 1500\n});\n",[24,4299,4297],{"__ignoreMap":22},[27,4301,4302],{},"uni-app 已支持代码块见下方列表。",[27,4304,4305],{},"Tag 代码块",[16,4307,4310],{"className":4308,"code":4309,"language":2197,"meta":22},[2195],"uButton\nuCheckbox\nuGrid\nuList\nuListMedia\nuRadio\nuSwiper\n",[24,4311,4309],{"__ignoreMap":22},[27,4313,4314],{},"JS 代码块",[16,4316,4319],{"className":4317,"code":4318,"language":2197,"meta":22},[2195],"uRequest\nuGetLocation\nuShowToast\nuShowLoading\nuHideLoading\nuShowModal\nuShowActionSheet\nuNavigateTo\nuNavigateBack\nuRedirectTo\nuStartPullDownRefresh\nuStopPullDownRefresh\nuLogin\nuShare\nuPay\n",[24,4320,4318],{"__ignoreMap":22},[456,4322,4323,4326,4329],{},[459,4324,4325],{},"使用 Chrome 调试",[459,4327,4328],{},"使用各家小程序开发工具调试",[459,4330,4331,4332],{},"关于 App 的调试 +常见问题\nuni-app 中可使用的 UI 框架：",[497,4333,4334],{"href":4334,"rel":4335},"http://ask.dcloud.net.cn/article/35489",[590],[27,4337,4338,4339],{},"uni-app App 整包升级检测： ",[497,4340,4341],{"href":4341,"rel":4342},"https://ask.dcloud.net.cn/article/34972",[590],[27,4344,4345,4346],{},"uni-app 资源热更新： ",[497,4347,4348],{"href":4348,"rel":4349},"https://ask.dcloud.net.cn/article/35667",[590],[27,4351,4352,4353],{},"uni-app 导航栏开发指南： ",[497,4354,4355],{"href":4355,"rel":4356},"https://ask.dcloud.net.cn/article/34921",[590],[27,4358,4359,4360],{},"uni-app 实现全局变量： ",[497,4361,4362],{"href":4362,"rel":4363},"https://ask.dcloud.net.cn/article/35021",[590],[27,4365,4366,4367],{},"微信小程序转换 uni-app 指南：",[497,4368,4369],{"href":4369,"rel":4370},"https://ask.dcloud.net.cn/article/35786",[590],[27,4372,4373,4374],{},"mpvue 项目（组件）迁移指南、示例及资源汇总： ",[497,4375,4376],{"href":4376,"rel":4377},"https://ask.dcloud.net.cn/article/34945",[590],[27,4379,4380,4381],{},"uni-app 引用 npm 第三方库： ",[497,4382,4383],{"href":4383,"rel":4384},"https://ask.dcloud.net.cn/article/19727",[590],[27,4386,4387,4388],{},"uni-app 中使用微信小程序第三方 SDK 及资源汇总：",[497,4389,4390],{"href":4390,"rel":4391},"https://ask.dcloud.net.cn/article/35070",[590],[27,4393,4394,4395],{},"uni-app 中使用 5+ 的原生界面控件（包括 map、video、livepusher、barcode、nview）：",[497,4396,4397],{"href":4397,"rel":4398},"https://ask.dcloud.net.cn/article/35036",[590],[27,4400,4401,4402],{},"uni-app 的 H5 版使用注意事项：",[497,4403,4404],{"href":4404,"rel":4405},"https://ask.dcloud.net.cn/article/35232",[590],[27,4407,4408,4409],{},"uni-app 各环节（HBuilderX、cli、自定义基座、本地 sdk、云打包引擎）版本兼容性说明：",[497,4410,4411],{"href":4411,"rel":4412},"https://ask.dcloud.net.cn/article/35845",[590],[27,4414,4415,4416],{},"uni-app 中选择和上传非图像、视频文件：",[497,4417,4418],{"href":4418,"rel":4419},"https://ask.dcloud.net.cn/article/35547",[590],[27,4421,4422],{},"=============================================",[27,4424,4425],{},"模版语法和数据绑定\n实例 1.请求数据，把数据 data 存储入 news 数组，页面模版自动刷新列表",[16,4427,4430],{"className":4428,"code":4429,"language":54,"meta":22},[52],"uni.request({\nurl: 'https://unidemo.dcloud.net.cn/api/news',\nmethod: 'GET',\ndata: {},\nsuccess: res => {\nconsole.log(res);\nthis.news = res.data;\n},\nfail: () => {},\ncomplete: () => {}\n}); \n",[24,4431,4429],{"__ignoreMap":22},[27,4433,4434],{},"2.点击传入文章 id，通过 uni.navigateTo url+id 打开文章页",[16,4436,4439],{"className":4437,"code":4438,"language":54,"meta":22},[52],"@tap=\"tapnews\" :data-newsid=\"item.post_id\"\n\n            tapnews(e){\n                console.log(e);\n                var newsids= e.currentTarget.dataset.newsid;\n                console.log(newsids);\n                uni.navigateTo({\n                    url: '../info/info?newsid='+ newsids\n                });\n                }\n",[24,4440,4438],{"__ignoreMap":22},[27,4442,4443],{},"3.显示文章页面",[16,4445,4448],{"className":4446,"code":4447,"language":54,"meta":22},[52],"uni.request({\nurl: 'https://unidemo.dcloud.net.cn/api/news/36kr/'+ e.newsid,\nmethod: 'GET',\ndata: {},\nsuccess: res => {\nthis.title = res.data.title;\nthis.strings = res.data.content;\nconsole.log(strings);\nuni.hideLoading();\n},\nfail: () => {},\ncomplete: () => {}\n});\n",[24,4449,4447],{"__ignoreMap":22},[4451,4452,4454],"ol",{"start":4453},4,[459,4455,4456],{},"根目录的 pages.json 严格模式可以跳转自定义页面测试",[16,4458,4461],{"className":4459,"code":4460,"language":69,"meta":22},[67],"   \"condition\": { //模式配置，仅开发期间生效\n   \"current\": 0, //当前激活的模式（list 的索引项）\n   \"list\": [{\n   \"name\": \"news\", //模式名称\n   \"path\": \"pages/info/info\", //启动页面，必选\n   \"query\": \"newsid=5196737\" //启动参数，在页面的 onLoad 函数里面得到。\n   }\n   ]\n   }\n",[24,4462,4460],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":4464},[],"uni-app 跨平台框架官方教程\n链接：https://ke.qq.com/course/343370",{"date":4467,"tags":4468},"2020-06-07 23:50:33",[4469,271],"uni-app","/uniapp",{"title":3728,"description":4465},"uniapp","hDzJLNoMECR9uJuhiWCq_nQazBj74yr2GMTm3rg-4XU",{"id":4475,"title":4476,"body":4477,"description":22,"extension":104,"meta":5207,"navigation":110,"path":5213,"seo":5214,"stem":4552,"__hash__":5215},"content/javascript.md","web大前端面试——JavaScript",{"type":8,"value":4478,"toc":5180},[4479,4483,4487,4490,4493,4497,4500,4503,4506,4509,4512,4516,4522,4525,4528,4531,4534,4537,4541,4544,4547,4555,4558,4560,4566,4570,4573,4577,4583,4587,4593,4597,4600,4603,4606,4609,4612,4615,4621,4624,4627,4633,4636,4640,4643,4647,4651,4654,4657,4663,4667,4670,4676,4679,4685,4688,4694,4697,4703,4706,4712,4715,4721,4724,4728,4731,4735,4738,4741,4744,4747,4750,4753,4757,4760,4763,4767,4771,4774,4780,4783,4787,4790,4796,4799,4803,4806,4810,4814,4817,4821,4824,4828,4831,4834,4841,4849,4852,4856,4859,4865,4869,4872,4875,4878,4881,4884,4888,4892,4895,4898,4902,4905,4911,4915,4918,4924,4927,4930,4936,4939,4945,4948,4954,4957,4963,4966,4969,4975,4978,4984,4987,4990,4996,4999,5005,5008,5011,5014,5017,5020,5024,5030,5033,5039,5043,5046,5049,5055,5058,5064,5067,5073,5076,5082,5085,5091,5094,5100,5103,5109,5112,5116,5119,5122,5125,5128,5131,5135,5138,5140,5146,5149,5155,5158,5164,5167,5171,5174],[123,4480,4482],{"id":4481},"_1关于闭包","1.关于闭包",[11,4484,4486],{"id":4485},"什么是闭包","什么是闭包？",[27,4488,4489],{},"闭包是有权限访问其它函数作用域内的变量的一个函数。",[27,4491,4492],{},"在js中，变量分为全局变量和局部变量，局部变量的作用域属于函数作用域，在函数执行完以后作用域就会被销毁，内存也会被回收，但是由于闭包是建立在函数内部的子函数，由于其可访问上级作用域的原因，即使上级函数执行完，作用域也不会被销毁，此时的子函数——也就是闭包，便拥有了访问上级作用域中变量的权限，即使上级函数执行完以后作用域内的值也不会被销毁。",[11,4494,4496],{"id":4495},"闭包解决了什么","闭包解决了什么？",[27,4498,4499],{},"本质上，闭包就是将函数内部和函数外部连接起来的一座桥梁。由于闭包可以缓存上级作用域，这样函数外部就可以访问到函数内部的变量。",[11,4501,4502],{"id":4502},"闭包的应用场景",[27,4504,4505],{},"ajax请求成功的回调\n一个事件绑定的回调方法\nsetTimeout的延时回调\n一个函数内部返回另一个匿名函数",[11,4507,4508],{"id":4508},"闭包的优缺点",[27,4510,4511],{},"优点：让代码更加规范、简介\n缺点：使用闭包过多，内存消耗大，造成内存的泄露",[123,4513,4515],{"id":4514},"_2原型和原型链","2.原型和原型链",[27,4517,4518],{},[551,4519],{"alt":3545,"src":4520,"title":4521},"images/proto.png","原型图",[27,4523,4524],{},"这张图看不懂就算了\n(1).所有的引用类型都有一个_proto_（隐式原型）属性，属性值是一个普通的对象\n(2).所有的函数除了有_proto_属性，还都有一个prototype（显式原型）属性，属性值是一个普通的对象\n(3).所有引用类型都有一个constructor（构造函数）属性，该属性（是一个指针）指向它的构造函数\n(4).所有引用类型的_proto_属性指向它构造函数的prototype",[27,4526,4527],{},"当一个对象调用自身不存在的属性/方法时，会先去它的_proto_上查找，也就是它的构造函数的prototype；如果没有找到，就会去该构造函数的prototype的proto指向的上一级函数的prototype中查找，最后指向null。这样一层一层向上查找的关系会形成一个链式结构，称为原型链。",[27,4529,4530],{},"深入理解JS的原型和原型链",[27,4532,4533],{},"用自己的方式（图）理解constructor、prototype、proto和原型链",[27,4535,4536],{},"举例理解JS的原型和原型链",[123,4538,4540],{"id":4539},"_3es5继承和es6继承","3.ES5继承和ES6继承",[27,4542,4543],{},"ES5：组合式继承，先创建子类的实例对象，然后再将父类的方法通过call方法添加到this上，通过原型和构造函数的机制来实现",[27,4545,4546],{},"示例：",[16,4548,4553],{"className":4549,"code":4551,"language":4552,"meta":22},[4550],"language-javascript","// 定义一个父类\nfunction Parent() {\n    this.name = '爸爸'\n    this.age = 50\n    this.sex = '男'\n}\n// 在父类的原型上添加一个方法\nParent.prototype.play = function () {\n    console.log('去打麻将')\n}\n\n// 定义一个子类\nfunction Child(name) {\n    this.name = name\n    // 第一步：继承父类的属性\n    Parent.call(this)\n}\n// 第二步：实现子类继承父类的方法（儿子从爸爸那里学会了打麻将）\nChild.prototype = new Parent()\n// 第三步：找回丢失的构造函数\nChild.prototype.constructor = Child\n","javascript",[24,4554,4551],{"__ignoreMap":22},[27,4556,4557],{},"ES6：先创建父类的实例对象this（所以必须先调用父类的super()方法，然后在用子类的构造函数修改this），通过class关键字定义类，类之间通过extends关键字实现继承，子类必须在constructor方法中调用super方法。因为子类没有自己的this对象，而是继承了父类的this对象，然后对其加工，如果不调用super方法，子类得不到this对象。",[27,4559,4546],{},[16,4561,4564],{"className":4562,"code":4563,"language":4552,"meta":22},[4550],"// 定义一个父类\nclass Parent {\n    constructor(name, sex) {\n        this.name = name\n        this.sex = sex\n    }\n    play() {\n        console.log(this.name + '打麻将超级垃圾')\n    }\n    speak() {\n        console.log('性别是：' + this.sex)\n    }\n}\n// 父类的实例化\nlet father = new Parent('老高','男')\nfather.play() // 打麻将超级垃圾\nfather.speak() // 性别是男\n\n// 创建一个子类去继承父类\nclass Child extends Parent{\n    constructor(name, sex) {\n        // 调用父类的 constructor\n        super(name, sex)\n    }\n}\n// 子类的实例化\nlet son = new Child('小高', '女')\nson.play()\nson.speak()\n",[24,4565,4563],{"__ignoreMap":22},[123,4567,4569],{"id":4568},"_4原生ajax请求步骤","4.原生AJAX请求步骤",[27,4571,4572],{},"五步使用法：\n(1).创建XMLHTTPRequest对象\n(2).使用open方法设置和服务器的交互信息\n(3).设置发送的数据，开始和服务器端交互\n(4).注册事件\n(5).更新界面",[11,4574,4576],{"id":4575},"get请求","Get请求：",[16,4578,4581],{"className":4579,"code":4580,"language":4552,"meta":22},[4550],"// 第一步：创建异步对象\nlet xhr = new XMLHttpRequest()\n// 第二步：设置请求的url参数，参数1是请求的类型，参数2是请求的url，可以携带参数\nxhr.open('get', '/baidu.com?username=1')\n// 第三步：设置发送的数据，开始和服务端交互\nxhr.send()\n// 第四步：注册事件onreadystatechange，当状态改变时会调用\nxhr.onreadystatechange = function () {\n    if (xhr.readyState === 4 && xhr.status === 200) {\n        // 第五步：如果到达这一步，说明数据返回，请求的页面是存在的\n        console.log(xhr.responseText)\n    }\n}\n",[24,4582,4580],{"__ignoreMap":22},[11,4584,4586],{"id":4585},"post请求","POST请求：",[16,4588,4591],{"className":4589,"code":4590,"language":4552,"meta":22},[4550],"// 第一步：创建异步对象\nlet xhr = new XMLHttpRequest()\n// post请求一定要添加请求头，不然会报错\nxhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')\n// 第二步：设置请求的url参数，参数1是请求的类型，参数2是请求的url，可以携带参数\nxhr.open('post', '/baidu.com')\n// 第三步：设置发送的数据，开始和服务端交互\nxhr.send('username=1&password=123')\n// 第四步：注册事件onreadystatechange，当状态改变时会调用\nxhr.onreadystatechange = function () {\n    if (xhr.readyState === 4 && xhr.status === 200) {\n        // 第五步：如果到达这一步，说明数据返回，请求的页面是存在的\n        console.log(xhr.responseText)\n    }\n}\n",[24,4592,4590],{"__ignoreMap":22},[123,4594,4596],{"id":4595},"_5关于事件委托","5.关于事件委托",[11,4598,4599],{"id":4599},"什么是事件委托",[27,4601,4602],{},"事件委托也叫事件代理，就是利用事件冒泡，只指定一个事件处理程序，就可以管理某一类型的所有事件。",[11,4604,4605],{"id":4605},"事件委托的作用",[27,4607,4608],{},"(1).提高性能：每一个函数都会占用内存空间，只需添加一个时间处理程序代理所有事件，所占用的内存空间更少；\n(2).动态监听：使用事件委托可以自动绑定动态添加的元素，即新增的节点不需要主动添加也可以具有和其它元素一样的事件。",[11,4610,4611],{"id":4611},"实现方式",[27,4613,4614],{},"我们先来看看，如果不用事件委托，需要绑定多个相同事件的时候是如何实现的：",[16,4616,4619],{"className":4617,"code":4618,"language":256,"meta":22},[254],"\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n    \u003Cmeta charset=\"UTF-8\">\n    \u003Ctitle>Title\u003C/title>\n\u003C/head>\n\u003Cbody>\n\u003Cul>\n    \u003Cli class=\"item\">按钮\u003C/li>\n    \u003Cli class=\"item\">按钮\u003C/li>\n    \u003Cli class=\"item\">按钮\u003C/li>\n    \u003Cli class=\"item\">按钮\u003C/li>\n\u003C/ul>\n\u003C/body>\n\u003Cscript>\n    window.onload = function () {\n        let lis = document.getElementsByClassName('item')\n        for (let i = 0; i \u003C lis.length; i++) {\n            lis[i].onclick = function () {\n                console.log('用力的点我')\n            }\n        }\n    }\n\u003C/script>\n\u003C/html>\n",[24,4620,4618],{"__ignoreMap":22},[27,4622,4623],{},"不使用事件委托，那就要遍历每一个li元素，给每个li元素绑定一个点击事件，这样的做法非常耗费内存，如果有100个、1000个li元素，那对性能的影响是非常大的。",[27,4625,4626],{},"那么使用事件委托是怎么实现的呢？",[16,4628,4631],{"className":4629,"code":4630,"language":256,"meta":22},[254],"\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n    \u003Cmeta charset=\"UTF-8\">\n    \u003Ctitle>Title\u003C/title>\n\u003C/head>\n\u003Cbody>\n\u003Cul id=\"wrap\">\n    \u003Cli class=\"item\">按钮\u003C/li>\n    \u003Cli class=\"item\">按钮\u003C/li>\n    \u003Cli class=\"item\">按钮\u003C/li>\n    \u003Cli class=\"item\">按钮\u003C/li>\n\u003C/ul>\n\u003C/body>\n\u003Cscript>\n    window.onload = function () {\n        let ul = document.getElementById('wrap')\n        ul.onclick = function (ev) {\n            // 获取到事件对象\n            let e = ev || window.event\n            // 如果点击的元素的calssName为item\n            if (e.target.className === 'item') {\n                console.log('用力的点我')\n            }\n        }\n    }\n\u003C/script>\n\u003C/html>\n",[24,4632,4630],{"__ignoreMap":22},[27,4634,4635],{},"这样一来，通过事件委托，只需要在li元素的父元素ul上绑定一个点击事件，通过事件冒泡的机制，就可以实现li的点击效果。并且通过js动态添加li元素，也能绑定点击事件。",[123,4637,4639],{"id":4638},"_6null不是一个对象但为什么typeof-null-object","6.null不是一个对象，但为什么typeof null === object",[27,4641,4642],{},"原理是这样的，不同的对象在底层都会表示为二进制，在js中如果二进制的前三位都为0，就会被判断为object类型，null的二进制全为0，自然前三位也是0，所以typeof null === objcet。",[123,4644,4646],{"id":4645},"_7关于深拷贝和浅拷贝","7.关于深拷贝和浅拷贝",[11,4648,4650],{"id":4649},"浅拷贝只复制指向某个对象的指针而不复制对象本身新旧对象还是共享同一块内存","浅拷贝：只复制指向某个对象的指针，而不复制对象本身，新旧对象还是共享同一块内存",[27,4652,4653],{},"实现：",[27,4655,4656],{},"方法1：直接用=赋值",[16,4658,4661],{"className":4659,"code":4660,"language":4552,"meta":22},[4550],"let obj1 = {a: 1}\nlet obj2 = obj1\n方法2：Object.assign\n\nlet obj1 = {a: 1}\nlet obj2 = {}\nObject.assign(obj2, obj1)\n方法3：for in循环只遍历第一层\n\nfunction shallowObj(obj) {\n    let result = {}\n    for (let key in obj) {\n        if (obj.hasOwnProperty(key)) {\n            result[key] = obj[key]\n        }\n    }\n    return result\n}\nlet obj1 = {\n    a: 1,\n    b: {\n        c: 2\n    }\n}\nlet obj2 = shallowObj(obj1)\nobj1.b.c = 3\nconsole.log(obj2.b.c) // 3\n",[24,4662,4660],{"__ignoreMap":22},[11,4664,4666],{"id":4665},"深拷贝","深拷贝：",[27,4668,4669],{},"方法1：用 JSON.stringify 把对象转换成字符串，再用 JSON.parse 把字符串转换成新的对象",[16,4671,4674],{"className":4672,"code":4673,"language":4552,"meta":22},[4550],"let obj1 = {\n    a: 1,\n    b: 2,\n}\nlet obj2 = JSON.parse(JSON.stringify(obj1))\n",[24,4675,4673],{"__ignoreMap":22},[27,4677,4678],{},"方法2：采用递归去拷贝所有层级属性",[16,4680,4683],{"className":4681,"code":4682,"language":4552,"meta":22},[4550],"function deepClone(obj) {\n    // 如果传入的值不是一个对象，就不执行\n    if (Object.prototype.toString.call(obj) !== '[object Object]') return\n    // 根据传入值的类型初始化返回结果\n    let result = obj instanceof Array ? [] : {}\n    for (let key in obj) {\n        if (obj.hasOwnProperty(key)) {\n            // 如果obj是个对象，就递归调用deepClone去遍历obj的每一层属性，如果不是对象就直接返回值\n            result[key] = Object.prototype.toString.call(obj[key]) === '[object Object]' ? deepClone(obj[key]) : obj[key]\n        }\n    }\n    return result\n}\n// 改进判断对象的方法\nconsole.log(typeof null === 'object') // true\nconsole.log(Object.prototype.toString.call(null) === '[object Object]') // false\n",[24,4684,4682],{"__ignoreMap":22},[27,4686,4687],{},"方法3：lodash函数库实现深拷贝",[16,4689,4692],{"className":4690,"code":4691,"language":4552,"meta":22},[4550],"let obj1 = {\n    a: 1,\n    b: 2,\n}\nlet obj2 = _.cloneDeep(obj1)\n",[24,4693,4691],{"__ignoreMap":22},[27,4695,4696],{},"方法4：通过jQuery的extend方法实现深拷贝",[16,4698,4701],{"className":4699,"code":4700,"language":4552,"meta":22},[4550],"let array = [1,2,3,4]\nlet newArray = $.extend(true,[],array) // true为深拷贝，false为浅拷贝\n",[24,4702,4700],{"__ignoreMap":22},[27,4704,4705],{},"方法5：用slice实现对数组的深拷贝",[16,4707,4710],{"className":4708,"code":4709,"language":4552,"meta":22},[4550],"let arr1 = [\"1\",\"2\",\"3\"]\nlet arr2 = arr1.slice(0)\narr2[1] = \"9\"\nconsole.log(arr2) // ['1', '9', '3']\nconsole.log(arr1) // ['1', '2', '3']\n",[24,4711,4709],{"__ignoreMap":22},[27,4713,4714],{},"方法6：使用扩展运算符实现深拷贝",[16,4716,4719],{"className":4717,"code":4718,"language":4552,"meta":22},[4550],"let obj1 = {brand: \"BMW\", price: \"380000\", length: \"5米\"}\nlet obj2 = { ...car, price: \"500000\" }\n",[24,4720,4718],{"__ignoreMap":22},[27,4722,4723],{},"js浅拷贝与深拷贝的区别和实现方式",[123,4725,4727],{"id":4726},"_8谈谈js的垃圾回收机制","8.谈谈js的垃圾回收机制",[27,4729,4730],{},"js拥有自动的垃圾回收机制，当一个值在内存中失去引用时，垃圾回收机制会根据特殊的算法找到它，并将其回收，释放内存。",[11,4732,4734],{"id":4733},"标记清除法常用","标记清除法（常用）",[27,4736,4737],{},"(1).标记阶段：垃圾回收器会从根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识，于是这个对象就被标识为可到达对象；\n(2).清除阶段：垃圾回收器会对堆内存从头到尾进行线性遍历，如果发现有对象没有被标识为可到达对象，那么就将此对象占用的内存回收，并且将原来标记为可到达对象的标识清除，以便进行下一次垃圾回收操作；",[27,4739,4740],{},"优点：实现简单\n缺点：可能会造成大量的内存碎片",[11,4742,4743],{"id":4743},"引用计数清除法",[27,4745,4746],{},"(1).引用计数的含义就是跟踪记录每个值被引用的次数，当声明了一个变量并将一个引用类型赋值给该变量时，这个值的引用次数就是1。相反，如果包含对这个值引用的变量又取得了另外一个值，这个值的引用次数就减1。\n(2).当这个引用次数变成0时，则说明没有办法再访问这个值了，就可以将其所占的内存空间给回收。这样，垃圾收集器下次再运行时，就会释放那些引用次数为0的值所占的内存。",[27,4748,4749],{},"优点：\n(1).可即刻回收垃圾",[27,4751,4752],{},"缺点：\n(1).计数器值的增减处理繁重\n(2).实现繁琐复杂\n(3).循环引用无法回收",[123,4754,4756],{"id":4755},"_9如何阻止事件冒泡和默认事件","9.如何阻止事件冒泡和默认事件",[27,4758,4759],{},"标准的DOM对象中可以使用事件对象的stopPropagation()方法来阻止事件冒泡，但在IE8以下中的事件对象通过设置事件对象的cancelBubble属性为true来阻止冒泡",[27,4761,4762],{},"默认事件通过事件对象的preventDefault()方法来阻止，而IE通过设置事件对象的returnValue属性为false来阻止默认事件",[123,4764,4766],{"id":4765},"_10函数去抖和函数节流","10.函数去抖和函数节流",[11,4768,4770],{"id":4769},"函数去抖debounce","函数去抖（debounce）：",[27,4772,4773],{},"当调用函数n秒后，才会执行该动作，若在这n秒内又调用该函数则取消前一次并重新计算执行时间（频繁触发的情况下，只有足够的空闲时间，才执行代码一次）",[16,4775,4778],{"className":4776,"code":4777,"language":4552,"meta":22},[4550],"function debounce(delay, cb) {\n    let timer\n    return function () {\n        if (timer) clearTimeout(timer)\n        timer = setTimeout(function () {\n            cb()\n        }, delay)\n    }\n}\n",[24,4779,4777],{"__ignoreMap":22},[27,4781,4782],{},"JavaScript专题之跟着underscore学防抖",[11,4784,4786],{"id":4785},"函数节流throttle","函数节流（throttle）：",[27,4788,4789],{},"函数节流的基本思想是函数预先设定一个执行周期，当调用动作的时刻大于等于执行周期则执行该动作，然后进入下一个新周期（一定时间内js方法只跑一次。比如人的眨眼睛，就是一定时间内眨一次）",[16,4791,4794],{"className":4792,"code":4793,"language":4552,"meta":22},[4550],"function throttle(cb, delay) {\n    let startTime = Date.now()\n    return function () {\n        let currTime = Date.now()\n        if (currTime - startTime > delay) {\n            cb()\n            startTime = currTime\n        }\n    }\n}\n",[24,4795,4793],{"__ignoreMap":22},[27,4797,4798],{},"JavaScript专题之跟着 underscore 学节流",[123,4800,4802],{"id":4801},"_11谈谈js的事件循环机制","11.谈谈js的事件循环机制",[27,4804,4805],{},"程序开始执行之后，主程序则开始执行同步任务，碰到异步任务就把它放到任务队列之中，等到同步任务全部执行完后，js引擎便去查看任务队列有没有可以执行的异步任务，将异步任务转成同步任务并开始执行，执行完同步任务后继续查看任务队列。这个过程是一直循环的，因此这个过程就是所谓的事件循环，其中任务队列也被称为事件队列。通过一个任务队列，单线程的js实现了异步任务的执行，给人的感觉好像是多线程的。",[123,4807,4809],{"id":4808},"_12箭头函数和普通函数的区别","12.箭头函数和普通函数的区别",[11,4811,4813],{"id":4812},"普通函数","普通函数：",[27,4815,4816],{},"(1).this总是代表它的直接调用者\n(2).在默认情况下，没找到直接调用者，this指向window\n(3).在严格模式下，没有直接调用者的函数中的this是undefined\n(4).使用call，apply，bind绑定，this指的是绑定的对象",[11,4818,4820],{"id":4819},"箭头函数","箭头函数：",[27,4822,4823],{},"(1).在使用 => 定义函数的时候，this的指向是定义时所在的对象，而不是使用时所在的对象，bind()、call()、apply()均无法改变指向\n(2).不能用做构造函数，也就是说不能使用new命令，否则就会抛出一个错误\n(3).不能使用arguments对象，但是可以使用…rest参数\n(4).不能使用yield命令\n(5).没有原型属性",[123,4825,4827],{"id":4826},"_13callapplybind的区别","13.call()、apply()、bind()的区别",[27,4829,4830],{},"call()、apply()、bind()是用来改变this的指向的",[27,4832,4833],{},"call()： Function.call(obj, param1,param2,param3)\n接收到的是param1，param2，param3三个参数",[27,4835,4836,4837,4840],{},"apply()： Function.apply(obj, ",[1373,4838,4839],{},"param1,param2,param3",")\n接收到的是param1，param2，param3三个参数",[27,4842,4843,4844,4846,4847],{},"call和apply的区别是参数一个不用",[1373,4845],{},"，一个要用",[1373,4848],{},[27,4850,4851],{},"bind()： const newFn = Funtion.bind(obj, param1,param2)\n返回值是一个函数，需要()来调用\nnewFn(param3,param4)\n接收到的是param1，param2，param3，param4四个参数",[123,4853,4855],{"id":4854},"_14实现一个sleep函数","14.实现一个sleep函数",[27,4857,4858],{},"js不像java一样有sleep()方法，但由于js是单线程的，可以利用伪死循环阻塞主线程来达到延迟执行的效果",[16,4860,4863],{"className":4861,"code":4862,"language":4552,"meta":22},[4550],"function sleep(delay) {\n    // 获取一个初始时间\n    let startTime = new Date().getTime()\n    // 如果时间差小于延迟时间，就一直循环\n    while (new Date().getTime() - startTime \u003C delay) {\n        continue\n    }\n}\n",[24,4864,4862],{"__ignoreMap":22},[123,4866,4868],{"id":4867},"_15进程和线程的区别","15.进程和线程的区别",[27,4870,4871],{},"进程（process）：是cpu资源分配的最小单位（是能拥有资源和独立运行的最小单位），是并发执行的程序在执行过程中分配和管理资源的基本单位，是一个动态概念。",[27,4873,4874],{},"线程（thread）：是cpu调度的最小单位（是建立在进程基础上的一次程序运行单位），是进程内可调度的实体，比进程更小的独立运行的基本单位。",[27,4876,4877],{},"一个进程有一个或多个线程，线程之间共同完成进程分配下来的任务，打个比方：\n● 假如进程是一个工厂，工厂有它的独立的资源\n● 工厂之间相互独立\n● 线程是工厂中的工人，多个工人协作完成任务\n● 工厂内有一个或多个工人\n● 工人之间共享空间",[27,4879,4880],{},"再完善完善概念：\n● 工厂的资源 -> 系统分配的内存（独立的一块内存）\n● 工厂之间的相互独立 -> 进程之间相互独立\n● 多个工人协作完成任务 -> 多个线程在进程中协作完成任务\n● 工厂内有一个或多个工人 -> 一个进程由一个或多个线程组成\n● 工人之间共享空间 -> 同一进程下的各个线程之间共享程序的内存空间（包括代码段、数据集、堆等）",[27,4882,4883],{},"浅谈浏览器多进程与JS线程",[123,4885,4887],{"id":4886},"_16es6es7es8的新特性","16.ES6、ES7、ES8的新特性",[11,4889,4891],{"id":4890},"es6的特性","ES6的特性",[27,4893,4894],{},"(1). 类（class）\n对熟悉Java、C、C++等语言的开发者来说，class一点都不陌生。ES6引入了class（类），让JS的面向对象编程变得更加简单和易于理解。",[27,4896,4897],{},"(2).模块化（Module）\nES5不支持原生的模块化，在ES6中模块作为重要的组成部分被添加进来。模块的功能主要由export和import组成。每一个模块都有自己单独的作用域，模块之间的相互调用关系是通过 export 来规定模块对外暴露的接口，通过import来引用其它模块提供的接口。同时还为模块创造了命名空间，防止函数的命名冲突。",[73,4899,4901],{"id":4900},"导出export","导出（export）",[27,4903,4904],{},"ES6运行在一个模块中使用export来导出多个变量或函数",[16,4906,4909],{"className":4907,"code":4908,"language":4552,"meta":22},[4550],"// 导出变量\nexport let name = 'gg'\n\n// 导出常量\nexport const name = 'gg'\n\n// 导出多个变量\nlet a = 2\nlet b = 4\nexport {a, b}\n\n// 导出函数\nexport function myModule(someArg) {\n    return someArg\n}  \n",[24,4910,4908],{"__ignoreMap":22},[73,4912,4914],{"id":4913},"导入import","导入（import）",[27,4916,4917],{},"定义好模块的输出以后就可以在另外一个模块通过import引用。",[16,4919,4922],{"className":4920,"code":4921,"language":4552,"meta":22},[4550],"import {myModule} from 'myModule'\nimport {a,b} from 'test'\n",[24,4923,4921],{"__ignoreMap":22},[27,4925,4926],{},"(3).箭头（Arrow）函数\n这是ES6中最令人激动的特性之一。=>不只是关键字function的简写，它还带来了其它好处。箭头函数与包围它的代码共享同一个this,能很好的解决this的指向问题。",[27,4928,4929],{},"(4).函数参数默认值\nES6支持在定义函数的时候为其设置默认值，当函数的参数为布尔值false时，可以规避一些问题",[16,4931,4934],{"className":4932,"code":4933,"language":4552,"meta":22},[4550],"// 使用默认值\nfunction foo(height = 50, color = 'red') {\n    //\n}\n\n// 不使用默认值\nfunction foo(height, color) {\n    let height = height || 50\n    let color = color || 'red'\n}\n",[24,4935,4933],{"__ignoreMap":22},[27,4937,4938],{},"(5).模板字符串",[16,4940,4943],{"className":4941,"code":4942,"language":4552,"meta":22},[4550],"// 不使用模板字符串\nlet name = 'Your name is ' + first + ' ' + last + '.'\n\n// 使用模板字符串\nlet name = `Your name is ${first} ${last}.`\n",[24,4944,4942],{"__ignoreMap":22},[27,4946,4947],{},"(6).解构赋值\n通过解构赋值可以方便的交换两个变量的值：",[16,4949,4952],{"className":4950,"code":4951,"language":4552,"meta":22},[4550],"let a = 1\nlet b = 3\n\n[a, b] = [b, a];\nconsole.log(a) // 3\nconsole.log(b) // 1\n获取对象中的值:\n\nconst student = {\n    name:'Ming',\n    age:'18',\n    city:'Shanghai'\n}\n\nconst {name,age,city} = student\nconsole.log(name) // \"Ming\"\nconsole.log(age) // \"18\"\nconsole.log(city) // \"Shanghai\"\n",[24,4953,4951],{"__ignoreMap":22},[27,4955,4956],{},"(7).延展操作符(Spread operator)和剩余运算符（rest operator）\n当三个点(...)在等号右边，或者放在实参上，是 spread运算符",[16,4958,4961],{"className":4959,"code":4960,"language":4552,"meta":22},[4550],"myFunction(...arr)\n\nlet arr1 = [1, 2, 3, 4]\nlet arr2 = [...arr1, 4, 5, 6]\nconsole.log(arr2) // [1, 2, 3, 4, 4, 5, 6]\n当三个点(...)在等号左边，或者放在形参上，是 rest 运算符\n\nfunction myFunction(...arr) {\n\n}\n\nlet [a,...temp]=[1, 2, 4]\nconsole.log(a) // 1\nconsole.log(temp) // [2, 4]\n",[24,4962,4960],{"__ignoreMap":22},[27,4964,4965],{},"(8).对象属性简写\n在ES6中允许我们在设置一个对象的属性的时候不指定属性名",[27,4967,4968],{},"不使用ES6：",[16,4970,4973],{"className":4971,"code":4972,"language":4552,"meta":22},[4550],"const name='Ming',age='18',city='Shanghai';\n\nconst student = {\n    name:name,\n    age:age,\n    city:city\n};\nconsole.log(student)//{name: \"Ming\", age: \"18\", city: \"Shanghai\"}\n",[24,4974,4972],{"__ignoreMap":22},[27,4976,4977],{},"使用ES6：",[16,4979,4982],{"className":4980,"code":4981,"language":4552,"meta":22},[4550],"const name='Ming',age='18',city='Shanghai'\n\nconst student = {\n    name,\n    age,\n    city\n};\nconsole.log(student)//{name: \"Ming\", age: \"18\", city: \"Shanghai\"}\n",[24,4983,4981],{"__ignoreMap":22},[27,4985,4986],{},"(9).Promise\nPromise 是异步编程的一种解决方案，比传统的解决方案callback更加的优雅。它最早由社区提出和实现的，ES6 将其写进了语言标准，统一了用法，原生提供了Promise对象。",[27,4988,4989],{},"不使用ES6",[16,4991,4994],{"className":4992,"code":4993,"language":4552,"meta":22},[4550],"setTimeout(function()\n{\n    console.log('Hello') // 1秒后输出\"Hello\"\n    setTimeout(function()\n    {\n        console.log('Hi') // 2秒后输出\"Hi\"\n    }, 1000)\n}, 1000)\n",[24,4995,4993],{"__ignoreMap":22},[27,4997,4998],{},"使用ES6",[16,5000,5003],{"className":5001,"code":5002,"language":4552,"meta":22},[4550]," let waitSecond = new Promise(function(resolve, reject)\n{\n    setTimeout(resolve, 1000)\n});\n\nwaitSecond\n    .then(function()\n    {\n        console.log(\"Hello\") // 1秒后输出\"Hello\"\n        return waitSecond\n    })\n    .then(function()\n    {\n        console.log(\"Hi\") // 2秒后输出\"Hi\"\n    })\n",[24,5004,5002],{"__ignoreMap":22},[27,5006,5007],{},"(10).支持let与const\n在之前JS是没有块级作用域的，const与let填补了这方便的空白，const与let都是块级作用域。",[27,5009,5010],{},"let和var的区别：",[27,5012,5013],{},"● let没有变量提升，存在暂时性死区，必须等let声明完以后，变量才能使用",[27,5015,5016],{},"● let变量不能重复声明",[27,5018,5019],{},"● let声明的变量只在let代码块有效",[11,5021,5023],{"id":5022},"es7的特性","ES7的特性",[16,5025,5028],{"className":5026,"code":5027,"language":4552,"meta":22},[4550],"(1).Array.prototype.includes()\nincludes() 函数用来判断一个数组是否包含一个指定的值，如果包含则返回 true，否则返回false\n\nlet arr = ['react', 'angular', 'vue']\nif  (arr.includes('react')) {\n    console.log('react存在')\n}\n",[24,5029,5027],{"__ignoreMap":22},[27,5031,5032],{},"(2).指数操作符\n在ES7中引入了指数运算符，具有与Math.pow(..)等效的计算结果。",[16,5034,5037],{"className":5035,"code":5036,"language":4552,"meta":22},[4550],"console.log(Math.pow(2, 10)) // 输出1024\n\nconsole.log(2**10) // 输出1024\n",[24,5038,5036],{"__ignoreMap":22},[11,5040,5042],{"id":5041},"es8的特性","ES8的特性",[27,5044,5045],{},"(1).async/await\n在ES8中加入了对async/await的支持，也就我们所说的异步函数，这是一个很实用的功能。 async/await相当于一个语法糖，解决了回调地狱的问题",[27,5047,5048],{},"(2).Object.values()\nObject.values()是一个与Object.keys()类似的新函数，但返回的是Object自身属性的所有值，不包括继承的值。",[16,5050,5053],{"className":5051,"code":5052,"language":4552,"meta":22},[4550],"const obj = {\n    a: 1,\n    b: 2,\n    c: 3,\n}\n// 不使用Object.values()\nconst vals = Object.keys(obj).map(e => obj[e])\nconsole.log(vals) // [ 1, 2, 3 ]\n\n// 使用Object.values()\nconsole.log(Object.values(obj)) // [ 1, 2, 3 ]\n",[24,5054,5052],{"__ignoreMap":22},[27,5056,5057],{},"(3).Object.entries\nObject.entries()函数返回一个给定对象自身可枚举属性的键值对的数组。",[16,5059,5062],{"className":5060,"code":5061,"language":4552,"meta":22},[4550],"const obj = {\n    a: 1,\n    b: 2,\n    c: 3,\n}\n// 不使用Object.entries()\nObject.keys(obj).forEach(key=>{\n    console.log('key:'+key+' value:'+obj[key])\n})\n//key:a value:1\n//key:b value:2\n//key:c value:3\n\n// 使用Object.entries()\nfor(let [key,value] of Object.entries(obj1)){\n    console.log(`key: ${key} value:${value}`)\n}\n//key:a value:1\n//key:b value:2\n//key:c value:3\n",[24,5063,5061],{"__ignoreMap":22},[27,5065,5066],{},"(4).String padding\n在ES8中String新增了两个实例函数String.prototype.padStart和String.prototype.padEnd，允许将空字符串或其他字符串添加到原始字符串的开头或结尾",[16,5068,5071],{"className":5069,"code":5070,"language":4552,"meta":22},[4550],"String.padStart(targetLength,[padString])\n",[24,5072,5070],{"__ignoreMap":22},[27,5074,5075],{},"targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度，则返回当前字符串本身。\npadString:(可选)填充字符串。如果字符串太长，使填充后的字符串长度超过了目标长度，则只保留最左侧的部分，其他部分会被截断，此参数的缺省值为 \" \"。",[16,5077,5080],{"className":5078,"code":5079,"language":4552,"meta":22},[4550],"console.log('0.0'.padStart(4,'10')) //10.0\nconsole.log('0.0'.padStart(20))//                0.00  \nString.padEnd(targetLength,padString])\n",[24,5081,5079],{"__ignoreMap":22},[27,5083,5084],{},"targetLength:当前字符串需要填充到的目标长度。如果这个数值小于当前字符串的长度，则返回当前字符串本身。\npadString:(可选) 填充字符串。如果字符串太长，使填充后的字符串长度超过了目标长度，则只保留最左侧的部分，其他部分会被截断，此参数的缺省值为 \" \"；",[16,5086,5089],{"className":5087,"code":5088,"language":4552,"meta":22},[4550],"console.log('0.0'.padEnd(4,'0')) //0.00    \nconsole.log('0.0'.padEnd(10,'0'))//0.00000000\n",[24,5090,5088],{"__ignoreMap":22},[27,5092,5093],{},"(5).函数参数列表结尾允许逗号",[16,5095,5098],{"className":5096,"code":5097,"language":4552,"meta":22},[4550],"// 不使用ES8\n//程序员A\nlet f = function(a,\n                 b\n) {\n...\n}\n\n//程序员B\nlet f = function(a,\n                 b,   //变更行\n                 c   //变更行\n) {\n...\n}\n\n//程序员C\nlet f = function(a,\n                 b,\n                 c,   //变更行\n                 d   //变更行\n) {\n...\n}\n\n// 使用ES8\n//程序员A\nlet f = function(a,\n                 b,\n) {\n...\n}\n\n//程序员B\nlet f = function(a,\n                 b,\n                 c,   //变更行\n) {\n...\n}\n\n//程序员C\nlet f = function(a,\n                 b,\n                 c,\n                 d,   //变更行\n) {\n...\n}\n",[24,5099,5097],{"__ignoreMap":22},[27,5101,5102],{},"(6).Object.getOwnPropertyDescriptors()\nObject.getOwnPropertyDescriptors()函数用来获取一个对象的所有自身属性的描述符,如果没有任何自身属性，则返回空对象。",[16,5104,5107],{"className":5105,"code":5106,"language":4552,"meta":22},[4550],"const obj2 = {\n    name: 'Jine',\n    get age() { return '18' }\n};\nObject.getOwnPropertyDescriptors(obj2)\n// {\n//   age: {\n//     configurable: true,\n//     enumerable: true,\n//     get: function age(){}, //the getter function\n//     set: undefined\n//   },\n//   name: {\n//     configurable: true,\n//     enumerable: true,\n//      value:\"Jine\",\n//      writable:true\n//   }\n// }\n",[24,5108,5106],{"__ignoreMap":22},[27,5110,5111],{},"ES6、ES7、ES8特性一锅炖(ES6、ES7、ES8学习指南)",[123,5113,5115],{"id":5114},"_17性能监控平台是如何捕获错误的","17.性能监控平台是如何捕获错误的",[11,5117,5118],{"id":5118},"全局捕获",[27,5120,5121],{},"通过全局的接口，将捕获代码集中写在一个地方，可以利用的接口有：\n(1).window.addEventListener(‘error’) / window.addEventListener(“unhandledrejection”) / document.addEventListener(‘click’) 等\n(2).框架级别的全局监听，例如aixos中使用interceptor进行拦截，vue、react都有自己的错误采集接口\n(3).通过对全局函数进行封装包裹，实现在在调用该函数时自动捕获异常\n(4).对实例方法重写（Patch），在原有功能基础上包裹一层，例如对console.error进行重写，在使用方法不变的情况下也可以异常捕获",[11,5123,5124],{"id":5124},"单点捕获",[27,5126,5127],{},"在业务代码中对单个代码块进行包裹，或在逻辑流程中打点，实现有针对性的异常捕获：\n(1).try…catch中throw err\n(2).专门写一个函数来收集异常信息，在异常发生时，调用该函数\n(3).专门写一个函数来包裹其他函数，得到一个新函数，该新函数运行结果和原函数一模一样，只是在发生异常时可以捕获异常",[27,5129,5130],{},"前端异常监控解决方案研究",[123,5132,5134],{"id":5133},"_18函数柯里化","18.函数柯里化",[27,5136,5137],{},"在数学和计算机科学中，柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术",[27,5139,4546],{},[16,5141,5144],{"className":5142,"code":5143,"language":4552,"meta":22},[4550],"function add(a, b) {\n    return a + b;\n}\n\n// 执行 add 函数，一次传入两个参数即可\nadd(1, 2) // 3\n\n// 假设有一个 curry 函数可以做到柯里化\nvar addCurry = curry(add);\naddCurry(1)(2) // 3\n\naddCurry(1)(2) = add(1, 2)\n",[24,5145,5143],{"__ignoreMap":22},[27,5147,5148],{},"实现：\n(1).最简单的方式，使用lodash库的_.curry",[16,5150,5153],{"className":5151,"code":5152,"language":4552,"meta":22},[4550],"function sum(a, b) {\n  return a + b\n}\n\n// 使用来自 lodash 库的 _.curry\nlet curriedSum = _.curry(sum)\n\nalert( curriedSum(1, 2) ) // 3\nalert( curriedSum(1)(2) ) // 3\n",[24,5154,5152],{"__ignoreMap":22},[27,5156,5157],{},"(2).自定义函数实现",[16,5159,5162],{"className":5160,"code":5161,"language":4552,"meta":22},[4550],"function curry(func) {\n    return function curried(...args) {\n        if (args.length >= func.length) {\n            return func.apply(this, args)\n        } else {\n            return function(...args2) {\n                return curried.apply(this, args.concat(args2))\n            }\n        }\n    }\n}\n\nlet currfn = curry(add)\nconsole.log(currfn(1)(3)) // 4\n",[24,5163,5161],{"__ignoreMap":22},[27,5165,5166],{},"JavaScript专题之函数柯里化",[123,5168,5170],{"id":5169},"_19new关键字做了什么","19.new关键字做了什么？",[27,5172,5173],{},"使用new操作符调用构造函数实际上会经历以下4个步骤：\n(1).创建一个新对象\n(2).将构造函数的作用域赋给新对象（因此this就指向了这个新对象）\n(3).执行构造函数中的代码（为这个新对象添加属性、方法）\n(4).返回新对象",[16,5175,5178],{"className":5176,"code":5177,"language":4552,"meta":22},[4550],"var obj = {}\nobj.__proto__ = Base.prototype\nBase.call(obj)\n",[24,5179,5177],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":5181},[5182,5183,5184,5185,5186,5187,5188,5189,5190,5191,5192,5193,5194,5195,5196,5197,5198,5199,5203,5204,5205,5206],{"id":4485,"depth":95,"text":4486},{"id":4495,"depth":95,"text":4496},{"id":4502,"depth":95,"text":4502},{"id":4508,"depth":95,"text":4508},{"id":4575,"depth":95,"text":4576},{"id":4585,"depth":95,"text":4586},{"id":4599,"depth":95,"text":4599},{"id":4605,"depth":95,"text":4605},{"id":4611,"depth":95,"text":4611},{"id":4649,"depth":95,"text":4650},{"id":4665,"depth":95,"text":4666},{"id":4733,"depth":95,"text":4734},{"id":4743,"depth":95,"text":4743},{"id":4769,"depth":95,"text":4770},{"id":4785,"depth":95,"text":4786},{"id":4812,"depth":95,"text":4813},{"id":4819,"depth":95,"text":4820},{"id":4890,"depth":95,"text":4891,"children":5200},[5201,5202],{"id":4900,"depth":102,"text":4901},{"id":4913,"depth":102,"text":4914},{"id":5022,"depth":95,"text":5023},{"id":5041,"depth":95,"text":5042},{"id":5118,"depth":95,"text":5118},{"id":5124,"depth":95,"text":5124},{"date":5208,"tags":5209},"2020-05-31 21:43:18",[5210,5211,5212],"前端","JS","JavaScript","/javascript",{"title":4476,"description":22},"HQLdXEqIT-ow-gtW7IPZaaQtZxMD1YGR-irhK7FUc10",{"id":5217,"title":5218,"body":5219,"description":5223,"extension":104,"meta":5330,"navigation":110,"path":5335,"seo":5336,"stem":5337,"__hash__":5338},"content/qqcard.md","如何制作QQ动态个性名片",{"type":8,"value":5220,"toc":5324},[5221,5224,5227,5231,5234,5237,5240,5243,5246,5249,5257,5261,5264,5267,5270,5298,5304,5307,5312,5315,5318],[27,5222,5223],{},"Lottie 是Airbnb开源的一个面向 iOS、Android、React Native 的动画库，能分析 Adobe After Effects 导出的动画，并且能让原生 App 像使用静态素材一样使用这些动画，完美实现动画效果。",[27,5225,5226],{},"现在使用各平台的 native 代码实现一套复杂的动画是一件很困难并且耗时的事，我们需要为不同尺寸的屏幕加载不同的素材资源，还需要写大量难维护的代码，而Lottie可以做到同一个动画文件在不同平台上实现相同的效果，极大减少开发时间，实现不同的动画，只需要设置不同的动画文件即可，极大减少开发和维护成本。",[11,5228,5230],{"id":5229},"为什么使用lottie","为什么使用Lottie？",[27,5232,5233],{},"跨平台只有制作一套json动画文件便可以跨平台在 Android ios ReactNeative上使用，lottie库负责解析json文件并播放动画",[27,5235,5236],{},"可以支持网络下载json文件，本地播放，实时更新动画资源。",[27,5238,5239],{},"运行时效率上仅仅用Canvas去draw而已，流畅度非常棒，所以哪怕在Listview里去大量显示，内存占用和绘图效率都远远高于帧动画。",[27,5241,5242],{},"实现效果可以按设计出的100%还原到产品中。",[27,5244,5245],{},"开发周期大大减少。\n定制DIY名片并不是件简单的事情，官方本身是禁止这种行为的，被和谐了很多次：\n特殊unicode字符法——已和谐\n域名伪装法——已和谐\nHttp法——已和谐\n半屏显示法——已和谐\n经过TX几次的和谐之后终于算是稳定了，最终方案为伪装图片zip\n大致原理就是制作lottie动画在通过JSON上传就可以实现了",[11,5247,5248],{"id":5248},"开始教程",[27,5250,5251,5252,5256],{},"准备工具：\n1.Xposed框架(很多安卓手机已经不支持这个了，建议在虚拟机下运行)\n2.Adobe After Effects CC(2019)+bodymovin(5.6.10)（必须CC版本，CS不支持）\n3.自定义DIY名片工具，下载地址：",[497,5253,5254],{"href":5254,"rel":5255},"https://lanzous.com/ianhi6j",[590],"\n4.SVIP",[73,5258,5260],{"id":5259},"怎样使用bodymovin","怎样使用bodymovin？",[27,5262,5263],{},"拷贝bodymovin文件夹到如下位置:",[27,5265,5266],{},"Win : C:\\Program Files (x86)\\Common Files\\Adobe\\CEP\\extensions\\",[27,5268,5269],{},"Mac : ~/Library/Application Support/Adobe/CEP/extensions",[27,5271,5272,5273,5277,5278,5281,5282,5285,5286,5289,5290,5293,5294,5297],{},"大概就这些关键工具，全部就绪好后开始。\n1.安装QQ、Xposed框架、DIY工具等，在Xposed框架中激活DIY名片模块.\n2.image_view是静态图片，lottie_view是lottie动画，自行根据需要替换，静态图片很简单，直接上传到图床上，粘贴图片直链就行，视频就比较麻烦，很考验对视频的后期处理能力，这里我建议的导出参数有10帧，281",[5274,5275,5276],"em",{},"500分辨率（也有297","700的），图片序列控制在200张以内(150张以下最佳)，一张图保持在20KB-50KB，压缩后的zip应小于5MB，,帧数太高或者分辨率太高可能会导致闪屏或者加载慢，下面来讲视频的处理过程\n",[551,5279],{"alt":3545,"src":5280},"/images/aeconfig3.jpg","\n3.先把原视频导出成PNG序列，然后在AE中合成PNG，勾选单个合成，序列图层，渲染队列。不用勾，静止持续时间0:00:00:03\n",[551,5283],{"alt":3545,"src":5284},"images/aeconfig.jpg","\n4.窗口->扩展->bodymovin,选择目标路径，开始渲染，生成data.json，data.json的代码就不说了，里面全是动画参数，看也看不懂。\n",[551,5287],{"alt":3545,"src":5288},"images/aeconfig2.jpg","\n5.打包image文件夹和data.json成zip格式，在用copy /b xx.jpg+xx.zip xx.jpg 合并命令伪装图片\n上传至云盘，获得直链，复制到type为lottie_view下的content对象里。\n",[551,5291],{"alt":3545,"src":5292},"images/aeconfig4.jpg","\n6.在DIY名片插件中粘贴生成JSON代码，重启QQ，登录QQ，任选一张名片再次刷新，自定义名片就生成了。\n",[551,5295],{"alt":3545,"src":5296},"images/aeconfig5.jpg","\nDIY名片工具所需JSON代码如下：",[16,5299,5302],{"className":5300,"code":5301,"language":69,"meta":22},[67],"{\n    \"styleId\": 22,\n    \"bgId\": 3807,\n    \"cardId\": 3807,\n    \"renderInfo\": {\n        \"bg\": [{\n            \"type\": \"image_view\",\n            \"scale_type\": \"center_crop\",\n            \"content\": \"\"\n        }, {\n            \"type\": \"mask_view\",\n            \"content\": \"#00000000\"\n        }, {\n            \"loop\": \"1\",\n            \"type\": \"lottie_view\",\n            \"content\": \"https:\\/\\/imgcache.qq.com@r.photo.store.qq.com/psc?\\/1e3eee36-58c1-427c-adbd-b483a71f8360/bqQfVz5yrrGYSXMvKr.cqZngYd1GpR.7w3J2dVjWPyu.SOqMTBsXAQzgfy7J.eAS7FgDxsd9RUQd5Uvtu4kD0e6.5HHWCfzWwdydGIpgwKg!\\/b&bo=OASABzgEgAcDCSw!&rf=viewer_4\\/r\"\n        }],\n        \"v\": 909125644,\n        \"header\": {\n            \"width\": \"fill\",\n            \"type\": \"layout\",\n            \"order\": 0,\n            \"height\": \"fill\",\n            \"child\": [{\n                \"loop\": \"1\",\n                \"width\": \"fill\",\n                \"type\": \"lottie_view\",\n                \"content\": \"\",\n                \"height\": \"fill\"\n            }, {\n                \"loop\": \"1\",\n                \"type\": \"lottie_view\",\n                \"content\": \"\"\n            }, {\n                \"border\": \"1\",\n                \"x\": \"520w\",\n                \"y\": \"-7100h\",\n                \"id\": \"1002\",\n                \"type\": \"pf_avatar\",\n                \"positionLimit\": {\n                    \"top\": \"88px\",\n                    \"bottom\": \"88px\"\n                }\n            }, {\n                \"rotate\": 0,\n                \"x\": \"3000w\",\n                \"width\": \"6000w\",\n                \"y\": \"-7250h\",\n                \"id\": \"2001\",\n                \"type\": \"image_view\",\n                \"scale_type\": \"center_crop\",\n                \"content\": \"https:\\/\\/imgcache.qq.com@images-cdn.shimo.im\\/756kVABIRQaCae3d.png?club.vip.qq.com&\",\n                \"positionLimit\": {\n                    \"bottom\": \"75px\"\n                },\n                \"height\": \"3500w\",\n                \"stickerId\": 2063\n            }, {\n                \"loop\": \"1\",\n                \"width\": \"10000w\",\n                \"x\": \"0w\",\n                \"y\": \"200h\",\n                \"type\": \"lottie_view\",\n                \"content\": \"\",\n                \"height\": \"10000w\"\n            }, {\n                \"loop\": \"1\",\n                \"type\": \"lottie_view\",\n                \"content\": \"\"\n            }, {\n                \"loop\": \"1\",\n                \"width\": \"5000w\",\n                \"x\": \"-14200w\",\n                \"y\": \"-10200w\",\n                \"type\": \"lottie_view\",\n                \"content\": \"\",\n                \"height\": \"5000w\"\n            }, {\n                \"width\": \"4000w\",\n                \"x\": \"33766w\",\n                \"y\": \"28099h\",\n                \"id\": \"1\",\n                \"type\": \"image_view\",\n                \"scale_type\": \"center_crop\",\n                \"content\": \"\",\n                \"stickerId\": 2068,\n                \"height\": \"4000w\"\n            }, {\n                \"rotate\": 2,\n                \"x\": \"2630w\",\n                \"width\": \"730w\",\n                \"y\": \"-8200h\",\n                \"id\": \"1004\",\n                \"type\": \"image_view\",\n                \"scale_type\": \"center_crop\",\n                \"content\": \"https:\\/\\/imgcache.qq.com@images-cdn.shimo.im\\/LuMyc0w4wpVT0rcl.png?club.vip.qq.com&\",\n                \"height\": \"730w\",\n                \"stickerId\": 2065\n            }, {\n                \"bg\": \"\",\n                \"f\": 0,\n                \"width\": \"800w\",\n                \"x\": \"283300w\",\n                \"y\": \"920000h\",\n                \"id\": \"1003\",\n                \"type\": \"pf_name\",\n                \"ft\": 1,\n                \"positionLimit\": {\n                    \"top\": \"88px\",\n                    \"left\": \"32px\",\n                    \"bottom\": \"120px\",\n                    \"right\": \"32px\"\n                },\n                \"height\": \"\"\n            }, {\n                \"lpd\": 120000,\n                \"bg\": \"\",\n                \"rpd\": 12,\n                \"x\": \"-36000w\",\n                \"y\": \"0h\",\n                \"style\": 1,\n                \"id\": \"1001\",\n                \"type\": \"pf_like\",\n                \"positionLimit\": {\n                    \"top\": \"88px\",\n                    \"bottom\": \"88px\"\n                },\n                \"height\": \"100050w\"\n            }]\n        },\n        \"body\": {\n            \"arr\": \"https:\\/\\/imgcache.qq.com@s1.ax1x.com\\/2020\\/07\\/18\\/U2YL3F.png??imgcache.qq.com&\",\n            \"c\": \"#00000000\",\n            \"cpd\": 8,\n            \"f\": \"GROUP99799305\",\n            \"line\": \"#00000000\",\n            \"t_bg\": \"https:\\/\\/imgcache.qq.com\\/xydata\\/card\\/item\\/3807\\/r\\/dcc6ef2de2bd4853c3293678d54ff-t_bg.png?1304009050?imgcache.qq.com&\",\n            \"module\": [{\n                \"arr\": \"https:\\/\\/imgcache.qq.com\\/\\/xydata\\/\\/card\\/\\/customProfileSticker\\/\\/2011\\/\\/content.png\",\n                \"p\": \"https:\\/\\/imgcache.qq.com@uploader.uploader.shimo.im\\/f\\/fb5XkFnj9i5n1FIF.png?imgcache.qq.com&\",\n                \"qq\": \"https:\\/\\/imgcache.qq.com@uploader.uploader.shimo.im\\/f\\/fb5XkFnj9i5n1FIF.png?imgcache.qq.com&\",\n                \"metal\": \"https:\\/\\/imgcache.qq.com@uploader.uploader.shimo.im\\/f\\/fb5XkFnj9i5n1FIF.png?imgcache.qq.com&\",\n                \"sign\": \"https:\\/\\/imgcache.qq.com@uploader.uploader.shimo.im\\/f\\/fb5XkFnj9i5n1FIF.png?imgcache.qq.com&\",\n                \"lv\": \"https:\\/\\/imgcache.qq.com@uploader.uploader.shimo.im\\/f\\/fb5XkFnj9i5n1FIF.png?imgcache.qq.com&\",\n                \"type\": \"info\"\n            }, {\n                \"ph\": \"https:\\/\\/imgcache.qq.com\\/xydata\\/card\\/item\\/3807\\/r\\/dcc6ef2de2bd4853c3293678d54ff-t_bg.png?imgcache.qq.com&\",\n                \"type\": \"state\"\n            }, {\n                \"ph\": \"https:\\/\\/imgcache.qq.com\\/xydata\\/card\\/item\\/3807\\/r\\/dcc6ef2de2bd4853c3293678d54ff-t_bg.png?imgcache.qq.com&\",\n                \"type\": \"qz\"\n            }, {\n                \"type\": \"photo\"\n            }, {\n                \"type\": \"tag\"\n            }, {\n                \"type\": \"exp\"\n            }],\n            \"cbg\": \"https:\\/\\/imgcache.qq.com\\/xydata\\/card\\/item\\/3807\\/r\\/dcc6ef2de2bd4853c3293678d54ff-cbg.png?imgcache.qq.com&\",\n            \"sbg\": \"https:\\/\\/imgcache.qq.com@uploader.shimo.im\\/f\\/risYqmv8XTQIPJda.png?imgcache.qq.com&\",\n            \"id\": 22,\n            \"ts\": 0\n        }\n    },\n    \"callback\": \"__MQQ_CALLBACK_AUTO_7\"\n}\n",[24,5303,5301],{"__ignoreMap":22},[27,5305,5306],{},"最终效果：",[27,5308,5309],{},[551,5310],{"alt":3545,"src":5311},"images/qqcard.gif",[27,5313,5314],{},"2021年某日起动态名片一旦换掉就无法换回来，\n1001为点赞，1002为头像，1003为昵称\nbg_id决定背景ID，\ncard_md5决定框的类型，不能为空，MD5不存在则隐藏\n已知MD5：\n杂志：189eaa75f991a24c5e67bd89cc4f5d1f\n唯美：c9f374febf69d976de4840be07059063\n潮流派：dcc6ef2de2bd4853c3b10293678d54ff\n甜甜爱情：42939e51cd18a09aad371a67caf93062\nanimation_md5只是一个校验，动画还是由bg_id决定\nbg_id为0时base64_bg_pic不为空\nbody_info里的color为#00000000时完全透明\n2022年1月 静态名片和谐",[27,5316,5317],{},"最后一次代码如下",[16,5319,5322],{"className":5320,"code":5321,"language":69,"meta":22},[67],"{\n    \"style_id\": 22,\n    \"bg_id\": 0,\n    \"card_id\": 3807,\n    \"card_md5\": \"d37f1c233aa888b125fbc4f8a6308790\",\n    \"render_info\": {\n        \"bg_info\": {\n            \"base64_bg_pic\": \"\",\n            \"mask_view\": \"#00000000\",\n            \"lottie_id\": \"\",\n            \"animation_md5\": \"\",\n            \"animation_loop\": \"1\"\n        },\n        \"head_info\": {\n            \"head_items\": {\n                \"1001\": {\n                    \"position_info\": {\n                        \"x_axis\": \"-36000w\",\n                        \"y_axis\": \"0h\",\n                        \"height\": \"100050w\",\n                        \"position_limit\": {\n                            \"top\": \"88px\",\n                            \"bottom\": \"88px\"\n                        }\n                    },\n                    \"int_kv\": {\n                        \"style\": 1,\n                        \"lpd\": 120000,\n                        \"rpd\": 12\n                    },\n                    \"str_kv\": {\n                        \"id\": \"1001\",\n                        \"type\": \"pf_like\"\n                    }\n                },\n                \"1002\": {\n                    \"position_info\": {\n                        \"x_axis\": \"520w\",\n                        \"y_axis\": \"-7100h\",\n                        \"position_limit\": {\n                            \"top\": \"88px\",\n                            \"bottom\": \"88px\"\n                        }\n                    },\n                    \"int_kv\": {\n                        \"border\": 1\n                    },\n                    \"str_kv\": {\n                        \"id\": \"1002\",\n                        \"type\": \"pf_avatar\"\n                    }\n                },\n                \"1003\": {\n                    \"position_info\": {\n                        \"x_axis\": \"283300w\",\n                        \"y_axis\": \"920000h\",\n                        \"width\": \"800w\",\n                        \"height\": \"\",\n                        \"gravity\": \"center_horizontal\",\n                        \"position_limit\": {\n                            \"top\": \"88px\",\n                            \"bottom\": \"120px\",\n                            \"left\": \"32px\",\n                            \"right\": \"32px\"\n                        }\n                    },\n                    \"int_kv\": {\n                        \"f\": 0,\n                        \"ft\": 1\n                    },\n                    \"str_kv\": {\n                        \"id\": \"1003\",\n                        \"type\": \"pf_name\"\n                    }\n                }\n            }\n        },\n        \"body_info\": {\n            \"color\": \"#00000000\",\n            \"cpd\": 8,\n            \"line_color\": \"#00000000\",\n            \"ts\": 0\n        }\n    }\n}\n",[24,5323,5321],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":5325},[5326,5327],{"id":5229,"depth":95,"text":5230},{"id":5248,"depth":95,"text":5248,"children":5328},[5329],{"id":5259,"depth":102,"text":5260},{"date":5331,"tags":5332},"2020-04-16 23:48:53",[5333,5334,69],"qq","ae","/qqcard",{"title":5218,"description":5223},"qqcard","Vc7d95eyCCxLXMbxbZT99e7wKTwCdG5mAfYphQ_2cQk",{"id":5340,"title":5341,"body":5342,"description":5397,"extension":104,"meta":5398,"navigation":110,"path":5402,"seo":5403,"stem":877,"__hash__":5404},"content/selenium.md","Python实现滑块验证",{"type":8,"value":5343,"toc":5395},[5344,5362,5365,5369,5375,5379,5385,5390],[27,5345,5346,5347,5352,5353,5356,5357,5361],{},"python中安装好selenium包  pip install selenium\n点击下载chrome的webdriver： ",[497,5348,5351],{"href":5349,"rel":5350},"http://chromedriver.storage.googleapis.com/index.html%EF%BC%8C%E7%9B%AE%E5%89%8D%E5%8F%AA%E6%9C%8932%E4%BD%8D%E7%9A%84%E3%80%82",[590],"http://chromedriver.storage.googleapis.com/index.html，目前只有32位的。","\n1.驱动下载完成，解压\n2.将解压后文件chromedriver.exe复制到python的Scripts安装目录下（我的：E:\\Python\\Python36-32\\Scripts），并且添加到path环境变量\n3.将目录chrome的安装目录添加到path环境变量。（我的：C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe）\n4.运行下面代码：成功打开百度即为成功：（别拉到cmd里直接运行，各种报错，我是在VS终端下调试成功的）\nfrom selenium import webdriver\ndriver = webdriver.Chrome()",[5354,5355],"br",{},"\n　　driver.get('",[497,5358,5359],{"href":5359,"rel":5360},"http://www.baidu.com",[590],"')",[27,5363,5364],{},"然后是详细代码：(代码转自奥哥)",[123,5366,5368],{"id":5367},"seleclasspy主入口文件","seleclass.py主入口文件",[16,5370,5373],{"className":5371,"code":5372,"language":774,"meta":22},[772],"# -*- coding: utf-8 -*-\nimport json\nimport time\nfrom selenium import webdriver\nfrom selenium.webdriver.common.action_chains import ActionChains\nfrom image_diff import Diff\n\n\nclass SeleClass:\n    def __init__(self, url, username, password):\n        self.url = url\n        self.wdc_option = webdriver.ChromeOptions()\n        self.wdc_option.add_argument('--headless')\n        self.wdc_option.add_argument('--disable-gpu')\n        self.wdc = webdriver.Chrome(options=self.wdc_option)\n        self.wdc.implicitly_wait(10)\n        self.wdc.get(self.url)\n        self.wdc.maximize_window()\n        self.input(username, password)\n\n    def input(self, username, password):\n        self.wdc.find_element_by_id('login-username').send_keys(username)\n        self.wdc.find_element_by_id('login-passwd').send_keys(password)\n        self.mouseLogin()\n\n    def mouseLogin(self):\n        ActionChains(self.wdc).move_to_element(self.wdc.find_elements_by_class_name('btn-login')[0]).perform()\n        ActionChains(self.wdc).click().perform()\n        self.mouseMove(Diff(**self.getBase64Data()).hOption())\n        self.isLogin()\n\n    def doScript(self, script):\n        for _ in range(10):\n            try:\n                if len(self.wdc.execute_script(script)) \u003C 3000:\n                    print('错误数据大小：%s' % len(self.wdc.execute_script(script)), end='\\t')\n                    raise BaseException()\n                return self.wdc.execute_script(script)\n            except:\n                time.sleep(1)\n        raise BaseException('拉取数据超时')\n\n    def mouseMove(self, long):\n        print('鼠标移动位移为：%s' % long)\n        ActionChains(self.wdc).click_and_hold(\n            self.wdc.find_elements_by_class_name('geetest_slider_button')[0]).perform()\n        ActionChains(self.wdc).move_by_offset(long, 0).perform()\n        time.sleep(1)\n        ActionChains(self.wdc).release().perform()\n\n    def getBase64Data(self):\n        bg = self.doScript(\"return $('.geetest_canvas_bg')[0].toDataURL('image/png')\")[22::]\n        full = self.doScript(\"return $('.geetest_canvas_fullbg')[0].toDataURL('image/png')\")[22::]\n        slice = self.doScript(\"return $('.geetest_canvas_slice')[0].toDataURL('image/png')\")[22::]\n        print('真实数据大小：', len(bg), len(full), len(slice))\n        return {\n            'b1': bg,\n            'b2': full,\n            'b3': slice\n        }\n\n    def isLogin(self):\n        for _ in range(10):\n            if self.wdc.get_cookie('DedeUserID'):\n                self.wdc.get('https://space.bilibili.com/' + self.wdc.get_cookie('DedeUserID')['value'])\n                print('登陆成功：%s' % self.wdc.find_element_by_id('h-name').text)\n                return\n            else:\n                time.sleep(1)\n        raise BaseException('页面跳转超时')\n\n    def __del__(self):\n        time.sleep(2)\n        self.wdc.close()\n\n\nif __name__ == '__main__':\n    s = SeleClass('https://passport.bilibili.com/login', 'username', 'password')\n\n",[24,5374,5372],{"__ignoreMap":22},[123,5376,5378],{"id":5377},"image_diffpy图片处理文件","image_diff.py图片处理文件",[16,5380,5383],{"className":5381,"code":5382,"language":774,"meta":22},[772],"from skimage.measure import compare_ssim\nimport argparse\nimport imutils\nimport cv2\nimport numpy as np\nfrom base64 import b64decode\n\n\nclass Diff:\n    def __init__(self, b1, b2, b3):\n        self.imageA = cv2.imdecode(np.frombuffer(b64decode(b1), np.uint8), cv2.COLOR_RGB2BGR)\n        self.imageB = cv2.imdecode(np.frombuffer(b64decode(b2), np.uint8), cv2.COLOR_RGB2BGR)\n        self.imageC = cv2.imdecode(np.frombuffer(b64decode(b3), np.uint8), cv2.COLOR_RGB2BGR)\n\n    def show(self):\n        cv2.imshow('imga', self.imageA)\n        cv2.imshow('imgb', self.imageB)\n        cv2.imshow('imgc', self.imageC)\n        if chr(cv2.waitKey(0)) == 'q':\n            cv2.destroyAllWindows()\n\n    def hOption(self):\n        grayA = cv2.cvtColor(self.imageA, cv2.COLOR_BGR2GRAY)\n        grayB = cv2.cvtColor(self.imageB, cv2.COLOR_BGR2GRAY)\n        grayC = cv2.cvtColor(self.imageC, cv2.COLOR_BGR2GRAY)\n        npArr_3 = np.array(grayC)\n        return self.diff(grayA, grayB, npArr_3)\n\n    def diff(self, grayA, grayB, npArr_3):\n        (score, diff) = compare_ssim(grayA, grayB, full=True)\n        diff = (diff * 255).astype(\"uint8\")\n        print('图片相似度为：%s' % score)\n        return self.tf(diff, npArr_3)\n\n    def tf(self, diff, npArr_3):\n        thresh = cv2.threshold(diff, 0, 255,\n                               cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU)[1]\n        cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL,\n                                cv2.CHAIN_APPROX_SIMPLE)\n        cnts = cnts[1] if imutils.is_cv3() else cnts[0]\n\n        img3_t = cv2.threshold(npArr_3, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1]\n\n        cnts_3 = cv2.findContours(img3_t.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n        cnts_3 = cnts_3[1] if imutils.is_cv3() else cnts_3[0]\n        return self.getBr(cnts, cnts_3)\n\n    def getBr(self, cnts, cnts_3):\n        x1, y1, w1, h1 = cv2.boundingRect(cnts[-1])\n        cv2.rectangle(self.imageA, (x1, y1), (x1 + w1, y1 + h1), (0, 0, 255), 1)\n        cv2.rectangle(self.imageB, (x1, y1), (x1 + w1, y1 + h1), (0, 0, 255), 1)\n        x2, y2, w2, h2 = cv2.boundingRect(cnts_3[-1])\n        cv2.rectangle(self.imageC, (x2, y2), (x2 + w2, y2 + h2), (0, 0, 255), 1)\n        cv2.rectangle(self.imageC, (x2, y2), (x2 + w2, y2 + h2), (0, 0, 255), 1)\n        return x1 - x2\n",[24,5384,5382],{"__ignoreMap":22},[27,5386,5387],{},[551,5388],{"alt":22,"src":5389},"images/weiyi.jpg",[27,5391,5392],{},[551,5393],{"alt":22,"src":5394},"images/weiyi2.jpg",{"title":22,"searchDepth":95,"depth":95,"links":5396},[],"python中安装好selenium包  pip install selenium\n点击下载chrome的webdriver： http://chromedriver.storage.googleapis.com/index.html，目前只有32位的。\n1.驱动下载完成，解压\n2.将解压后文件chromedriver.exe复制到python的Scripts安装目录下（我的：E:\\Python\\Python36-32\\Scripts），并且添加到path环境变量\n3.将目录chrome的安装目录添加到path环境变量。（我的：C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe）\n4.运行下面代码：成功打开百度即为成功：（别拉到cmd里直接运行，各种报错，我是在VS终端下调试成功的）\nfrom selenium import webdriver\ndriver = webdriver.Chrome()\n　　driver.get('http://www.baidu.com')",{"date":5399,"tags":5400},"2020-04-07 23:18:53",[2568,5401],"验证","/selenium",{"title":5341,"description":5397},"qWLX_LBs8W4bOlKKhpa71xLrnVCOtyKBWlZ0ZlVNZW4",{"id":5406,"title":5407,"body":5408,"description":5412,"extension":104,"meta":5628,"navigation":110,"path":5630,"seo":5631,"stem":5632,"__hash__":5633},"content/vuex.md","5分钟带你Vuex入门",{"type":8,"value":5409,"toc":5621},[5410,5413,5416,5422,5428,5431,5436,5442,5445,5449,5452,5457,5462,5465,5470,5474,5477,5482,5485,5490,5493,5498,5502,5505,5510,5513,5518,5521,5524,5529,5532,5537,5540,5544,5547,5552,5555,5560,5563,5568,5571,5576,5581,5584,5588,5591,5594,5599,5602,5613,5618],[27,5411,5412],{},"如果你之前使用过 vue.js，你一定知道在 vue 中各个组件之间传值的痛苦，在 vue 中我们可以使用 vuex 来保存我们需要管理的状态值，值一旦被修改，所有引用该值的地方就会自动更新，那么接下来我们就来学习一下 vuex 是如何修改状态值的：",[27,5414,5415],{},"我们新建一个 vue 项目（这里由于我们是讲解 vuex，所以对于 vue 项目的创建我们不会讲解太详细）；在命令行输入 vue init webpack web（使用 webpack 创建一个项目名为 web 的项目）；",[27,5417,5418,5419],{},"项目创建后，然后安装 vuex，使用命令：npm install vuex --save（安装 vuex 保存到本地），安装成功后你会看到这个界面：\n",[551,5420],{"alt":22,"src":5421},"images/v1.jpg",[27,5423,5424,5425],{},"然后我们执行 npm run dev 启动项目，在浏览器输入：\"localhost:8080\"；正常的话然后我们会看到项目的启动页，\n",[551,5426],{"alt":22,"src":5427},"images/v2.jpg",[27,5429,5430],{},"看到这个界面说明项目启动成功，然后我们在项目的 src 目录下新建一个目录 store，在该目录下新建一个 index.js 文件，我们用来创建 vuex 实例，然后在该文件中引入 vue 和 vuex，创建 Vuex.Store 实例保存到变量 store 中，最后使用 export default 导出 store：",[27,5432,5433],{},[551,5434],{"alt":22,"src":5435},"images/v3.jpg",[27,5437,5438,5439],{},"然后我们在 main.js 文件中引入该文件，在文件里面添加 import store from ‘./store’;，再在 vue 实例全局引入 store 对象；\n",[551,5440],{"alt":22,"src":5441},"images/v4.jpg",[27,5443,5444],{},"然后我们就可以开始编写我们的 vuex 业务代码了，那么，我们的数据如何保存？",[11,5446,5448],{"id":5447},"state","State：",[27,5450,5451],{},"vuex 中的数据源，我们需要保存的数据就保存在这里，可以在页面通过 this.$store.state 来获取我们定义的数据；",[27,5453,5454],{},[551,5455],{"alt":22,"src":5456},"images/v5.jpg",[27,5458,5459],{},[551,5460],{"alt":22,"src":5461},"images/v6.jpg",[27,5463,5464],{},"这时候我们页面上就得到了这个 count 值 1：",[27,5466,5467],{},[551,5468],{"alt":22,"src":5469},"images/v21.jpg",[11,5471,5473],{"id":5472},"getters","Getters：",[27,5475,5476],{},"Getter 相当于 vue 中的 computed 计算属性，getter 的返回值会根据它的依赖被缓存起来，且只有当它的依赖值发生了改变才会被重新计算，这里我们可以通过定义 vuex 的 Getter 来获取，Getters 可以用于监听、state 中的值的变化，返回计算后的结果，这里我们修改 Hello World.vue 文件如下：",[27,5478,5479],{},[551,5480],{"alt":22,"src":5481},"images/v7.jpg",[27,5483,5484],{},"再修改 index.js 文件如下，其中 getters 中的 getStateCount 方法接收一个参数 state，这个参数就是我们用来保存数据的那个对象；",[27,5486,5487],{},[551,5488],{"alt":22,"src":5489},"images/v8.jpg",[27,5491,5492],{},"然后我们在页面显示：",[27,5494,5495],{},[551,5496],{"alt":22,"src":5497},"images/v9.jpg",[11,5499,5501],{"id":5500},"mutations","Mutations：",[27,5503,5504],{},"数据我们在页面是获取到了，但是如果我们需要修改 count 值怎么办？如果需要修改 store 中的值唯一的方法就是提交 mutation 来修改，我们现在 Hello World.vue 文件中添加两个按钮，一个加 1，一个减 1；这里我们点击按钮调用 addFun（执行加的方法）和 reductionFun（执行减法的方法），然后在里面直接提交 mutations 中的方法修改值：",[27,5506,5507],{},[551,5508],{"alt":22,"src":5509},"images/v10.jpg",[27,5511,5512],{},"修改 index.js 文件，添加 mutations，在 mutations 中定义两个函数，用来对 count 加 1 和减 1，这里定义的两个方法就是上面 commit 提交的两个方法如下：",[27,5514,5515],{},[551,5516],{"alt":22,"src":5517},"images/v11.jpg",[27,5519,5520],{},"页面上点击+、- 按钮操作数据：",[27,5522,5523],{},"点击 “+”按钮",[27,5525,5526],{},[551,5527],{"alt":22,"src":5528},"images/v12.jpg",[27,5530,5531],{},"执行成功，我们再连续点击三次减 “-” 按钮",[27,5533,5534],{},[551,5535],{"alt":22,"src":5536},"images/v13.jpg",[27,5538,5539],{},"ok！完美。",[11,5541,5543],{"id":5542},"actions","Actions：",[27,5545,5546],{},"我们看到，当点击三次后值从 2 变成了-1；页面上的值是改变了；我们达到了修改 store 中状态值的目的，但是，官方并不介意我们这样直接去修改 store 里面的值，而是让我们去提交一个 actions，在 actions 中提交 mutation 再去修改状态值，接下来我们修改 index.js 文件，先定义 actions 提交 mutation 的函数：",[27,5548,5549],{},[551,5550],{"alt":22,"src":5551},"images/v14.jpg",[27,5553,5554],{},"然后我们去修改 Hello World.vue 文件：",[27,5556,5557],{},[551,5558],{"alt":22,"src":5559},"images/v15.jpg",[27,5561,5562],{},"这里我们把 commit 提交 mutations 修改为使用 dispatch 来提交 actions；我们点击页面，效果是一样的。",[27,5564,5565],{},[551,5566],{"alt":22,"src":5567},"images/v16.jpg",[27,5569,5570],{},"好了，我们这里已经实现了一个基本的 vuex 修改状态值的完整流程，如果我们需要指定加减的数值，那么我们直接传入 dispatch 中的第二个参数，然后在 actions 中的对应函数中接受参数在传递给 mutations 中的函数进行计算：",[27,5572,5573],{},[551,5574],{"alt":22,"src":5575},"images/v17.jpg",[27,5577,5578],{},[551,5579],{"alt":22,"src":5580},"images/v18.jpg",[27,5582,5583],{},"这个时候我们再去点击“ - ”按钮就会发现不再是减 1 了，而是减去 10 了。",[11,5585,5587],{"id":5586},"mapstatemapgettersmapactions","mapState、mapGetters、mapActions",[27,5589,5590],{},"如果我们不喜欢这种在页面上使用“this.$stroe.state.count”和“this.$store.dispatch('funName')”这种很长的写法，那么我们可以使用 mapState、mapGetters、mapActions 就不会这么麻烦了；",[27,5592,5593],{},"我们修改 Hello World.vue 文件如下：",[27,5595,5596],{},[551,5597],{"alt":22,"src":5598},"images/v19.jpg",[27,5600,5601],{},"此时我们在页面使用 count1 调用：",[27,5603,5604,5605,5609,5610],{},"相当于定义了一个全局变量",[5606,5607],"binding",{"value":5608},"count1","替代了",[5606,5611],{"value":5612},"this.$store.state.count",[27,5614,5615],{},[551,5616],{"alt":22,"src":5617},"images/v20.jpg",[27,5619,5620],{},"正常显示，效果是一样的，我们就可以不再使用很长的写法来调用了。",{"title":22,"searchDepth":95,"depth":95,"links":5622},[5623,5624,5625,5626,5627],{"id":5447,"depth":95,"text":5448},{"id":5472,"depth":95,"text":5473},{"id":5500,"depth":95,"text":5501},{"id":5542,"depth":95,"text":5543},{"id":5586,"depth":95,"text":5587},{"date":5629,"tags":271},"2020-03-15 16:45:13","/vuex",{"title":5407,"description":5412},"vuex","QQgOkchY8kbDEtGG3WiElmgRiczd7YoXybh_z2ilFQM",{"id":5635,"title":5636,"body":5637,"description":22,"extension":104,"meta":6780,"navigation":110,"path":6784,"seo":6785,"stem":5991,"__hash__":6786},"content/webpack.md","Webpack入门教程",{"type":8,"value":5638,"toc":6776},[5639,5643,5647,5650,5664,5667,5671,5678,5682,5685,5696,5706,5709,5713,5716,5719,5722,5728,5732,5766,5846,5852,5858,5864,5870,6443,6530,6765],[73,5640,5642],{"id":5641},"什么是webpack为什么要使用它","什么是WebPack，为什么要使用它？",[995,5644,5646],{"id":5645},"为什要使用webpack","为什要使用WebPack",[27,5648,5649],{},"现今的很多网页其实可以看做是功能丰富的应用，它们拥有着复杂的JavaScript代码和一大堆依赖包。为了简化开发的复杂度，前端社区涌现出了很多好的实践方法",[456,5651,5652,5655,5658,5661],{},[459,5653,5654],{},"模块化，让我们可以把复杂的程序细化为小的文件;",[459,5656,5657],{},"类似于TypeScript这种在JavaScript基础上拓展的开发语言：使我们能够实现目前版本的JavaScript不能直接使用的特性，并且之后还能转换为JavaScript文件使浏览器可以识别；",[459,5659,5660],{},"Scss，less等CSS预处理器",[459,5662,5663],{},"...",[27,5665,5666],{},"这些改进确实大大的提高了我们的开发效率，但是利用它们开发的文件往往需要进行额外的处理才能让浏览器识别,而手动处理又是非常繁琐的，这就为WebPack类的工具的出现提供了需求。",[995,5668,5670],{"id":5669},"什么是webpack","什么是Webpack",[27,5672,5673,5674,5677],{},"WebPack可以看做是",[3316,5675,5676],{},"模块打包机","：它做的事情是，分析你的项目结构，找到JavaScript模块以及其它的一些浏览器不能直接运行的拓展语言（Scss，TypeScript等），并将其转换和打包为合适的格式供浏览器使用。",[995,5679,5681],{"id":5680},"webpack和grunt以及gulp相比有什么特性","WebPack和Grunt以及Gulp相比有什么特性",[27,5683,5684],{},"其实Webpack和另外两个并没有太多的可比性，Gulp/Grunt是一种能够优化前端的开发流程的工具，而WebPack是一种模块化的解决方案，不过Webpack的优点使得Webpack在很多场景下可以替代Gulp/Grunt类的工具。",[27,5686,5687,5688],{},"Grunt和Gulp的工作方式是：在一个配置文件中，指明对某些文件进行类似编译，组合，压缩等任务的具体步骤，工具之后可以自动替你完成这些任务。\n",[1373,5689,5692],{"className":5690},[5691],"img-wrap",[551,5693],{"alt":5694,"src":5695,"title":5694},"Grunt和Gulp的工作流程","images/grunt.png",[27,5697,5698,5699],{},"Webpack的工作方式是：把你的项目当做一个整体，通过一个给定的主文件（如：index.js），Webpack将从这个文件开始找到你的项目的所有依赖文件，使用loaders处理它们，最后打包为一个（或多个）浏览器可识别的JavaScript文件。\n",[1373,5700,5702],{"className":5701},[5691],[551,5703],{"alt":5704,"src":5705,"title":5704},"Webpack工作方式","images/webpack.png",[27,5707,5708],{},"如果实在要把二者进行比较，Webpack的处理速度更快更直接，能打包更多不同类型的文件。",[73,5710,5712],{"id":5711},"开始使用webpack","开始使用Webpack",[27,5714,5715],{},"初步了解了Webpack工作方式后，我们一步步的开始学习使用Webpack。",[995,5717,5718],{"id":5718},"安装",[27,5720,5721],{},"Webpack可以使用npm安装，新建一个空的练习文件夹（此处命名为webpack sample project），在终端中转到该文件夹后执行下述指令就可以完成安装。",[16,5723,5726],{"className":5724,"code":5725,"language":21,"meta":22},[19],"    //全局安装\n    npm install -g webpack\n    //安装到你的项目目录\n    npm install --save-dev webpack\n",[24,5727,5725],{"__ignoreMap":22},[995,5729,5731],{"id":5730},"正式使用webpack前的准备","正式使用Webpack前的准备",[4451,5733,5734,5750,5759],{},[459,5735,5736,5737,5740,5741,5747,5749],{},"在上述练习文件夹中创建一个package.json文件，这是一个标准的npm说明文件，里面蕴含了丰富的信息，包括当前项目的依赖模块，自定义的脚本任务等等。在终端中使用",[24,5738,5739],{},"npm init","命令可以自动创建这个package.json文件",[16,5742,5745],{"className":5743,"code":5744,"language":2197},[2195],"npm init\n",[24,5746,5744],{"__ignoreMap":22},[5354,5748],{},"输入这个命令后，终端会问你一系列诸如项目名称，项目描述，作者等信息，不过不用担心，如果你不准备在npm中发布你的模块，这些问题的答案都不重要，回车默认即可。",[459,5751,5752,5753],{},"package.json文件已经就绪，我们在本项目中安装Webpack作为依赖包",[16,5754,5757],{"className":5755,"code":5756,"language":2197},[2195],"// 安装Webpack\nnpm install --save-dev webpack\n",[24,5758,5756],{"__ignoreMap":22},[459,5760,5761,5762,5765],{},"回到之前的空文件夹，并在里面创建两个文件夹,app文件夹和public文件夹，app文件夹用来存放原始数据和我们将写的JavaScript模块，public文件夹用来存放之后供浏览器读取的文件（包括使用webpack打包生成的js文件以及一个",[24,5763,5764],{},"index.html","文件）。接下来我们再创建三个文件:",[456,5767,5768,5773,5779],{},[459,5769,5770,5772],{},[24,5771,5764],{}," --放在public文件夹中;",[459,5774,5775,5778],{},[24,5776,5777],{},"Greeter.js","-- 放在app文件夹中;",[459,5780,5781,5778,5784,5786,5787,5794,5796,5797,5799,5800,5803,5804,5810,5796,5812,5814,5815,5817,5818,5824,5826,5828,5829,5832,5833,5839,5843,5845],{},[24,5782,5783],{},"main.js",[5354,5785],{},"此时项目结构如下图所示\n",[1373,5788,5790],{"className":5789},[5691],[551,5791],{"alt":5792,"src":5793,"title":5792},"项目结构","images/xmjg.png",[5354,5795],{},"我们在",[3316,5798,5764],{},"文件中写入最基础的html代码，它在这里目的在于引入打包后的js文件（这里我们先把之后打包后的js文件命名为",[24,5801,5802],{},"bundle.js","，之后我们还会详细讲述）。",[16,5805,5808],{"className":5806,"code":5807,"language":2197},[2195],"\u003C!-- index.html -->\n\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n    \u003Cmeta charset=\"utf-8\">\n    \u003Ctitle>Webpack Sample Project\u003C/title>\n\u003C/head>\n\u003Cbody>\n    \u003Cdiv id='root'>\n    \u003C/div>\n    \u003Cscript src=\"bundle.js\">\u003C/script>\n\u003C/body>\n\u003C/html>\n",[24,5809,5807],{"__ignoreMap":22},[5354,5811],{},[24,5813,5777],{},"中定义一个返回包含问候信息的",[24,5816,256],{},"元素的函数,并依据CommonJS规范导出这个函数为一个模块：",[16,5819,5822],{"className":5820,"code":5821,"language":2197},[2195],"// Greeter.js\nmodule.exports = function() {\n  var greet = document.createElement('div');\n  greet.textContent = \"Hi there and greetings!\";\n  return greet;\n};\n",[24,5823,5821],{"__ignoreMap":22},[5354,5825],{},[24,5827,5783],{},"文件中我们写入下述代码，用以把",[24,5830,5831],{},"Greeter模块","返回的节点插入页面。",[16,5834,5837],{"className":5835,"code":5836,"language":2197},[2195],"//main.js \nconst greeter = require('./Greeter.js');\ndocument.querySelector(\"#root\").appendChild(greeter());\n",[24,5838,5836],{"__ignoreMap":22},[995,5840,5842],{"id":5841},"正式使用webpack","正式使用Webpack",[5354,5844],{},"webpack可以在终端中使用，在基本的使用方法如下：",[16,5847,5850],{"className":5848,"code":5849,"language":21,"meta":22},[19]," # {extry file}出填写入口文件的路径，本文中就是上述main.js的路径，\n # {destination for bundled file}处填写打包文件的存放路径\n # 填写路径的时候不用添加{}\n webpack {entry file} {destination for bundled file}\n",[24,5851,5849],{"__ignoreMap":22},[16,5853,5856],{"className":5854,"code":5855,"language":2197},[2195],"指定入口文件后，webpack将自动识别项目所依赖的其它文件，不过需要注意的是如果你的webpack不是全局安装的，那么当你在终端中使用此命令时，需要额外指定其在node_modules中的地址，继续上面的例子，在终端中输入如下命令\n",[24,5857,5855],{"__ignoreMap":22},[16,5859,5862],{"className":5860,"code":5861,"language":21,"meta":22},[19]," # webpack非全局安装的情况\n node_modules/.bin/webpack app/main.js public/bundle.js\n ```\n\n 结果如下\n\n \u003Cspan class=\"img-wrap\">![使用命令行打包](images/symlh.png \"使用命令行打包\")\u003C/span>\n\n 可以看出`webpack`同时编译了`main.js` 和`Greeter,js`,现在打开`index.html`,可以看到如下结果\n \u003Cspan class=\"img-wrap\">![htmlResult1](images/htmlresult.png \"htmlResult1\")\u003C/span>\n\n 有没有很激动，已经成功的使用`Webpack`打包了一个文件了。不过在终端中进行复杂的操作，其实是不太方便且容易出错的，接下来看看Webpack的另一种更常见的使用方法。\n\n #### 通过配置文件来使用`Webpack`\n\n Webpack拥有很多其它的比较高级的功能（比如说本文后面会介绍的`loaders`和`plugins`），这些功能其实都可以通过命令行模式实现，但是正如前面提到的，这样不太方便且容易出错的，更好的办法是定义一个配置文件，这个配置文件其实也是一个简单的JavaScript模块，我们可以把所有的与打包相关的信息放在里面。\n\n 继续上面的例子来说明如何写这个配置文件，在当前练习文件夹的根目录下新建一个名为`webpack.config.js`的文件，我们在其中写入如下所示的简单配置代码，目前的配置主要涉及到的内容是入口文件路径和打包后文件的存放路径。\n\n ```\n module.exports = {\n   entry:  __dirname + \"/app/main.js\",//已多次提及的唯一入口文件\n   output: {\n     path: __dirname + \"/public\",//打包后的文件存放的地方\n     filename: \"bundle.js\"//打包后输出文件的文件名\n   }\n }\n ```\n  **注**：“__dirname”是node.js中的一个全局变量，它指向当前执行脚本所在的目录。\n\n 有了这个配置之后，再打包文件，只需在终端里运行`webpack(非全局安装需使用node_modules/.bin/webpack)`命令就可以了，这条命令会自动引用`webpack.config.js`文件中的配置选项，示例如下：\n\n \u003Cspan class=\"img-wrap\">![配合配置文件进行打包](images/ph.png \"配合配置文件进行打包\")\u003C/span>\n\n 又学会了一种使用`Webpack`的方法，这种方法不用管那烦人的命令行参数，有没有感觉很爽。如果我们可以连`webpack(非全局安装需使用node_modules/.bin/webpack)`这条命令都可以不用，那种感觉会不会更爽~，继续看下文。\n\n #### 更快捷的执行打包任务\n\n 在命令行中输入命令需要代码类似于`node_modules/.bin/webpack`这样的路径其实是比较烦人的，不过值得庆幸的是`npm`可以引导任务执行，对`npm`进行配置后可以在命令行中使用简单的`npm start`命令来替代上面略微繁琐的命令。在`package.json`中对`scripts`对象进行相关设置即可，设置方法如下。\n\n ```\n {\n   \"name\": \"webpack-sample-project\",\n   \"version\": \"1.0.0\",\n   \"description\": \"Sample webpack project\",\n   \"scripts\": {\n     \"start\": \"webpack\" // 修改的是这里，JSON文件不支持注释，引用时请清除\n   },\n   \"author\": \"zhang\",\n   \"license\": \"ISC\",\n   \"devDependencies\": {\n     \"webpack\": \"3.10.0\"\n   }\n }`\n ```\n > **注：**`package.json`中的`script`会安装一定顺序寻找命令对应位置，本地的`node_modules/.bin`路径就在这个寻找清单中，所以无论是全局还是局部安装的Webpack，你都不需要写前面那指明详细的路径了。\n\n npm的`start`命令是一个特殊的脚本名称，其特殊性表现在，在命令行中使用`npm start`就可以执行其对于的命令，如果对应的此脚本名称不是`start`，想要在命令行中运行时，需要这样用`npm run {script name}`如`npm run build`，我们在命令行中输入`npm start`试试，输出结果如下：\n\n \u003Cspan class=\"img-wrap\">![使用npm start 打包代码](images/npm.png \"使用npm start 打包代码\")\u003C/span>\n\n 现在只需要使用`npm start`就可以打包文件了，有没有觉得`webpack`也不过如此嘛，不过不要太小瞧`webpack`，要充分发挥其强大的功能我们需要修改配置文件的其它选项，一项项来看。\n\n ### Webpack的强大功能\n\n #### 生成Source Maps（使调试更容易）\n\n 开发总是离不开调试，方便的调试能极大的提高开发效率，不过有时候通过打包后的文件，你是不容易找到出错了的地方，对应的你写的代码的位置的，`Source Maps`就是来帮我们解决这个问题的。\n\n 通过简单的配置，`webpack`就可以在打包时为我们生成的`source maps`，这为我们提供了一种对应编译文件和源文件的方法，使得编译后的代码可读性更高，也更容易调试。\n\n 在`webpack`的配置文件中配置`source maps`，需要配置`devtool`，它有以下四种不同的配置选项，各具优缺点，描述如下：\n\n \u003Ctable>\n \u003Cthead>\u003Ctr>\n \u003Cth>devtool选项\u003C/th>\n \u003Cth>配置结果\u003C/th>\n \u003C/tr>\u003C/thead>\n \u003Ctbody>\n \u003Ctr>\n \u003Ctd>`source-map`\u003C/td>\n \u003Ctd>在一个单独的文件中产生一个完整且功能完全的文件。这个文件具有最好的`source map`，但是它会减慢打包速度；\u003C/td>\n \u003C/tr>\n \u003Ctr>\n \u003Ctd>`cheap-module-source-map`\u003C/td>\n \u003Ctd>在一个单独的文件中生成一个不带列映射的`map`，不带列映射提高了打包速度，但是也使得浏览器开发者工具只能对应到具体的行，不能对应到具体的列（符号），会对调试造成不便；\u003C/td>\n \u003C/tr>\n \u003Ctr>\n \u003Ctd>`eval-source-map`\u003C/td>\n \u003Ctd>使用`eval`打包源文件模块，在同一个文件中生成干净的完整的`source map`。这个选项可以在不影响构建速度的前提下生成完整的`sourcemap`，但是对打包后输出的JS文件的执行具有性能和安全的隐患。在开发阶段这是一个非常好的选项，在生产阶段则一定不要启用这个选项；\u003C/td>\n \u003C/tr>\n \u003Ctr>\n \u003Ctd>`cheap-module-eval-source-map`\u003C/td>\n \u003Ctd>这是在打包文件时最快的生成`source map`的方法，生成的`Source Map` 会和打包后的`JavaScript`文件同行显示，没有列映射，和`eval-source-map`选项具有相似的缺点；\u003C/td>\n \u003C/tr>\n \u003C/tbody>\n \u003C/table>\n\n 正如上表所述，上述选项由上到下打包速度越来越快，不过同时也具有越来越多的负面作用，较快的打包速度的后果就是对打包后的文件的的执行有一定影响。\n\n 对小到中型的项目中，`eval-source-map`是一个很好的选项，再次强调你只应该开发阶段使用它，我们继续对上文新建的`webpack.config.js`，进行如下配置:\n\n ```\n module.exports = {\n   devtool: 'eval-source-map',\n   entry:  __dirname + \"/app/main.js\",\n   output: {\n     path: __dirname + \"/public\",\n     filename: \"bundle.js\"\n   }\n }\n",[24,5863,5861],{"__ignoreMap":22},[16,5865,5868],{"className":5866,"code":5867,"language":2197},[2195]," `cheap-module-eval-source-map`方法构建速度更快，但是不利于调试，推荐在大型项目考虑时间成本时使用。\n\n#### 使用webpack构建本地服务器\n\n想不想让你的浏览器监听你的代码的修改，并自动刷新显示修改后的结果，其实`Webpack`提供一个可选的本地开发服务器，这个本地服务器基于node.js构建，可以实现你想要的这些功能，不过它是一个单独的组件，在webpack中进行配置之前需要单独安装它作为项目依赖\n\n```\nnpm install --save-dev webpack-dev-server\n```\n\ndevserver作为webpack配置选项中的一项，以下是它的一些配置选项，更多配置可参考[这里](https://webpack.js.org/configuration/dev-server/)\n\n\u003Ctable>\n\u003Cthead>\u003Ctr>\n\u003Cth>devserver的配置选项\u003C/th>\n\u003Cth>功能描述\u003C/th>\n\u003C/tr>\u003C/thead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>contentBase\u003C/td>\n\u003Ctd>默认webpack-dev-server会为根文件夹提供本地服务器，如果想为另外一个目录下的文件提供本地服务器，应该在这里设置其所在目录（本例设置到“public\"目录）\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>port\u003C/td>\n\u003Ctd>设置默认监听端口，如果省略，默认为”8080“\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>inline\u003C/td>\n\u003Ctd>设置为`true`，当源文件改变时会自动刷新页面\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>historyApiFallback\u003C/td>\n\u003Ctd>在开发单页应用时非常有用，它依赖于HTML5 history API，如果设置为`true`，所有的跳转将指向index.html\u003C/td>\n\u003C/tr>\n\u003C/tbody>\n\u003C/table>\n\n把这些命令加到webpack的配置文件中，现在的配置文件`webpack.config.js`如下所示\n\n```\nmodule.exports = {\n  devtool: 'eval-source-map',\n\n  entry:  __dirname + \"/app/main.js\",\n  output: {\n    path: __dirname + \"/public\",\n    filename: \"bundle.js\"\n  },\n\n  devServer: {\n    contentBase: \"./public\",//本地服务器所加载的页面所在的目录\n    historyApiFallback: true,//不跳转\n    inline: true//实时刷新\n  } \n}\n```\n\n在`package.json`中的`scripts`对象中添加如下命令，用以开启本地服务器：\n\n```\n \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" &amp;&amp; exit 1\",\n    \"start\": \"webpack\",\n    \"server\": \"webpack-dev-server --open\"\n  },\n```\n\n在终端中输入`npm run server`即可在本地的`8080`端口查看结果\n\n\u003Cspan class=\"img-wrap\">![开启本地服务器](images/kqbdfwq.png \"开启本地服务器\")\u003C/span>\n\n#### Loaders\n\n**鼎鼎大名的Loaders登场了！**\n\n`Loaders`是`webpack`提供的最激动人心的功能之一了。通过使用不同的`loader`，`webpack`有能力调用外部的脚本或工具，实现对不同格式的文件的处理，比如说分析转换scss为css，或者把下一代的JS文件（ES6，ES7)转换为现代浏览器兼容的JS文件，对React的开发而言，合适的Loaders可以把React的中用到的JSX文件转换为JS文件。\n\nLoaders需要单独安装并且需要在`webpack.config.js`中的`modules`关键字下进行配置，Loaders的配置包括以下几方面：\n",[24,5869,5867],{"__ignoreMap":22},[456,5871,5872,5878,5884,5890,5952,5955,6286,6291,6296],{},[459,5873,5874,5877],{},[24,5875,5876],{},"test","：一个用以匹配loaders所处理文件的拓展名的正则表达式（必须）",[459,5879,5880,5883],{},[24,5881,5882],{},"loader","：loader的名称（必须）",[459,5885,5886,5889],{},[24,5887,5888],{},"include/exclude",":手动添加必须处理的文件（文件夹）或屏蔽不需要处理的文件（文件夹）（可选）；",[459,5891,5892,5895,5896,5898,5899,5901,5902,5904,5905,5907,5908,5911,5912,5918,5920,5921,5927,5929,5932,5933,5936,5937,5940,5941,5944,5945,5949,5951],{},[24,5893,5894],{},"query","：为loaders提供额外的设置选项（可选）",[5354,5897],{},"不过在配置loader之前，我们把",[24,5900,5777],{},"里的问候消息放在一个单独的JSON文件里,并通过合适的配置使",[24,5903,5777],{},"可以读取该JSON文件的值，各文件修改后的代码如下：",[5354,5906],{},"在app文件夹中创建带有问候信息的JSON文件(命名为",[24,5909,5910],{},"config.json",")",[16,5913,5916],{"className":5914,"code":5915,"language":2197},[2195],"{\n  \"greetText\": \"Hi there and greetings from JSON!\"\n}\n",[24,5917,5915],{"__ignoreMap":22},[5354,5919],{},"更新后的Greeter.js",[16,5922,5925],{"className":5923,"code":5924,"language":2197},[2195],"var config = require('./config.json');\n\nmodule.exports = function() {\n  var greet = document.createElement('div');\n  greet.textContent = config.greetText;\n  return greet;\n};\n",[24,5926,5924],{"__ignoreMap":22},[5354,5928],{},[3316,5930,5931],{},"注"," 由于",[24,5934,5935],{},"webpack3.*/webpack2.*","已经内置可处理JSON文件，这里我们无需再添加",[24,5938,5939],{},"webpack1.*","需要的",[24,5942,5943],{},"json-loader","。在看如何具体使用loader之前我们先看看Babel是什么？",[73,5946,5948],{"id":5947},"babel","Babel",[5354,5950],{},"Babel其实是一个编译JavaScript的平台，它可以编译代码帮你达到以下目的：",[459,5953,5954],{},"让你能使用最新的JavaScript代码（ES6，ES7...），而不用管新标准是否被当前使用的浏览器完全支持；",[459,5956,5957,5958,5962,5964,5965,5968,5969,5972,5973,5976,5977,5979,5980,5986,5988,5989,5992,5993,5999,6001,6002,6008,6010,6011,6013,6014,6020,6022,6023,6025,6026,6032,6034,6035,6038,6039,6042,6043,6046,6047,6050,6051,6053,6059,6063,6065,6066,6069,6070,6072,6073,6076,6077,6083,6089,6091,6092,6095,6097,6098,6102,6104,6105,6108,6109,6112,6113,6115,6116,6108,6119,6122,6123,6126,6127,6129,6130,6132,6133,6139,6141,6142,6144,6145,6151,6153,6154,6156,6157,6160,6161,6160,6164,6167,6168,6174,6180,6182,6183,6187,6189,6190,6192,6193,6195,6196,6199,6200,6206,6208,6209,6212,6213,6219,6221,6222,6225,6226,6232,6234,6235,6237,6244,6246,6247,6252,6253,6257,6259,6108,6262,6265,6266,6160,6269,6160,6272,6160,6275,6278,6279,6281,6282,6285],{},"让你能使用基于JavaScript进行了拓展的语言，比如React的JSX；",[995,5959,5961],{"id":5960},"babel的安装与配置","Babel的安装与配置",[5354,5963],{},"Babel其实是几个模块化的包，其核心功能位于称为",[24,5966,5967],{},"babel-core","的npm包中，webpack可以把其不同的包整合在一起使用，对于每一个你需要的功能或拓展，你都需要安装单独的包（用得最多的是解析Es6的",[24,5970,5971],{},"babel-env-preset","包和解析JSX的",[24,5974,5975],{},"babel-preset-react","包）。",[5354,5978],{},"我们先来一次性安装这些依赖包",[16,5981,5984],{"className":5982,"code":5983,"language":2197},[2195],"// npm一次性安装多个依赖模块，模块之间用空格隔开\nnpm install --save-dev babel-core babel-loader babel-preset-env babel-preset-react\n",[24,5985,5983],{"__ignoreMap":22},[5354,5987],{},"在",[24,5990,5991],{},"webpack","中配置Babel的方法如下:",[16,5994,5997],{"className":5995,"code":5996,"language":2197},[2195],"module.exports = {\n    entry: __dirname + \"/app/main.js\",//已多次提及的唯一入口文件\n    output: {\n        path: __dirname + \"/public\",//打包后的文件存放的地方\n        filename: \"bundle.js\"//打包后输出文件的文件名\n    },\n    devtool: 'eval-source-map',\n    devServer: {\n        contentBase: \"./public\",//本地服务器所加载的页面所在的目录\n        historyApiFallback: true,//不跳转\n        inline: true//实时刷新\n    },\n    module: {\n        rules: [\n            {\n                test: /(\\.jsx|\\.js)$/,\n                use: {\n                    loader: \"babel-loader\",\n                    options: {\n                        presets: [\n                            \"env\", \"react\"\n                        ]\n                    }\n                },\n                exclude: /node_modules/\n            }\n        ]\n    }\n};\n",[24,5998,5996],{"__ignoreMap":22},[5354,6000],{},"现在你的webpack的配置已经允许你使用ES6以及JSX的语法了。继续用上面的例子进行测试，不过这次我们会使用React，记得先安装 React 和 React-DOM",[16,6003,6006],{"className":6004,"code":6005,"language":2197},[2195],"npm install --save react react-dom\n",[24,6007,6005],{"__ignoreMap":22},[5354,6009],{},"接下来我们使用ES6的语法，更新",[24,6012,5777],{},"并返回一个React组件",[16,6015,6018],{"className":6016,"code":6017,"language":2197},[2195],"//Greeter,js\nimport React, {Component} from 'react'\nimport config from './config.json';\n\nclass Greeter extends Component{\n  render() {\n    return (\n      &lt;div&gt;\n        {config.greetText}\n      &lt;/div&gt;\n    );\n  }\n}\n\nexport default Greeter\n",[24,6019,6017],{"__ignoreMap":22},[5354,6021],{},"修改",[24,6024,5783],{},"如下，使用ES6的模块定义和渲染Greeter模块",[16,6027,6030],{"className":6028,"code":6029,"language":2197},[2195],"// main.js\nimport React from 'react';\nimport {render} from 'react-dom';\nimport Greeter from './Greeter';\n\nrender(&lt;Greeter /&gt;, document.getElementById('root'));\n",[24,6031,6029],{"__ignoreMap":22},[5354,6033],{},"重新使用",[24,6036,6037],{},"npm start","打包，如果之前打开的本地服务器没有关闭，你应该可以在",[24,6040,6041],{},"localhost:8080","下看到与之前一样的内容，这说明",[24,6044,6045],{},"react","和",[24,6048,6049],{},"es6","被正常打包了。",[5354,6052],{},[1373,6054,6056],{"className":6055},[5691],[551,6057],{"alt":6041,"src":6058,"title":6041},"images/8080.png",[995,6060,6062],{"id":6061},"babel的配置","Babel的配置",[5354,6064],{},"Babel其实可以完全在 ",[24,6067,6068],{},"webpack.config.js"," 中进行配置，但是考虑到babel具有非常多的配置选项，在单一的",[24,6071,6068],{},"文件中进行配置往往使得这个文件显得太复杂，因此一些开发者支持把babel的配置选项放在一个单独的名为 \".babelrc\" 的配置文件中。我们现在的babel的配置并不算复杂，不过之后我们会再加一些东西，因此现在我们就提取出相关部分，分两个配置文件进行配置（webpack会自动调用",[24,6074,6075],{},".babelrc","里的babel配置选项），如下：",[16,6078,6081],{"className":6079,"code":6080,"language":2197},[2195],"module.exports = {\n    entry: __dirname + \"/app/main.js\",//已多次提及的唯一入口文件\n    output: {\n        path: __dirname + \"/public\",//打包后的文件存放的地方\n        filename: \"bundle.js\"//打包后输出文件的文件名\n    },\n    devtool: 'eval-source-map',\n    devServer: {\n        contentBase: \"./public\",//本地服务器所加载的页面所在的目录\n        historyApiFallback: true,//不跳转\n        inline: true//实时刷新\n    },\n    module: {\n        rules: [\n            {\n                test: /(\\.jsx|\\.js)$/,\n                use: {\n                    loader: \"babel-loader\"\n                },\n                exclude: /node_modules/\n            }\n        ]\n    }\n};\n",[24,6082,6080],{"__ignoreMap":22},[16,6084,6087],{"className":6085,"code":6086,"language":2197},[2195],"//.babelrc\n{\n  \"presets\": [\"react\", \"env\"]\n}\n",[24,6088,6086],{"__ignoreMap":22},[5354,6090],{},"到目前为止，我们已经知道了，对于模块，Webpack能提供非常强大的处理功能，那那些是模块呢。",[73,6093,6094],{"id":6094},"一切皆模块",[5354,6096],{},"Webpack有一个不可不说的优点，它把所有的文件都都当做模块处理，JavaScript代码，CSS和fonts以及图片等等通过合适的loader都可以被处理。",[995,6099,6101],{"id":6100},"css","CSS",[5354,6103],{},"webpack提供两个工具处理样式表，",[24,6106,6107],{},"css-loader"," 和 ",[24,6110,6111],{},"style-loader","，二者处理的任务不同，",[24,6114,6107],{},"使你能够使用类似",[24,6117,6118],{},"@import",[24,6120,6121],{},"url(...)","的方法实现 ",[24,6124,6125],{},"require()","的功能,",[24,6128,6111],{},"将所有的计算后的样式加入页面中，二者组合在一起使你能够把样式表嵌入webpack打包后的JS文件中。",[5354,6131],{},"继续上面的例子",[16,6134,6137],{"className":6135,"code":6136,"language":2197},[2195],"//安装\nnpm install --save-dev style-loader css-loader\n//使用\nmodule.exports = {\n\n   ...\n    module: {\n        rules: [\n            {\n                test: /(\\.jsx|\\.js)$/,\n                use: {\n                    loader: \"babel-loader\"\n                },\n                exclude: /node_modules/\n            },\n            {\n                test: /\\.css$/,\n                use: [\n                    {\n                        loader: \"style-loader\"\n                    }, {\n                        loader: \"css-loader\"\n                    }\n                ]\n            }\n        ]\n    }\n};\n",[24,6138,6136],{"__ignoreMap":22},[5354,6140],{},"请注意这里对同一个文件引入多个loader的方法。",[5354,6143],{},"接下来，在app文件夹里创建一个名字为\"main.css\"的文件，对一些元素设置样式",[16,6146,6149],{"className":6147,"code":6148,"language":2197},[2195],"/* main.css */\nhtml {\n  box-sizing: border-box;\n  -ms-text-size-adjust: 100%;\n  -webkit-text-size-adjust: 100%;\n}\n\n*, *:before, *:after {\n  box-sizing: inherit;\n}\n\nbody {\n  margin: 0;\n  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;\n}\n\nh1, h2, h3, h4, h5, h6, p, ul {\n  margin: 0;\n  padding: 0;\n}\n",[24,6150,6148],{"__ignoreMap":22},[5354,6152],{},"我们这里例子中用到的",[24,6155,5991],{},"只有单一的入口，其它的模块需要通过 ",[24,6158,6159],{},"import",", ",[24,6162,6163],{},"require",[24,6165,6166],{},"url","等与入口文件建立其关联，为了让webpack能找到”main.css“文件，我们把它导入”main.js “中，如下",[16,6169,6172],{"className":6170,"code":6171,"language":2197},[2195],"//main.js\nimport React from 'react';\nimport {render} from 'react-dom';\nimport Greeter from './Greeter';\n\nimport './main.css';//使用require导入css文件\n\nrender(&lt;Greeter /&gt;, document.getElementById('root'));\n",[24,6173,6171],{"__ignoreMap":22},[6175,6176,6177],"blockquote",{},[27,6178,6179],{},"通常情况下，css会和js打包到同一个文件中，并不会打包为一个单独的css文件，不过通过合适的配置webpack也可以把css打包为单独的文件的。",[5354,6181],{},"上面的代码说明webpack是怎么把css当做模块看待的，咱们继续看一个更加真实的css模块实践。",[995,6184,6186],{"id":6185},"css-module","CSS module",[5354,6188],{},"在过去的一些年里，JavaScript通过一些新的语言特性，更好的工具以及更好的实践方法（比如说模块化）发展得非常迅速。模块使得开发者把复杂的代码转化为小的，干净的，依赖声明明确的单元，配合优化工具，依赖管理和加载管理可以自动完成。",[5354,6191],{},"不过前端的另外一部分，CSS发展就相对慢一些，大多的样式表却依旧巨大且充满了全局类名，维护和修改都非常困难。",[5354,6194],{},"被称为",[24,6197,6198],{},"CSS modules","的技术意在把JS的模块化思想带入CSS中来，通过CSS模块，所有的类名，动画名默认都只作用于当前模块。Webpack对CSS模块化提供了非常好的支持，只需要在CSS loader中进行简单配置即可，然后就可以直接把CSS的类名传递到组件的代码中，这样做有效避免了全局污染。具体的代码如下",[16,6201,6204],{"className":6202,"code":6203,"language":2197},[2195],"module.exports = {\n\n    ...\n\n    module: {\n        rules: [\n            {\n                test: /(\\.jsx|\\.js)$/,\n                use: {\n                    loader: \"babel-loader\"\n                },\n                exclude: /node_modules/\n            },\n            {\n                test: /\\.css$/,\n                use: [\n                    {\n                        loader: \"style-loader\"\n                    }, {\n                        loader: \"css-loader\",\n                        options: {\n                            modules: true, // 指定启用css modules\n                            localIdentName: '[name]__[local]--[hash:base64:5]' // 指定css的类名格式\n                        }\n                    }\n                ]\n            }\n        ]\n    }\n};\n",[24,6205,6203],{"__ignoreMap":22},[5354,6207],{},"我们在app文件夹下创建一个",[24,6210,6211],{},"Greeter.css","文件来进行一下测试",[16,6214,6217],{"className":6215,"code":6216,"language":2197},[2195],"/* Greeter.css */\n.root {\n  background-color: #eee;\n  padding: 10px;\n  border: 3px solid #ccc;\n}\n",[24,6218,6216],{"__ignoreMap":22},[5354,6220],{},"导入",[24,6223,6224],{},".root","到Greeter.js中",[16,6227,6230],{"className":6228,"code":6229,"language":2197},[2195],"import React, {Component} from 'react';\nimport config from './config.json';\nimport styles from './Greeter.css';//导入\n\nclass Greeter extends Component{\n  render() {\n    return (\n      &lt;div className={styles.root}&gt; //使用cssModule添加类名的方法\n        {config.greetText}\n      &lt;/div&gt;\n    );\n  }\n}\n\nexport default Greeter\n",[24,6231,6229],{"__ignoreMap":22},[5354,6233],{},"放心使用把，相同的类名也不会造成不同组件之间的污染。",[5354,6236],{},[1373,6238,6240],{"className":6239},[5691],[551,6241],{"alt":6242,"src":6243,"title":6242},"应用了css module后的样式","images/css.png",[5354,6245],{},"CSS modules 也是一个很大的主题，有兴趣的话可以去其",[497,6248,6251],{"href":6249,"rel":6250},"https://github.com/css-modules/css-modules",[590],"官方文档","了解更多。",[995,6254,6256],{"id":6255},"css预处理器","CSS预处理器",[5354,6258],{},[24,6260,6261],{},"Sass",[24,6263,6264],{},"Less"," 之类的预处理器是对原生CSS的拓展，它们允许你使用类似于",[24,6267,6268],{},"variables",[24,6270,6271],{},"nesting",[24,6273,6274],{},"mixins",[24,6276,6277],{},"inheritance","等不存在于CSS中的特性来写CSS，CSS预处理器可以这些特殊类型的语句转化为浏览器可识别的CSS语句，",[5354,6280],{},"你现在可能都已经熟悉了，在webpack里使用相关loaders进行配置就可以使用了，以下是常用的CSS 处理",[24,6283,6284],{},"loaders",":",[459,6287,6288],{},[24,6289,6290],{},"Less Loader",[459,6292,6293],{},[24,6294,6295],{},"Sass Loader",[459,6297,6298,6301,6303,6304,6307,6308,6312,6313,6315,6316,6318,6319,6108,6322,6325,6326,6332,6334,6335,6337,6338,6341,6342,6344,6345,6351,6357,6359,6360,6362,6363,6366,6370,6372,6373,6375,6376,6379,6381,6382,6385,6386,6391,6392,6398,6400,6401,6403,6410,6412,6413,6417,6419,6420,6422,6423,6425,6426,6429,6430,6432,6434,6440,6442],{},[24,6299,6300],{},"Stylus Loader",[5354,6302],{},"不过其实也存在一个CSS的处理平台",[24,6305,6306],{},"-PostCSS","，它可以帮助你的CSS实现更多的功能，在其",[497,6309,6251],{"href":6310,"rel":6311},"https://github.com/postcss/postcss",[590],"可了解更多相关知识。",[5354,6314],{},"举例来说如何使用PostCSS，我们使用PostCSS来为CSS代码自动添加适应不同浏览器的CSS前缀。",[5354,6317],{},"首先安装",[24,6320,6321],{},"postcss-loader",[24,6323,6324],{},"autoprefixer","（自动添加前缀的插件）",[16,6327,6330],{"className":6328,"code":6329,"language":2197},[2195],"npm install --save-dev postcss-loader autoprefixer\n",[24,6331,6329],{"__ignoreMap":22},[5354,6333],{},"接下来，在webpack配置文件中添加",[24,6336,6321],{},"，在根目录新建",[24,6339,6340],{},"postcss.config.js",",并添加如下代码之后，重新使用",[24,6343,6037],{},"打包时，你写的css会自动根据Can i use里的数据添加不同前缀了。",[16,6346,6349],{"className":6347,"code":6348,"language":2197},[2195],"//webpack.config.js\nmodule.exports = {\n    ...\n    module: {\n        rules: [\n            {\n                test: /(\\.jsx|\\.js)$/,\n                use: {\n                    loader: \"babel-loader\"\n                },\n                exclude: /node_modules/\n            },\n            {\n                test: /\\.css$/,\n                use: [\n                    {\n                        loader: \"style-loader\"\n                    }, {\n                        loader: \"css-loader\",\n                        options: {\n                            modules: true\n                        }\n                    }, {\n                        loader: \"postcss-loader\"\n                    }\n                ]\n            }\n        ]\n    }\n}\n",[24,6350,6348],{"__ignoreMap":22},[16,6352,6355],{"className":6353,"code":6354,"language":2197},[2195],"// postcss.config.js\nmodule.exports = {\n    plugins: [\n        require('autoprefixer')\n    ]\n}\n",[24,6356,6354],{"__ignoreMap":22},[5354,6358],{},"至此，本文已经谈论了处理JS的Babel和处理CSS的PostCSS的基本用法，它们其实也是两个单独的平台，配合",[24,6361,5991],{},"可以很好的发挥它们的作用。接下来介绍Webpack中另一个非常重要的功能-",[24,6364,6365],{},"Plugins",[73,6367,6369],{"id":6368},"插件plugins","插件（Plugins）",[5354,6371],{},"插件（Plugins）是用来拓展Webpack功能的，它们会在整个构建过程中生效，执行相关的任务。\nLoaders和Plugins常常被弄混，但是他们其实是完全不同的东西，可以这么来说，loaders是在打包构建过程中用来处理源文件的（JSX，Scss，Less..），一次处理一个，插件并不直接操作单个文件，它直接对整个构建过程其作用。",[5354,6374],{},"Webpack有很多内置插件，同时也有很多第三方插件，可以让我们完成更加丰富的功能。",[995,6377,6378],{"id":6378},"使用插件的方法",[5354,6380],{},"要使用某个插件，我们需要通过",[24,6383,6384],{},"npm","安装它，然后要做的就是在webpack配置中的plugins关键字部分添加该插件的一个实例（plugins是一个数组）继续上面的例子，我们添加了一个给打包后代码",[497,6387,6390],{"href":6388,"rel":6389},"https://webpack.js.org/plugins/banner-plugin/",[590],"添加版权声明的插件","。",[16,6393,6396],{"className":6394,"code":6395,"language":2197},[2195],"const webpack = require('webpack');\n\nmodule.exports = {\n...\n    module: {\n        rules: [\n            {\n                test: /(\\.jsx|\\.js)$/,\n                use: {\n                    loader: \"babel-loader\"\n                },\n                exclude: /node_modules/\n            },\n            {\n                test: /\\.css$/,\n                use: [\n                    {\n                        loader: \"style-loader\"\n                    }, {\n                        loader: \"css-loader\",\n                        options: {\n                            modules: true\n                        }\n                    }, {\n                        loader: \"postcss-loader\"\n                    }\n                ]\n            }\n        ]\n    },\n    plugins: [\n        new webpack.BannerPlugin('版权所有，翻版必究')\n    ],\n};\n",[24,6397,6395],{"__ignoreMap":22},[5354,6399],{},"通过这个插件，打包后的JS文件显示如下",[5354,6402],{},[1373,6404,6406],{"className":6405},[5691],[551,6407],{"alt":6408,"src":6409,"title":6408},"版权所有，翻版必究","images/bqsy.png",[5354,6411],{},"这就是webpack插件的基础用法了，下面给大家推荐几个常用的插件",[995,6414,6416],{"id":6415},"htmlwebpackplugin","HtmlWebpackPlugin",[5354,6418],{},"这个插件的作用是依据一个简单的",[24,6421,5764],{},"模板，生成一个自动引用你打包后的JS文件的新",[24,6424,5764],{},"。这在每次生成的js文件名称不同时非常有用（比如添加了",[24,6427,6428],{},"hash","值）。",[5354,6431],{},[3316,6433,5718],{},[16,6435,6438],{"className":6436,"code":6437,"language":2197},[2195],"npm install --save-dev html-webpack-plugin\n",[24,6439,6437],{"__ignoreMap":22},[5354,6441],{},"这个插件自动完成了我们之前手动做的一些事情，在正式使用之前需要对一直以来的项目结构做一些更改：",[4451,6444,6445,6451,6518,6521],{},[459,6446,6447,6448,6450],{},"移除public文件夹，利用此插件，",[24,6449,5764],{},"文件会自动生成，此外CSS已经通过前面的操作打包到JS中了。",[459,6452,6453,6454,6457,6458,6461,6462,6464,6465,6471,6473,6474,6476,6477,6480,6481,6487,6489,6490,6492,6493,6046,6495,6391,6497,6499,6506,6510,6512,6514,6515,6517],{},"在app目录下，创建一个",[24,6455,6456],{},"index.tmpl.html","文件模板，这个模板包含",[24,6459,6460],{},"title","等必须元素，在编译过程中，插件会依据此模板生成最终的html页面，会自动添加所依赖的 css, js，favicon等文件，",[24,6463,6456],{},"中的模板源代码如下：",[16,6466,6469],{"className":6467,"code":6468,"language":2197},[2195],"\u003C!DOCTYPE html>\n\u003Chtml lang=\"en\">\n\u003Chead>\n    \u003Cmeta charset=\"utf-8\">\n    \u003Ctitle>Webpack Sample Project\u003C/title>\n\u003C/head>\n\u003Cbody>\n    \u003Cdiv id='root'>\n    \u003C/div>\n\u003C/body>\n\u003C/html>\n",[24,6470,6468],{"__ignoreMap":22},[5354,6472],{},"3.更新",[24,6475,5991],{},"的配置文件，方法同上,新建一个",[24,6478,6479],{},"build","文件夹用来存放最终的输出文件",[16,6482,6485],{"className":6483,"code":6484,"language":2197},[2195],"const webpack = require('webpack');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\n\nmodule.exports = {\n    entry: __dirname + \"/app/main.js\",//已多次提及的唯一入口文件\n    output: {\n        path: __dirname + \"/build\",\n        filename: \"bundle.js\"\n    },\n    devtool: 'eval-source-map',\n    devServer: {\n        contentBase: \"./public\",//本地服务器所加载的页面所在的目录\n        historyApiFallback: true,//不跳转\n        inline: true//实时刷新\n    },\n    module: {\n        rules: [\n            {\n                test: /(\\.jsx|\\.js)$/,\n                use: {\n                    loader: \"babel-loader\"\n                },\n                exclude: /node_modules/\n            },\n            {\n                test: /\\.css$/,\n                use: [\n                    {\n                        loader: \"style-loader\"\n                    }, {\n                        loader: \"css-loader\",\n                        options: {\n                            modules: true\n                        }\n                    }, {\n                        loader: \"postcss-loader\"\n                    }\n                ]\n            }\n        ]\n    },\n    plugins: [\n        new webpack.BannerPlugin('版权所有，翻版必究'),\n        new HtmlWebpackPlugin({\n            template: __dirname + \"/app/index.tmpl.html\"//new 一个这个插件的实例，并传入相关的参数\n        })\n    ],\n};\n",[24,6486,6484],{"__ignoreMap":22},[5354,6488],{},"再次执行",[24,6491,6037],{},"你会发现，build文件夹下面生成了",[24,6494,5802],{},[24,6496,5764],{},[5354,6498],{},[1373,6500,6502],{"className":6501},[5691],[551,6503],{"alt":6504,"src":6505,"title":6504},"build文件夹","images/build.png",[995,6507,6509],{"id":6508},"hot-module-replacement","Hot Module Replacement",[5354,6511],{},[24,6513,6509],{},"（HMR）也是webpack里很有用的一个插件，它允许你在修改组件代码后，自动刷新实时预览修改后的效果。",[5354,6516],{},"在webpack中实现HMR也很简单，只需要做两项配置",[459,6519,6520],{},"在webpack配置文件中添加HMR插件；",[459,6522,6523,6524,6526,6527,6529],{},"在Webpack Dev Server中添加“hot”参数；",[5354,6525],{},"不过配置完这些后，JS模块其实还是不能自动热加载的，还需要在你的JS模块中执行一个Webpack提供的API才能实现热加载，虽然这个API不难使用，但是如果是React模块，使用我们已经熟悉的Babel可以更方便的实现功能热加载。",[5354,6528],{},"整理下我们的思路，具体实现方法如下",[456,6531,6532,6539,6542,6545,6548,6630,6636,6642],{},[459,6533,6534,6046,6536,6538],{},[24,6535,5948],{},[24,6537,5991],{},"是独立的工具",[459,6540,6541],{},"二者可以一起工作",[459,6543,6544],{},"二者都可以通过插件拓展功能",[459,6546,6547],{},"HMR是一个webpack插件，它让你能浏览器中实时观察模块修改后的效果，但是如果你想让它工作，需要对模块进行额外的配额；",[459,6549,6550,6551,6554,6555,6557,6558,6564,5718,6566,6569,6575,6577,6578,6584,6586,6587,6590,6592,6593,6595,6596,6599,6600,6606,6612,6624,6627,6629],{},"Babel有一个叫做",[24,6552,6553],{},"react-transform-hrm","的插件，可以在不对React模块进行额外的配置的前提下让HMR正常工作；",[5354,6556],{},"还是继续上例来实际看看如何配置",[16,6559,6562],{"className":6560,"code":6561,"language":2197},[2195],"const webpack = require('webpack');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\n\nmodule.exports = {\n    entry: __dirname + \"/app/main.js\",//已多次提及的唯一入口文件\n    output: {\n        path: __dirname + \"/build\",\n        filename: \"bundle.js\"\n    },\n    devtool: 'eval-source-map',\n    devServer: {\n        contentBase: \"./public\",//本地服务器所加载的页面所在的目录\n        historyApiFallback: true,//不跳转\n        inline: true,\n        hot: true\n    },\n    module: {\n        rules: [\n            {\n                test: /(\\.jsx|\\.js)$/,\n                use: {\n                    loader: \"babel-loader\"\n                },\n                exclude: /node_modules/\n            },\n            {\n                test: /\\.css$/,\n                use: [\n                    {\n                        loader: \"style-loader\"\n                    }, {\n                        loader: \"css-loader\",\n                        options: {\n                            modules: true\n                        }\n                    }, {\n                        loader: \"postcss-loader\"\n                    }\n                ]\n            }\n        ]\n    },\n    plugins: [\n        new webpack.BannerPlugin('版权所有，翻版必究'),\n        new HtmlWebpackPlugin({\n            template: __dirname + \"/app/index.tmpl.html\"//new 一个这个插件的实例，并传入相关的参数\n        }),\n        new webpack.HotModuleReplacementPlugin()//热加载插件\n    ],\n};\n",[24,6563,6561],{"__ignoreMap":22},[5354,6565],{},[24,6567,6568],{},"react-transform-hmr",[16,6570,6573],{"className":6571,"code":6572,"language":2197},[2195],"npm install --save-dev babel-plugin-react-transform react-transform-hmr\n",[24,6574,6572],{"__ignoreMap":22},[5354,6576],{},"配置Babel",[16,6579,6582],{"className":6580,"code":6581,"language":2197},[2195],"// .babelrc\n{\n  \"presets\": [\"react\", \"env\"],\n  \"env\": {\n    \"development\": {\n    \"plugins\": [[\"react-transform\", {\n       \"transforms\": [{\n         \"transform\": \"react-transform-hmr\",\n\n         \"imports\": [\"react\"],\n\n         \"locals\": [\"module\"]\n       }]\n     }]]\n    }\n  }\n}\n",[24,6583,6581],{"__ignoreMap":22},[5354,6585],{},"现在当你使用React时，可以热加载模块了,每次保存就能在浏览器上看到更新内容。",[73,6588,6589],{"id":6589},"产品阶段的构建",[5354,6591],{},"目前为止，我们已经使用webpack构建了一个完整的开发环境。但是在产品阶段，可能还需要对打包的文件进行额外的处理，比如说优化，压缩，缓存以及分离CSS和JS。",[5354,6594],{},"对于复杂的项目来说，需要复杂的配置，这时候分解配置文件为多个小的文件可以使得事情井井有条，以上面的例子来说，我们创建一个",[24,6597,6598],{},"webpack.production.config.js","的文件，在里面加上基本的配置,它和原始的webpack.config.js很像，如下",[16,6601,6604],{"className":6602,"code":6603,"language":2197},[2195],"// webpack.production.config.js\nconst webpack = require('webpack');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\n\nmodule.exports = {\n    entry: __dirname + \"/app/main.js\", //已多次提及的唯一入口文件\n    output: {\n        path: __dirname + \"/build\",\n        filename: \"bundle.js\"\n    },\n    devtool: 'null', //注意修改了这里，这能大大压缩我们的打包代码\n    devServer: {\n        contentBase: \"./public\", //本地服务器所加载的页面所在的目录\n        historyApiFallback: true, //不跳转\n        inline: true,\n        hot: true\n    },\n    module: {\n        rules: [{\n            test: /(\\.jsx|\\.js)$/,\n            use: {\n                loader: \"babel-loader\"\n            },\n            exclude: /node_modules/\n        }, {\n            test: /\\.css$/,\n            use: ExtractTextPlugin.extract({\n                fallback: \"style-loader\",\n                use: [{\n                    loader: \"css-loader\",\n                    options: {\n                        modules: true\n                    }\n                }, {\n                    loader: \"postcss-loader\"\n                }],\n            })\n        }]\n    },\n    plugins: [\n        new webpack.BannerPlugin('版权所有，翻版必究'),\n        new HtmlWebpackPlugin({\n            template: __dirname + \"/app/index.tmpl.html\" //new 一个这个插件的实例，并传入相关的参数\n        }),\n        new webpack.HotModuleReplacementPlugin() //热加载插件\n    ],\n};\n",[24,6605,6603],{"__ignoreMap":22},[16,6607,6610],{"className":6608,"code":6609,"language":2197},[2195],"//package.json\n{\n  \"name\": \"test\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" &amp;&amp; exit 1\",\n    \"start\": \"webpack\",\n    \"server\": \"webpack-dev-server --open\",\n    \"build\": \"NODE_ENV=production webpack --config ./webpack.production.config.js --progress\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n...\n  },\n  \"dependencies\": {\n    \"react\": \"^15.6.1\",\n    \"react-dom\": \"^15.6.1\"\n  }\n}\n",[24,6611,6609],{"__ignoreMap":22},[6175,6613,6614],{},[27,6615,6616,6617,6619,6620,6623],{},"**注意:**如果是window电脑，",[24,6618,6479],{},"需要配置为",[24,6621,6622],{},"\"build\": \"set NODE_ENV=production &amp;&amp; webpack --config ./webpack.production.config.js --progress\"",".谢谢评论区简友提醒。",[995,6625,6626],{"id":6626},"优化插件",[5354,6628],{},"webpack提供了一些在发布阶段非常有用的优化插件，它们大多来自于webpack社区，可以通过npm安装，通过以下插件可以完成产品发布阶段所需的功能",[459,6631,6632,6635],{},[24,6633,6634],{},"OccurenceOrderPlugin"," :为组件分配ID，通过这个插件webpack可以分析和优先考虑使用最多的模块，并为它们分配最小的ID",[459,6637,6638,6641],{},[24,6639,6640],{},"UglifyJsPlugin","：压缩JS代码；",[459,6643,6644,6647,6648,6650,6651,6657,6659,6660,6666,6668,6669,6672,6673,6675,6682,6685,6687,6688,6690,6691,6160,6694,6697,6698,6700,6701,6707,6709,6710,6712,6719,6726,6728,6729,6731,6732,6391,6735,6737,6739,6740,6743,6745,6748,6749,6751,6752,6754,6755,6758,6759],{},[24,6645,6646],{},"ExtractTextPlugin","：分离CSS和JS文件",[5354,6649],{},"我们继续用例子来看看如何添加它们，OccurenceOrder 和 UglifyJS plugins 都是内置插件，你需要做的只是安装其它非内置插件",[16,6652,6655],{"className":6653,"code":6654,"language":2197},[2195],"npm install --save-dev extract-text-webpack-plugin\n",[24,6656,6654],{"__ignoreMap":22},[5354,6658],{},"在配置文件的plugins后引用它们",[16,6661,6664],{"className":6662,"code":6663,"language":2197},[2195],"// webpack.production.config.js\nconst webpack = require('webpack');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst ExtractTextPlugin = require('extract-text-webpack-plugin');\n\nmodule.exports = {\n    entry: __dirname + \"/app/main.js\",//已多次提及的唯一入口文件\n    output: {\n        path: __dirname + \"/build\",\n        filename: \"bundle.js\"\n    },\n    devtool: 'none',\n    devServer: {\n        contentBase: \"./public\",//本地服务器所加载的页面所在的目录\n        historyApiFallback: true,//不跳转\n        inline: true,\n        hot: true\n    },\n    module: {\n        rules: [\n            {\n                test: /(\\.jsx|\\.js)$/,\n                use: {\n                    loader: \"babel-loader\"\n                },\n                exclude: /node_modules/\n            },\n            {\n                test: /\\.css$/,\n                use: [\n                    {\n                        loader: \"style-loader\"\n                    }, {\n                        loader: \"css-loader\",\n                        options: {\n                            modules: true\n                        }\n                    }, {\n                        loader: \"postcss-loader\"\n                    }\n                ]\n            }\n        ]\n    },\n    plugins: [\n        new webpack.BannerPlugin('版权所有，翻版必究'),\n        new HtmlWebpackPlugin({\n            template: __dirname + \"/app/index.tmpl.html\"\n        }),\n        new webpack.optimize.OccurrenceOrderPlugin(),\n        new webpack.optimize.UglifyJsPlugin(),\n        new ExtractTextPlugin(\"style.css\")\n    ],\n};\n",[24,6665,6663],{"__ignoreMap":22},[5354,6667],{},"此时执行",[24,6670,6671],{},"npm run build","可以看见代码是被压缩后的",[5354,6674],{},[1373,6676,6678],{"className":6677},[5691],[551,6679],{"alt":6680,"src":6681,"title":6680},"压缩后的代码","images/1031000-f9b89ce486539162.png",[995,6683,6684],{"id":6684},"缓存",[5354,6686],{},"缓存无处不在，使用缓存的最好方法是保证你的文件名和文件内容是匹配的（内容改变，名称相应改变）",[5354,6689],{},"webpack可以把一个哈希值添加到打包的文件名中，使用方法如下,添加特殊的字符串混合体（",[1373,6692,6693],{},"name",[1373,6695,6696],{},"id"," and ",[1373,6699,6428],{},"）到输出文件名前",[16,6702,6705],{"className":6703,"code":6704,"language":2197},[2195],"const webpack = require('webpack');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst ExtractTextPlugin = require('extract-text-webpack-plugin');\n\nmodule.exports = {\n..\n    output: {\n        path: __dirname + \"/build\",\n        filename: \"bundle-[hash].js\"\n    },\n   ...\n};\n",[24,6706,6704],{"__ignoreMap":22},[5354,6708],{},"现在用户会有合理的缓存了。",[5354,6711],{},[1373,6713,6715],{"className":6714},[5691],[551,6716],{"alt":6717,"src":6718,"title":6717},"带hash值的js名","images/hash.png",[995,6720,6722,6723,6725],{"id":6721},"去除build文件中的残余文件","去除",[24,6724,6479],{},"文件中的残余文件",[5354,6727],{},"添加了",[24,6730,6428],{},"之后，会导致改变文件内容后重新打包时，文件名不同而内容越来越多，因此这里介绍另外一个很好用的插件",[24,6733,6734],{},"clean-webpack-plugin",[5354,6736],{},[3316,6738,5718],{},"：\n",[24,6741,6742],{},"cnpm install clean-webpack-plugin --save-dev",[5354,6744],{},[3316,6746,6747],{},"使用","：",[5354,6750],{},"引入",[24,6753,6734],{},"插件后在配置文件的",[24,6756,6757],{},"plugins","中做相应配置即可：",[16,6760,6763],{"className":6761,"code":6762,"language":2197},[2195],"const CleanWebpackPlugin = require(\"clean-webpack-plugin\");\n  plugins: [\n    ...// 这里是之前配置的其它各种插件\n    new CleanWebpackPlugin('build/*.*', {\n      root: __dirname,\n      verbose: true,\n      dry: false\n  })\n  ]\n",[24,6764,6762],{"__ignoreMap":22},[27,6766,6767,6768,6770,6771],{},"关于",[24,6769,6734],{},"的详细使用可参考",[497,6772,6775],{"href":6773,"rel":6774},"https://github.com/johnagan/clean-webpack-plugin",[590],"这里",{"title":22,"searchDepth":95,"depth":95,"links":6777},[6778,6779],{"id":5641,"depth":102,"text":5642},{"id":5711,"depth":102,"text":5712},{"date":6781,"tags":6782},"2020-02-29 17:56:27",[5991,6783],"模块化","/webpack",{"title":5636,"description":22},"I9CXdjDWCH1EKiBJmN8RicbiUh0zIxbcDDuTZORJxgw",{"id":6788,"title":6789,"body":6790,"description":6794,"extension":104,"meta":6836,"navigation":110,"path":6842,"seo":6843,"stem":6844,"__hash__":6845},"content/vuecarousel.md","Vue实现新旧版B站轮播图",{"type":8,"value":6791,"toc":6832},[6792,6795,6798,6801,6807,6814,6817,6820,6826],[27,6793,6794],{},"很喜欢bilibili上的轮播图造型，这次就来自己写一下B站上的轮播图吧。\n首先，B站分旧版和新版，仔细观察会发现，轮播图用的明显不是一套代码，因为旧版轮播图，播到最后一张时就倒回去了，是的，因为每一张图都有过渡，必须遵循这个规则，而且不可能第一张跑在最后一张的后面，这样用户体验不是很好，但我们还是要来做一下这种轮播图。\n本次采用Vue框架进行开发，当然原生js和JQuery也可以，但是Vue支持数据双向绑定，响应速度快，简单，更便于维护。",[73,6796,6797],{"id":6797},"旧版轮播图",[27,6799,6800],{},"代码如下：",[16,6802,6805],{"className":6803,"code":6804,"language":256,"meta":22},[254],"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n        \u003Cmeta charset=\"utf-8\">\n        \u003Ctitle>\u003C/title>\n        \u003Cscript src=\"js/vue.js\" type=\"text/javascript\" charset=\"utf-8\">\u003C/script>\n        \u003Cstyle type=\"text/css\">\n            * {\n                margin: 0;\n                padding: 0;\n            }\n            a{\n                text-decoration: none;\n            }\n            li{\n                list-style-type: none;\n            }\n            .carousel {\n                max-width: 440px;\n                height: 220px;\n                position: relative;\n                overflow: hidden;\n                border-radius: 4px;\n                float: left;\n            }\n            .carousel .pic {\n                height: 220px;\n                transition: 1s ease;\n            }\n            .carousel img {\n                width: 440px;\n                height: 220px;\n            }\n            .carousel .pic li {\n                float: left;\n            }\n            /* 标题过渡 */\n            .carousel .title li {\n                position: absolute;\n                bottom: 10px;\n                left: 10px;\n                transition: .5s;            \n            }\n            .carousel .title a {\n                color: white;\n            }\n            .carousel .trig {\n                position: absolute;\n                right: 35px;\n                bottom: 10px;\n            }\n            .carousel .trig li {\n                float: left;\n                background-image: url(img/icons.png);\n                background-position: -855px -790px;\n                width: 18px;\n                height: 18px;\n                margin-left: 5px;\n                cursor: pointer;\n            }\n            .carousel .trig li:hover {\n                background-position: -919px -790px;\n            }\n            .on {\n                background-position: -855px -727px !important;\n                width: 18px;\n                height: 18px;\n            }\n            .show {\n                opacity: 1;\n            }\n            .hide {\n                opacity: 0;\n            }\n        \u003C/style>\n    \u003C/head>\n    \u003Cbody>\n        \u003Cdiv class=\"carousel\" @mouseover=\"mouseover\" @mouseout=\"mouseout\">\n            \u003Cul class=\"pic\" :style=\"style\">\n                \u003Cli v-for=\"(item,index) in pics\">\u003Ca :href=\"pics[index].url\">\u003Cimg :src=\"pics[index].src\">\u003C/a>\u003C/li>\n            \u003C/ul>\n\n            \u003Cul class=\"title\">\n                \u003Cli v-for=\"(item,index) in pics\" :class=\"pics[index].on?'show':'hide'\">\u003Ca :href=\"pics[index].url\">{{pics[index].title}}\u003C/a>\u003C/li>\n\n            \u003C/ul>\n\n            \u003Cul class=\"trig\">\n                \u003Cli v-for=\"(item,index) in pics\" :class=\"{on:pics[index].on}\" @click=\"toggle(index)\">\u003C/li>\n            \u003C/ul>\n        \u003C/div>\n        \u003Cscript>\n            var vm = new Vue({\n                el: '.carousel',\n                data: {\n                    index: 0,\n                    time: '',\n                    pics: [{\n                            url: '#',\n                            src: 'img/1.jpg',\n                            title: '标题1',\n                            on: true\n                        },\n                        {\n                            url: '#',\n                            src: 'img/2.jpg',\n                            title: '标题2',\n                            on: false\n                        },\n                        {\n                            url: '#',\n                            src: 'img/3.jpg',\n                            title: '标题3',\n                            on: false\n                        },\n                        {\n                            url: '#',\n                            src: 'img/4.jpg',\n                            title: '标题4',\n                            on: false\n                        },\n                        {\n                            url: '#',\n                            src: 'img/5.jpg',\n                            title: '标题5',\n                            on: false\n                        }\n                    ]\n                },\n                methods: {\n                    toggle: function(index) {\n                        this.index = index;\n                        this.pics[index].on = true;\n                        for (var i = 0; i \u003C this.pics.length; i++) {\n                            if (i !== index)\n                                this.pics[i].on = false;\n                        }\n                    },\n                    mouseover: function() {\n                        clearInterval(this.time);\n                    },\n                    mouseout: function() {\n                        var that = this;\n                        this.time = setInterval(function() {\n                            if (that.index == that.pics.length - 1) {\n                                that.index = 0;\n                                that.pics[0].on = true;\n                                that.pics[that.pics.length - 1].on = false;\n                            } else {\n                                ++that.index;\n                                that.pics[that.index].on = true;\n                                that.pics[that.index - 1].on = false;\n                            }\n                        }, 3000)\n                    }\n                },\n                computed: {\n                    style: function() {\n                        return {\n                            marginLeft: this.index * (-100) + '%',\n                            width: this.pics.length * 100 + '%'\n                        }\n                    },\n                },\n                mounted: function() {\n                    this.mouseout();\n                }\n            });\n        \u003C/script>\n    \u003C/body>\n\u003C/html>\n",[24,6806,6804],{"__ignoreMap":22},[27,6808,6809,6810,6813],{},"实现效果如图：\n",[551,6811],{"alt":3545,"src":6812,"title":6797},"images/old.gif","\n这种轮播图原理是一串图片通过浮动拼在一起，利用marginLeft的位移作为动画实现的，比较容易实现，缺点是中间隔几个图的时候点击感觉比较晃，而且不是无缝轮播。",[73,6815,6816],{"id":6816},"新版轮播图",[27,6818,6819],{},"接下来我们就来实现一下无缝轮播图，也就是新版B站首页所用的。\n代码如下：",[16,6821,6824],{"className":6822,"code":6823,"language":256,"meta":22},[254],"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n        \u003Cmeta charset=\"utf-8\">\n        \u003Ctitle>\u003C/title>\n        \u003Cscript src=\"js/vue.js\" type=\"text/javascript\" charset=\"utf-8\">\u003C/script>\n        \u003Cstyle type=\"text/css\">\n            * {\n                margin: 0;\n                padding: 0;\n            }\n\n            a {\n                text-decoration: none;\n            }\n\n            li {\n                list-style-type: none;\n            }\n\n            .carousel {\n                max-width: 440px;\n                height: 220px;\n                position: relative;\n                overflow: hidden;\n                border-radius: 4px;\n                float: left;\n            }\n\n            .carousel .pic {\n                width: 440px;\n                height: 220px;\n\n            }\n\n            .carousel img {\n                width: 440px;\n                height: 220px;\n            }\n\n            .carousel .pic li {\n                position: absolute;\n                top: 0;\n                left: 0;\n                /* 这里一定要改成绝对定位 */\n            }\n\n            /* 标题过渡 */\n            .carousel .title li {\n                position: absolute;\n                bottom: 10px;\n                left: 10px;\n                transition: .5s;\n                z-index: 999999;\n            }\n\n            .carousel .title a {\n                color: white;\n            }\n\n            .carousel .trig {\n                position: absolute;\n                right: 35px;\n                bottom: 10px;\n                z-index: 999999;\n            }\n\n            .carousel .trig li {\n                float: left;\n                background-image: url(img/icons.png);\n                background-position: -855px -790px;\n                width: 18px;\n                height: 18px;\n                margin-left: 5px;\n                cursor: pointer;\n            }\n\n            .carousel .trig li:hover {\n                background-position: -919px -790px;\n            }\n\n            .on {\n                background-position: -855px -727px !important;\n                width: 18px;\n                height: 18px;\n            }\n\n            .show {\n                opacity: 1;\n            }\n\n            .hide {\n                opacity: 0;\n            }\n        \u003C/style>\n    \u003C/head>\n    \u003Cbody>\n        \u003Cdiv class=\"carousel\" @mouseover=\"mouseover\" @mouseout=\"mouseout\">\n            \u003Cul class=\"pic\" ref=\"pic\">\n                \u003Cli v-for=\"(item,index) in pics\">\u003Ca :href=\"pics[index].url\">\u003Cimg :src=\"pics[index].src\">\u003C/a>\u003C/li>\n            \u003C/ul>\n            \u003Cul class=\"title\">\n                \u003Cli v-for=\"(item,index) in pics\" :class=\"pics[index].on?'show':'hide'\">\u003Ca :href=\"pics[index].url\">{{pics[index].title}}\u003C/a>\u003C/li>\n            \u003C/ul>\n\n            \u003Cul class=\"trig\">\n                \u003Cli v-for=\"(item,index) in pics\" :class=\"{on:pics[index].on}\" @click=\"toggle(index)\">\u003C/li>\n            \u003C/ul>\n        \u003C/div>\n        \u003Cscript>\n            var vm = new Vue({\n                el: '.carousel',\n                data: {\n                    index: 0,\n                    time: '',\n                    zindex: 1,\n                    pics: [{\n                            url: '#',\n                            src: 'img/1.jpg',\n                            title: '标题1',\n                            on: true\n                        },\n                        {\n                            url: '#',\n                            src: 'img/2.jpg',\n                            title: '标题2',\n                            on: false\n                        },\n                        {\n                            url: '#',\n                            src: 'img/3.jpg',\n                            title: '标题3',\n                            on: false\n                        },\n                        {\n                            url: '#',\n                            src: 'img/4.jpg',\n                            title: '标题4',\n                            on: false\n                        },\n                        {\n                            url: '#',\n                            src: 'img/5.jpg',\n                            title: '标题5',\n                            on: false\n                        }\n                    ]\n                },\n                methods: {\n                    toggle: function(i) {\n                        this.index = i;\n                        this.pics[i].on = true;\n                        for (let j = 0; j \u003C this.pics.length; j++) {\n                            if (j !== i)\n                                this.pics[j].on = false;\n                        }\n                        this.zindex++;\n                        this.$refs.pic.children[i].style.zIndex = this.zindex;\n                        this.$refs.pic.children[i].style.transform = 'translate3d(0px,0px,0px)';\n                        this.$refs.pic.children[i].style.transition = 'all 0.25s ease 0s';\n                        if (i >= 1) {\n                            //当前轮播图前面的全部向左移\n                            for (let j = 0; j \u003C i; j++) {\n                                this.$refs.pic.children[j].style.transition = 'all 0.55s ease 0s';\n                                this.$refs.pic.children[j].style.transform = 'translate3d(-440px,0px,0px)';\n                            }\n                        } else {\n                            //点击第一个按钮，此时第一张图在最后一张图左边\n                            this.$refs.pic.children[this.pics.length - 1].style.transition = 'all 0.55s ease 0s';\n                            this.$refs.pic.children[this.pics.length - 1].style.transform = 'translate3d(440px,0px,0px)';\n                        }\n                        if (i \u003C this.pics.length - 1) {\n                            //当前轮播图后面的全部向右移\n                            for (let j = i + 1; j \u003C this.pics.length; j++) {\n                                this.$refs.pic.children[j].style.transition = 'all 0.55s ease 0s';\n                                this.$refs.pic.children[j].style.transform = 'translate3d(440px,0px,0px)';\n                            }\n                        } else {\n                            //点击最后一个按钮，此时第一张图在最后一张图左边\n                            this.$refs.pic.children[0].style.transition = 'all 0.55s ease 0s';\n                            this.$refs.pic.children[0].style.transform = 'translate3d(-440px,0px,0px)';\n                        }\n                    },\n                    mouseover: function() {\n                        clearInterval(this.time);\n                        //鼠标移入，第一张图在最后一张图左边\n                        if (this.index == this.pics.length - 1) {\n                            this.$refs.pic.children[0].style.transform = 'translate3d(-440px,0px,0px)';\n                        }\n                        //鼠标移入，第一张图在最后一张图左边\n                        else if(this.index ==0){\n                            this.$refs.pic.children[this.pics.length - 1].style.transform = 'translate3d(440px,0px,0px)';\n                        }\n                    },\n                    mouseout: function() {\n                        var that = this;\n                        var i = that.index;\n                        //鼠标移出，第一张图在最后一张图右边\n                        if (i == that.pics.length - 1) {\n                            that.$refs.pic.children[0].style.transform = 'translate3d(440px,0px,0px)';\n                        }\n                        //自动轮播\n                        this.time = setInterval(function() {\n                            that.zindex++;\n                            if (i == that.pics.length - 1) {\n                                i = 0;\n                            } else {\n                                ++i\n                            }\n                            that.index = i;//必须储存index的值，否则值恒为0\n                            that.$refs.pic.children[i].style.zIndex = that.zindex;\n                            that.$refs.pic.children[i].style.transform = 'translate3d(0px,0px,0px)';\n                            that.$refs.pic.children[i].style.transition = 'all 0.25s ease 0s';\n                            that.pics[i].on = true;\n                            //向左移动\n                            if (i > 0) {\n                                that.$refs.pic.children[i - 1].style.transition = 'all 0.55s ease 0s';\n                                that.$refs.pic.children[i - 1].style.transform = 'translate3d(-440px,0px,0px)';\n                                that.pics[i - 1].on = false;\n\n                            } else {\n                                that.$refs.pic.children[that.pics.length - 1].style.transition = 'all 0.55s ease 0s';\n                                that.$refs.pic.children[that.pics.length - 1].style.transform = 'translate3d(-440px,0px,0px)';\n                                that.pics[that.pics.length - 1].on = false;\n                            }\n                            //向右移动\n                            if (i \u003C that.pics.length - 1) {\n                                that.$refs.pic.children[i + 1].style.transition = 'none 0s ease 0s';\n                                that.$refs.pic.children[i + 1].style.transform = 'translate3d(440px,0px,0px)';\n                            } else {\n                                that.$refs.pic.children[0].style.transition = 'none 0s ease 0s';\n                                that.$refs.pic.children[0].style.transform = 'translate3d(440px,0px,0px)';\n                            }\n                        }, 3000)\n                    }\n                },\n                mounted: function() {\n                    this.mouseout();\n                    //初始状态，即0++++\n                    for (let j = 1; j \u003C this.pics.length; j++) {\n                        this.$refs.pic.children[j].style.transform = 'translate3d(440px,0px,0px)'\n                    }\n                    this.$refs.pic.children[0].style.zIndex = '1';\n                }\n            });\n        \u003C/script>\n    \u003C/body>\n\u003C/html>\n",[24,6825,6823],{"__ignoreMap":22},[27,6827,6828,6829],{},"这种轮播图比起上一种要复杂的多，但是看起来非常舒服，甚至比swiper的轮播图还要高档，给人的感觉就好像是两张图在相互切换，其他图在怎么变换根本看不出来。\n根据我仔细的观察，分析出了原理，主要是通过translate3d变换位移，还需要加上叠放次序zindex，不然顺序会乱，你会看到其中一张图在过渡的时候压到其他图，这里的过渡要分开加，而不是写在CSS中，不是无脑加，第一张往最后一张图后面移动时不能有过渡动画，重点还要考虑第一张图和最后一张图的位置关系，这里一个过渡0.25s和一个过渡0.55s并不是没有道理,如果都设为0.55s可能会看到其它图的过渡惨杂进来\n实现效果如图：\n",[551,6830],{"alt":3545,"src":6831,"title":6816},"images/new.gif",{"title":22,"searchDepth":95,"depth":95,"links":6833},[6834,6835],{"id":6797,"depth":102,"text":6797},{"id":6816,"depth":102,"text":6816},{"date":6837,"tags":6838,"categories":6841},"2020-02-15 18:48:53",[6839,6840],"Vue","轮播图","原创","/vuecarousel",{"title":6789,"description":6794},"vuecarousel","qGs6DBoiDHxIEdQ2WJF4-WWF2BIbGoHgXUuz18hhr1o",{"id":6847,"title":6848,"body":6849,"description":6920,"extension":104,"meta":6921,"navigation":110,"path":6927,"seo":6928,"stem":6929,"__hash__":6930},"content/openmmd.md","人工智能真人动作数据提取",{"type":8,"value":6850,"toc":6916},[6851,6864,6867,6870,6887,6890,6893,6897,6901,6905,6909],[27,6852,6853,6854,6858,6859,6863],{},"如果你是一位MMDer，而且想提高K帧效率，那么这篇基于Python的k帧软件你就可以试一试了。\nOpenMMD到底是干啥的？简洁地讲，OpenMMD是一个可以直接分析现成视频（各种MP4, AVI等视频格式），自动生成vmd动作文件的工具；也就是说，你可以随便从网上找一个（或者自己录一个）人类的舞蹈动作视频，然后直接使用这个分析工具就可以生成该舞蹈动作对应的vmd格式的文件了。\n不过这里有一点值得说明，这个仅仅是辅助工具而已，至少目前是做不到完美复现原视频的所有动作的，因此想制作出好的视频依旧需要你对k帧去修补美化一番。\n原github项目：",[497,6855,6856],{"href":6856,"rel":6857},"https://github.com/peterljq/OpenMMD",[590],"\n由于github下载慢，推荐网盘：",[497,6860,6861],{"href":6861,"rel":6862},"https://pan.baidu.com/s/1HihyHvivUjkKo6uQP2Bfuw",[590],"\n提取码：9ita",[73,6865,6866],{"id":6866},"配置环境",[27,6868,6869],{},"先配置python环境:\n我这里用的是python3.7版本，win7系统，CPU：g4560",[456,6871,6872,6875,6878,6881,6884],{},[459,6873,6874],{},"pip install h5py",[459,6876,6877],{},"pip install opencv-python",[459,6879,6880],{},"pip install matplotlib==3.0.0",[459,6882,6883],{},"pip install tensorflow==1.13.1  (这个东西对系统的兼容不是很友好，如果你的CPU不支持AVX,你需要自己手动去找适合你CPU的tensorflow，我这里用的是tensorflow-1.13.1-cp37-cp37m-win_amd64.whl)",[459,6885,6886],{},"pip install pyqt5",[73,6888,6889],{"id":6889},"开始运行",[27,6891,6892],{},"准备一个视频或者图片（要清晰，最好是全身）",[995,6894,6896],{"id":6895},"_1打开openposevideobat拖入你的视频文件或者图片一路回车执行完毕后会生成_json文件夹和生成的视频文件","1.打开OpenposeVideo.bat，拖入你的视频文件或者图片，一路回车，执行完毕后会生成_json文件夹和生成的视频文件",[995,6898,6900],{"id":6899},"_2打开openposeto3dbat拖入_json文件夹生成类似json_3d_2019xxxx_1xxxx_idx01的文件夹一路回车如果出现tensorflow的报错你就得重下tensorflow了","2.打开OpenposeTo3D.bat，拖入_json文件夹，生成类似json_3d_2019xxxx_1xxxx_idx01的文件夹，一路回车，如果出现tensorflow的报错你就得重下tensorflow了",[995,6902,6904],{"id":6903},"_3打开videotodepthbat拖入生成好的视频文件和json_3d_2019xxxx_1xxxx_idx01文件夹一路回车","3.打开VideoToDepth.bat，拖入生成好的视频文件和json_3d_2019xxxx_1xxxx_idx01文件夹，一路回车",[995,6906,6908],{"id":6907},"_4打开3dtovmdbat先拖入json_3d_2019xxxx_1xxxx_idx01文件夹接着找到born文件夹里的あにまさ式ミクボーンcsv拖入不拖入的话会出现编码错误一路回车如果出现unrecognized-arguments-s-1错误就删掉3dtovmdbat文件最后一行里的-s-smooth_times","4.打开3DToVmd.bat，先拖入json_3d_2019xxxx_1xxxx_idx01文件夹，接着找到born文件夹里的あにまさ式ミクボーン.csv拖入，不拖入的话会出现编码错误，一路回车，如果出现unrecognized arguments: -s 1错误，就删掉3DToVmd.bat文件最后一行里的'-s %SMOOTH_TIMES%'",[27,6910,6911,6912],{},"最后会生成一个VMD就是我们所要的了\n效果如下图（亲自测试）\n",[551,6913],{"alt":3545,"src":6914,"title":6915},"images/openmmd.gif","效果图",{"title":22,"searchDepth":95,"depth":95,"links":6917},[6918,6919],{"id":6866,"depth":102,"text":6866},{"id":6889,"depth":102,"text":6889},"如果你是一位MMDer，而且想提高K帧效率，那么这篇基于Python的k帧软件你就可以试一试了。\nOpenMMD到底是干啥的？简洁地讲，OpenMMD是一个可以直接分析现成视频（各种MP4, AVI等视频格式），自动生成vmd动作文件的工具；也就是说，你可以随便从网上找一个（或者自己录一个）人类的舞蹈动作视频，然后直接使用这个分析工具就可以生成该舞蹈动作对应的vmd格式的文件了。\n不过这里有一点值得说明，这个仅仅是辅助工具而已，至少目前是做不到完美复现原视频的所有动作的，因此想制作出好的视频依旧需要你对k帧去修补美化一番。\n原github项目：https://github.com/peterljq/OpenMMD\n由于github下载慢，推荐网盘：https://pan.baidu.com/s/1HihyHvivUjkKo6uQP2Bfuw\n提取码：9ita",{"date":6922,"tags":6923,"categories":2568},"2020-02-12 14:42:35",[774,6924,6925,6926],"mmd","tensorflow","opencv","/openmmd",{"title":6848,"description":6920},"openmmd","axTnUzc6GUhpTeDxv0WDvoKsqYLuMEc_YAwunhy6dn8",{"id":6932,"title":6933,"body":6934,"description":6938,"extension":104,"meta":6951,"navigation":110,"path":6955,"seo":6956,"stem":271,"__hash__":6957},"content/vue.md","Vue实现购物车",{"type":8,"value":6935,"toc":6949},[6936,6943],[27,6937,6938,6939],{},"主要实现的功能有：商品的单价，商品的总价，商品的单选和全选，商品数量的修改，商品的删除等功能。\n该小型项目采用Vue框架作为主要操作DOM的手段，代码完全原创\n实现效果如下：\n",[551,6940],{"alt":3545,"src":6941,"title":6942},"images/shop.gif","购物车效果图",[16,6944,6947],{"className":6945,"code":6946,"language":256,"meta":22},[254],"\u003C!DOCTYPE html>\n\u003Chtml>\n    \u003Chead>\n        \u003Cmeta charset=\"utf-8\" />\n        \u003Cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n        \u003Ctitle>\u003C/title>\n        \u003Cscript src=\"js/vue.js\" type=\"text/javascript\" charset=\"utf-8\">\u003C/script>\n        \u003Cstyle type=\"text/css\">\n            html {\n                font-size: 100px;\n            }\n\n            body {\n                font-size: 16px;\n            }\n\n            * {\n                margin: 0;\n                padding: 0;\n            }\n\n            li {\n                list-style-type: none;\n            }\n\n            #box {\n                margin-top: 0.4rem;\n            }\n\n            .shop-item {\n                height: 1.3rem;\n                display: flex;\n                justify-content: space-between;\n                align-items: center;\n                background: rgb(252, 252, 252);\n                margin-top: 0.1rem;\n                position: relative;\n            }\n            .shop-item:nth-last-of-type(1){\n                margin-bottom: 0.6rem;\n            }\n\n            .shop-item {\n                border: 0.01rem solid #e5e5e5;\n            }\n\n            .delete {\n                height: 100%;\n                width: 0.6rem;\n                color: white;\n                outline: none;\n                background: rgb(221, 0, 27);\n                border: none;\n            }\n\n            .delete:after {\n                content: '删除';\n            }\n\n            a:link {\n                text-decoration: none;\n            }\n\n            .shop-item img {\n                margin-left: 0.05rem;\n                max-height: 0.8rem;\n                max-width: 0.8rem;\n            }\n\n            .shop-item a {\n                color: #3c3c3c;\n            }\n\n            .shop-name {\n                font-size: 0.12rem;\n                overflow: hidden;\n                height: 0.5rem;\n            }\n\n            .shop-name a:hover {\n                color: #f40;\n                text-decoration: underline;\n            }\n\n            .size {\n                color: #9c9c9c;\n                font-size: 0.12rem;\n                overflow: hidden;\n            }\n\n            .item-main {\n                box-sizing: border-box;\n                padding: 0 0 0 0.05rem;\n                height: 0.8rem;\n                width: 1.5rem;\n            }\n\n            .price {\n                color: #3c3c3c;\n                font-size: 0.12rem;\n                font-weight: 700;\n                font-family: Verdana, Tahoma, arial;\n            }\n\n            .count {\n                outline: none;\n                box-sizing: border-box;\n                height: 0.25rem;\n                line-height: 0.15rem;\n                border: 0.01rem solid #aaa;\n                color: #343434;\n                width: 0.4rem;\n                text-align: center;\n                padding: 0.04rem 0.05rem;\n                background-color: #fff;\n                background-position: -0.75rem -3.75rem;\n                float: left;\n            }\n            .reduce,.add {\n                float: left;\n                height: 0.25rem;\n                width: 0.15rem;\n                border: 0.01rem solid #e5e5e5;\n                background: #f0f0f0;\n                text-align: center;\n                line-height: 0.23rem;\n                color: #444;\n                top: 0;\n                outline: none;\n            }\n\n            #countbox {\n                display: flex;\n                flex-flow: nowrap;\n            }\n\n            .item-sum {\n                text-align: center;\n                color: #f40;\n                font-size: 0.12rem;\n                font-weight: 700;\n            }\n\n            .total .item-total {\n                font-size: 0.12rem;\n                color: #f40;\n                font-weight: 700;\n                right: 100px;\n            }\n\n            .head {\n                height: 0.3rem;\n                background: rgb(221, 0, 27);\n                width: 100%;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                color: white;\n                position: fixed;\n                top: 0;\n                z-index: 999;\n            }\n\n            .foot {             \n                z-index: 999;\n                height: 0.5rem;\n                background: #E5E5E5;\n                width: 100%;\n                display: flex;\n                justify-content: space-between;\n                align-items: center;\n                color: white;\n                position: fixed;\n                bottom: 0;\n\n            }\n\n            .total {\n                display: flex;\n                flex-flow: nowrap;\n                align-items: center;\n                color: black;\n                height: 100%;\n            }\n\n            .head:after {\n                content: '购物车';\n            }\n\n            .footse {\n                align-items: center;\n                margin-left: 0.1rem;\n                display: flex;\n                flex-flow: nowrap;\n            }\n\n            .foottext {\n                color: black;\n                font-size: 14px;\n                white-space: nowrap;\n            }\n\n            .foottext:after {\n                content: '全选';\n            }\n\n            .submit {\n                border: none;\n                outline: none;\n                height: 100%;\n                width: 0.6rem;\n                color: white;\n                background: rgb(221, 0, 27);\n            }\n\n            .submit:after {\n                content: '结算';\n            }\n\n            .singleselect {\n                margin-left: 0.1rem;\n            }\n            .nodata{\n                display: flex;\n                margin: 0 auto;\n                height: 3rem;\n                width: 3rem;\n                background-image: url(img/nodata02.png);\n                background-repeat: no-repeat;\n                justify-content: center;\n                align-items: flex-end;\n                background-size: cover;\n                font-size: 14px;\n                color: #6d757a;\n            }\n            .totaltxt{\n                padding-right: 0.1rem;\n            }\n        \u003C/style>\n    \u003C/head>\n    \u003Cbody>\n        \u003Cheader>\n            \u003Cdiv class=\"head\">\u003C/div>\n        \u003C/header>\n        \u003Cdiv id=\"box\">\n        \u003Cdiv v-if=\"items.length\">           \n            \u003Cmain>\n                    \u003Ctemplate v-for=\"(item,index) in items\">\n                        \u003Cdiv class=\"shop-item\">\n                            \u003Cinput v-model=\"items[index].checked\" type=\"checkbox\" name=\"\" class=\"singleselect\" value=\"\" />\n                            \u003Cimg :src=\"item.src\">\n                            \u003Cdiv class=\"item-main\">\n                                \u003Cdiv class=\"shop-name\">\u003Ca href=\"#\">{{item.name}}\u003C/a>\u003C/div>\n                                \u003Cdiv class=\"size\">{{item.size}}\u003C/div>\n                            \u003C/div>\n                            \u003Cdiv class=\"price\">￥{{item.price}}\u003C/div>\n                            \u003Cdiv id=\"countbox\">\n                                \u003Cbutton type=\"button\" @click=\"countReduce(index)\" class=\"reduce\">-\u003C/button>\n                                \u003Cinput v-model=\"item.count\" type=\"text\" name=\"\" class=\"count\" value=\"\" />\n                                \u003Cbutton type=\"button\" @click=\"countAdd(index)\" class=\"add\">+\u003C/button>\n                            \u003C/div>\n                            \u003Cdiv class=\"item-sum\">￥{{item.price*item.count}}\u003C/div>\n                            \u003Cbutton type=\"button\" v-on:click=\"bookRemove\" class=\"delete\">\u003C/button>\n                        \u003C/div>\n                    \u003C/template>\n            \u003C/main>\n            \u003Cfooter>\n                \u003Cdiv class=\"foot\">\n                    \u003Cdiv class=\"footse\">\n                        \u003Cinput v-model=\"checked\" @click=\"all\" type=\"checkbox\" name=\"\" id=\"allselect\" value=\"\" />\n                        \u003Clabel for=\"allselect\" class=\"foottext\">\u003C/label>\n                    \u003C/div>\n                    \u003Cdiv class=\"total\">\n                        \u003Cdiv class=\"totaltxt\">\n                            \u003Cspan style=\"font-size: 14px;white-space: nowrap;\">合计:\u003C/span>\u003Cspan class=\"item-total\">￥{{sum}}\u003C/span>\n                        \u003C/div>                      \n                    \u003Cbutton type=\"button\" class=\"submit\">\u003C/button>\u003C/div>\n                \u003C/div>\n            \u003C/footer>           \n        \u003C/div>\n        \u003Cdiv v-else>\n            \u003Cmain>\n                \u003Cdiv class=\"nodata\">\n                    \u003Cp>你还暂时没有商品哦~\u003C/p>\n                \u003C/div>\n            \u003C/main>\n            \u003Cfooter>\n                    \u003Cdiv class=\"foot\">\n                        \u003Cdiv class=\"footse\">                        \n                        \u003C/div>\n                    \u003C/div>\n                \u003C/footer>\n        \u003C/div>\n        \u003C/div>\n    \u003C/body>\n    \u003Cscript type=\"text/javascript\">\n        var app = new Vue({\n            el: '#box',\n            data: {\n                keywords:'',\n                checked: false,\n                items: [{\n                        src: 'https://img.alicdn.com/bao/uploaded/i1/2200798231128/O1CN01OpBz5w1KCfK7ZIb5g_!!2200798231128.jpg_80x80.jpg',\n                        name: '优质舰队collection舰娘响手办响爷二次元动漫美少女机箱精品摆件',\n                        size: '尺寸：精品现货送海报',\n                        price: 79,\n                        count: 1,\n                        checked: false\n                    },\n                    {\n                        src: 'https://img.alicdn.com/bao/uploaded/i3/1085315961/O1CN013r8Hhq1tuBWw3AoNp_!!0-item_pic.jpg_80x80.jpg',\n                        name: '【年货价】Razer雷蛇黑寡妇蜘蛛竞技幻彩版V2电竞游戏87机械键盘RGB背光吃鸡',\n                        size: '套餐类型：官方标配 颜色分类：87键(绿轴)',\n                        price: 749,\n                        count: 1,\n                        checked: false\n                    }\n                ]\n            },\n            mounted: function() {},\n            methods: {\n                all: function() {\n                    if (!this.checked) {\n                        for (var i = 0; i \u003C this.items.length; i++) {                       \n                            this.items[i].checked=true;\n                        }\n                    }\n                    else{\n                        for (var i = 0; i \u003C this.items.length; i++) {                       \n                            this.items[i].checked=false;\n                        }\n                    }\n                },\n                countAdd: function(index) {\n                    this.items[index].count++;\n\n                },\n                countReduce: function(index) {\n                    if (this.items[index].count == 1) {\n                        return;\n                    }\n                    this.items[index].count--;\n                },\n                bookRemove: function() {\n                    for (var i = 0; i \u003C this.items.length; i++) {\n                        this.items.splice(i, 1);\n                    }\n                },\n                search:function(keywords){\n                 var newList=[];\n                 for(var i=0;i\u003Cthis.items.length;i++){\n                  if(this.items[i].name.indexOf(keywords)!==-1){\n                    newList.push(this.items[i])\n                  }\n                 }\n                 return newList\n                }\n\n            },\n            computed: {\n                sum: function() {\n                    var total = 0;                  \n                    for (var i = 0; i \u003C this.items.length; i++) {\n                        if(this.items[i].checked==true){\n                        total += this.items[i].price * this.items[i].count;}\n                    }\n                    return total;\n                }\n            }\n        });\n    \u003C/script>\n\u003C/html>\n",[24,6948,6946],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":6950},[],{"date":6952,"tags":6953,"categories":6841},"2020-02-07 14:46:14",[6839,6954],"购物车","/vue",{"title":6933,"description":6938},"CLB9g3MEWLYvrB6jZGQWNPYbC_H-FT8teWLdSx6c2Z8",{"id":6959,"title":6960,"body":6961,"description":22,"extension":104,"meta":7980,"navigation":110,"path":7986,"seo":7987,"stem":7988,"__hash__":7989},"content/exp.md","正则表达式详解",{"type":8,"value":6962,"toc":7972},[6963,7399,7447,7451,7455,7461,7464,7467,7470,7476,7485,7488,7491,7500,7508,7520,7529,7535,7541,7550,7558,7581,7600,7608,7612,7619,7629,7632,7639,7645,7650,7655,7658,7664,7673,7679,7685,7689,7703,7713,7735,7752,7755,7758,7761,7775,7782,7789,7798,7801,7811,7820,7823,7828,7833,7838,7846,7853,7860,7871,7884,7887,7900,7905,7912,7915,7922,7925,7931,7937,7940,7962,7966],[3821,6964,6965,6974],{},[3824,6966,6967],{},[3827,6968,6969,6972],{},[3830,6970,6971],{},"元字符",[3830,6973,3835],{},[3845,6975,6976,6984,6992,7000,7008,7016,7024,7032,7040,7048,7062,7074,7082,7090,7098,7106,7114,7122,7134,7148,7162,7175,7188,7196,7204,7212,7224,7236,7244,7252,7260,7271,7282,7290,7298,7310,7322,7330,7338,7345,7353,7361,7369,7377,7385,7393],{},[3827,6977,6978,6981],{},[3850,6979,6980],{},"/",[3850,6982,6983],{},"将下一个字符标记符、或一个向后引用、或一个八进制转义符。例如，“\\n”匹配\\n。“\\n”匹配换行符。序列“\\”匹配“\\”而“(”则匹配“(”。即相当于多种编程语言中都有的“转义字符”的概念。",[3827,6985,6986,6989],{},[3850,6987,6988],{},"^",[3850,6990,6991],{},"匹配输入字行首。如果设置了RegExp对象的Multiline属性，^也匹配“\\n”或“\\r”之后的位置。",[3827,6993,6994,6997],{},[3850,6995,6996],{},"$",[3850,6998,6999],{},"匹配输入行尾。如果设置了RegExp对象的Multiline属性，$也匹配“\\n”或“\\r”之前的位置。",[3827,7001,7002,7005],{},[3850,7003,7004],{},"*",[3850,7006,7007],{},"匹配前面的子表达式任意次。例如，zo*能匹配“z”，也能匹配“zo”以及“zoo”。*等价于{0,}。",[3827,7009,7010,7013],{},[3850,7011,7012],{},"+",[3850,7014,7015],{},"匹配前面的子表达式一次或多次(大于等于1次）。例如，“zo+”能匹配“zo”以及“zoo”，但不能匹配“z”。+等价于{1,}。",[3827,7017,7018,7021],{},[3850,7019,7020],{},"?",[3850,7022,7023],{},"匹配前面的子表达式零次或一次。例如，“do(es)?”可以匹配“do”或“does”。?等价于{0,1}。",[3827,7025,7026,7029],{},[3850,7027,7028],{},"{n}",[3850,7030,7031],{},"n是一个非负整数。匹配确定的n次。例如，“o{2}”不能匹配“Bob”中的“o”，但是能匹配“food”中的两个o。",[3827,7033,7034,7037],{},[3850,7035,7036],{},"{n,}",[3850,7038,7039],{},"n是一个非负整数。至少匹配n次。例如，“o{2,}”不能匹配“Bob”中的“o”，但能匹配“foooood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。",[3827,7041,7042,7045],{},[3850,7043,7044],{},"{n,m}",[3850,7046,7047],{},"m和n均为非负整数，其中n\u003C=m。最少匹配n次且最多匹配m次。例如，“o{1,3}”将匹配“fooooood”中的前三个o为一组，后三个o为一组。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。",[3827,7049,7050,7052],{},[3850,7051,7020],{},[3850,7053,7054,7055,7058,7059],{},"当该字符紧跟在任何一个其他限制符（*,+,?，{n}，{n,}，{n,m}）后面时，匹配模式是非贪婪的。非贪婪模式尽可能少地匹配所搜索的字符串，而默认的贪婪模式则尽可能多地匹配所搜索的字符串。例如，对于字符串“oooo”，“o+”将尽可能多地匹配“o”，得到结果",[1373,7056,7057],{},"“oooo”","，而“o+?”将尽可能少地匹配“o”，得到结果 ",[1373,7060,7061],{},"'o', 'o', 'o', 'o'",[3827,7063,7064,7067],{},[3850,7065,7066],{},".点",[3850,7068,7069,7070,7073],{},"匹配除“\\n”和\"\\r\"之外的任何单个字符。要匹配包括“\\n”和\"\\r\"在内的任何字符，请使用像“",[1373,7071,7072],{},"\\s\\S","”的模式。",[3827,7075,7076,7079],{},[3850,7077,7078],{},"(pattern)",[3850,7080,7081],{},"匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到，在VBScript中使用SubMatches集合，在JScript中则使用$0…$9属性。要匹配圆括号字符，请使用“(”或“)”。",[3827,7083,7084,7087],{},[3850,7085,7086],{},"(?:pattern)",[3850,7088,7089],{},"非获取匹配，匹配pattern但不获取匹配结果，不进行存储供以后使用。这在使用或字符“(",[3827,7091,7092,7095],{},[3850,7093,7094],{},"(?=pattern)",[3850,7096,7097],{},"非获取匹配，正向肯定预查，在任何匹配pattern的字符串开始处匹配查找字符串，该匹配不需要获取供以后使用。例如，“Windows(?=95",[3827,7099,7100,7103],{},[3850,7101,7102],{},"(?!pattern)",[3850,7104,7105],{},"非获取匹配，正向否定预查，在任何不匹配pattern的字符串开始处匹配查找字符串，该匹配不需要获取供以后使用。例如“Windows(?!95",[3827,7107,7108,7111],{},[3850,7109,7110],{},"(?\u003C=pattern)",[3850,7112,7113],{},"非获取匹配，反向肯定预查，与正向肯定预查类似，只是方向相反。例如，“(?\u003C=95",[3827,7115,7116,7119],{},[3850,7117,7118],{},"(?\u003C!patte_n)",[3850,7120,7121],{},"非获取匹配，反向否定预查，与正向否定预查类似，只是方向相反。例如“(?\u003C!95",[3827,7123,7124,7127],{},[3850,7125,7126],{},"x|y",[3850,7128,7129,7130,7133],{},"匹配x或y。例如，“z|food”能匹配“z”或“food”(此处请谨慎)。“",[1373,7131,7132],{},"z|f","ood”则匹配“zood”或“food”。",[3827,7135,7136,7141],{},[3850,7137,7138],{},[1373,7139,7140],{},"xyz",[3850,7142,7143,7144,7147],{},"字符集合。匹配所包含的任意一个字符。例如，“",[1373,7145,7146],{},"abc","”可以匹配“plain”中的“a”。",[3827,7149,7150,7155],{},[3850,7151,7152],{},[1373,7153,7154],{},"^xyz",[3850,7156,7157,7158,7161],{},"负值字符集合。匹配未包含的任意字符。例如，“",[1373,7159,7160],{},"^abc","”可以匹配“plain”中的“plin”任一字符,匹配除了xyz以外的任意字符",[3827,7163,7164,7169],{},[3850,7165,7166],{},[1373,7167,7168],{},"a-z",[3850,7170,7171,7172,7174],{},"字符范围。匹配指定范围内的任意字符。例如，“",[1373,7173,7168],{},"”可以匹配“a”到“z”范围内的任意小写字母字符。   注意:只有连字符在字符组内部时,并且出现在两个字符之间时,才能表示字符的范围; 如果出字符组的开头,则只能表示连字符本身.",[3827,7176,7177,7182],{},[3850,7178,7179],{},[1373,7180,7181],{},"^a-z",[3850,7183,7184,7185,7187],{},"负值字符范围。匹配任何不在指定范围内的任意字符。例如，“",[1373,7186,7181],{},"”可以匹配任何不在“a”到“z”范围内的任意字符。",[3827,7189,7190,7193],{},[3850,7191,7192],{},"\\b",[3850,7194,7195],{},"匹配一个单词的边界，也就是指单词和空格间的位置（即正则表达式的“匹配”有两种概念，一种是匹配字符，一种是匹配位置，这里的\\b就是匹配位置的）。例如，“er\\b”可以匹配“never”中的“er”，但不能匹配“verb”中的“er”；“\\b1_”可以匹配“1_23”中的“1_”，但不能匹配“21_3”中的“1_”。",[3827,7197,7198,7201],{},[3850,7199,7200],{},"\\B",[3850,7202,7203],{},"匹配非单词边界。“er\\B”能匹配“verb”中的“er”，但不能匹配“never”中的“er”。",[3827,7205,7206,7209],{},[3850,7207,7208],{},"\\cx",[3850,7210,7211],{},"匹配由x指明的控制字符。例如，\\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则，将c视为一个原义的“c”字符。",[3827,7213,7214,7217],{},[3850,7215,7216],{},"\\d",[3850,7218,7219,7220,7223],{},"匹配一个数字字符。等价于",[1373,7221,7222],{},"0-9","。grep 要加上-P，perl正则支持",[3827,7225,7226,7229],{},[3850,7227,7228],{},"\\D",[3850,7230,7231,7232,7235],{},"匹配一个非数字字符。等价于",[1373,7233,7234],{},"^0-9","。grep要加上-P，perl正则支持",[3827,7237,7238,7241],{},[3850,7239,7240],{},"\\f",[3850,7242,7243],{},"匹配一个换页符。等价于\\x0c和\\cL。",[3827,7245,7246,7249],{},[3850,7247,7248],{},"\\n",[3850,7250,7251],{},"匹配一个换行符。等价于\\x0a和\\cJ。",[3827,7253,7254,7257],{},[3850,7255,7256],{},"\\r",[3850,7258,7259],{},"匹配一个回车符。等价于\\x0d和\\cM。",[3827,7261,7262,7265],{},[3850,7263,7264],{},"\\s",[3850,7266,7267,7268,6391],{},"匹配任何不可见字符，包括空格、制表符、换页符等等。等价于",[1373,7269,7270],{}," \\f\\n\\r\\t\\v",[3827,7272,7273,7276],{},[3850,7274,7275],{},"\\S",[3850,7277,7278,7279,6391],{},"匹配任何可见字符。等价于",[1373,7280,7281],{},"^ \\f\\n\\r\\t\\v",[3827,7283,7284,7287],{},[3850,7285,7286],{},"\\t",[3850,7288,7289],{},"匹配一个制表符。等价于\\x09和\\cI。",[3827,7291,7292,7295],{},[3850,7293,7294],{},"\\v",[3850,7296,7297],{},"匹配一个垂直制表符。等价于\\x0b和\\cK。",[3827,7299,7300,7303],{},[3850,7301,7302],{},"\\w",[3850,7304,7305,7306,7309],{},"匹配包括下划线的任何单词字符。类似但不等价于“",[1373,7307,7308],{},"A-Za-z0-9_","”，这里的\"单词\"字符使用Unicode字符集。",[3827,7311,7312,7315],{},[3850,7313,7314],{},"\\W",[3850,7316,7317,7318,7321],{},"匹配任何非单词字符。等价于“",[1373,7319,7320],{},"^A-Za-z0-9_","”。",[3827,7323,7324,7327],{},[3850,7325,7326],{},"\\xn",[3850,7328,7329],{},"匹配n，其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如，“\\x41”匹配“A”。“\\x041”则等价于“\\x04&1”。正则表达式中可以使用ASCII编码。",[3827,7331,7332,7335],{},[3850,7333,7334],{},"\\num",[3850,7336,7337],{},"匹配num，其中num是一个正整数。对所获取的匹配的引用。例如，“(.)\\1”匹配两个连续的相同字符。",[3827,7339,7340,7342],{},[3850,7341,7248],{},[3850,7343,7344],{},"标识一个八进制转义值或一个向后引用。如果\\n之前至少n个获取的子表达式，则n为向后引用。否则，如果n为八进制数字（0-7），则n为一个八进制转义值。",[3827,7346,7347,7350],{},[3850,7348,7349],{},"\\nm",[3850,7351,7352],{},"标识一个八进制转义值或一个向后引用。如果\\nm之前至少有nm个获得子表达式，则nm为向后引用。如果\\nm之前至少有n个获取，则n为一个后跟文字m的向后引用。如果前面的条件都不满足，若n和m均为八进制数字（0-7），则\\nm将匹配八进制转义值nm。",[3827,7354,7355,7358],{},[3850,7356,7357],{},"\\nml",[3850,7359,7360],{},"如果n为八进制数字（0-7），且m和l均为八进制数字（0-7），则匹配八进制转义值nml。",[3827,7362,7363,7366],{},[3850,7364,7365],{},"\\un",[3850,7367,7368],{},"匹配n，其中n是一个用四个十六进制数字表示的Unicode字符。例如，\\u00A9匹配版权符号（©）。",[3827,7370,7371,7374],{},[3850,7372,7373],{},"\\p{P}",[3850,7375,7376],{},"小写 p 是 property 的意思，表示 Unicode 属性，用于 Unicode 正表达式的前缀。中括号内的“P”表示Unicode 字符集七个字符属性之一：标点字符。",[3827,7378,7379,7382],{},[3850,7380,7381],{},"\u003C  >",[3850,7383,7384],{},"匹配词（word）的开始（\u003C）和结束（>）。例如正则表达式\u003Cthe>能够匹配字符串\"for the wise\"中的\"the\"，但是不能匹配字符串\"otherwise\"中的\"the\"。注意：这个元字符不是所有的软件都支持的。",[3827,7386,7387,7390],{},[3850,7388,7389],{},"( )",[3850,7391,7392],{},"将( 和 ) 之间的表达式定义为“组”（group），并且将匹配这个表达式的字符保存到一个临时区域（一个正则表达式中最多可以保存9个），它们可以用 \\1 到\\9 的符号来引用。",[3827,7394,7395,7397],{},[3850,7396],{},[3850,7398],{},[3821,7400,7401,7413],{},[3824,7402,7403],{},[3827,7404,7405,7408,7411],{},[3830,7406,7407],{},"表达式",[3830,7409,7410],{},"名称",[3830,7412,3835],{},[3845,7414,7415,7426,7437],{},[3827,7416,7417,7420,7423],{},[3850,7418,7419],{},"?=exp",[3850,7421,7422],{},"正向前瞻",[3850,7424,7425],{},"零宽度，断言出现的位置后面能匹配exp，则匹配成功，匹配不占据匹配长度，非捕获",[3827,7427,7428,7431,7434],{},[3850,7429,7430],{},"?!exp",[3850,7432,7433],{},"负向前瞻",[3850,7435,7436],{},"零宽度，断言出现的位置后面不能匹配exp，则匹配成功，匹配不占据匹配长度，非捕获",[3827,7438,7439,7442,7444],{},[3850,7440,7441],{},"?:exp",[3850,7443,7422],{},[3850,7445,7446],{},"非捕获性分组，断言出现位置后面匹配exp，占据查询的匹配长度",[11,7448,7450],{"id":7449},"_65条最常用正则表达式你要的都在这里了","65条最常用正则表达式，你要的都在这里了！",[73,7452,7454],{"id":7453},"一校验数字的表达式","一、校验数字的表达式",[27,7456,7457,7458,7460],{},"1 数字：^",[1373,7459,7222],{},"*$",[27,7462,7463],{},"2 n位的数字：^\\d{n}$",[27,7465,7466],{},"3 至少n位的数字：^\\d{n,}$",[27,7468,7469],{},"4 m-n位的数字：^\\d{m,n}$",[27,7471,7472,7473,7475],{},"5 零和非零开头的数字：^(0|[1-9]",[1373,7474,7222],{},"*)$",[27,7477,7478,7479,7481,7482,7484],{},"6 非零开头的最多带两位小数的数字：^([1-9]",[1373,7480,7222],{},"*)+(.",[1373,7483,7222],{},"{1,2})?$",[27,7486,7487],{},"7 带1-2位小数的正数或负数：^(-)?\\d+(.\\d{1,2})?$",[27,7489,7490],{},"8 正数、负数、和小数：^(-|+)?\\d+(.\\d+)?$",[27,7492,7493,7494,7496,7497,7499],{},"9 有两位小数的正实数：^",[1373,7495,7222],{},"+(.",[1373,7498,7222],{},"{2})?$",[27,7501,7502,7503,7496,7505,7507],{},"10 有1~3位小数的正实数：^",[1373,7504,7222],{},[1373,7506,7222],{},"{1,3})?$",[27,7509,7510,7511,7514,7515,7517,7518,7460],{},"11 非零的正整数：^",[1373,7512,7513],{},"1-9","\\d$ 或 ^([1-9]",[1373,7516,7222],{},"){1,3}$ 或 ^+?[1-9]",[1373,7519,7222],{},[27,7521,7522,7523,7525,7526,7528],{},"12 非零的负整数：^-[1-9]",[1373,7524],{},"0-9\"$ 或 ^-",[1373,7527,7513],{},"\\d$",[27,7530,7531,7532,7534],{},"13 非负整数：^\\d+$ 或 ^",[1373,7533,7513],{},"\\d*|0$",[27,7536,7537,7538,7540],{},"14 非正整数：^-",[1373,7539,7513],{},"\\d*|0$ 或 ^((-\\d+)|(0+))$",[27,7542,7543,7544,7546,7547,7549],{},"15 非负浮点数：^\\d+(.\\d+)?$ 或 ^",[1373,7545,7513],{},"\\d.\\d|0.\\d",[1373,7548,7513],{},"\\d|0?.0+|0$",[27,7551,7552,7553,7546,7555,7557],{},"16 非正浮点数：^((-\\d+(.\\d+)?)|(0+(.0+)?))$ 或 ^(-(",[1373,7554,7513],{},[1373,7556,7513],{},"\\d))|0?.0+|0$",[27,7559,7560,7561,7546,7563,7565,7566,7568,7569,7571,7572,7574,7575,7577,7578,7580],{},"17 正浮点数：^",[1373,7562,7513],{},[1373,7564,7513],{},"\\d$ 或 ^((",[1373,7567,7222],{},"+.[0-9][1-9]",[1373,7570,7222],{},")|([0-9][1-9]",[1373,7573,7222],{},".",[1373,7576,7222],{},"+)|([0-9][1-9]",[1373,7579,7222],{},"))$",[27,7582,7583,7584,7546,7586,7588,7589,7568,7591,7571,7593,7574,7595,7577,7597,7599],{},"18 负浮点数：^-(",[1373,7585,7513],{},[1373,7587,7513],{},"\\d)$ 或 ^(-((",[1373,7590,7222],{},[1373,7592,7222],{},[1373,7594,7222],{},[1373,7596,7222],{},[1373,7598,7222],{},")))$",[27,7601,7602,7603,7546,7605,7607],{},"19 浮点数：^(-?\\d+)(.\\d+)?$ 或 ^-?(",[1373,7604,7513],{},[1373,7606,7513],{},"\\d|0?.0+|0)$",[73,7609,7611],{"id":7610},"二校验字符的表达式","二、校验字符的表达式",[27,7613,7614,7615,7618],{},"1 汉字：^",[1373,7616,7617],{},"\\u4e00-\\u9fa5","{0,}$",[27,7620,7621,7622,7625,7626,7628],{},"2 英文和数字：^",[1373,7623,7624],{},"A-Za-z0-9","+$ 或 ^",[1373,7627,7624],{},"{4,40}$",[27,7630,7631],{},"3 长度为3-20的所有字符：^.{3,20}$",[27,7633,7634,7635,7638],{},"4 由26个英文字母组成的字符串：^",[1373,7636,7637],{},"A-Za-z","+$",[27,7640,7641,7642,7638],{},"5 由26个大写英文字母组成的字符串：^",[1373,7643,7644],{},"A-Z",[27,7646,7647,7648,7638],{},"6 由26个小写英文字母组成的字符串：^",[1373,7649,7168],{},[27,7651,7652,7653,7638],{},"7 由数字和26个英文字母组成的字符串：^",[1373,7654,7624],{},[27,7656,7657],{},"8 由数字、26个英文字母或者下划线组成的字符串：^\\w+$ 或 ^\\w{3,20}$",[27,7659,7660,7661,7638],{},"9 中文、英文、数字包括下划线：^",[1373,7662,7663],{},"\\u4E00-\\u9FA5A-Za-z0-9_",[27,7665,7666,7667,7625,7670,7672],{},"10 中文、英文、数字但不包括下划线等符号：^",[1373,7668,7669],{},"\\u4E00-\\u9FA5A-Za-z0-9",[1373,7671,7669],{},"{2,20}$",[27,7674,7675,7676,7012],{},"11 可以输入含有^%&',;=?$\"等字符：",[1373,7677,7678],{},"^%&',;=?$\\x22",[27,7680,7681,7682,7012],{},"12 禁止输入含有~的字符：",[1373,7683,7684],{},"^~\\x22",[73,7686,7688],{"id":7687},"三特殊需求表达式","三、特殊需求表达式",[27,7690,7691,7692,7695,7696,7699,7700,7702],{},"1 Email地址：^\\w+(",[1373,7693,7694],{},"-+.","\\w+)@\\w+(",[1373,7697,7698],{},"-.","\\w+).\\w+(",[1373,7701,7698],{},"\\w+)*$",[27,7704,7705,7706,7709,7710,7712],{},"2 域名：[a-zA-Z0-9]",[1373,7707,7708],{},"-a-zA-Z0-9","{0,62}(/.[a-zA-Z0-9]",[1373,7711,7708],{},"{0,62})+/.?",[27,7714,7715,7716,7719,7720,7723,7724,7727,7728,7730,7731,7734],{},"3 InternetURL：",[1373,7717,7718],{},"a-zA-z","+://",[1373,7721,7722],{},"^\\s"," 或 ^http://(",[1373,7725,7726],{},"\\w-","+.)+",[1373,7729,7726],{},"+(/",[1373,7732,7733],{},"\\w-./?%&=",")?$",[27,7736,7737,7738,7740,7741,7744,7745,7748,7749,7751],{},"4 手机号码：^(13",[1373,7739,7222],{},"|14",[1373,7742,7743],{},"5|7","|15",[1373,7746,7747],{},"0|1|2|3|5|6|7|8|9","|18",[1373,7750,7747],{},")\\d{8}$",[27,7753,7754],{},"5 电话号码(\"XXX-XXXXXXX\"、\"XXXX-XXXXXXXX\"、\"XXX-XXXXXXX\"、\"XXX-XXXXXXXX\"、\"XXXXXXX\"和\"XXXXXXXX)：^((\\d{3,4}-)|\\d{3.4}-)?\\d{7,8}$",[27,7756,7757],{},"6 国内电话号码(0511-4405222、021-87888822)：\\d{3}-\\d{8}|\\d{4}-\\d{7}",[27,7759,7760],{},"7 身份证号(15位、18位数字)：^\\d{15}|\\d{18}$",[27,7762,7763,7764,7766,7767,7770,7771,7774],{},"8 短身份证号码(数字、字母x结尾)：^(",[1373,7765,7222],{},"){7,18}(x|X)?$ 或 ^\\d{8,18}|",[1373,7768,7769],{},"0-9x","{8,18}|",[1373,7772,7773],{},"0-9X","{8,18}?$",[27,7776,7777,7778,7781],{},"9 帐号是否合法(字母开头，允许5-16字节，允许字母数字下划线)：^[a-zA-Z]",[1373,7779,7780],{},"a-zA-Z0-9_","{4,15}$",[27,7783,7784,7785,7788],{},"10 密码(以字母开头，长度在6~18之间，只能包含字母、数字和下划线)：^",[1373,7786,7787],{},"a-zA-Z","\\w{5,17}$",[27,7790,7791,7792,7794,7795,7797],{},"11 强密码(必须包含大小写字母和数字的组合，不能使用特殊字符，长度在8-10之间)：^(?=.\\d)(?=.",[1373,7793,7168],{},")(?=.*",[1373,7796,7644],{},").{8,10}$",[27,7799,7800],{},"12 日期格式：^\\d{4}-\\d{1,2}-\\d{1,2}",[27,7802,7803,7804,7806,7807,7810],{},"13 一年的12个月(01～09和1～12)：^(0?",[1373,7805,7513],{},"|1",[1373,7808,7809],{},"0-2",")$",[27,7812,7813,7814,7816,7817,7819],{},"14 一个月的31天(01～09和1～31)：^((0?",[1373,7815,7513],{},")|((1|2)",[1373,7818,7222],{},")|30|31)$",[27,7821,7822],{},"15 钱的输入格式：",[27,7824,7825,7826,7460],{},"16 1.有四种钱的表示形式我们可以接受:\"10000.00\" 和 \"10,000.00\", 和没有 \"分\" 的 \"10000\" 和 \"10,000\"：^[1-9]",[1373,7827,7222],{},[27,7829,7830,7831,7475],{},"17 2.这表示任意一个不以0开头的数字,但是,这也意味着一个字符\"0\"不通过,所以我们采用下面的形式：^(0|[1-9]",[1373,7832,7222],{},[27,7834,7835,7836,7475],{},"18 3.一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号：^(0|-?[1-9]",[1373,7837,7222],{},[27,7839,7840,7841,7496,7843,7845],{},"19 4.这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧.下面我们要加的是说明可能的小数部分：^",[1373,7842,7222],{},[1373,7844,7222],{},"+)?$",[27,7847,7848,7849,7496,7851,7499],{},"20 5.必须说明的是,小数点后面至少应该有1位数,所以\"10.\"是不通过的,但是 \"10\" 和 \"10.2\" 是通过的：^",[1373,7850,7222],{},[1373,7852,7222],{},[27,7854,7855,7856,7496,7858,7484],{},"21 6.这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样：^",[1373,7857,7222],{},[1373,7859,7222],{},[27,7861,7862,7863,7865,7866,7868,7869,7484],{},"22 7.这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样：^",[1373,7864,7222],{},"{1,3}(,",[1373,7867,7222],{},"{3})*(.",[1373,7870,7222],{},[27,7872,7873,7874,7876,7877,7865,7879,7881,7882,7484],{},"23 8.1到3个数字,后面跟着任意个 逗号+3个数字,逗号成为可选,而不是必须：^(",[1373,7875,7222],{},"+|",[1373,7878,7222],{},[1373,7880,7222],{},"{3})*)(.",[1373,7883,7222],{},[27,7885,7886],{},"24 备注：这就是最终结果了,别忘了\"+\"可以用\"*\"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里",[27,7888,7889,7890,7892,7893,7896,7897,6996],{},"25 xml文件：^(",[1373,7891,7787],{},"+-?)+",[1373,7894,7895],{},"a-zA-Z0-9","+.[x|X][m|M]",[1373,7898,7899],{},"l|L",[27,7901,7902,7903],{},"26 中文字符的正则表达式：",[1373,7904,7617],{},[27,7906,7907,7908,7911],{},"27 双字节字符：",[1373,7909,7910],{},"^\\x00-\\xff"," (包括汉字在内，可以用来计算字符串的长度(一个双字节字符长度计2，ASCII字符计1))",[27,7913,7914],{},"28 空白行的正则表达式：\\n\\s*\\r (可以用来删除空白行)",[27,7916,7917,7918,7921],{},"29 HTML标记的正则表达式：\u003C(\\S?)",[1373,7919,7920],{},"^>",">.?\u003C/\\1>|\u003C.? /> (网上流传的版本太糟糕，上面这个也仅仅能部分，对于复杂的嵌套标记依旧无能为力)",[27,7923,7924],{},"30 首尾空白字符的正则表达式：^\\s|\\s$或(^\\s)|(\\s$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等)，非常有用的表达式)",[27,7926,7927,7928,7930],{},"31 腾讯QQ号：[1-9]",[1373,7929,7222],{},"{4,} (腾讯QQ号从10000开始)",[27,7932,7933,7934,7936],{},"32 中国邮政编码：",[1373,7935,7513],{},"\\d{5}(?!\\d) (中国邮政编码为6位数字)",[27,7938,7939],{},"33 IP地址：\\d+.\\d+.\\d+.\\d+ (提取IP地址时有用)",[27,7941,7942,7943,7946,7947,7950,7951,7954,7955,7946,7957,7950,7959,7961],{},"34 IP地址：((?:(?:25",[1373,7944,7945],{},"0-5","|2",[1373,7948,7949],{},"0-4","\\d|",[1373,7952,7953],{},"01","?\\d?\\d).){3}(?:25",[1373,7956,7945],{},[1373,7958,7949],{},[1373,7960,7953],{},"?\\d?\\d))",[73,7963,7965],{"id":7964},"正则用于小程序富文本渲染html标签添设置样式","正则用于小程序富文本渲染HTMl标签添设置样式:",[16,7967,7970],{"className":7968,"code":7969,"language":54,"meta":22},[52],"    res.data.content = res.data.content.replace(/width\\s*:\\s*[0-9]+px/g, 'width:100%');\n    res.data.content = res.data.content.replace(/\u003C([\\/]?)(center)((:?\\s*)(:?[^>]*)(:?\\s*))>/g, '\u003C$1div$3>');//替换center标签\n    res.data.content = res.data.content.replace(/\\\u003Cimg/gi, '\u003Cimg class=\"rich-img\" ');//正则给img标签增加class\n    //或者这样直接添加修改style\n    res.data.content = res.data.content.replace(/style\\s*?=\\s*?([‘\"])[\\s\\S]*?\\1/ig, 'style=\"width:100%;height:auto;display: block;margin:auto\"');\n    res.data.content = res.data.content.replace(/\\\u003Cp/gi, '\u003CP class=\"rich-p\" ');//正则给p标签增加class\n",[24,7971,7969],{"__ignoreMap":22},{"title":22,"searchDepth":95,"depth":95,"links":7973},[7974],{"id":7449,"depth":95,"text":7450,"children":7975},[7976,7977,7978,7979],{"id":7453,"depth":102,"text":7454},{"id":7610,"depth":102,"text":7611},{"id":7687,"depth":102,"text":7688},{"id":7964,"depth":102,"text":7965},{"date":7981,"tags":7982,"categories":7985},"2020-02-06 19:34:41",[7983,7984],"Regexp","正则表达式","语法","/exp",{"title":6960,"description":22},"exp","tYON25LEGP0ZsRN7_1QTXLYvMY93dwS9RN_VXElJ-04",{"id":7991,"title":7992,"body":7993,"description":7997,"extension":104,"meta":8481,"navigation":110,"path":8486,"seo":8487,"stem":8483,"__hash__":8488},"content/api.md","腾讯QQ常用API收集",{"type":8,"value":7994,"toc":8479},[7995,7998,8004,8007,8013,8021,8029,8032,8035,8043,8046,8049,8057,8064,8072,8079,8086,8094,8103,8111,8118,8125,8132,8139,8147,8155,8163,8170,8183,8191,8199,8207,8215,8223,8231,8239,8247,8255,8263,8271,8279,8287,8295,8303,8331,8339,8347,8355,8363,8370,8377,8384,8391,8403,8410,8417,8425,8432,8440,8443,8450,8457,8464,8472],[27,7996,7997],{},"个人收集的一些腾讯的相关接口,主要用于与QQ用户信息交互\n最常见的用于网页推广：\n加群a标签",[16,7999,8002],{"className":8000,"code":8001,"language":256,"meta":22},[254],"\u003Ca target=\"_blank\" href=\"//shang.qq.com/wpa/qunwpa?idkey=e9a5b03d975943a70449aecd9b9724411694c67bb20edb7f3c52a016ce5a93ea\">\u003Cimg border=\"0\" src=\"//pub.idqqimg.com/wpa/images/group.png\" alt=\"****\" title=\"****\">\u003C/a>\n",[24,8003,8001],{"__ignoreMap":22},[27,8005,8006],{},"qq通讯a标签",[16,8008,8011],{"className":8009,"code":8010,"language":256,"meta":22},[254],"\u003Ca target=\"_blank\" href=\"http://wpa.qq.com/msgrd?v=3&uin=qq号&site=qq&menu=yes\">\u003Cimg border=\"0\" src=\"http://wpa.qq.com/pa?p=2:qq号:51\" alt=\"点击这里给我发消息\" title=\"点击这里给我发消息\"/>\u003C/a>\n",[24,8012,8010],{"__ignoreMap":22},[27,8014,8015,8016],{},"QQ昵称\n",[497,8017,8020],{"href":8018,"rel":8019},"https://users.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins=QQ%E5%8F%B7",[590],"https://users.qzone.qq.com/fcg-bin/cgi_get_portrait.fcg?uins=QQ号",[27,8022,8023,8024],{},"QQ空间头像:\n",[497,8025,8028],{"href":8026,"rel":8027},"http://qlogo1.store.qq.com/qzone/QQ%E5%8F%B7/QQ%E5%8F%B7/100",[590],"http://qlogo1.store.qq.com/qzone/QQ号/QQ号/100",[27,8030,8031],{},"QQ资料:\ntencent://ContactInfo/?subcmd=ViewInfo&puin=0&uin=QQ号&fuin=QQ号",[27,8033,8034],{},"加好友:\ntencent://AddContact/?fromId=45&fromSubId=1&subcmd=all&uin=QQ号&fuin=QQ号",[27,8036,8037,8038],{},"加群：\n",[497,8039,8042],{"href":8040,"rel":8041},"http://shang.qq.com/wpa/qunwpa?idkey=%E7%BE%A4key",[590],"http://shang.qq.com/wpa/qunwpa?idkey=群key",[27,8044,8045],{},"发起群会话:\ntencent://groupwpa/?subcmd=all\\u0026param=群字段fuin=QQ号",[27,8047,8048],{},"发起好友临时会话:\ntencent://Message/?Uin=QQ号&websiteName=qzone.qq.com&Menu=yes&fuin=QQ号",[27,8050,8051,8052],{},"手机版名片:\n",[497,8053,8056],{"href":8054,"rel":8055},"https://res.abeim.cn/api/qq/?qq=QQ%E5%8F%B7",[590],"https://res.abeim.cn/api/qq/?qq=QQ号",[27,8058,8059,8060],{},"打卡\n打卡 - 主页\n",[497,8061,8062],{"href":8062,"rel":8063},"https://ti.qq.com/signin/public/index.html",[590],[27,8065,8066,8067,8071],{},"打卡 - 我的卡片\n",[497,8068,8069],{"href":8069,"rel":8070},"https://ti.qq.com/signin/public/cardsv2.html",[590],"\n2020-07-02",[27,8073,8074,8075],{},"达人\n",[497,8076,8077],{"href":8077,"rel":8078},"https://ti.qq.com/xman/self.html",[590],[27,8080,8081,8082],{},"个性装扮\n",[497,8083,8084],{"href":8084,"rel":8085},"https://zb.vip.qq.com/sonic/index",[590],[27,8087,8088,8089],{},"好友互动标识\n",[497,8090,8093],{"href":8091,"rel":8092},"https://ti.qq.com/hybrid-h5/interactive_logo/two?target_uin=%E5%AF%B9%E6%96%B9Q%E5%8F%B7&_nav_txtclr=000000",[590],"https://ti.qq.com/hybrid-h5/interactive_logo/two?target_uin=对方Q号&_nav_txtclr=000000",[27,8095,8096,8097,8102],{},"空间\n空间 - 访客周报\n",[497,8098,8101],{"href":8099,"rel":8100},"https://h5.qzone.qq.com/qzoneVisitor/reporter/detail/Q%E5%8F%B7",[590],"https://h5.qzone.qq.com/qzoneVisitor/reporter/detail/Q号","\n2020-08-10",[27,8104,8105,8106,8110],{},"空间 - 好友动态权限设置\n",[497,8107,8108],{"href":8108,"rel":8109},"https://h5.qzone.qq.com/qzone/setting",[590],"\n2020-09-03",[27,8112,8113,8114,8102],{},"空间 - 花藤\n",[497,8115,8116],{"href":8116,"rel":8117},"https://qzs.qq.com/qzone/phone/m/v4/module/playbar/index.html?appid=1101255595",[590],[27,8119,8120,8121],{},"空间 - 我的好友\n",[497,8122,8123],{"href":8123,"rel":8124},"https://h5.qzone.qq.com/platform/myfriend",[590],[27,8126,8127,8128],{},"空间 - 小秘密\n",[497,8129,8130],{"href":8130,"rel":8131},"https://h5.qzone.qq.com/secret/list",[590],[27,8133,8134,8135],{},"亲密关系\n",[497,8136,8137],{"href":8137,"rel":8138},"https://ti.qq.com/hybrid-h5/intimate/list",[590],[27,8140,8141,8142,8071],{},"礼物\n",[497,8143,8146],{"href":8144,"rel":8145},"https://h5.qzone.qq.com/v2/vip/giftv2/vuemall?friends=Q%E5%8F%B7",[590],"https://h5.qzone.qq.com/v2/vip/giftv2/vuemall?friends=Q号",[27,8148,8149,8150,8102],{},"群聊\n群成员等级\n",[497,8151,8154],{"href":8152,"rel":8153},"https://qun.qq.com/interactive/levellist?gc=%E7%BE%A4%E5%8F%B7",[590],"https://qun.qq.com/interactive/levellist?gc=群号",[27,8156,8157,8158],{},"群公告\n",[497,8159,8162],{"href":8160,"rel":8161},"https://web.qun.qq.com/mannounce/index.html#gc=%E7%BE%A4%E5%8F%B7",[590],"https://web.qun.qq.com/mannounce/index.html#gc=群号",[27,8164,8165,8166],{},"群发消息\n",[497,8167,8168],{"href":8168,"rel":8169},"https://qun.qq.com/qqweb/m/qun/supermaster/msg.html",[590],[27,8171,8172,8173,8178],{},"群机器人\n",[497,8174,8177],{"href":8175,"rel":8176},"https://qun.qq.com/qqweb/m/qun/qun_robot/setting.html?gc=%E7%BE%A4%E5%8F%B7",[590],"https://qun.qq.com/qqweb/m/qun/qun_robot/setting.html?gc=群号",[497,8179,8182],{"href":8180,"rel":8181},"https://web.qun.qq.com/qunrobot/data.html?gc=%E7%BE%A4%E5%8F%B7&robot_uin=%E6%9C%BA%E5%99%A8%E4%BA%BAQ%E5%8F%B7",[590],"https://web.qun.qq.com/qunrobot/data.html?gc=群号&robot_uin=机器人Q号",[27,8184,8185,8186,8110],{},"群精华消息\n",[497,8187,8190],{"href":8188,"rel":8189},"https://qun.qq.com/essence/index?gc=%E7%BE%A4%E5%8F%B7",[590],"https://qun.qq.com/essence/index?gc=群号",[27,8192,8193,8194],{},"群聊等级PK\n",[497,8195,8198],{"href":8196,"rel":8197},"https://qun.qq.com/qqweb/m/qun/rank/rank.html?gc=%E7%BE%A4%E5%8F%B7&uin=Q%E5%8F%B7",[590],"https://qun.qq.com/qqweb/m/qun/rank/rank.html?gc=群号&uin=Q号",[27,8200,8201,8202],{},"群匿名\n",[497,8203,8206],{"href":8204,"rel":8205},"https://qqweb.qq.com/m/business/anonymoustalk/index.html?gcode=%E7%BE%A4%E5%8F%B7&role=1&self=1",[590],"https://qqweb.qq.com/m/business/anonymoustalk/index.html?gcode=群号&role=1&self=1",[27,8208,8209,8210],{},"群签到\n群签到 - 列表\n",[497,8211,8214],{"href":8212,"rel":8213},"https://qun.qq.com/qqweb/m/qun/checkin/index.html?gc=%E7%BE%A4%E5%8F%B7&state=0",[590],"https://qun.qq.com/qqweb/m/qun/checkin/index.html?gc=群号&state=0",[27,8216,8217,8218],{},"群签到 - 新签到\n",[497,8219,8222],{"href":8220,"rel":8221},"https://qun.qq.com/qqweb/m/qun/checkin/index.html?gc=%E7%BE%A4%E5%8F%B7&state=1",[590],"https://qun.qq.com/qqweb/m/qun/checkin/index.html?gc=群号&state=1",[27,8224,8225,8226],{},"群日历\n",[497,8227,8230],{"href":8228,"rel":8229},"https://qun.qq.com/qqweb/m/qun/calendar/index.html?gc=%E7%BE%A4%E5%8F%B7",[590],"https://qun.qq.com/qqweb/m/qun/calendar/index.html?gc=群号",[27,8232,8233,8234],{},"群荣誉\n",[497,8235,8238],{"href":8236,"rel":8237},"https://qun.qq.com/interactive/qunhonor?gc=%E7%BE%A4%E5%8F%B7",[590],"https://qun.qq.com/interactive/qunhonor?gc=群号",[27,8240,8241,8242],{},"群收钱\n",[497,8243,8246],{"href":8244,"rel":8245},"https://mqq.tenpay.com/mqq/groupreceipts/index.shtml?uin=Q%E5%8F%B7&type=3",[590],"https://mqq.tenpay.com/mqq/groupreceipts/index.shtml?uin=Q号&type=3",[27,8248,8249,8250],{},"群数据\n",[497,8251,8254],{"href":8252,"rel":8253},"https://qqweb.qq.com/m/qun/activedata/active.html?gc=%E7%BE%A4%E5%8F%B7",[590],"https://qqweb.qq.com/m/qun/activedata/active.html?gc=群号",[27,8256,8257,8258],{},"群投票\n群投票 - 列表\n",[497,8259,8262],{"href":8260,"rel":8261},"https://client.qun.qq.com/qqweb/m/qun/vote/index.html?groupuin=%E7%BE%A4%E5%8F%B7",[590],"https://client.qun.qq.com/qqweb/m/qun/vote/index.html?groupuin=群号",[27,8264,8265,8266],{},"群投票 - 发布投票\n",[497,8267,8270],{"href":8268,"rel":8269},"https://client.qun.qq.com/qqweb/m/qun/vote/form.html?groupuin=%E7%BE%A4%E5%8F%B7",[590],"https://client.qun.qq.com/qqweb/m/qun/vote/form.html?groupuin=群号",[27,8272,8273,8274],{},"群头衔\n群头衔 - 设置\n",[497,8275,8278],{"href":8276,"rel":8277},"https://qinfo.clt.qq.com/qlevel/setting.html#gc=%E7%BE%A4%E5%8F%B7",[590],"https://qinfo.clt.qq.com/qlevel/setting.html#gc=群号",[27,8280,8281,8282],{},"群头衔 - 佩戴\n",[497,8283,8286],{"href":8284,"rel":8285},"https://qun.qq.com/qqweb/m/qun/medal/index.html?gc=%E7%BE%A4%E5%8F%B7&uin=Q%E5%8F%B7",[590],"https://qun.qq.com/qqweb/m/qun/medal/index.html?gc=群号&uin=Q号",[27,8288,8289,8290,8102],{},"群头像\n",[497,8291,8294],{"href":8292,"rel":8293},"https://p.qlogo.cn/gh/%E7%BE%A4%E5%8F%B7/%E7%BE%A4%E5%8F%B7_%E5%A4%B4%E5%83%8F%E5%BA%8F%E5%8F%B7/640",[590],"https://p.qlogo.cn/gh/群号/群号_头像序号/640",[27,8296,8297,8298],{},"群相册\n群相册 - 普通\n",[497,8299,8302],{"href":8300,"rel":8301},"https://h5.qzone.qq.com/groupphoto/inqq/album/%E7%BE%A4%E5%8F%B7",[590],"https://h5.qzone.qq.com/groupphoto/inqq/album/群号",[27,8304,8305,8306,8311,8316,8321,8326],{},"群相册 - 最近照片\n",[497,8307,8310],{"href":8308,"rel":8309},"https://h5.qzone.qq.com/groupphoto/inqq/recent/%E7%BE%A4%E5%8F%B7",[590],"https://h5.qzone.qq.com/groupphoto/inqq/recent/群号",[497,8312,8315],{"href":8313,"rel":8314},"https://h5.qzone.qq.com/groupphoto/index?inqq=1&groupId=%E7%BE%A4%E5%8F%B7",[590],"https://h5.qzone.qq.com/groupphoto/index?inqq=1&groupId=群号",[497,8317,8320],{"href":8318,"rel":8319},"https://h5.qzone.qq.com/groupphoto/index?inqq=2&groupId=%E7%BE%A4%E5%8F%B7",[590],"https://h5.qzone.qq.com/groupphoto/index?inqq=2&groupId=群号",[497,8322,8325],{"href":8323,"rel":8324},"https://h5.qzone.qq.com/groupphoto/index?inqq=3&groupId=%E7%BE%A4%E5%8F%B7",[590],"https://h5.qzone.qq.com/groupphoto/index?inqq=3&groupId=群号",[497,8327,8330],{"href":8328,"rel":8329},"https://h5.qzone.qq.com/groupphoto/index?inqq=4&groupId=%E7%BE%A4%E5%8F%B7",[590],"https://h5.qzone.qq.com/groupphoto/index?inqq=4&groupId=群号",[27,8332,8333,8334,8071],{},"群应用\n群应用 - 普通\n",[497,8335,8338],{"href":8336,"rel":8337},"https://qun.qq.com/qqweb/m/qunopen/appstore/index.html?gc=%E7%BE%A4%E5%8F%B7",[590],"https://qun.qq.com/qqweb/m/qunopen/appstore/index.html?gc=群号",[27,8340,8341,8342,8071],{},"群应用 - 管理\n",[497,8343,8346],{"href":8344,"rel":8345},"https://qun.qq.com/slidepanel/manage?gc=%E7%BE%A4%E5%8F%B7",[590],"https://qun.qq.com/slidepanel/manage?gc=群号",[27,8348,8349,8350],{},"群员分布\n",[497,8351,8354],{"href":8352,"rel":8353},"https://web.qun.qq.com/statistics/index.html?gc=%E7%BE%A4%E5%8F%B7",[590],"https://web.qun.qq.com/statistics/index.html?gc=群号",[27,8356,8357,8358,8102],{},"群作业\n",[497,8359,8362],{"href":8360,"rel":8361},"https://qun.qq.com/homework/features/index.html#gid=%E7%BE%A4%E5%8F%B7",[590],"https://qun.qq.com/homework/features/index.html#gid=群号",[27,8364,8365,8366,8110],{},"设置\n设置 - 撤回消息提示文本\n",[497,8367,8368],{"href":8368,"rel":8369},"https://zb.vip.qq.com/v2/pages/withdrawMessage",[590],[27,8371,8372,8373,8110],{},"设置 - 发现我的方式\n",[497,8374,8375],{"href":8375,"rel":8376},"https://ti.qq.com/friendshipauth/find",[590],[27,8378,8379,8380,8110],{},"设置 - 单向好友管理\n",[497,8381,8382],{"href":8382,"rel":8383},"https://ti.qq.com/friends/unidirection",[590],[27,8385,8386,8387,8110],{},"设置 - 加我为好友的方式\n",[497,8388,8389],{"href":8389,"rel":8390},"https://ti.qq.com/friendship_auth/index.html",[590],[27,8392,8393,8394,8398,8399,8110],{},"设置 - 聊天记录漫游\nQQ\n",[497,8395,8396],{"href":8396,"rel":8397},"https://gxh.vip.qq.com/club/client/msgRoam/rel/html/index_v2.html",[590],"\nTIM\n",[497,8400,8401],{"href":8401,"rel":8402},"https://tim.qq.com/htdocs/roaming/index.html",[590],[27,8404,8405,8406,8110],{},"设置 - 群互动标识开关（个人）\n",[497,8407,8408],{"href":8408,"rel":8409},"https://qun.qq.com/interactive/usersetting",[590],[27,8411,8412,8413],{},"坦白说\n",[497,8414,8415],{"href":8415,"rel":8416},"https://ti.qq.com/honest-say/main.html",[590],[27,8418,8419,8420],{},"天气\n",[497,8421,8424],{"href":8422,"rel":8423},"https://weather.mp.qq.com/?asyncMode=1&city=%E5%9F%8E%E5%B8%82",[590],"https://weather.mp.qq.com/?asyncMode=1&city=城市",[27,8426,8427,8428],{},"广州天气\n",[497,8429,8430],{"href":8430,"rel":8431},"https://weather.mp.qq.com/?asyncMode=1&city=%E5%B9%BF%E5%B7%9E",[590],[27,8433,8434,8435],{},"资料\n头像\n头像 - 获取\n",[497,8436,8439],{"href":8437,"rel":8438},"https://q1.qlogo.cn/g?b=qq&nk=Q%E5%8F%B7&s=%E6%95%B0%E5%80%BC",[590],"https://q1.qlogo.cn/g?b=qq&nk=Q号&s=数值",[27,8441,8442],{},"数值 (s / spec)\t分辨率 (px)\n1\t40 × 40\n2\t40 × 40\n3\t100 × 100\n4\t140 × 140\n5\t640 × 640\n40\t40 × 40\n100\t100 × 100\n参考: CSDN",[27,8444,8445,8446],{},"头像 - 挂件\n",[497,8447,8448],{"href":8448,"rel":8449},"https://zb.vip.qq.com/widget/mine",[590],[27,8451,8452,8453,8102],{},"头像 - 历史头像\n",[497,8454,8455],{"href":8455,"rel":8456},"https://ti.qq.com/avatarlist/public/index.html",[590],[27,8458,8459,8460,8102],{},"消息列表（资料卡）\n",[497,8461,8462],{"href":8462,"rel":8463},"https://ti.qq.com/signature/msglist",[590],[27,8465,8466,8467],{},"详细资料\n",[497,8468,8471],{"href":8469,"rel":8470},"https://ti.qq.com/qcard/index.html?qq=Q%E5%8F%B7",[590],"https://ti.qq.com/qcard/index.html?qq=Q号",[27,8473,8474,8475],{},"账号信息（个人）\n",[497,8476,8477],{"href":8477,"rel":8478},"https://mc.vip.qq.com/card/index",[590],{"title":22,"searchDepth":95,"depth":95,"links":8480},[],{"date":8482,"tags":8483,"categories":8483,"password":8484,"message":8485},"2020-02-06 19:29:10","api",1234567,"输入密码，查看文章","/api",{"title":7992,"description":7997},"XwQAl7ehOvF8tU0lFvo5jK9cIFGFRF-sKvLI4MBlNj4",{"id":5,"title":6,"body":8490,"description":22,"extension":104,"meta":8552,"navigation":110,"path":111,"seo":8554,"stem":108,"__hash__":113},{"type":8,"value":8491,"toc":8544},[8492,8494,8499,8501,8506,8508,8510,8512,8514,8519,8521,8523,8528,8530,8535,8537,8542],[11,8493,14],{"id":13},[16,8495,8497],{"className":8496,"code":20,"language":21,"meta":22},[19],[24,8498,20],{"__ignoreMap":22},[27,8500,29],{},[16,8502,8504],{"className":8503,"code":33,"language":21,"meta":22},[19],[24,8505,33],{"__ignoreMap":22},[27,8507,38],{},[11,8509,42],{"id":41},[27,8511,45],{},[27,8513,48],{},[16,8515,8517],{"className":8516,"code":53,"language":54,"meta":22},[52],[24,8518,53],{"__ignoreMap":22},[11,8520,60],{"id":59},[27,8522,63],{},[16,8524,8526],{"className":8525,"code":68,"language":69,"meta":22},[67],[24,8527,68],{"__ignoreMap":22},[73,8529,75],{"id":75},[16,8531,8533],{"className":8532,"code":79,"language":21,"meta":22},[19],[24,8534,79],{"__ignoreMap":22},[73,8536,84],{"id":84},[16,8538,8540],{"className":8539,"code":88,"language":21,"meta":22},[19],[24,8541,88],{"__ignoreMap":22},[27,8543,93],{},{"title":22,"searchDepth":95,"depth":95,"links":8545},[8546,8547,8548],{"id":13,"depth":95,"text":14},{"id":41,"depth":95,"text":42},{"id":59,"depth":95,"text":60,"children":8549},[8550,8551],{"id":75,"depth":102,"text":75},{"id":84,"depth":102,"text":84},{"date":106,"tags":8553},[108,109],{"title":6,"description":22},1777535894062]