8、脚本、两种前端、全局变量、三大框架之权限|登录到退出的流程|MVVM|单页优点、环境host、服务器与上传、Linux乌班图VMware、web互联网|ipping|500、yapi、node、npm含Live、自动化工具gulp|webpack|vite、rollup、前端框架工具版本发行时间(含AI豆包、solid、svelte)、神策插件源码解读、aplus插件源码解读(3900行)

附、脚本
  1、脚本‌是一种使用特定描述性语言编写的可执行文件,通常以文本形式保存
  2、脚本包含了一系列指令,这些指令由计算机的‌解释器或‌脚本引擎执行,用于自动化执行常规任务,如‌自动化数据处理、‌网页开发等。
附、两种前端
  1、客户端前端(公开前端),客户将数据发给后台,后台将数据存到数据库
  2、管理端前端(私密前端),管理向后台索要数据,后台从数据库查找数据
附、全局变量
  1、process,node自身的全局变量,node开发服务器webpack使用了这个变量,如
    A、process.env,不能在网页中使用
    B、process.cwd(),当前Node.js进程执行时的文件夹地址,即当前工作目录,根目录
  2、import,ES模块的导入命令,node开发服务器vite利用了import相关的元数据对象来扩展功能,如
    A、import.meta.env,能在网页中使用
    B、import.meta.glob动态导入模块(可以用通配符),const components = import.meta.glob('../components/*.vue');
    
一、三大框架之权限、登录、路由嵌套、状态管理、请求响应拦截、导航守卫、MVVM架构、SPA(单页面应用)优缺点
1、在angular1中,
  (1)权限:(自动)输入网址或IP,渲染index.html时,执行js,初始化模块,执行到<ui-view>,
    A、触发$urlRouterProvider.otherwise('/login'),前往登录页,触发前端路由,
    B、触发$stateChangeStart函数,不向后台发送请求,跳转到登录页。
    C、登录成功后,后台通过session或cookie,记录登录状态,前端跳转到首页,遇拦截,
    D、向后台发送请求,前端获取权限信息保存到本机,建立左侧导航栏和上方选项卡,再次跳转到首页
    E、后来刷新浏览器,保存在本机的信息会被清空,前端跳转到当前页,遇拦截,向后台发送请求,
    F、获取权限信息保存到本机,建立左侧导航栏和上方选项卡。
  (2)路由嵌套:<ui-view></ui-view>
  (3)状态管理:ui-router
  (4)请求响应拦截:在$http前后,由用户手写,主要用于处理请求参数和返回值
  (5)导航守卫拦截:$stateChangeStart等,前端路由变化时触发,主要用于获取用户权限、防止浏览器刷新时前端数据被清除
  (6)跳转示例,$state.go('/firstPage')
    $rootScope.$on('$stateChangeStart', function (event, toState) {
      myServer.changeState(event, toState);
    }) 
    var whiteList = ['login','license']; 
    var islogin = getLoginStage();
    if (whiteList.indexOf(toState.name) > -1) return; //首页初次渲染时,触发$urlRouterProvider.otherwise('/login'),跳转到登录页,不能往下执行
    if (islogin) {
      if (roles.length === 0) {
        //向后台发送请求,获取权限表,建立左侧导航栏和上方选项卡
      } else {
        //正常访问,建立上方选项卡
      }
    }else{
      $state.go('login')
    }
  (7)错误说明
    A、一个包含所有权限项的列表
    B、项目开始时给所有的路由和页面关联
    C、在登录成功后,根据返回的角色,从权限列表中摘出权限项,组成导航
    D、错误之处在于,在浏览器手动输入一个权限外的路由,也会出现对应的页面  
2、在vue中,
  (1)权限:(自动)输入网址或IP,渲染index.html时,执行js,初始化模块,执行到<router-view>,
    A、触发router.beforeEach,前往登录页,触发前端路由,再次触发router.beforeEach,不向后台发送请求,跳转到登录页。
    B、登录成功后,后台通过session或cookie,记录登录状态,前端跳转到首页,遇拦截,向后台发送请求,
    C、前端获取权限信息保存到本机,建立左侧导航栏和上方选项卡,再次跳转到首页。后来刷新浏览器,
    D、保存在本机的信息会被清空,前端跳转到当前页,遇拦截,向后台发送请求,
    E、获取权限信息保存到本机,建立左侧导航栏和上方选项卡。
  (2)路由嵌套:<router-view></router-view>
  (3)状态管理:vuex
  (4)请求响应拦截:service.interceptors,主要用于处理请求参数和返回值
  (5)导航守卫拦截:beforeEach等,前端路由变化时触发,主要用于获取用户权限、防止浏览器刷新时前端数据被清除
  (6)跳转示例,this.$router.push({ path: '/firstPage' }),next('/firstPage')
    router.beforeEach((to, from, next) => {
      var whiteList = ['/login']; 
      var islogin = getLoginStage();
      if (islogin) {
        if (store.getters.roles.length === 0) {
          //向后台发送请求,获取权限表
        } else {
          //正常访问
          document.title = to.meta.title //url变化,浏览器的title也随之变化
        }
      } else {
        if (to && whiteList.indexOf(to.path) > -1) {
          next()//不可以拦截
        } else {
          next(`/login`) //首页初次渲染时,从此处跳转到登录页
        }
      }
    })
3、在react中,
 来源,https://blog.csdn.net/sinat_36728518/article/details/106254395 React-Router4.0以后,路由即组件
  (1)权限:(自动)输入网址或IP,渲染index.html时,执行js,初始化模块,渲染组件FrontendAuth,跳转到登录页。
    A、登录成功后,后台通过session或cookie,记录登录状态,前端跳转到首页,遇拦截,向后台发送请求,
    B、前端获取权限信息保存到本机,建立左侧导航栏和上方选项卡,再次跳转到首页。
    C、后来刷新浏览器,保存在本机的信息会被清空,前端跳转到当前页,遇拦截,向后台发送请求,
    D、获取权限信息保存到本机,建立左侧导航栏和上方选项卡。----大概如此
  (2)路由嵌套:{React.Children.map}或{props.children}或<header><Route path="/user" component={UsersMenu}/></header>
  (3)状态管理:react-redux
  (4)请求响应拦截:service.interceptors,主要用于处理请求参数和返回值
  (5)导航守卫拦截:FrontendAuth等,前端路由变化时触发,主要用于获取用户权限、防止浏览器刷新时前端数据被清除
  (6)跳转示例,<Redirect to="/login" />;browserHistory.push('/')
    <Router>
      <Switch>
        <FrontendAuth routerConfig={routerMap} />
      </Switch>
    </Router>
    class FrontendAuth extends Component {
      constructor(props) {
        super(props);
      }
      render() {
        const { routerConfig, location } = this.props;
        const { pathname } = location;
        const targetRouter = routerConfig.find(function(item){
          return item.path === pathname
        });
        const isAuth = targetRouter && targetRouter.auth;
        const isLogin = sessionStorage.getItem("username");
        if (isLogin) {
          if (pathname === "/login") {
            return <Redirect to="/" />;
          } else {
            if (targetRouter) {          //此处建立上方选项卡
              return <Route path={pathname} component={targetRouter.component} />
            } else {
              return <Redirect to="/404"/>;
            }
          }
        } else {
          if(isAuth){
            const { component } = targetRouter;
            return <Route exact path={pathname} component={component} />;
          }else{
            return <Redirect to="/login" />;
          }
        }
      }
    }
    export default FrontendAuth;
4、从登录到退出的简易流程
   附、需要重新登录的两种情形
     A、后台判断登录过期
     B、前端判断登录标志缺失,比如退出时清空了登录标志
  (1)登录:
    A、初次访问服务器,返回#前面的内容,即index.html,加载style和cript标签里的静态文件,
    B、初始化状态管理插件的数据,获取登录者身份标志localStorage.getItem(TokenKey)失败,跳到登录页,
    C、登陆成功,存储登录者身份标志localStorage.setItem(TokenKey, JSON.stringify(token));根据权限列表,生成导航
  (2)跳转:用状态管理插件存储“临时数据”
  (3)刷新:
    A、访问服务器,返回#前面的内容,即index.html,加载style和cript标签里的静态文件, 
    B、初始化状态管理插件的数据,即清除“临时数据”,获取登录者身份标志localStorage.getItem(TokenKey)成功,
    C、获取权限列表(有可能返回登录过期,跳到登录页),生成导航
  (4)退出:清除登录者身份标志,localStorage.removeItem(TokenKey),跳到登录页
5、登录前滑块验证的逻辑
  (1)点击验证,向后台发送请求,后台返回带孔图片、填孔图片、相对坐标,前端根据坐标,把2张图片叠放在弹窗上
  (2)前端拖动图块或划块,放手,将移动距离发给后台,
  (3)后台告诉前端验证的成功标志,前端隐藏弹窗;后台告诉前端验证失败,前端再次向后台请求图片,直到验证成功
  (4)前端向后台发送用户名、密码、成功标志,后台告诉前端登陆成功,前端跳往首页
  (5)图片代码
    <img :src="'data:image/png;base64,' + verify.backImgBase" alt=""/>
    <img :src="'data:image/png;base64,' + verify.blockBackImgBase" alt="">
  附1、MVVM
    (1)M:Model,数据模型,前端定义的初始数据
    (2)V:View,页面视图,前端定义的页面组件
    (3)VM:ViewModel,视图模型,通过数据绑定将数据模型渲染到页面视图,将视图转化成模型,通过事件监听,更改数据模型并重新渲染页面视图
    (4)MVC,手动同步;MVVM,自动同步
  附2、SPA(单页面应用)优缺点
    优点:
    (1)对服务器压力小
    (2)前后端职责分离,前端进行交互逻辑,后端负责数据处理
    缺点
    (1)初次加载耗时多
    (2)前进后退路由管理,需要自己建立堆栈管理
    (3)SEO难度较大:由于所有的内容都在一个页面中动态替换显示

二、环境
1、生产环境:服务器
2、开发环境
  (1)前端:node
  (2)后台:ubuntu
3、window环境下的上传工具
  (1)MobaXterm
  (2)xshell
4、ubuntu环境下的上传命令
  scp -r localfile.txt username@192.168.0.1:/home/username/
  (1)scp是命令,-r是参数
  (2)localfile.txt 是文件的路径和文件名
  (3)username是服务器账号
  (4)192.168.0.1是要上传的服务器ip地址
  (5)/home/username/是要拷入的文件夹路径

三、服务器
1、安装服务器系统
  (1)给服务器--插上--系统U盘
  (2)重启服务器:拔、插服务器电源(服务器自动安装系统,安装成功时,服务器自动断电)
  (3)从服务器--拔掉--系统U盘
  (4)再次重启服务器:拔、插服务器电源
2、登陆服务器
  (1)用“网线”把“我的电脑”和“服务器”进行物理连接
  (2)在“终端工具”上,用“终端工具”的用户名和密码登录“终端工具”
  (3)在“终端工具”上,用“服务器”的用户名和密码登录“服务器”
3、给服务器配置IP
  (1)root@CyOS:/root# ifconfig mgmt0 192.168.10.156(给接口配置IP。它有65535个端口,ifconfig(配置))mgmt0(接口))
  (2)按enter键,IP配置成功
4、启动服务器
  (1)root@CyOS:/root# cd /usr/local/audit-web
  (2)root@CyOS:/usr/local/audit-web# python manage.py runserver
5、辅助步骤
  (1)Ctrl+C(退出进程)
  (2)root@CyOS:/usr/local/audit-web# pkill -f python -9(杀死python进程,出现Address already in use时,需要用到这个命令)
  (3)root@CyOS:/usr/local/audit-web# pkill -f uwsgi -9(杀死名为uwsgi的进程,-f匹配所有参数列表,-9强制)
  (4)root@CyOS:/usr/local/audit-web# iptables -P INPUT ACCEPT(关闭防火墙,P INPUT ACCEPT均大写)
  (5)root@CyOS:/usr/local/audit-web# ps aux|grep python(查看活进程,aux显示所有进程和进程状态,grep在这些里搜索)
  (6)root@CyOS:/root# ifconfig(显示或设置网络设备)/////////////////////////
  (7)root@CyOS:/root# cat /usr/local/etc/suricata/version.autogen(查看当前版本)
6、通过CMD命令窗口,给本地服务器更新文件的流程
  (1)window + r;cmd
  (2)ssh root@172.18.58.30,root为登录名
    (2-1)SSH(Secure Shell,安全外壳)之所以能够保证安全,原因在于它采用了公钥加密。整个过程是这样的:
    (2-2)远程主机收到用户的登录请求,把自己的公钥发给用户。
    (2-3)用户使用这个公钥,将登录密码加密后,发送回来。
    (2-4)远程主机用自己的私钥,解密登录密码,如果密码正确,就同意用户登录。
  (3)输入密码
  (4)wget -O /my.sh "http://192.168.80.152:3000/audit/shell?branch=develop-2.0.8&command=gulp build" 
      && chmod +x /my.sh && /my.sh && rm -f /my.sh
    (4-1)-O /my.sh:将下载的文件存储在根目录下并重命名为“my.sh”
    (4-2)chmod +x /my.sh:将执行权限赋给根目录下的“my.sh”
    (4-3)/my.sh:运行根目录下“my.sh”
    (4-4)rm -f /my.sh:删除根目录下“my.sh”
  (5)Windows运行CMD常用命令:https://blog.csdn.net/gaofengyan/article/details/89447293
  (6)在WINDOWS操作系统里执行CMD命令,可以模拟出DOS操作系统。
7、负载均衡,来源https://blog.csdn.net/yiXin_Chen/article/details/123158091
  (1)正向代理,客户端访问代理服务器,前者指定目标服务器,代理服务器代理的是客户端,服务器不知道客户端的地址
  (2)反向代理,客户端访问代理服务器,后者指定目标服务器,代理服务器代理的是服务器,客户端不知道服务器的地址
  (3)负载均衡,反向代理时,代理服务器把不同的用户访问,指派给不同的目标服务器处理,避免某一目标服务器负载过重
  (4)Nginx,性能非常好的反向代理服务器,用来做负载均衡,可以在Linux系统中运行

四、本地文件上传至服务器(以MobaXterm为例)
1、基本快捷键
  (1)复制:Ctrl + Insert
  (2)粘贴:Shift + Insert
2、连接服务器
  (1)点击左上方Session
  (2)点击左上方SSH
  (3)Remote host *:172.18.57.88;勾选方框;Specify username:root;点击OK;
  (4)password:cy888888;回车
  (5)是否保存密码:是
3、关闭服务
  (1)root@OS:~# ls
  (2)lsof -i:5000
  (3)kill 31096
4、开启服务
  (1)python /usr/local/process_monitor/process_monitor.py
5、重启服务
  (1)pkill -9 uwsgi3
6、上传
  (1)在箭头下方的输入框输入:/usr/local/audit-html/template/;enter
  (2)点击绿色向上虚线箭头,选择要上传的文件,上传
  (3)重启服务器,pkill -9 uwsgi3
7、进入文件夹
(1)cd /usr/local/audit-html/template/
  附、Portable之本地替换服务器文件的步骤,以景宝为例
  (1)下载软件并登录
  (2)进入到相关文件夹下:root@OS:~# cd /usr/local/audit-html/template/
  (3)删除原文件:rm -rf audit-with-reports.html
  (4)上传新文件:rz
  (5)关闭服务:pkill -9 uwsgi
  (6)重启服务:uwsgi3 /usr/local/audit-web/WSGI/uwsgi.ini
  (7)实时观察:tail -f /data/flask_web/logs/uwsgi.log 
附、Jenkins,基于Java开发的一种持续集成工具
  (1)软件版本发布/测试
  (2)监控外部调用

五、Linux命令与目录
1、清除终端内容
  (1)ctr+l:终端内容滚到顶部上面            
  (2)clear:终端内容滚到顶部上面
  (3)reset:清除终端内容
2、ps:显示系统进程
  (1)ps -ef:用标准格式显示进程,如ps -ef|grep python 
  (2)ps aux:用BSD格式显示进程,如ps aux|grep python
3、cd:进入目录
  (1)cd /:进入系统根目录
  (2)cd ~:进入用户根目录
  (3)cd ../:切换到上一级目录
4、ls:显示本目录下之内容
  (1)-a 显示所有文件及目录 (. 开头的隐藏文件也会列出)
  (2)-l 除文件名称外,亦将文件型态、权限、拥有者、文件大小等资讯详细列出
  (3)-r 将文件以相反次序显示(原定依英文字母次序)
  (4)-t 将文件依建立时间之先后次序列出
  (5)-A 同 -a ,但不列出 "." (目前目录) 及 ".." (父目录)
  (6)-F 在列出的文件名称后加一符号;例如可执行档则加 "*", 目录则加 "/"
  (7)-R 若目录下有文件,则以下之文件亦皆依序列出
5、ls -l运行结果说明
  示例:drwxr-xr-x 1 qiancheng qiancheng 54713 11月 19 13:08 0.js
  (1)文件属性:drwxr-xr-x。d(-文件,d目录)rwx(用户权限)r-x(组用户权限)r-x(其他用户权限);rwx-的含义:r可读,w可写,x可执行,-没有权限
  (2)文件硬链接数量:1
  (3)所有者:qiancheng(user)
  (4)所属用户组:qiancheng(group)
  (5)文件大小:54713
  (6)修改时间:11月 19 13:08
  (7)文件名:0.js(Filename)
6、关于目录的命令
  (1)目录创建:mkdir dir
  (2)目录改名:mv/cp oldDir newDir(不存在);
  (3)目录复制:mv/cp oneDir newDir(存在);
  (4)目录查找:find dir -type d;
  (5)目录清空:A、根目录清空:rm -rf /*;B、本目录清空:rm -rf *;C、子目录清空:rm -rf dir/*;
  (6)目录删除:rm -rf dir;
7、关于文件的命令  
  (1)文件创建:A、touch aaa.js;B、cat >> aaa.js
  (2)文件改名:mv aaa.js bbb.js
  (3)文件复制:cp aaa.js bbb.js
  (4)文件查找:A、find dir -type f;B、find dir -name "*.js"
  (5)文件清空:A、> aaa.js;B、cat /dev/null > aaa.js
  (6)文件删除:rm aaa.js
8、文件内容查看
  (1)nl:显示所有文件内容,并输出行号
  (2)cat:由第一行开始显示文件内容
  (3)tac:从最后一行开始显示文件内容
  (4)less:一页一页的显示文件内容,可以用上下键上下翻页
  (5)more:一页一页的显示文件内容,可以往enter键向下翻页
  (6)head:只看头几行
  (7)tail:只看尾巴几行
9、grep:与字符串相关的搜索
  (1)grep "grep" html/*.js
  (2)grep "g.\{0,4\}p" html/*.js
  (3)grep -i "grep" html/*.js (忽略大小写)
  (4)grep -w "grep" html/*.js (搜索整个词)
  (5)grep -n "grep" html/*.js (显示行号)
  (5)grep -l "grep" html/*.js (只显示文件名)
  (6)grep -r "grep" html (递归搜索当前目录及其子目录的全部文件)
  (7)grep -v "grep" html/*.js (显示不匹配字串的行)
  (8)grep -c "grep" html/*.js (统计匹配的行数)
  (9)grep -B 2 "grep" html/*.js (显示之前2行,A后,C前后各)
  (10)grep,全局正则表达式输出,global regular expression print */
10、echo:输入字符串
  (1)覆盖写入字符串(无引号):echo "字符串" > 1.js
  (2)覆盖写入字符串(有引号):echo "\"字符串\"" > 1.js
  (3)转义覆盖写入字符串(有引号):echo -e "\"这\n是\n换\n行\"" > 1.js;e开启转义,\n换行,\c:不换行
  (4)追加写入字符串:echo "字符串" >> 1.js
11、关机
  (1)shutdown:关机
  (2)shutdown -t 2:2秒后关机
  (3)shutdown -h now:立刻关机
  (4)shutdown -h 20:00:20:00关机
  (5)shutdown -h +10:10分钟后关机
  (6)shutdown -r now:系统立刻重启
  (7)shutdown -r +30:30分钟后重启
  (8)halt:关机
  (9)poweroff:关机
  (10)init 0:关机
  (11)reboot:重启
12、压缩与解压(解压导出需要加参数)
  (1)压缩:tar -czvf file.tar ./aaa/file1;zip html.zip ./aaa
  (2)解压:tar -xzvf file.tar -C ./aaa/file2;unzip -d ./aaa file.zip
  (3)tar参数之-c建立新的备份文件
  (4)tar参数之-x从备份文件中还原文件
  (5)tar参数之-z通过gzip指令处理备份文件
  (6)tar参数之-v显示指令执行过程
  (7)tar参数之-f指定备份文件
  (8)unzip参数之-d指定文件解压缩后所要存储的目录
附、其它
  (1)打开终端:Ctrl+Alt+T
  (2)查看当前目录:pwd
13、Linux一级目录:
 来源:https://www.runoob.com/linux/linux-system-contents.html
  (1)srv:存放着-服务启动-之后-需要提取-的数据
  (2)root:存放着-系统管理员-的主目录
  (3)sbin:存放着-系统管理员-使用的-系统管理程序
  (4)home:存放着-用户-的主目录
  (5)bin: 存放着-用户-的常用命令
  (6)boot:存放着-系统启动-时使用的文件
  (7)run:存放着-系统启动-以来的信息
  (8)etc:存放着-系统管理-所需要的文件
  (9)dev:存放着-外部硬件-的设备,包括硬盘,U盘,光驱,串口,打印机等等
  (10)media:存放着-自动识别-的设备,比如U盘下的ubuntu
  (11)mnt:存放着-临时挂载的-别的文件,比如光驱,进入后可以查看光驱内容
  (12)tmp:存放着-临时文件
  (13)var:存放着-经常被修改-的文件
  (14)opt:存放着-主机-额外安装-的软件,比如搜狗输入法
  (15)lib:存放着-动态连接共享库。类似于Windows 里的 DLL 文件
  (16)proc:存放着-当前内核-运行状态-的文件
  (17)selinux:防火墙,存放selinux相关的文件的
  (18)usr:存放着-应用程序。类似于 windows 下的 program files 目录,unix shared resources(共享资源) 的缩写
  (18-1)/usr/bin:系统用户使用的应用程序
  (18-2)/usr/sbin:超级用户使用的比较高级的管理程序和系统守护程序
  (18-3)/usr/src:内核源代码默认的放置目录
附、Linux文件的时间属性
  (1)atime:文件内容查看时间,Access time,使用more、cat对该文件进行查看时,atime将更新,ls -lu
  (2)mtime:文件内容修改时间,Modify time,使用vi、vim对文件进行修改后保存,mtime将更新,ls -l
  (3)ctime:文件属性变更时间,Change time,文件名、内容、大小、权限、所属组等改变时,ctime将更新,ls -lc

六、Linux,ubuntu和VMware
1、三者之间的关系
  (1)Linux是一个操作系统;
  (2)Ubuntu是一个以桌面应用为主的Linux操作系统
  (3)VMware是一个在安装的过程中可以导入ubuntu的虚拟主机
  (4)在ubuntu里,应该用Linux命令来操作里面的文件
  (5)安装步骤;点击VMware.exe文件直到结束-->点击桌面VMware图标-->点击“创建新的虚拟机”-->安装依赖(最后一步,耗时约40分钟)
  (6)4步完全卸载VMware。A、关闭VMware;B、应用卸载-->vmware-->修改-->下一步-->删除;C、C:\Program Files (x86)下删除VMware;D、C:\Users\用户名\Documents下删除Virtual Machines,内含ubuntu 
  (7)层级关系:Unix-->Linux-->Debian(得便,自由操作系统)-->Ubuntu
2、Linux的vim编辑器的安装和使用
  (1)安装:sudo apt install vim
  (2)显示文件内容:vim 路径,如sudo vim ~/.bashrc
  (3)命令模式。i 切换到输入模式;: 切换到底线命令模式;x 删除当前光标所在处的字符。
  (4)输入模式。ESC 退回命令模式。
  (5)底线命令模式。q 退出vim,回到(2)执行前的状态;w 保存文件;字母后面加! 强制。
  (6)退出vim。输入模式ESC--命令模式:--底线命令模式q!
3、ubuntu的常见命令和依赖包安装
  (1)获取超级用户权限:sudo su
  (2)重启虚拟机:sudo reboot
  (3)本地安装:sudo dpkg -i package,“dpkg”是“Debian”的包管理器
  (4)本地安装:sudo apt install package,“apt”是“Debian”和“Ubuntu”的包管理器
  (5)自由安装:wget http://url
  (6)层级关系:Unix-->Linux-->Debian(得便,自由操作系统)-->Ubuntu
4、ubuntu实现路径补全
  (1)创建文件:touch ~/.pythonrc
  (2)添加如下内容:
    import rlcompleter, readline
    readline.parse_and_bind('tab:complete')
  (3)在home目录下,.bashrc文件末尾追加如下内容:export PYTHONSTARTUP=~/.pythonrc
  (4)更新环境变量:source ~/.bashrc

七、在Ubuntu16.04(乌班图)里,安装插件,如搜狗输入法、谷歌浏览器、微信、时间同步、gulp
1、安装简体中文输入法:
  (1)设置-系统设置-语言支持-安装-密码-确认-(安装fcitx,耗时约10分钟)
  (2)语言-安装/卸载语言-(勾选)中文简体-应用-密码-确认-(安装字体,耗时约10分钟)
  (3)区域格式-汉语中国-语言-(汉语中国)拉到第一位-(键盘输入法系统)fcitx-关闭
  (4)删除包sudo apt-get remove fcitx-ui-qimpanel
  附1:Fcitx是(Free Chinese Input Toy for X)的英文缩写,可为支持 XIM 的 X 应用程序提供输入服务。
    可以输入UTF-8编码中的汉字。
2、安装搜狗输入法
  (1)下载搜狗输入法安装包http://pinyin.sogou.com/linux,进入下载目录cd Downloads
  (2)安装搜狗输入法sudo dpkg -i sogoupinyin_2.4.0.3469_amd64.deb
  (3)重启虚拟机sudo reboot
  (4)文字图标-配置当前输入法-(搜狗输入法个人版)调至第一个选项
3、1和2安装过程中或结束后,可能遇到的问题及解决
(1)问题1:
  问题:updating cache ; waiting for apt-get to exit
  解决:sudo fuser -vki /var/lib/apt/lists/lock
(2)问题2:
  问题:由于找不到vcruntime140_1.dll,无法继续执行代码重新安装程序可能会解决此问题
  解决:下载文件vcruntime140_1.dll,复制到32/64位版本电脑的的C:\Windows\system64/32下
(3)问题3:
  问题:搜狗输入时出现乱码
  解决:killall fcitx
(4)问题4:
  问题:Windows和Ubuntu16之间不能复制粘贴
  解决:sudo apt-get autoremove open-vm-tools ; sudo apt-get install open-vm-tools-desktop ; Ctrl+c ; sudo reboot
4、安装谷歌浏览器
  (1)sudo wget http://www.linuxidc.com/files/repo/google-chrome.list -P /etc/apt/sources.list.d/
  (2)wget -q -O - https://dl.google.com/linux/linux_signing_key.pub  | sudo apt-key add -
  (3)sudo apt-get update
  (4)sudo apt-get install google-chrome-stable
  (5)/usr/bin/google-chrome-stable
  (6)在屏幕左侧的图标上,右键“锁定到启动器”
5、安装微信(可登陆,2021年11月11日)
  (1)安装 deepin-wine:wget -O- https://deepin-wine.i-m.dev/setup.sh | sh
    以下主要来源:https://blog.csdn.net/sinat_39369871/article/details/110095705
  (2)下载微信安装包:http://packages.deepin.com/deepin/pool/non-free/d/deepin.com.wechat/,点击最后一个
  (3)一次运行下列命令(复制以下内容,粘贴到终端,按回车)
    #!/bin/bash
    mkdir ./deepintemp
    cd ./deepintemp
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine/deepin-wine_2.18-22~rc0_all.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine/deepin-wine32_2.18-22~rc0_i386.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine/deepin-wine32-preloader_2.18-22~rc0_i386.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine-helper/deepin-wine-helper_1.2deepin8_i386.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine-plugin/deepin-wine-plugin_1.0deepin2_amd64.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine-plugin-virtual/deepin-wine-plugin-virtual_1.0deepin3_all.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine-uninstaller/deepin-wine-uninstaller_0.1deepin2_i386.deb
    wget http://packages.deepin.com/deepin/pool/non-free/u/udis86/udis86_1.72-2_i386.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine/deepin-fonts-wine_2.18-22~rc0_all.deb
    wget http://packages.deepin.com/deepin/pool/non-free/d/deepin-wine/deepin-libwine_2.18-22~rc0_i386.deb
    wget https://packages.deepin.com/deepin/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_1.5.1-2_amd64.deb --no-check-certificate
    wget https://packages.deepin.com/deepin/pool/main/libj/libjpeg-turbo/libjpeg62-turbo_1.5.1-2_i386.deb --no-check-certificate
    sudo dpkg --add-architecture i386
    sudo apt update
    sudo dpkg -i *.deb
    sudo apt install -fy
    rm -vfr ./deepintemp
  (4)安装微信 sudo dpkg -i deepin.com.wechat_2.6.8.65deepin0_i386.deb
6、解决微信不能输入中文的问题
  (1)cd /opt/deepinwine/tools/
  (2)sudo chmod 777 run.sh  #文件默认为只读,修改权限
  (3)vim run.sh   #编辑脚本,加入以下内容:
    export GTK_IM_MODULE="fcitx"
    export QT_IM_MODULE="fcitx" 
    export XMODIFIERS="@im=fcitx"
  (4)重启微信,切换为系统自带的fcitx输入法即可输入中文,比如搜狗输入法。
7、系统时间跟本地时间同步
  (1)sudo timedatectl set-local-rtc 1
  (2)sudo timedatectl set-timezone Asia/Shanghai
  (3)data(查看系统时间,可以不执行)
8、全局安装gulp
    在ubuntu里,全局安装的gulp才能运行gulp任务,npm全局安装gulp会出错,以下用cnpm安装
  (1)sudo  npm install -g cnpm -registry=https://registry.npm.taobao.org
  (2)sudo cnpm install -g gulp
  (3)gulp

八、web相关
1、专业术语
  (1)网络,web、net、network
  (2)超链接,hyperlink,从一个网页指向另一个网页的链接关系
  (3)超文本,Hypertext,是超链接的载体;<a href='url'>文本或图像</a>
  (4)超媒体,hypermedia,是超文本和多媒体(文本、声音、图像)在信息浏览环境下的结合
2、互联网,internet,是通过“TCP/IP协议”互联的庞大网络,该协议包含4个层级
  来源,https://baike.baidu.com/item/%E4%BA%92%E8%81%94%E7%BD%91/199186
  来源,https://baike.baidu.com/item/TCP/IP%E5%8D%8F%E8%AE%AE/212915
  (1)应用层,使用不同协议,如Http、FTP、SMTP、Telnet,来加密、解密、格式化数据
  (2)传输层
  (3)网络层
  (4)网络接口层
    注、TCP/IP协议,Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议,是指能够在多个不同网络间实现信息传输的协议簇
    注、HTTP,Hypertext Transfer Protocol,超文本传输协议,是一个简单的“请求-响应”协议,它不仅保证正确传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等
3、万维网,web,是通过“客户机-服务器”这种方式,连接的互联网,应用层使用http协议
  (1)是一种基于超文本和超文本传输协议(HTTP)的、全球性的、动态交互的、跨平台的分布式图形信息系统
  (2)是建立在Internet上的一种网络服务,为浏览者提供了图形化的直观界面
  (3)其中的文档和超链接将Internet上的信息节点组织成一个互为关联的网状结构
  (4)包含浏览器、URL、HTTP、HTML
    注、万维网,World Wide Web,全球广域网,简称 WWW、3W、Web
4、互联网发展史
  来源1,https://baike.baidu.com/item/%E4%BA%92%E8%81%94%E7%BD%91/199186?fr=ge_ala
  来源2,https://baike.baidu.com/item/www/109924?fr=ge_ala
  来源3,https://baike.baidu.com/item/web/150564?fr=ge_ala
  (1)1969年,互联网(internet,因特网),诞生于美国,首先用于军事连接
  (2)1973年-1984年,TCP/IP协议被开发出来,并成为多数计算机共同遵守的一个标准
  (3)1989年,万维网(Web),在欧洲提出
  (4)1990年,在一台计算机实现
  (5)1991年,在其他计算机实现,并正式发布
  (6)1994年,Web1.0开始,主要特征是大量使用静态的HTML网页来发布信息,并开始使用浏览器来获取信息,这个时候主要是单向的信息传递
  (7)2004年,Web2.0开始,更加注重交互性,与网络服务器之间交互,不同用户之间的交互,不同网站之间信息的交互
  (8)2014年,Web3.0开始,以网络化和个性化为特征,可以提供更多人工智能服务,用户可以实现实时参与
5、用户与内外网安全连接
  (1)外网,iNode智能客户端
    A、iNode智能客户端是H3C(华三通信)自行设计开发的基于Windows的多业务接入客户端软件,提供802.1x、Portal(门户网站)、VPN等多种认证方式,
    B、可以与H3C以太网交换机、路由器、VPN网关等网络设备共同组网,实现对宽带接入、VPN接入和无线接入的用户认证,
    C、是对用户终端进行身份验证、安全状态评估以及安全策略实施的主体,可以按照企业接入安全策略的要求,实现基于角色/身份的权限和安全控制。
  (2)内网,MotionPro安全隧道
    A、企业用户服务器安全保护软件
    B、这是一种VPN软件,Virtual Private Network,虚拟专用网络
    C、站点地址1,tech.vpn.cntv.cn;站点地址2,202.18.19.157
    D、motion,运动
  (3)本机,奇安信天擎
    A、致力于一体化“终端安全”解决方案的终端安全管理系统
    B、帮助政企客户准确识别、保护和监管终端,并确保这些终端在任何时候都能可信、安全、合规地访问数据和业务
    C、高性能病毒查杀、漏洞防护、主动防御引擎,深度融合威胁情报、大数据分析和安全可视化
    D、帮助政企客户构建持续有效的终端安全
6、hosts文件
  来源,https://blog.csdn.net/qq_41169544/article/details/123895563
  附、2个地址,
    a、IPv4地址,是本机“供其他设备访问”的ip地址,绑在“⽹络接⼝”上,通常“本机的ip地址”就是这个地址
      在他人的浏览器地址栏输入,我的IPv4地址+我的端口号,如,http://172.20.69.43:3000,可以访问我的本地开发服务器
    b、环回地址,是本机“向自身发送通信”的ip地址,写在“hosts文件”里,即C:\Windows\System32\drivers\etc\hosts,
      环回地址(Loopback Address)范围为,127.0.0.0到127.255.255.255
  (1)作用:将域名映射到IP地址的纯文本文件
  (2)示例
    # Copyright (c) 1993-2009 Microsoft Corp.
    #
    # This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
    #
    # This file contains the mappings of IP addresses to host names. Each
    # entry should be kept on an individual line. The IP address should
    # be placed in the first column followed by the corresponding host name.
    # The IP address and the host name should be separated by at least one
    # space.
    #
    # Additionally, comments (such as these) may be inserted on individual
    # lines or following the machine name denoted by a '#' symbol.
    #
    # For example:
    #
    #      102.54.94.97     rhino.acme.com          # source server
    #       38.25.63.10     x.acme.com              # x client host

    # localhost name resolution is handled within DNS itself.
    #	127.0.0.1       localhost
    #	::1          localhost
    10.51.33.7 gzx.zhixue.com 
     //以上,把-联调服务器的域名-指向-联调服务器的ip
    127.0.0.1 test.cctv.com 
     //以上,把-联调服务器的域名-指向-本机的ip
     //a、浏览器输入http://localhost:3000,无效,
     //b、浏览器输入http://127.0.0.1:3000,无效,
     //c、浏览器输入http://test.cctv.com:3000,有效 
7、ipconfig命令的执行与说明
  (1)示例1,未联网
    PS C:\Users\Haier\Desktop> ipconfig

    Windows IP 配置


    以太网适配器 本地连接* 9:

      媒体状态  . . . . . . . . . . . . : 媒体已断开连接
      连接特定的 DNS 后缀 . . . . . . . :

    以太网适配器 以太网:

      连接特定的 DNS 后缀 . . . . . . . :
      本地链接 IPv6 地址. . . . . . . . : fe80::d5f:3ca4:75b7:cbe7%16
      自动配置 IPv4 地址  . . . . . . . : 169.254.17.146
      子网掩码  . . . . . . . . . . . . : 255.255.0.0
      默认网关. . . . . . . . . . . . . :
    PS C:\Users\Haier\Desktop>
  (2)示例2,已联网
    PS C:\Users\Haier\Desktop> ipconfig

    Windows IP 配置


    以太网适配器 本地连接* 9:

      媒体状态  . . . . . . . . . . . . : 媒体已断开连接
      连接特定的 DNS 后缀 . . . . . . . :

    PPP 适配器 Array Networks SSL VPN:

      连接特定的 DNS 后缀 . . . . . . . :
      IPv4 地址 . . . . . . . . . . . . : 10.65.205.89  //外网给的
      子网掩码  . . . . . . . . . . . . : 255.255.255.255
      默认网关. . . . . . . . . . . . . :

    以太网适配器 以太网:

      连接特定的 DNS 后缀 . . . . . . . : example.org
      本地链接 IPv6 地址. . . . . . . . : fe80::d5f:3ca4:75b7:cbe7%16
      IPv4 地址 . . . . . . . . . . . . : 172.20.69.43  //路由器分配的
      子网掩码  . . . . . . . . . . . . : 255.255.255.0  
      默认网关. . . . . . . . . . . . . : 172.20.69.1  
    PS C:\Users\Haier\Desktop>
  (3)这是查看“IP配置”的命令
    A、找到界面:
      a、window + r 
      b、cdm + enter
    B、命令用途:根据“是否有PPP适配器信息”判断,本主机是否联网,如iNode未登录,无法访问百度
  附1、IPv4地址
    A、PPP适配器,IPv4地址,外网给的,子网掩码255.255.255.255
    B、以太网适配器,IPv4地址,路由器分配的,子网掩码255.255.255.0
    C、IP地址分类
      a、A类,前1段为网络地址,后3段为主机地址,IP地址范围,0.0.0.0--127.255.255.255,最大主机数,16777214;默认子网掩码,255.0.0.0
      b、B类,前2段为网络地址,后2段为主机地址,IP地址范围,128.0.0.0--191.255.255.255,最大主机数,65534;默认子网掩码,255.255.0.0
      c、C类,前3段为网络地址,后1段为主机地址,IP地址范围,192.0.0.0--223.255.255.255,最大主机数,254;默认子网掩码,255.255.255.0
      d、公司IP地址一般为C类,D类和E类为未来使用
  附2、子网掩码(Subnet Mask)
    来源,https://blog.51cto.com/u_15329201/3403517
    A、作用
      a、把‌IP地址可以分为“网络标识”和主机标识两部分
      b、在一个“网络标识”下的计算机,可以直接互通
      c、不在一个“网络标识”下的计算机,要通过网关(Gateway)才能互通
      d、指明IP地址是在局域网上,还是在广域网上
    B、子网(Subnet),公司网管
      a、根据公司IP地址的类别和默认子网掩码
      b、生成子网IP,分发给各个开发者使用
    C、使用情形1,开发时,
      a、前端通过代理服务器拼接后端ip(本条去掉下面内容依然成立),
      b、本机根据本机ip和子网掩码判断,目标主机是否在本地子网中
      c、如果目标主机在本地子网中,则直接发送即可
      d、如果目标主机不在本地子网中,则将该信息送到默认网关/路由器,由路由器将其转发到其他网络中,进一步寻找目标主机
    D、使用情形2,浏览器地址栏域名访问时,
      a、首先,系统自动从hosts文件(将域名映射到IP地址的纯文本文件)中寻找对应的IP地址,
      b、如果能找到,则结合子网掩码判断该IP属于局域网还是远程网,然后访问该网
      c、如果没找到,系统会将网址提交DNS域名解析服务器,解析出IP地址,然后访问该网
  附3、默认网关
    A、default gateway
    B、网关(Gateway),通常是一个路由器,子网与外网连接的设备    
8、ping命令的执行与说明
  (1)示例1,该主机不可达  
    C:\Users\Haier>ping 192.0.0.0

    正在 Ping 192.0.0.0 具有 32 字节的数据:
    请求超时。
    请求超时。
    请求超时。
    请求超时。

    192.0.0.0 的 Ping 统计信息:
        数据包: 已发送 = 4,已接收 = 0,丢失 = 4 (100% 丢失),

    C:\Users\Haier>
  (2)示例2,该主机可达 
    C:\Users\Haier>ping www.baidu.com

    正在 Ping www.a.shifen.com [180.101.50.242] 具有 32 字节的数据:
    来自 180.101.50.242 的回复: 字节=32 时间=21ms TTL=48
    来自 180.101.50.242 的回复: 字节=32 时间=21ms TTL=48
    来自 180.101.50.242 的回复: 字节=32 时间=21ms TTL=48
    来自 180.101.50.242 的回复: 字节=32 时间=21ms TTL=48

    180.101.50.242 的 Ping 统计信息:
        数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
    往返行程的估计时间(以毫秒为单位):
        最短 = 21ms,最长 = 21ms,平均 = 21ms

    C:\Users\Haier>
  (3)这是测试“网络连通性”的命令
    A、找到界面:
      a、window + r 
      b、cdm + enter
    B、命令用途:根据“Ping统计信息”判断,该主机是否可达,如MotionPro未连接,无法从开发后台获取数据
    C、命令原理:它通过向指定的网络地址发送一定长度的数据包,‌并等待接收返回的数据包来检测网络的连通性
  附1、命令示例
    A、ping www.baidu.com,另一台主机的域名
    B、ping 192.168.0.101,另一台主机的IP地址
    C、ping localhost,测试本地网络连接是否正常
    D、ping 127.0.0.1,测试本地网络连接是否正常
9、500错误
  (1)现象:前端带着base64数据向后台发出了post请求,后台没有请求记录,前端收到500
  (2)原因:后台服务器已满

九、yapi
1、下载
  (1)nodejs(7.6+)
  (2)mongodb(2.6+),默认安装在C盘,应改在D盘,且路径中的目录名不能有空格。
2、安装步骤及注意事项
  (1)卸载nodejs,
  (2)删除npm,C:\Users\XXXXX\AppData\Roaming\npm
  (3)安装node,遇到“Custom Setup”时,选择“npm package manager”
  (4)安装mongodb,遇到“Installing MongoDB Compass”时,去掉默认的勾选
  (5)每次重新搭建yapi之前,需要删除my-yapi目录、卸载并安装1次mongodb
3、安装并运行yapi
  (1)安装yapi脚手架,npm install -g yapi-cli --registry https://registry.npm.taobao.org
  (2)搭建yapi项目,yapi server,想把yapi搭建在哪个目录下,就在哪个目录下运行此命令。 
  (3)访问yapi项目,http://127.0.0.1:9090/
  (4)配置并连接数据库,即填写公司名称、点击开始部署
  (5)启动yapi,在my-yapi目录下运行node vendors/server/app.js
  --另、在谷歌浏览器上装corss-request插件后,可以在在yapi上发送请求
4、登录
  (1)http://127.0.0.1:3000(仅限自己)或者http://172.18.10.12:3000(自己和别人)
  (2)登录/注册,选登录
  (3)账号名:"admin@admin.com",
  (4)密码:"ymfe.org"
  (5)登录
5、yapi的层级
  (1)分组
  (2)项目
  (3)分类
  (4)接口

十、node(应用程序,能让JS在服务端运行,也能让服务器搭建在本地)
 附、node简介:(搜索node版)
  (1)2009年5月,Ryan Dahl发布Node.js,它基于Chrome V8引擎的JS运行环境,使用了一个事件驱动、非阻塞式I/O模型
  (2)node.js让JS成为服务端的一种脚本语言
  (3)node.js让前端可以安装和运行webpack、gulp或vite等开发构建工具,实现:
    A、安装、导入和导出模块;
    B、合并、转译、压缩和打包模块
1、导出与导入
  (1)node模块块开发:导出模块module.exports = {},导入模块require('./url/moduleName')
  (2)ES6模块块开发:导出模块export default xxx,导入模块import xxx from './url/moduleName'
2、__dirname和join
  var path = require("path");
  console.log(__dirname); c:\Users\公司\Desktop\yuanma
  console.log(path.join(__dirname, "..", "src")); c:\Users\公司\Desktop\src
  console.log(path.join(__dirname, "../", "src")); c:\Users\公司\Desktop\src
3、__dirname和resolve
  var path = require("path");
  console.log(__dirname); c:\Users\公司\Desktop\yuanma
  console.log(path.resolve(__dirname, "..", "src")); c:\Users\公司\Desktop\src
  console.log(path.resolve(__dirname, "../", "src")); c:\Users\公司\Desktop\src
4、url解析,问号传参给服务器,井号传参给浏览器
  var url_ = 'http://www.zhu.cn:80/ccc/index.html?name=zxt&age=26#33';
  var url = require('url');
  console.log(url.parse(url_, true));
  /* winUrl{
    protocol: 'http:',
    slashes: true,
    auth: null,
    host: 'www.zhu.cn:80',
    port: '80',
    hostname: 'www.zhu.cn',
    hash: '#33',
    search: '?name=zxt&age=26',
    query: { name: 'zxt', age: '26' },//当true不存在时query: 'name=zxt&age=26',
    pathname: '/ccc/index.html',
    path: '/ccc/index.html?name=zxt&age=26',
    href: 'http://www.zhu.cn:80/index.html?name=zxt&age=26#33' 
  } */
  function queryUrlParam1(url) {
    var obj = {};
    var reg1 = /([^?=&#]+)=([^?=&#]+)/g;
    var reg2 = /#([^?=&#]+)/;
    url.replace(reg1, function (keyValue,key,value) {
      obj[key] = value;
    });
    if (reg2.test(url)) {
      obj['HASH'] = reg2.exec(url)[1];
    }
    return obj;
  }
  function queryUrlParam2(url) {
    var  obj = {};
    var askText = '';
    var hashText = '';
    var askIndex = url.indexOf('?') === -1 ? url.length : url.indexOf('?');
    var hashIndex = url.indexOf('#') === -1 ? url.length : url.indexOf('#');
    if (askIndex < hashIndex) {
        askText = url.substring(askIndex + 1, hashIndex);
        hashText = url.substring(hashIndex + 1);
    } else {
        askText = url.substring(askIndex + 1);
        hashText = url.substring(hashIndex + 1, askIndex);
    }
    if (askText) {
        askText.split('&').forEach(function(item){
          let [key, value] = item.split('=');
          obj[key] = value;
        });
    }
    if (hashText) obj['HASH'] = hashText;
    return obj;
  };
  console.log(queryUrlParam1("https://www.baidu.com/newspage/data/landingsuper?AAA=1111&BBB=222&CCC=333#1234"));
  console.log(queryUrlParam2("https://www.baidu.com/newspage/data/landingsuper?AAA=1111&BBB=222&CCC=333#1234"));

十一、npm(Node Package Manager,node包管理器,与node捆绑安装)
1、淘宝npm镜像使用方法
  来源https://blog.csdn.net/quuqu/article/details/64121812
  (1)临时使用
    npm --registry https://registry.npm.taobao.org install express
    验证 npm info express
  (2)持久使用
    npm config set registry https://registry.npm.taobao.org
    验证 npm config get registry 
  (3)通过cnpm使用
    npm install -g cnpm --registry=https://registry.npm.taobao.org
    使用 cnpm install express
2、node包管理器之npm|cnpm|yarn|pnpm
  注、node管理器之nvm
  (1)npm:最常用的包管理器
    A、2010年发布
    B、生成package-lock.json文件,从npm5.0(内置于2017年发布的node8.0.0)开始
    C、按顺序依次下载每个包,速度慢
  (2)cnpm:china npm,中国npm。
    A、2014年发布,淘宝推出的npm替代工具
    A、不生成lock文件 
  (3)yarn:纱线、故事
    A、2016年发布
    B、生成yarn.lock文件
    C、并行下载包,速度块
    D、2022年淘宝发布yarn的国内镜像tyarn
  (4)pnpm:performant npm,高性能的npm。
    A、2016年11月发布,解决npm和yarn的扁平化安装方式带来的重复和幽灵依赖的问题
    B、生成pnpm-lock.yaml文件
    C、优势
      a、将不同版本间有差异的文件添加到仓库,不会因为一个文件的改变,而复制整个新版本包的内容
      b、所有文件都会存储在硬盘上的某一位置,允许跨项目地共享同一版本的依赖
    D、使用
      a、安装,npm install -g pnpm
      b、配置镜像源,pnpm config set registry https://registry.npm.taobao.org/
      c、安装依赖,pnpm install
3、npm install 安装命令示例,包名和后面的参数,位置可以互换
  (1)npm install 把package.json中的(开发devDependencies|开发生产dependencies)依赖配置,下载到本地项目的node_modules
  (2)npm install X 会把X包安装到项目的node_modules目录中,不会将模块依赖写入package.json中
  (3)npm install X -g 会把X包安装到全局的node_modules目录中,不会将模块依赖写入package.json中
  (4)npm install X --save 会把X包安装到项目的node_modules目录中,会在package.json的dependencies属性下添加X,--save简写为-S
  (5)npm install X --save-dev 会把X包安装到项目的node_modules目录中,会在package.json的devDependencies属性下添加X,--save-dev简写为-D 
4、npm其它命令
  (1)npm help <command>可查看某条命令的详情
  (2)npm update <package>可以把当前目录下node_modules子目录里边的对应模块更新至最新版本
  (3)npm run start/stop/test可以简写为npm start/stop/test
  (4)获取npm的安装路径,npm config get prefix
5、包的运行
  (1)npm,必须在根目录下运行包,必须写入到package.json里面,且已安装
  (2)npx,可以在任何地方运行包,不必写入到package.json里面,不必安装,从本目录开始逐层向上直至全局(nodejs环境变量)寻找该包,
    找到就运行,没有找到就临时安装并直接运行,运行结束后删掉;
    如果想运行层级以外的版本,则加上版本号即可,如npx webpack-dev-server@3.10.3;
6、“npm run dev”的运行说明
  {
    "scripts": {
      "dev": "xxx abc",
      "serve": "vue-cli-service serve",
    },
  }
  注意:直接执行“xxx abc”命令会报错,因为操作系统里只有npm相关的命令
  (1)在运行“npm install xxx”时,会在“node_modules/.bin”目录下,为包“xxx”创建名为“xxx”的3个文件,
    没有后缀名的是对应Unix系的shell脚本,.cmd为后缀名的是windows bat脚本,.ps1为后缀名的则是PowerShell脚本(可以跨平台),
    三者都是用node执行1个js文件
  (2)“npm run dev”运行时,npm首先在"scripts"字段里,找到“dev”对应的“xxx”字段,然后在“node_modules/.bin”目录下,
    找到名为“xxx”的文件,该文件从“node_modules/xxx”文件里引入相应文件,并运行"xxx abc"
  (3)运行后,缺包(出错、报错、错误)排查
    A、node版本是否太新或太旧  
    B、本公司的公共包是否下载到对应位置  
附、Live Server,一个快速启动的本地服务器
  (1)在vscode的扩展中找到并安装
  (2)在项目中-使用步骤,
    A、右键根目录下index.html;
    B、点击Open with Live Server
    C、改动该项目任意文件的内容,保存,自动刷新
  (3)在demo中-使用步骤,
    A、右键任意.html文件;
    B、点击Open with Live Server
    C、改动该文件的内容,保存,自动刷新(无需点击浏览器的圆箭头)
  (4)主要功能
    A、实时重新加载,自动检测文件变化,并立即在浏览器中刷新页面
    B、跨域代理(需配置)

十二、gulp(JS打包工具,构建工具,自动化工具)
 附、类似产品
  (1)Webpack,2013年首发的前端构建工具
  (2)Gulp,2015年首发的前端构建工具
  (3)vite,2019年首发的前端构建工具
1、gulp的作用,借助于node环境,进行本地开发和打包生产
2、gulp的API
  (1)gulp.src(globs[, options])导入文件,参数为路径。webpack为entry自动导入文件
    ----globs参数匹配文件路径(路径包括文件名,分为具体路径和通配符路径)。当有多个路径时,该参数为一个数组。
    ----options为可选参数。通常情况下我们不需要用到。
    ----* 匹配文件路径中的0个或多个字符,但不会匹配路径分隔符,除非路径分隔符出现在末尾
    ----** 匹配路径中的0个到多个目录及其子目录,需要单独出现,即它左右不能有其他东西了。如果出现在末尾,也能匹配文件。
    ----gulp.src(['js/*.js','css/*.css','*.html'])
    ----使用数组的方式还有一个好处就是可以很方便的使用排除模式,在数组中的单个匹配模式前加上!即是排除模式,
      它会在匹配的结果中排除这个匹配,要注意一点的是不能在数组中的第一个元素中使用排除模式
    ----gulp.src([*.js,'!b*.js']) //匹配所有js文件,但排除掉以b开头的js文件
    ----gulp.src(['!b*.js',*.js]) //不会排除任何文件,因为排除模式不能出现在数组的第一个元素中
  (2)gulp.dest(path[,options])导出文件的目录,参数为路径
    ----path为写入文件的路径
    ----options为一个可选的参数对象,通常我们不需要用到
    ----gulp.dest()传入的路径参数,只能用来指定要生成的文件的目录,生成的文件名是由导入到它的文件流决定的。
    ----gulp.dest(path)生成的文件路径是我们传入的path参数后面再加上gulp.src()中有通配符开始出现的那部分路径。例如:
    ----gulp.src('script/**/*.js') .pipe(gulp.dest('dist')); //最后生成的文件路径为 dist/**/*.js
  (3)gulp.task(name[, deps], fn)定义任务,参数为任务名
    ----name 为任务名
    ----deps 是当前定义的任务需要依赖的其他任务,为一个数组。当前定义的任务会在所有依赖的任务执行完毕后才开始执行。
    ----fn 为任务函数,我们把任务要执行的代码都写在里面。
    ----gulp.task('mytask', ['array', 'of', 'task', 'names'], function() { //定义一个有依赖的任务 //Do something});
    //gulp中执行多个任务,可以通过任务依赖来实现。例如我想要执行one,two,three这三个任务,那我们就可以定义一个空的任务,然后把那三个任务当做这个空的任务的依赖就行了,只要执行default任务,就相当于把one,two,three这三个任务执行了
    ----gulp.task('default',['one','two','three']);
    ----如果任务相互之间没有依赖,任务会按你书写的顺序来执行,如果有依赖的话则会先执行依赖的任务。
    ----但是如果某个任务所依赖的任务是异步的,就要注意了,gulp并不会等待那个所依赖的异步任务完成,而是会接着执行后续的任务。
  (4)gulp.watch(globs[, opts], tasks)或gulp.watch(glob[, opts], cb)用来监视文件的变化,参数为路径,实际项目中,用Gaze插件代替
    ----globs参数匹配文件路径(路径包括文件名,分为具体路径和通配符路径)。当有多个路径时,该参数为一个数组。
    ----opts 为一个可选的配置对象,传给gaze的参数,通常不需要用到
    ----tasks 为文件变化后要执行的任务,为一个数组
    ----cb参数为一个函数。每当监视的文件发生变化时,就会调用这个函数,并且会给它传入一个对象,该对象包含了文件变化的一些信息,type属性为变化的类型,可以是added,changed,deleted;path属性为发生变化的文件的路径
    ----gulp.watch('js/**/*.js', function(event){
        console.log(event.type); //变化类型 added为新增,deleted为删除,changed为改变
        console.log(event.path); //变化的文件的路径
      });
3、gulp的插件
  (1)gulp-load-plugins,加载其它插件
  (2)gulp-inject,把文件插入到注释的位置
  (3)gulp-useref,把HTML引用的多个文件如CSS、JS合并起来,再用gulp-if进行分类
  (4)gulp-clean,把原来的文件清空
  (5)gulp-if,判断文件类型
  (6)gulp-htmlmin,压缩HTML代码
  (7)gulp-clean-css,压缩CSS代码
  (8)gulp-uglify,压缩JS代码
  (9)gulp-ng-annotate,解决angular中,依赖注入出错的问题
  (10)gulp-angular-templatecache,html压缩成js后,用此插件指定该js所属模块
  (11)gulp-rev,为静态文件随机添加一串hash值,同时生成manifest.json保存新旧文件名对应关系
  (12)gulp-rev-collector,根据gulp-rev生成的manifest.json文件中的映射,去替换文件名称
4、gulp应用实例
(1)全局内容
  var Gaze = require('gaze').Gaze;
  var gaze = null;
  var gaze1 = null;
  function getMenus(exclude){
    var last_menus = [];
    var all_menus = eval(
      fs.readFileSync('src/config/project/cy/menu.js', 'utf8');
    ); //menu.js自执行函数,返回一个数组
    if(exclude){
      var firstGrade = Object.keys(exclude);
      var length1 = all_menus.length;
      for(var i=0;i<length1;i++){
        var sub1 = deepCopy(all_menus[i]);
        if(firstGrade.indexOf(sub1.title) === -1){
          var subs2 = sub1.subs;
          var length2 = sub1.subs.length;
          var secondGrade = Object.keys(exclude[sub1.title]);
          for(var j=0; j<length2; j++){
            if(secondGrade.indexOf(subs2[j].title) > -1){
              subs2.splice(j,1);
              j--;
            }else{
              var subs3 = subs2.subs;
              var length3 = subs2.subs.length;
              var thirdGrade = Object.keys(exclude[sub1.title][subs2[j].title]);
              for(var jj=0; jj<length2; jj++){
                if(thirdGrade.indexOf(subs3[jj].title) > -1){
                  subs3.splice(jj,1);
                  jj--;
                }
              }
            }
          }
          last_menus.push(sub1)
        }
      }
    }else{
      last_menus = deepCopy(all_menus);
    }
    //另外,计算图标位置的函数可以写在全局,在last_menus内调用
    var allData = `(function () {
      var base_dir = 'cy';
      var menus = last_menus;
      angular
        .module('app')
        .constant('menus', menus);
    })();`
    fs.writeFile(
      'src/config/menus.js',
      allData,
      'utf8',
      function () {}
    );
  }
(2)本地开发任务
  function testServeConfig(configDirname) {//实际上,下面(1)(2)两种情况是特殊的http请求,剩下的http请求都是直接向本机发送url
    var array = ['/app','/oauth','/status'];//(1)跨域代理:开发过程中,以这些item开始的url,将向后台服务器发送以item开始的url。这种情况主要用于获取动态数据。
    var proxy_ = array.map(function (value) {
      var a = url.parse('https://172.18.10.23' + value);
      a.route = value;
      a.rejectUnauthorized = false;
      return proxy(a);
    });
    browserSync.init(
      {
        port: 8900,
        notify: false,
        open: false,
        server: {
          baseDir: ['src'],
          directory: false,
          index: 'index.html',
          middleware: proxy_,
          routes: {
            '/audit-html/static/img': 'src/img',//(2)本地代理:开发过程中,以key开始的url,将向本地服务器发送以value开始的url。这种情况主要用于获取静态资源,如html文件、css的背景图片和img标签的src属性。
          }
        }
      },
      function () {
        reload();
        //渲染index.html
        //执行main.js,根据menus服务,执行$stateProvider.state(it.state, it.cfg);关联状态、页面、控制器
      }
    );
  }
  function testServe(moduleDirName, configDirname) {
    fs.exists('devServerConfig.json', function (exists) {
      if (gaze && gaze1) {
        gaze.close();
        gaze1.close();
        browserSync.exit();
      }
      inject_file(moduleDirName, configDirname);//注入文件,(不应该通过menu.js)将各文件夹下的js和css文件注入到index.html,
      watch_file(moduleDirName, configDirname);//监听文件,监听各文件夹下的js和css文件
      if (exists) {
        testServeConfig(configDirname);//配置服务
      } else {
        fs.writeFile(
          'devServerConfig.json',
          '{"address":"http://192.168.80.152:7300"}',
          'utf8',
          function () {
            testServeConfig(configDirname);//配置服务
          }
        );
      }
    });
  }
  gulp.task('default', function () {
    var exclude = {
      '一级标题1':{
        '二级标题1':{
          '三级标题1':{
            '四级标题1':''
          }
        },
      }
    }
    getMenus(exclude);//把参数包含的标题,从总数据中排除,获取最终的menus服务
    testServe('cy','cy');//注入文件、监听文件、配置服务
  });
(3)打包生产任务
  gulp.task('templates', function () {
    gulp
      .src(tpl_html)
      .pipe(
        $.htmlmin({
          collapseWhitespace: true,
          removeComments: true,
          minifyCSS: true
        })
      )
      .pipe(
        $.angularTemplatecache({ /* html压缩成js后,用此插件指定该js所属模块 */
          module: 'app'
        })
      )
      .pipe($.uglify())
      .pipe(gulp.dest('.tmp'));
  });
  gulp.task('buildAll', function () {
    gulp
      .src(['src/index.html'])
      .pipe(
        $.inject(gulp.src('.tmp/templates.js'), {
          starttag: '<!-- inject:partials -->',
          relative: true
        })
      )
      .pipe($.useref())
      .pipe($.if('*.js', $.ngAnnotate())) /* 解决angular中,依赖注入出错的问题 */
      .pipe($.if('*.js',$.uglify())) /* 压缩JS代码 */
      .pipe($.if(
        '*.css',
        autoprefixer({
          browsers: ['last 8 versions'],
          cascade: false
        })
      ))
      .pipe($.if('*.css', $.cleanCss()))/* 压缩CSS代码 */
      .pipe(gulp.dest('dist'));
  });
  gulp.task('revCssJS', function () {
    gulp
      .src(['./dist/style/*.css', './dist/script/*.js'])
      .pipe(rev()) //添加hash后缀
      .pipe(gulp.dest('./dist')) //原路导出“添加hash后缀”后的文件
      .pipe(rev.manifest()) //生成映射文件
      .pipe(gulp.dest('./rev')); //将映射文件导出到rev
  });
  gulp.task('revHtml', function () {
    gulp
      .src(['./rev/*.json', './src/productTpl/index.html'])
      .pipe(revCollector()) //用前者的映射关系替换后者相应的文件
      .pipe(gulp.dest('./template')); //将替换后的文件导出
  });
  gulp.task('build', function () {
    runSequence(
      'clean:start',
      'template',
      'images-cy',
      'copy',
      'buildAll', //产生all.min.js
      'revCssJS',
      'revHtml',
      'clean:final'/* ,
      function () {
        copyConfiguration('cy');
      } */
    );
  });

十三、webpack(JS打包工具,构建工具,自动化工具)
 附、类似产品
  (1)Webpack,2013年首发的前端构建工具
  (2)Gulp,2015年首发的前端构建工具
  (3)vite,2019年首发的前端构建工具
1、webpack各版本发布时间
  (1)Webpack1,2013
  (2)Webpack2,2016,支持ES Module,支持Tree-Shaking,将没用到的代码剔除
  (3)Webpack3,2017.06.20,Scope Hoisting(作用域提升)和Magic Comment(魔法注释)等
  (4)Webpack4,2018.02.25,mode属性,WebAssembly(字节码格式),支持多种模块类型,0配置等
  (5)Webpack5,2020.10.10,优化缓存,改善Tree-Shaking等等
2、常见包
  (1)webpack:是打包的命令,也是模块打包器。将entry输入的文件,经过链式裂变导入、用module.rules编译打包后,
    根据mode取值不同而选择压不压缩最终的js,最后通过output把js显性输出到指定目录。
  (2)webpack-cli:是webpack命令行的工具,能使webpack命令带参数在命令行中运行,cli即命令行接口(Command Line Interface)。
  (3)webpack-dev-server:是开启本地node服务器的命令,也是一个小型的node服务器。将entry输入的文件用module.rules编译打包后,
    根据mode取值不同而选择压不压缩最终的js,最后通过output把js隐性输出到内存并自动打开浏览器,同时监听entry及其import引入的文件,
    一旦发生变化就自动编译、打包、隐性输出代码,手动刷新浏览器,可以看到最新效果。
3、常见疑问释疑
  (1)直接运行的包,如果本目录或全局安装了该包,可以直接运行,不需要npm和npx,如webpack、webpack-dev-server。
  (2)在编译的过程中,根据babel-loader的配置处理js的兼容,根据process.env.NODE_ENV取值不同,选择package.json里browserslist的不同配
    置项来处理css的兼容问题,根据插件配置决定最终的css压不压缩和输出目录,根据url-loader的配置决定最终的img输出目录和公共路径。
4、webpack优化方法
  (1)数组一个,只执行数组选项中的一个,oneOf:[]
  (2)一个数组,将多次使用的加载器放到一个数组里,供展开使用
  (3)懒加载,当需要使用文件时才加载,比如点击某个按钮后才加载某个文件
  (4)多进程,见下面配置thread-loader
  (5)使用cache,缓存,当只有js/css文件发生改变时,只打包js/css文件到最终目录里,用最新的js/css文件名替换掉index.html上原来的
    文件名,同时删掉原来的js/css文件
  (6)使用HMR,模块热替换,在程序运行过程中替换、添加或删除“模块”,而无需重新“加载整个页面”,Hot Module Replacement的缩写
    自悟,单页面应用,每个页面都由多个模块构成,哪个模块更新,就替换掉哪个模块,不会重新构建所有模块,最后重新渲染当前页面
  (7)使用dll,动态链接库,把第三方类库单独打包,生成映射库,然后每次只打包项目自身代码,Dynamic Link Library的缩写,
  (8)使用externals,排除第三方类库,用CND引入第三方库
  (9)使用gzip压缩,Webpack用compression-webpack-plugin对静态资源进行压缩,上传至服务器;服务器端根据请求头返回gzip资源,
    浏览器根据响应头解压gzip资源
    来源,https://blog.csdn.net/weixin_47516343/article/details/125392505
  (10)弃用sourcemap,资源地图,module.exports = {  productionSourceMap: false, }//打包时不会生成 .map 文件,加快打包速度 
  (11)使用webpack-bundle-analyzer,webpack打包分析,对webpack的打包性能进行分析
  (12)使用webpack的speed-measure-webpack-plugin看打包速度
附1、cache示例
  {
    test: /\.js$/,
    use: ['cache-loader', 'babel-loader'],
  },
  { 
    filename: "js/built.[contenthash:10].js"
  }
  { 
    filename: "css/built.[contenthash:10].css" 
  }
附2、HMR示例
  const webpack = require('webpack');
  module.exports = {
    //...
    plugins: [
      new webpack.HotModuleReplacementPlugin()
    ],
    devServer: {
      hot: true
    }
  }
附3、dll示例
  来源,https://www.jb51.net/article/118431.htm
  A、把第三方类库单独打包,如:jquery、react、vue...
  B、生成映射库,webpack.dll.config.js文件说明
    module.exports = {
      entry: { //导入
        vendors: ['react', 'lodash'] //别名:["库名","库名"]
      },
      output: { //导出
        filename: '[name].js', //生产环境需要导入该文件
        path: resolve(__dirname, 'dll'),
        library: '[name]_[hash]' //打包库里向外文件的名称
      },
      plugins: [
        new webpack.DllPlugin({ //生成映射库
          path: resolve(__dirname, "dll/manifest_[name].json"), 
          name: "[name]_[hash]", //打包库里向外文件的名称,与output.library必须一致
        }),
      ]
    };
  C、自动生成动态链接库,webpack.config.js文件说明,注意
    (1)路径中不能有../,
    (2)同级paceage.json中,要安装dll-link-webpack-plugin,
    (3)全局node、webpack、webpack-cli的版本
      var DllLinkPlugin = require("dll-link-webpack-plugin");
      module.exports = {
        plugins: [
          new DllLinkPlugin({
            config: require("./webpack.dll.config")
          })
        ]
      };
5、常见术语释义
  (1)chunk,代码块
  (2)polyfill,兜底,在计算机中,变相实现不能直接实现的操作
  (3)bootstrap,n.[计]引导程序,辅助程序;vt.启动(电脑)
  (4)process,对象是一个 global 变量,提供有关当前 Node.js 进程的信息并对其进行控制
  (5)dll,动态链接库英文为DLL,是Dynamic Link Library的缩写。(Dynamic,[daɪˈnæmɪk],有活力的)
6、package.json
(1)webpack开发/打包命令,npm run dev/build
  {
    "scripts": {
      "dev": "cross-env envNum=0 webpack-dev-server --config build/webpack.dev.conf.js",
      //用webpack-dev-server开启代理服务器,process.env.envNum的值为0
      "build": "cross-env envNum=1 webpack build/webpack.dev.conf.js"//用webpack打包
      //cross-env,在windows环境下可改为​​set,在其它环境下可省略
      //envNum的值,可以用于区分开发、生产环境
    },
    "dependencies": {},
    "devDependencies": { 
      "babel-core": "6.26.0",//把ES6+换为ES5的核心插件
      "babel-preset-es2015": "^6.24.1",//2017年Babel宣布ES2015/ES2016/ES2017被废弃
      "babel-preset-env": "^1.7.0",//根据配置的目标浏览器或运行环境,自动将ES2015+转换为ES5。
      "babel-loader": "7.1.5",//将ES2015+转换为ES5。
      "babel-eslint": "8.2.6",//ES6+语法检测。
      "url-loader": "1.0.1",//将引⼊的图⽚以base64编码并打包到⽂件中,最终只需要引⼊这个dataURL就能访问图⽚了。
      "vue-loader": "15.3.0",//解析和转换.vue文件,提取出其中的逻辑代码script、样式代码style、以及HTML模版template,再分别把它们交给对应的 Loader 去处理。
      "vue-style-loader": "4.1.2",//除了支持客户端渲染,还支持服务端渲染
    }
  }
(2)webpack-dev-server命令下,各参数的含义
  config,修改默认的配置文件
  content-base,与path、publicPath类似
  compress,开启gzip压缩
  hot,不刷新浏览器
  inline,刷新浏览器
  hot --inline,失败则刷新浏览器
  progress,显示打包的进度
  quiet,控制台中不输出打包的信息
  HRM:Hot-Module-Replacement,模块热更新。在应用运行时,无需刷新页面,便能替换、增加、删除必要的模块
7、webpack.config.js文件示例--1
  注意:各项配置中,mode、plugins、publicPath先执行,module、entry、output后执行。非常重要!!!
  const { resolve } = require('path');
  const HtmlWebpackPlugin = require('html-webpack-plugin');
  const MiniCssExtractPlugin = require('mini-css-extract-plugin');
  const webpack = require('webpack');
  module.exports = {
    entry: './src/js/index.js',//引入js,字符串和数组为单入口,对象为多入口;另外对象的value也可以是数组
    output: {
      filename: 'js/built.js',//导出js文件,如果和entry路径的深度不一样,那么webpack会自动调整图片的引用路径
      path: resolve(__dirname, 'build')//里面的css默认或者通过配置导出
    },
    publicPath: '/',//给所有资源引入公共路径前缀'/',如'imgs/a.jpg'==>'/imgs/a.jpg'
    mode: 'development',//用webpack打包时不压缩,取'production'则压缩
    module: {
      rules: [//loader配置
        {
          test: /\.less$/,//处理less资源
          use: ['style-loader', 'css-loader', 'less-loader']//从右向左执行
          //'style-loader'开发环境用。在下面的js文件里,隐式地为每个样式文件创建一个style标签并将该样式放入,再将每一个style标签插到head标签里;隐式地自动将output.filename引给JavaScript标签并插入到template里的body标签里。这样head标签里不会有style标签,但控制台里有。
          //'css-loader',将css文件整合到js文件中
          //'less-loader'将less文件转换为css文件
        },
        {
          test: /\.css$/,
          use: [//从下向上执行
            MiniCssExtractPlugin.loader,//把下面js文件里的样式提取到单独的样式文件里,根据new MiniCssExtractPlugin确定文件名,然后插到由html-webpack-plugin指定的html下的head下的link标签里
            'css-loader',//将css文件整合到js文件中
            {
              loader: 'postcss-loader',//css兼容处理
              /*
                一、css兼容处理:
                1、postcss-loader(提供postcss)
                2、postcss-preset-env(帮postcss找到package.json中browserslist里面的配置,加载指定的css兼容性样式)
                3、package.json中的相关配置
                "browserslist": { //浏览器列表
                  "development": [
                    "last 1 chrome version",
                    "last 1 firefox version",
                    "last 1 safari version"
                  ],
                  "production": [//默认是看生产环境
                    ">0.2%",
                    "not dead",
                    "not op_mini all"
                  ]
                }
              */
              options: {
                ident: 'postcss',
                plugins: () => [//postcss的插件
                  require('postcss-preset-env')()
                ]
              }
            }
          ]
        },
        {
          test: /\.js$/,//工程化优化之使用缓存
          use: ['cache-loader', 'babel-loader'],
        },
        {
          test: /\.js$/,
          exclude: /node_modules/,
          use: [
            {
              /*
                开启多进程打包。
                进程启动大概为600ms,进程通信也有开销。
                只有工作消耗时间比较长,才需要多进程打包
              */
              loader: 'thread-loader',
              options: {
                workers: 2 //进程2个
              }
            },
            {
              loader: 'babel-loader',//js兼容处理
              /*
                二、js兼容处理:
                1、基本兼容处理如箭头函数,用(1)babel-loader(显性使用)(2)@babel/core(隐性支持)(3)@babel/preset-env(显性使用)
                2、复杂兼容处理如promise,除了1以外,还需要用按需加载的(4)core-js(显性使用)或全部引入的(5)@babel/polyfill(显性使用)    
                3、【babel-loader@8 requires Babel 7.x (the package '@babel/core')】
              */
              options: {
                presets: [//预设:指示babel做怎么样的兼容性处理
                  [
                    '@babel/preset-env',
                    {
                      useBuiltIns: 'usage',//按需加载
                      corejs: {version: 3},//指定core-js版本
                      targets: {//指定兼容性做到哪个版本浏览器
                        chrome: '60',
                        firefox: '60',
                        ie: '9',
                        safari: '10',
                        edge: '17'
                      }
                    }
                  ]  
                ],
                cacheDirectory: true//开启babel缓存,第二次构建时,会读取之前的缓存
              }
            }
          ]
        },
        {
          test: /\.js$/,
          exclude: /node_modules/,
          include: resolve(__dirname, 'src'),//只检查 src 下的js文件
          enforce: 'pre',//2个js匹配,这个优先执行。也可以像处理css文件那样,把2个包放到1个use数组里。延后执行'post'
          loader: 'eslint-loader',
          /*
            三、js语法检查:
            1、eslint-loader(显性使用) eslint(隐性支持),eslint-config-airbnb-base(隐性支持) eslint-plugin-import(隐性支持)
            2、package.json中的相关配置
            "eslintConfig": {
              "extends": "airbnb-base"
            }
            3、//eslint-disable-next-line,下一行所有eslint规则都失效
          */
          options: {
            fix: true//自动修复eslint的错误
          }
        },
        {
          test: /\.(jpg|png|gif)$/,//处理.css中的图片
          loader: 'url-loader',//在.css中的图片,通过css-loader存储到js文件中,在js文件里改名后,可根据图片大小将图片转码为base64保存在js中或用common.js(默认用ES6,在配置中被esModule:false关闭)模块导出。js里存储了图片改前名和改后名的关联,后来再用到这张图片时会用到这个关联。
          options: {
            limit: 8 * 1024,
            name: '[hash:10].[ext]',
            esModule: false,//关闭es6模块化
            outputPath: "img",
            publicPath: "/img",
          }
        },
        {
          test: /\.html$/,//处理html中的图片
          loader: 'html-loader'//在.html文件img标签中的图片,用common.js模块处理,从而能被url-loader进行处理。
        },
        {
          exclude: /\.(html|js|css|less|jpg|png|gif)/,//处理其他资源
          loader: 'file-loader',//原封不动地输出文件
          options: {
            name: '[hash:10].[ext]',
            outputPath: 'media',
            publicPath: "/media",
          },
        }
      ]
    },
    plugins: [
      //1、以下指定CSS的导出位置的导出后的名字
      new MiniCssExtractPlugin({
        filename: 'css/built.css'//导出css文件。默认配置为filename: './main.css',最终的js按照output导出
      }),
      new OptimizeCssAssetsWebpackPlugin(),//对上面导出的css进行压缩
      //2、以下生成js文件
      new DllLinkPlugin({ //生成动态链接库存并放到"dll/manifest_[name].json"里
        config: require('webpack.dll.config.js')
      }),
      new webpack.DllReferencePlugin({//使用动态链接库,把依赖的名称映射到模块的id上
        manifest: resolve(__dirname, "dll/manifest_[name].json"),
      }),
      new AddAssetHtmlWebpackPlugin([//添加资源库,将资源打包出去,并在html中自动引入
        { filepath: resolve(__dirname, "dll/jquery.js") },//有多项时,用数组
        { filepath: resolve(__dirname, "dll/vue.js") },//只有一项时,直接用对象即可
      ]),
      //3、以下将导出的js文件或css文件插入到html文件head的link里或body的script里
      new HtmlWebpackPlugin({
        template: './src/index.html',
        minify: {
          collapseWhitespace: true,//去除index.html里面的空白
          removeComments: true//去除index.html里面的注释
        }
      }),
    ],
    devServer: {//开发环境必有
      compress: true,//启动gzip压缩
      contentBase: resolve(__dirname, 'build'),//告诉服务器内容的来源。仅在需要提供静态文件时才进行配置。
      watchContentBase: true,//监视 contentBase 目录下的所有文件,一旦文件变化就会 reload
      watchOptions: {
        ignored: /node_modules///忽略文件
      },
      port: 5000,//端口号
      host: 'localhost',
      open: true,//自动打开浏览器
      hot: true,//开启HMR功能,需要插件webpack.HotModuleReplacementPlugin配合
      proxy: {//服务器代理 --> 解决开发环境跨域问题
        '/api': {//一旦服务器5000接收到'/api/xxx'的请求
          target: 'http://localhost:3000',//就会把请求转发到另外一个服务器3000
          pathRewrite: {//发送请求时,请求路径重写:将/api去掉
            '^/api': ''
          }
        }
      },
      clientLogLevel: 'none',//不要显示启动服务器日志信息
      quiet: true,//除了一些基本启动信息以外,其他内容都不要显示
      overlay: false,//如果出错了,不要全屏提示~
    },
    devtool: "nosources-source-map",//正常情况下,此配置项缺失比加上更好
    resolve: {//解析模块的规则
      alias: {//配置解析模块路径别名: 优点简写路径 缺点路径没有提示
        $css: resolve(__dirname, 'src/css')
      },
      extensions: ['.js', '.json', '.jsx', '.css'],//配置省略文件路径的后缀名
      modules: [resolve(__dirname, '../../node_modules'), 'node_modules']//告诉 webpack 解析模块是去找哪个目录
    }
    optimization: {
      /*
        可以将node_modules中(除dll外)代码单独打包一个chunk最终输出
        自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk
      */
      splitChunks: {
        chunks: 'all'
      }
    },
    externals: {//用CDN时,需要此项配置;用dll时不能有此项配置。
      jquery: "jquery", //不打包第三方类库jquery
      vue: "vue", //不打包第三方类库vue
    },
  };
8、webpack.config.js文件示例--2
  // 附、环境类型、NODE_ENV的设置与获取
  //   a、设置,package.json 
  //     {
  //       "scripts": {
  //         //"dev": "webpack --mode development"//不适合持续的开发调试
  //         //"dev": "NODE_ENV=development webpack-dev-server"//适合持续的开发调试,是一个相对成熟的独立开发服务器工具
  //         //"dev": "NODE_ENV=development webpack serve"//适合持续的开发调试,Webpack新的开发服务器命令,旨在逐步替代上面
  //       }
  //     }
  //   b、获取,webpack.config.js
  //     process.env.NODE_ENV = "development"; //不能这样设置!!!
  //     module.exports = {
  //       mode: process.env.NODE_ENV,//获取、设置
  //     };
  // 附、环境变量、环境对象的设置与获取
  //   a、设置,package.json 
  //     {
  //       "scripts": {
  //         //"dev": "cross-env envNum=0 webpack-dev-server",//设置
  //       }
  //     }
  //   b、获取,webpack.config.js
  //     var envNum = process.env.envNum;
  var webpack = require('webpack');
  var path = require('path');
  var envNum = process.env.envNum;//0为开发环境,1为生产环境,package.json文件里有定义
  var proxyServer = "http://192.168.10.231:8081/"
  var baseRules = getBaseRules();
  var cssRules = getCssRules({
    extract: [false,true][envNum],
    sourceMap: [false,true][envNum],
    usePostCSS: [false,true][envNum],
  })
  module.exports = { 
    mode: ['development','production'][envNum] ,
    context: path.resolve(__dirname, '../'),
    entry: {
      app: './src/main.js',//app与其中一个chunks的最后一项对应
      report: './src/main-report.js'//report与其中一个chunks的最后一项对应
    },
    output: {
      filename: '[name].js',
      path: config.build.assetsRoot,//打包后的存放位置(绝对)
      publicPath: ['/','/dist/'][envNum]//打包时静态资源的存放位置(相对与path)
    },
    resolve: {
      extensions: ['.js', '.vue', '.json'],
      alias: {
        '@': resolve('src')
      }
    },
    module: {
      rules: baseRules.concat(cssRules)
    },
    devtool: ["cheap-source-map","source-map"][envNum],
    devServer: {
      host: 'localhost',//默认是localhost。如果你希望服务器外部可访问,指定如 host: '0.0.0.0'
      port: '7770',
      proxy: {
        "/app": {
          target: proxyServer, //目标接口域名
          changeOrigin: true, //是否跨域
          pathRewrite: {
            "^/app": "/" //重写接口
          }
        },
        "/isgapp": {
          target: proxyServer, //目标接口域名
          changeOrigin: false, //是否跨域
          pathRewrite: {
            "^/isgapp": "/isgapp" //重写接口
          }
        },
        "/dist": {
            target: `http://${devConfig.host}:${devConfig.port}/`, //目标接口域名
            changeOrigin: false, //是否跨域
            pathRewrite: {
              "^/dist/static/data": "/src/project/"+process.env.configDir, //重写接口
              "^/dist": "/", //重写接口
          }
        },
      },
    },
    plugins: [[
      new webpack.DefinePlugin({
        'process.env': merge({
          configDir: JSON.stringify(process.env.configDir)
        },require('../config/dev.env'))   
      }),
      new webpack.HotModuleReplacementPlugin(),
      new HtmlWebpackPlugin({
        filename: 'index.html',
        template: 'index.html',
        inject: true,
        favicon: resolve('favicon.ico'),
        chunks: ['manifest', 'vendor', 'app']
      }),
      new HtmlWebpackPlugin({
        filename: 'report-index.html',
        template: 'report-index.html',
        title: '报表管理',
        favicon: resolve('favicon.ico'),
        inject: true,
        chunks: ['manifest', 'vendor', 'report']
      }),
      new VueLoaderPlugin()
    ],[
      new webpack.DefinePlugin({
        'process.env': merge({
          configDir: JSON.stringify(process.env.configDir)
        },env)   
      }),
      new MiniCssExtractPlugin({
        filename: 'static/css/[name].[contenthash:8].css',//导出文件的文件名
        chunkFilename: utils.assetsPath('css/[name].[contenthash:8].css')//导入文件的文件名
      }),
      new HtmlWebpackPlugin({
        filename: path.resolve(__dirname, "../dist/index.html"),
        template: 'index.html',
        inject: true,
        favicon: resolve('favicon.ico'),
        minify: {
          removeComments: true,
          collapseWhitespace: true,
          removeAttributeQuotes: true
        },
        chunks: ['manifest', 'vendor', 'app']
      }),
      new HtmlWebpackPlugin({
        filename: config.build.report,
        template: 'report-index.html',
        inject: true,
        favicon: resolve('favicon.ico'),
        minify: {
          removeComments: true,
          collapseWhitespace: true,
          removeAttributeQuotes: true
        },
        chunks: ['manifest', 'vendor', 'report']
      }),
      new ScriptExtHtmlWebpackPlugin({
        inline: /runtime\..*\.js$/
      }),
      new webpack.NamedChunksPlugin(chunk => {
        if (chunk.name) {
          return chunk.name
        }
        const modules = Array.from(chunk.modulesIterable)
        if (modules.length > 1) {
          const hash = require('hash-sum')
          const joinedHash = hash(modules.map(m => m.id).join('_'))
          let len = nameLength
          while (seen.has(joinedHash.substr(0, len))) len++
          seen.add(joinedHash.substr(0, len))
          return `chunk-${joinedHash.substr(0, len)}`
        } else {
          return modules[0].id
        }
      }),
      new webpack.HashedModuleIdsPlugin(),
      new CopyWebpackPlugin([{
          from: path.resolve(__dirname, '../static'),
          to: config.build.assetsSubDirectory,
          ignore: ['.*']
        },
        {
          from: path.resolve(__dirname, '../src/project/'+process.env.configDir+'/setting.json'),
          to: config.build.assetsSubDirectory+'/data',
        },
      ]),
      new VueLoaderPlugin()
    ]][envNum]
  };
  function getBaseRules(){
    return [
      ...(config.dev.useEslint ? [createLintingRule()] : []),
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: vueLoaderConfig
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include: [
          resolve('src'),
          resolve('test'),
          resolve('node_modules/webpack-dev-server/client')
        ]
      },
      {
        test: /\.svg$/,
        loader: 'svg-sprite-loader',
        include: [resolve('src/icons')],
        options: {
          symbolId: 'icon-[name]'
        }
      },
      {
        test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
        loader: 'url-loader',
        exclude: [resolve('src/icons')],
        options: {
          limit: 10000,
          name: utils.assetsPath('img/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('media/[name].[hash:7].[ext]')
        }
      },
      {
        test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
        loader: 'url-loader',
        options: {
          limit: 10000,
          name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
        }
      }
    ]
  }
  function getCssRules(options){
    options = options || {};
    var extract = options.extract;
    var sourceMap = options.sourceMap;
    var usePostCSS = options.usePostCSS;
    function getLoader(loader, other) {
      var loaders = [];
      if (extract) {
        loaders.push(MiniCssExtractPlugin.loader)//把下面js文件里的样式提取到单独的样式文件里,然后插在head标签下,由html-webpack-plugin自动生成的link标签里
      } else {
        loaders.push('vue-style-loader')//创建了一个style标签,然后直接插入到了head标签中
      };
      loaders.push({
        loader: 'css-loader',//将css文件整合到js文件中
        options: {
          sourceMap: sourceMap
        }
      });
      if (usePostCSS) {
        loaders.push({
          loader: 'postcss-loader',
          options: {
            sourceMap: sourceMap
          }
        })
      };
      if (loader) {
        loaders.push({
          loader: loader + '-loader',
          options: Object.assign({}, other, {
            sourceMap: sourceMap
          })
        })
      };
      return loaders
    };
    return [
      {
        test: /\.css$/,
        use: getLoader()
      },
      {
        test: /\.less$/,
        use: getLoader('less')
      },
      {
        test: /\.sass$/,
        use: getLoader('sass', {
          indentedSyntax: true
        })
      },
      {
        test: /\.sass$/,
        use: getLoader('sass')
      }
    ]
  }

十四、vite,一种新型前端构建工具(JS打包工具,构建工具,自动化工具)
 附、类似产品
  (1)Webpack,2013年首发的前端构建工具
  (2)Gulp,2015年首发的前端构建工具
  (3)vite,2019年首发的前端构建工具
1、webpack打包与困境
  (1)webpack将模块文件打包成浏览器支持的文件
  (2)构建大型应用时,即使使用模块热替换(HMR),webpack的反应也不够迅速
2、vite的组成
  (1)开发服务器,它基于原生ES模块提供了丰富的内建功能
  (2)构建指令,它用预(默认)配置和多种模块、静态分析的Rollup打包代码,输出用于生产环境的高度优化过的静态资源
3、vite的优势,Vite将应用中的模块分为依赖和源码两类
  (1)预构建,用Go语言编写的esbuild“预构建-依赖模块”,比用JS编写的打包器“预构建-依赖模块”快10-100倍
    A、预构建,先将作为CommonJS或UMD发布的依赖项转换为ESM模块,再将ESM多模块依赖转为单模块依赖
    B、SFC,单文件组件,Single File Component
  (2)按需转译,“按需转译-ES组件模块”
  (3)热更新,“热更新HMR-ES组件模块”
4、vite的命令
  附、全局安装vite,(c)npm install -g create-vite
  (1)Vite,启动开发服务器
  (2)vite build,构建生产版本
  (3)vite optimize,预构建依赖
  (4)vite preview,本地预览构建产物
5、静态资源处理
  (1)将资源引入为URL,import imgUrl from './img.png',
    应当为,import zanIcon from '@/assets/images/zan.png';
    imgUrl在开发时会是/img.png,在生产构建后会是/assets/img.2d8efhg.png
  (2)显式引入URL,使用?url后缀显式导入为一个URL,import workletURL from 'extra-scalloped-border/worklet.js?url'
  (3)将资源引入为字符串,import shaderString from './shader.glsl?raw'
  (4)导入脚本作为Worker,import Worker from './shader.js?worker'
  (5)public目录,静态资源默认放在<root>/public里,如public/icon.png,在源码中被引用为/icon.png,
    public中的资源不应该被JavaScript文件引用
6、构建生产版本并部署,
  (1)运行vite build命令,
  (2)使用<root>/index.html作为其构建入口点,
  (3)生成能够静态部署的应用程序包dist文件夹,
  (4)部署文件夹到服务器
7、vite.config.js示例
  //附、参考文档,https://cn.vitejs.dev/guide/why.html
  //附、环境变量、环境对象的设置与获取
  // (1)设置,.env.development,示例如下
  //    VITE_APP_BASE_API = 'http://10.51.29.56:7010/ai-access-server/'
  // (2)获取,request.js,示例如下
  //    const service = axios.create({
  //      baseURL: import.meta.env.VITE_APP_BASE_API, //axios中请求配置有baseURL选项,表示请求URL公共部分
  //      timeout: 180000, //超时
  //    })
  //来源ai-web
  import { fileURLToPath, URL } from 'node:url'
  import path from 'path'
  import { defineConfig, loadEnv } from 'vite' //定义配置,加载环境
  import vue from '@vitejs/plugin-vue'
  import { viteMockServe } from 'vite-plugin-mock'
  import createVitePlugins from './vite/plugins'
  import legacy from '@vitejs/plugin-legacy' //为传统浏览器提供支持
  export default defineConfig((params)=>{
    // console.log( params ); 
    // { 
    //   mode: 'development', //环境类型、NODE_ENV的设置与获取(无需设置,用params.mode获取)
    //   command: 'serve', 
    //   ssrBuild: false 
    // }
    const envConfig = loadEnv(params.mode, process.cwd());
    // 默认不加载.env文件,Vite的loadEnv函数可以加载指定的.env文件
    // loadEnv(mode, envDir, prefixes)同步函数的参数说明
    //  1、mode,根据启动命令,确定mode值,加载对应的.env文件。配置文件提供一切
    //     A、为development时,加载.env.development文件;
    //     B、为production时,加载.env.production文件
    //     附、启动命令有,npm run dev;npm run build
    //  2、envDir,当前工作目录,
    //     A、相对于package.json的文件夹地址,
    //     B、可通过process.cwd()获取
    //     C、console.log(process.cwd()); //C:\Users\Haier\Desktop\ai-web; 
    //     D、cwd,Current Working Directory,即当前工作目录 
    //  3、prefixes,根据prefixes值,返回文件里的项
    //     A、缺失时,返回对应文件中,以字符串`VITE_`开始的项;
    //     B、为''时,返回对应文件中,所有项和内置项;
    //     C、为字符串时,返回对应文件中,以该字符串开始的项
    const { VITE_BASE_URL, VITE_OUTPUT_DIR, VITE_USE_MOCK }  = envConfig
    return {
      base: VITE_BASE_URL,// 在开发环境或生产环境中,请求静态资源时,都会加上。比如/app/image.jpg中的/app
      // 来源,https://cn.vitejs.dev/config/shared-options.html#define
      // define,定义全局常量,在开发环境下定义在全局,在构建时被静态替换
      // define,全局常量获取,console.log(__APP_ENV__);
      define: {//对于字符串以外的数据类型(如布尔值)最好使用JSON.stringify进行处理,以确保在不同环境下的正确替换
        'process.env': envConfig,
        __APP_ENV__: envConfig.APP_ENV,
        __APP_VERSION__: JSON.stringify('v1.0.0'),
        __API_URL__: 'window.__backend_api_url',
        //__DEBUG_MODE__: JSON.stringify(true),
      },
      // vite中define数据和环境变量有什么区别?
      //   1、define,在构建时,静态地注入到代码中
      //   2、环境变量,在不同环境下,项目参数可能变化
      //   3、环境变量的优先级高于define
      plugins: [
        createVitePlugins(),
        viteMockServe({
          // 问:在vite.config.js中,在plugins中引入mock.js,devServer.proxy还有效吗
          // 答:1个请求用3种方案中的1种来处理,优先级从大往小为,devServer.proxy、Mock.js、直接发出(浏览器跨域)
          // 在“开发环境或生产环境”中,请求静态资源时,下面配置为true的,会被模拟
          mockPath: "./src/mock/",//导入此路径下的-虚拟数据
          prodEnabled: false,//生产环境,禁用-模拟数据-服务
          localEnabled: VITE_USE_MOCK,//true,开发环境,启用-本地模拟数据-服务,network有相关记录
        }),
      ],
      build: {
        outDir: VITE_OUTPUT_DIR
      },
      css: {
        preprocessorOptions: {
          scss: {
            additionalData: `@use "@/assets/css/variables.scss" as *;`,
          },
        },
      },
      resolve: {
        alias: {
          '~': path.resolve(__dirname, './'),
          '@': path.resolve(__dirname, './src')
        },
        extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.vue']
      },
      server: {
        port: 80,
        host: true,
        open: true,
        proxy: {
          // 原理:改变源(不是跨域)
          // (1)在开发环境中,请求从浏览器发到开发服务器,在开发服务器改变源后,再发到后台服务器
          // (2)在生产环境中,请求从浏览器发到后台服务器,不可以改变源
          // 仅在“开发环境”中,请求动态资源时,下面配中的,会被代理
          // https://cn.vitejs.dev/config/#server-proxy
          '/sdapi':{
            target: 'http://192.168.116.32:7860',
            changeOrigin: true,
          },
          '/play':{
            target: 'http://192.168.116.32:8002',
            changeOrigin: true,
          },
          '/create_task':{
            target: 'http://192.168.116.32:8002',
            changeOrigin: true,
          },
          '/task_status':{
            target: 'http://192.168.116.32:8002',
            changeOrigin: true,
          },
          '/download':{
            target: 'http://192.168.116.32:8002',
            changeOrigin: true,
          },
          '/api': {
            target: 'https://reg.cctv.com',
            changeOrigin: true,
            rewrite: (p) => p.replace(/^\/api/, '')
          }
        }
      },
    }
  })
8、完整案例之package.json
  {
    "name": "online-class-web",
    "version": "0.0.1",
    "description": "在线课堂",
    "author": "maxiaolin",
    "license": "MIT",
    "scripts": {
      "dev": "vite", //启动开发服务器
      "build:prod": "vite build", //打包
      "build:stage": "vite build --mode staging",
      "preview": "vite preview" //预览
    },
    "type": "module",//此项配置,并非来自真实项目
    "repository": {
      "type": "git",
      "url": "http://10.71.59.14:30190/media/online-class-web.git"
    },
    "dependencies": {
      "@element-plus/hooks": "^0.0.5",
      "@element-plus/icons-vue": "^2.0.10",
      "@element-plus/utils": "^0.0.5",
      "@vueuse/core": "9.5.0",
      "axios": "0.27.2",
      "element-plus": "2.2.27",
      "file-saver": "2.0.5",
      "fuse.js": "6.6.2",
      "js-cookie": "3.0.1",
      "jsencrypt": "3.3.1",
      "mockjs": "^1.1.0",
      "nprogress": "0.2.0",
      "pinia": "2.0.22",
      "video.js": "^7.20.3",
      "videojs-contrib-hls": "^5.15.0",
      "vite-plugin-mock": "^3.0.0",
      "vue": "3.2.45",
      "vue-cropper": "1.0.3",
      "vue-router": "4.1.4"
    },
    "devDependencies": {
      "@vitejs/plugin-vue": "3.1.0",
      "@vue/compiler-sfc": "3.2.45",
      "sass": "1.56.1",
      "unplugin-auto-import": "0.11.4",
      "vite": "3.2.3",
      "vite-plugin-compression": "0.5.1",
      "vite-plugin-svg-icons": "^2.0.1",
      "vite-plugin-vue-setup-extend": "0.4.0"
    }
  }  
  
十五、Rollup,JS模块打包工具
1、Rollup的功能
  (1)Tree-Shaking,将没用到的代码剔除
  (2)兼容CommonJS模块
  (3)兼容ES模块
2、配置文件
  (1)配置文件示例,rollup.config.js
    import json from“ rollup - plugin - json”
    import resolve from“ rollup - plugin - node - resolve”
    import commonjs from‘ rollup - plugin - commonjs’
    export default {
      //input:”src/index.js”,  //指定入口文件路径
      //多入口打包
      //input:[‘src/index.js’,’src/album.js’],
      input: {
        foo: ’src / index.js’,
        bar: ’src / ablum.js’
      },
      output: {
        //file:”dist/bundle.js”, //指定输出的文件名
        //format:”iife”,  //指定输出的格式
        //下述是代码拆分需要使用的模式
        dir: ’dist’,
        format: ’amd’
      },
      plugins: [
        json(),
        resolve(), //rollup不能让node支持ESM规范,rollup通过resolve,让node支持ESM规范;webpack能让node支持ESM规范
        commonjs(), 
        babel({ babelHelpers: 'bundled' })
      ]
    }
  (2)运行配置文件,
    rollup,按照以下顺序运行配置文件:rollup.config.mjs -> rollup.config.cjs -> rollup.config.js
    rollup --config my.config.js,运行配置文件:my.config.js
3、API,扩展Rollup本身或者进行一些高级操作
  (1)rollup.rollup,参数为输入选项对象,返回一个Promise,该Promise解析为具有各种属性和方法的bundle对象
  (2)rollup.watch,当它检测到磁盘上某个模块已经改变,它会重新构建bundle(捆)
4、其他,在模块化方面,browserify和node都用commonjs规范(require导入,module.exports导出),ES6用ESM规范(import导入,exports导出)

十六、各框架各工具各版本的发行时间
 附、软件发布前的三个步骤
  (1)内测,Alpha(α,阿尔法)
  (2)公测,Beta(β,贝塔)
  (3)正式发布,Gamma(γ,伽玛)
 附、JS版本
  (1)1995年,发布JavaScript语言;美国人布兰登·艾克创造
  (2)1997年,发布ECMAscript1
  (3)1999年,发布ECMAscript3
  (4)2009年,发布ECMAscript5
    2012年,发布TypeScript
  (5)2015年,发布ECMAscript6
 附、JS其它框架 
  (1)SvelteJS,2016年创建,解决传统前端框架在运行时性能上的瓶颈 // /svelt/苗条的,纤细的;https://www.svelte.cn/
  (2)SolidJS,2018年创建,制作交互式Web应用程序的JS框架 // /ˈsɒlid/坚固的,实心的;https://www.solidjs.cn/
     A、3个核心API,createSignal、createMemo、createEffect,以createEffect为例,实现逐词插入数据,类似于发报效果
      import { createEffect, onMount } from 'solid-js';
      import { Marked } from '@ts-stack/markdown';
      type Props = {
        message: MessageType;
        chatflowid: string;
      };
      Marked.setOptions({ isNoP: true });
      export const BotBubble = (props: Props) => {
        let botMessageEle: HTMLDivElement | undefined;
        onMount(() => { });
        var i = 0;
        createEffect(() => {
          if (botMessageEle) {
            let before = props.message.message;
            let reg = /"answer": "(.+)",/g; 
            let after = [];
            if(!reg.test(before)){
              after.push(before) 
            }else{
              before.replace(reg,function(regAll,A1){
                after.push(A1) 
              });
            }
            const interval = setInterval(() => {
              botMessageEle.innerHTML = Marked.parse(after[i]);
              i++;
              if(i>after.length-2)clearInterval(interval)
            },100);
          }
        });
        return (
          <div class="flex flex-col justify-start">
            {props.message.message && (
              <span
                ref={botMessageEle}
              />
            )}
          </div>
        );
      };
     B、3个生命周期,onMount(可以在此处向后台请求数据)、onCleanup、onError
     C、PascalCase,帕斯卡命名法,将变量的所有单词的首字母大写
     D、splitProps,属性的使用
      //以下案例来源,chat-embed项目下ShortTextInput.tsx文件
      //Omit<Type, Keys>TypeScript创建新类型,从现有类型(Type)中排除指定属性(Keys)
      import { splitProps } from 'solid-js';
      import { JSX } from 'solid-js/jsx-runtime';
      type ShortTextInputProps = {
        ref: HTMLInputElement | HTMLTextAreaElement | undefined;
        onInput: (value: string) => void;
        fontSize?: number;
        disabled?: boolean;
      } & Omit<JSX.TextareaHTMLAttributes<HTMLTextAreaElement>, 'onInput'>;
      export const ShortTextInput = (props: ShortTextInputProps) => {
        const [local, others] = splitProps(props, ['ref', 'onInput']);
        const handleInput = (e) => {
          if (props.ref) {
            e.currentTarget.scrollTo(0, e.currentTarget.scrollHeight);
            local.onInput(e.currentTarget.value);
          }
        };
        return (
          <textarea
            ref={props.ref}
            onInput={handleInput}
            {...others}
          />
        );
      };
      //以下案例来源,chat-embed项目下TextInput.tsx文件
      import { ShortTextInput } from './ShortTextInput';
      export const TextInput = (props: Props) => {
        let inputRef: HTMLInputElement | HTMLTextAreaElement | undefined;
        return (
          <ShortTextInput
            ref={inputRef as HTMLTextAreaElement}
            onInput={handleInput}
          />
        );
      };
1、angular版本,Google发行
  (1)AngularJS,首版,2009年
  (2)AngularJS,1.0版,2010年,
  (3)Angular,2.0版,2016年09月
  (4)Angular,4.0版,2017年03月
  (5)Angular,5.0版,2017年11月
  (6)Angular,6.0版,2018年05月
  (7)Angular,7.0版,2018年10月
  (8)Angular,8.0版,2019年05月
  (9)Angular,9.0版,2020年02月
  (10)Angular,10.0版,2020年06月
  (11)Angular,11.0版,2020年11月
  (12)Angular,12.0版,2021年05月
  (13)Angular,13.0版,2021年12月
  (14)Angular,14.0版,2022年06月
  (15)Angular,15.0版,2022年11月
  (16)Angular,16.0版,2023年05月
2、vue版本
 来源,https://github.com/vuejs/core/blob/main/CHANGELOG.md
  (1)vue,1.0.0版,2015年10月27日
  (2)vue,2.0.0版,2016年10月01日
  (3)vue,3.0.0版,2020年01月04日,预发布
    A、vue,3.0.0-beta.20,2020年07月08日,此前setup函数作为组件的一个配置项
    B、vue,3.0.0-beta.21,2020年07月14日,此后增加了 <script setup> 的实验特性
  (4)vue,3.0.0版,2020年09月18日,正式发布
  (5)vue,3.1.0版,2021年06月07日
  (6)vue,3.2.0版,2021年08月09日,<script setup> 正式使用
  (7)vue,3.3.0版,2023年05月11日
  (8)vue,3.4.0版,2023年12月29日
 附、vue-cli版本
  (1)vue-cli,3.0.0版,2018年08月10日
  (2)vue-cli,4.0.0版,2019年10月16日
  (3)vue-cli,4.5.0版,2020年07月24日,开始默认使用vue3
  (4)vue-cli,5.0.0版,2022年02月17日
 附、uni-app版本
  (1)2018年8月,uni-app1.0.0 版本发布,
  (2)2021年9月,uni-app2.0.0 版本发布,
  (3)2023年1月,uni-app3.0.0 版本发布,
3、react版本,FaceBook发行
   附、2016年10月,发布Next.js,它是React的框架
    来源,https://github.com/facebook/react/releases
    react中国,https://react.docschina.org
    所有版本简介,https://github.com/facebook/react/blob/main/CHANGELOG.md#1702-march-22-2021
    reactdom版本,https://cdn.bootcdn.net/ajax/libs/react-dom/16.6.0/cjs/react-dom.development.js
  (1)React,0.3.0版,2013年05月29日
  (2)React,0.14.8版,2016年03月29日
  (3)React,15.0.0版,2016年04月07日
    A、15.1.0版,出现错误边界,error boundaries  
    B、15.2.3版,出现纯函数组件,PureComponent
  (4)React,16.0.0版,2017年09月26日,
    A、新增componentDidCatch(--记录错误--)
    B、新增纤维fiber架构,
    C、弃用旧虚拟DOM,
    D、解决了递归调用无法中断和卡顿掉帧的问题
  (5)React,16.3.0版,2018年03月29日,
    A、沿用旧生命周期componentWillMount,componentWillReceiveProps,componentWillUpdate,
    B、新增新生命周期getDerivedStateFromProps,getSnapshotBeforeUpdate,
    C、沿用方案、新增方案只能二选一,
    D、组件自身state更新,shouldComponentUpdate()>render()>getSnapshotBeforeUpdate()>componentDidUpdate()
    E、传递过来的props更新,getDerivedStateFromProps()>shouldComponentUpdate()>render()>getSnapshotBeforeUpdate()>componentDidUpdate()
  (6)React,16.4.0版,2018年06月24日,
    A、新增Suspense(--组件--)
  (7)React,16.6.0版,2018年10月23日,
    A、新增getDerivedStateFromError(--处理错误--)
    B、新增React.memo()
    C、新增React.lazy()
  (8)React,16.8.0版,2019年02月06日,见本页-Hooks详解-
    A、新增钩子函数Hooks,可以
    B、避免组件继承React实例
    C、实现状态管理
    D、弃用生命周期
  (9)React,17.0.0版,2020年10月20日
    A、并没有添加任何面向开发人员的新特性
  (10)React,18.0.0版,2022年03月29日,
    A、新增useTransition(--处理过渡--)
  (11)React,18.3.1版,2024年04月26日,最新版本  
4、node版本(内含npm)
  来源,https://nodejs.org/zh-cn/download/releases/
  来源,https://nodejs.org/en/blog/release/page/1
  来源,https://pnpm.io/zh/motivation
  (1)版本发布
    A、2009年,node出现
    B、2010年,npm出现(Node Package Manager,node包管理器)
    C、2011年,Windows版node出现
    D、2012年,原作者离开
    E、2015年,node发布4.0.0版、5.0.0版,内含npm的2.14.2版、3.3.6版
    F、2016年,node发布6.0.0版、7.0.0版,内含npm的3.8.6版、3.10.8版
    G、2017年,node发布8.0.0版、9.0.0版,内含npm的5.0.0版、5.5.1版
    H、2018年,node发布10.0.0版、11.0.0版,内含npm的5.6.0版、6.4.1版
    I、2019年,node发布12.0.0版、13.0.0版,内含npm的6.9.0版、6.12.0版
      node13.2.0开始支持ES6模块,此后无需用vue-loader把.vue文件转化为ES6模块
    J、2020年,node发布14.0.0版、15.0.0版,内含npm的6.14.4版、7.0.2版
    K、2021年,node发布16.0.0版、17.0.0版,内含npm的7.10.0版、8.1.0版
    L、2022年,node发布18.0.0版、19.0.0版,内含npm的8.6.0版、8.19.2版 
    M、2023年,node发布20.0.0版、21.0.0版,内含npm的9.6.4版、10.2.0版 
    N、2024年,node发布22.0.0版、2x.0.0版,内含npm的10.5.1版、1x.x.x版 
  (2)普通多版本node,安装、关联、切换、查看方法:
    A、安装:
      a、下载各版本的.zip,分别解压到“C:\Program Files\node10”下
      b、下载各版本的.exe,逐个先安装至“D:\”下,再粘贴到“C:\Program Files\node10”下
    B、关联:
      a、前往,桌面>右键计算机>属性>高级系统设置>环境变量;双击(系统变量的)Path></script>
      b、新建-输入“C:\Program Files\node10”-确定,重复此步骤...>
      c、确定>确定
    C、切换:
      a、前往,桌面>右键计算机>属性>高级系统设置>环境变量;双击(系统变量的)Path>
      b、点击“C:\Program Files\node10”-点击上移或下移
    D、查看版本:
      a、不能在切换前开启的命令框,
      b、只能在切换后开启的命令框->输入“node -v”->回车,
  (3)nvm多版本node,安装、切换、查看方法:
    A、下载与安装
      a、下载,https://gitcode.com/gh_mirrors/nv/nvm-windows/releases
      b、安装,C:\Users\Haier\AppData\Roaming\nvm
    B、设置镜像
      a、nvm node_mirror https://npmmirror.com/mirrors/node/
      b、nvm npm_mirror https://npmmirror.com/mirrors/npm/
    C、常见命令
      a、nvm -v,查看nvm版本号;node -v,查看当前node版本号
      b、nvm list available,能安装的版本(不完整)
      c、nvm install 16.20.0,安装版本-node@16.20.0
      d、nvm use 16.20.0,使用版本-node@16.20.0
      e、nvm ls(list),已安装版本列表,前面带*号的为在用版本 
    D、上面命令,在vscode编辑器里运行更有效,也可以“以管理员身份运行”
      a、点击任务栏上的放大镜,右键“命令提示符”,点击“以管理员身份运行” 
      b、点击任务栏上的放大镜,输入cmd,点击“以管理员身份运行”
5、webpack版本
  (1)Webpack1,2013
  (2)Webpack2,2016,支持ES Module,支持Tree-Shaking,将没用到的代码剔除
  (3)Webpack3,2017.06.20,Scope Hoisting(作用域提升)和Magic Comment(魔法注释)等
  (4)Webpack4,2018.02.25,mode属性,WebAssembly(字节码格式),支持多种模块类型,0配置等
  (5)Webpack5,2020.10.10,优化缓存,改善Tree-Shaking等等
 附、webpack及类似(JS打包工具,构建工具,自动化工具)
  (1)Webpack,2013年首发的前端构建工具
  (2)Gulp,2015年首发的前端构建工具
  (3)vite,2019年首发的前端构建工具
6、国外AI
  (1)codeium,https://codeium.com/playground
  (2)codegeex,https://codegeex.cn/zh-CN/playground
  (3)chatGPT,http://chat.178le.net/index
    A、2022年11月30日,美国OpenAI发布的聊天机器人程序
    B、全名:Chat Generative Pre-trained Transformer
    C、汉译:人工智能技术驱动的自然语言处理工具
    D、直译:聊天生成的、经过训练的、改革者
    E、Copilot Hub:基于ChatGPT创建个人的知识库AI,app.copilothub.co
7、国内AI    
  (1)豆包,https://www.doubao.com/chat 
    A、2023年8月,字节跳动的人工智能机器人“豆包”开始测试
    B、2024年5月,豆包App总下载量已达1亿次,价格相比同行便宜99.3%
    C、2024年8月,豆包上线音乐生成功能
    D、支持网页Web平台,iOS平台,安卓平台
    E、豆包的功能
      a、聊天机器人
      b、写作助手
      c、学习助手
      d、回答各种问题
  (2)文心一言,https://yiyan.baidu.com/
  (3)智谱清言,https://chatglm.cn/main/alltoolsdetail
  (4)Kimi,https://kimi.moonshot.cn/
  (5)通义千问,https://qianwen.aliyun.com/
  (6)讯飞星火,https://xinghuo.xfyun.cn/desk
  (7)腾讯元宝,https://yuanbao.tencent.com/chat/naQivTmsDa
  (8)百小应,https://ying.baichuan-ai.com/chat

十七、神策(本文档基于1.26.5版本)
  注意,先做项目(了解源码的功能),再看源码(了解功能的实现)
  作用,分析用户的行为
  来源,https://manual.sensorsdata.cn/sa/latest/zh_cn/tech_sdk_client_web_use-7545024.html
1、集成文档
  (1)自动生成
  (2)同步载入
  (3)CommonJS规范加载
  (4)ES6模块化引入
  (5)AMD规范加载
2、基础api介绍(属性-注册、获取、设置、上报)
  (1)SDK初始化参数,16项
  (2)注册公共属性
    // 以下项目旧代码备份
    // var i=0
    // sensors.init({
    //   server_url: server_url, //神策数据接收地址
    //   //web_url: web_url, //神策分析后台地址,神策1.10及以上版本,不需要配置这个参数,
    //   is_track_single_page: true, //单页面配置,默认开启,若页面中有锚点设计,需要将该配置删除,否则触发锚点会多触发 $pageview 事件
    //   use_client_time: true, 
    //   send_type: 'beacon',
    //   // heatmap: {
    //   //   clickmap: 'default', //(1/3)开启点击图,自动采集a input button textarea 四种元素的$WebClick事件,'not_collect'表示关闭
    //   //   scroll_notice_map: 'default', //(2/3)开启触达图,自动采集$WebStay事件,'not_collect'表示关闭
    //   // },
    //   show_log: true, //console会打印采集信息
    //   // preset_properties: { //子配置项 true 表示采集,false 表示不采集,未设置的参数取默认值
    //   //   latest_traffic_source_type: false, //是否采集 $latest_traffic_source_type 最近一次流量来源类型
    //   //   latest_search_keyword: false, //是否采集 $latest_search_keyword 最近一次搜索引擎关键字
    //   //   latest_referrer: false, //是否采集 $latest_referrer 最近一次前向地址
    //   // },
    // });
    // sensors.registerPage({
    //   current_url: location.href,
    //   referrer: document.referrer,
    //   description1: 'server_url字段为空或非神策地址,在开发者的控制台,依然可以看',
    //   description2: '到神策插件采集的数据,只是神策服务器接收不到该数据,没法分析!',
    //   prop_number_: function() {
    //     return ++i;
    //   },
    // });
    // sensors.quick('autoTrack'); //(3/3)用于采集$pageview事件
  (3)获取预置属性
    sensors.quick('isReady',function(){
      var presetProperties = sensors.getPresetProperties();
      console.log( presetProperties );
    });
  (4)设置用户属性
    A、直接设置用户的属性,如果存在则覆盖。
      sensors.setProfile({email:'xxx@xx'});
    B、如果不存在则设置,存在就不设置。
      sensors.setOnceProfile({email:'xxx@xx'});
    C、给数组属性添加值
      sensors.appendProfile({catrgory: ['玉米','白菜']});
      sensors.appendProfile({catrgory: '玉米'});//给 category 增加一个值
    D、对当前用户的属性做递增或者递减
      sensors.incrementProfile({'navClick': -1});//表示navClick递减
    E、删除当前用户及他的所有属性
      sensors.deleteProfile();
    F、删除当前用户的一些属性
      sensors.unsetProfile(['email','location']);
      sensors.unsetProfile('email');
  (5)物品元数据上报
    A、直接设置一个物品,如果已存在则覆盖
      sensors.setItem("food","2",{name:"玉米",flavour:"甜"});
      setItem(item_type,item_id,properties)
        除物品ID与物品所属类型外,其他物品属性需在properties中定义
        物品属性中,属性名称与属性值的约束条件与事件属性相同
        item_type:必选
        item_id:必选
        properties:可选
    B、sensors.deleteItem("food","2");
      deleteItem(item_type,item_id)
        如果物品不可被推荐需要下线,删除该物品即可,如不存在则忽略
        除物品ID与物品所属类型外,不解析其他物品属性
        item_type:必选
        item_id:必选
3、全埋点(标签埋点、属性埋点)
  附1、heatmap相关参数,https://manual.sensorsdata.cn/sa/latest/zh_cn/tech_sdk_client_web_all_use-7545310.html
  附2、server_url获取步骤,登录神策-基本设置-数据接入-(客户端埋点)生成导入代码-生成-server_url
  附3、示例,全埋点三个事件(元素点击$WebClick、视区停留$WebStay、页面浏览$pageview),其中$WebClick默认采集4种元素(a input button textarea)
    <script>
      //var sensors = window['sensorsDataAnalytic201505'];
      import sensors from 'sa-sdk-javascript';
      var server_url = 'https://10.50.16.15/api';
      var web_url = 'https://10.50.16.15/api';
      var i=0;
      sensors.init({
        server_url: server_url,// 数据接收地址
        web_url: web_url,// 神策分析后台地址,神策1.10及以上版本,不需要配置这个参数,
        is_track_single_page: true, // 单页面配置,默认开启,若页面中有锚点设计,需要将该配置删除,否则触发锚点会多触发 $pageview 事件
        use_client_time: true,
        send_type: 'beacon',
        heatmap: {
          clickmap:'default',//(1/3)开启点击图,$WebClick事件默认采集4种元素,'not_collect'表示关闭
          scroll_notice_map:'default',//(2/3)开启视区停留(触达图),自动采集$WebStay事件,'not_collect'表示关闭
          loadTimeout: 3000,
          collect_url: function(){
            //如果只采集首页
            if(location.href === 'example.com/index.html' || location.href === 'example.com/'){
              return true;
            }
          },
          //此参数针对预置 $WebClick 事件(包括 quick('trackHeatMap') quick('trackAllHeatMap') 触发的)生效。
          collect_element: function(element_target){
            // 如果这个元素有属性sensors-disable=true时候,不采集。
            if(element_target.getAttribute('sensors-disable') === 'true'){
              return false;
            }else{
              return true;
            }
          },
          //此参数针对预置 $WebClick 事件(包括 quick('trackHeatMap') quick('trackAllHeatMap') 触发的)生效。
          custom_property: function( element_target ){
            //比如您需要给有 data=test 属性的标签的点击事件增加自定义属性 name:'aa' ,则代码如下:
            if(element_target.getAttribute('data') === 'test'){
              return {
                name:'aa'
              }
            }
          },
          collect_input: function(element_target){
            if(element_target.id === 'a'){ //如果元素的 id 是a,就采集这个元素里的内容。
              return true;
            }
          },
          element_selector: 'not_use_id',
          renderRefreshTime: 1000
        },
        scrollmap: {
          collect_url: function(){//如果只采集首页 
            if(location.href === 'example.com/index.html' || location.href === 'example.com/'){
              return true;
            }
          },
        },
        show_log: true, // console会打印采集信息
      });
      sensors.quick('autoTrack'); //(3/3)自动采集$pageview事件
      sensors.quick('autoTrack', { //添加额外的属性
        platform: 'h5'
      })
    </script>
  (1)扩展至div元素,采集规则为
    A、div为叶子结点(无子元素)时采集div的点击
    B、div中有且只有样式标签(['mark','strong','b','em','i','u','abbr','ins','del','s','sup'])时,点击div或者样式标签都采集div的点击
      heatmap:{ 
        collect_tags:{
          div: true
        }
      }
  (2)扩展至任意元素,示例
    heatmap: {
      clickmap:'default',
      collect_tags: {
        div: { //div通过配置最多可以采集3层嵌套
          max_level: 1 //默认是1,即只支持叶子div。可配置范围是[1, 2, 3],非该范围配置值,会被当作1处理。
        },
        get_vtrack_config: true, //无限层级的div
        li: true,
        img: true
        //...其他标签
      }
    }
  (3)扩展至特殊属性,示例
    <div data-sensors-click>我是测试元素</div>
    <li data-sensors-click>我是测试元素</li>
  (4)扩展至自定义属性,示例
    heatmap: {
      clickmap:'default',
      track_attr: ['prop1', 'prop2', "prop3"],
    }
    <p prop1>prop1</p>
    <p><span prop2>prop2</span></p>
    <p><strong prop3>prop3</strong></p>
  (5)代码埋点(这是局部埋点)
    示例1,jQuery
      <div id="submit_order">提交订单</div>
      <script type="text/javascript">
        $('#submit_order').on('click', function() { //代码埋点
          sensors.quick('trackHeatMap', this, { //触发元素点击事件
            customProp1: 'test1', //如果需要添加自定义属性需要将SDK升级到 1.13.7 及以上版本。
            customProp2: 'test2'
          });
        });
      </script>
    示例2,vue
      <div v-on:click="track">点击</div>
      <script>
        export default {
          methods: {
            track: function(event) {//代码埋点
              sensors.quick('trackHeatMap', event.target, {//触发元素点击事件
                customProp1: 'test1', //如果需要添加自定义属性需要将SDK升级到 1.13.7 及以上版本。
                customProp2: 'test2'
              });
            }
          }
        }
      </script>
4、高级功能
    附、高级功能清单
      A、属性插件化,
      B、批量发送,
      C、预置属性是否采集,
      D、关闭页面时发送数据丢失的解决方案,
      E、单页面中事件的自动采集
  (1)属性插件化,
    作用,给指定的事件添加、修改或删除属性,
    实现,registerPropertyPlugin,包含properties和isMatchedWithFilter
      如果不配置后者,配置的 properties 方法始终执行
      如果配置了后者,仅当该方法返回 true 时,配置的 properties 方法才会得到执行
    示例如下
      A、直接修改
        sensors.registerPropertyPlugin({ //对所有类型数据,修改属性值,这种用法会引发意想不到的情况
          properties: function(data){
            data.properties['aaa'] = 'bbb';
          }
        });
        sensors.registerPropertyPlugin({ //删除所有事件中的 platform 属性
          properties: function(data){
            delete data.properties['platform'];
          }
        });
        sensors.registerPropertyPlugin({ //直接在properties里进行筛选和属性修改
          properties: function(data){
            if(data.event === '$pageview'){
              data.properties['$url'] = 'http://xxxx';
            }
          }
        });
      B、先筛选后修改
        sensors.registerPropertyPlugin({ //修改事件名为 $pageview 下的 $url 属性
          isMatchedWithFilter: function(data){
            return data.event === "$pageview";
          }
          properties: function(data){
            data.properties['$url'] = 'http://xxx';
          }
        });
        sensors.registerPropertyPlugin({
          isMatchedWithFilter: function(data){
            return data.type.slice(0, 4) === 'item' ||  data.type.slice(0, 7) === 'profile';
          }
          properties: function(data){
            delete data.properties['aaa'] = 'bbb';
          }
        });
  (2)批量发送
    示例如下
      sensors.init({
        batch_send:true,//开启批量发送
        batch_send:{//或者
          datasend_timeout: 6000, //一次请求超过多少毫秒的话自动取消,防止请求无响应。
          send_interval: 6000, //间隔多少毫秒发一次数据。
          storage_length: 200 //存储localStorage条数最大值,默认:200。如localStorage条数超过该值,则使用image方式立即发送数据。v1.24.8 以上支持。
        },
      });
    写入策略
      触发事件就写入localStorage
    发送策略
      定时触发发送
      遇到$pageview、使用quick('autoTrack')、使用$SignUp,立即存储并且发送
    重复策略
      必须请求success后,才会删除数据,不然会一直请求,直到数据满一定数量
  (3)预置属性是否采集
    sensors.init({
      preset_properties: { //子配置项 true 表示采集,false 表示不采集,未设置的参数取默认值
        latest_utm: true, //是否采集 $latest_utm 最近一次广告系列相关参数
        latest_traffic_source_type: true, //是否采集 $latest_traffic_source_type 最近一次流量来源类型
        latest_search_keyword: true, //是否采集 $latest_search_keyword 最近一次搜索引擎关键字
        latest_referrer: true, //是否采集 $latest_referrer 最近一次前向地址
        latest_referrer_host: false, //是否采集 $latest_referrer_host 最近一次前向地址,默认值先true后false
        latest_landing_page: false, //是否采集 $latest_landing_page 最近一次落地页地址,默认值false。
        url: true, //是否采集 $url 页面地址作为公共属性,默认值先false后true
        title: true, //是否采集 $title 页面标题作为公共属性,默认值先false后true
      },
      source_channel:'',//默认获取的来源是根据utm_source等ga标准来的
      //utm_source,指定流量的来源,例如搜索引擎、社交媒体网站等
      //utm,是Urchin(小脏孩,免费的分析工具) Tracking Module 的简称
    });
  (4)关闭页面时发送数据丢失的解决方案
    A、改为服务端发送事件
      在服务端中埋点采集
    B、采集跳转后页面的$pageview事件
      给A页面的a标签的href属性添加某个参数,如B页面www.xxx.com?urlfrom=123,
      跳转到B页面后,采集这个页面的$pageview事件,在神策后台中查看Web浏览事件,根据url是否包含urlfrom参数来筛选结果
    C、使用setTimeout
      // 点击链接执行
      function targetLinkIcon( url ){
        setTimeout(function(){ //延迟跳转页面,给 SDK 发送数据提供时间
          window.location.href = url; 
        },500);
        sensors.track('demo',{}); //神策自定义事件的方法
      }
    D、beacon的方式发送数据
      配置
        sensors.init({
          send_type: 'beacon',
        });
      原理说明
        来源,https://blog.csdn.net/Ed7zgeE9X/article/details/131545832
        Beacon API是HTML5提供的新型浏览器API,可以在不影响当前页面加载和性能的情况下,在浏览器后台异步发送数据
        借助Beacon API,开发人员可以在页面卸载或关闭时向服务器发送数据,从而实现一些监控和日志记录功能 
        //以下前端代码
          var data = "Hello, Beacon API!";
          var url = "https://example.com/endpoint";
          navigator.sendBeacon(url, data); //导航员.发送信标
        //以下后端代码
          app.post('/endpoint', function(req, res) {
            var data = req.body;
            // 处理接收到的数据
            // ...
            res.sendStatus(200); // 返回响应状态码
          });
  (5)单页面中事件的自动采集
    A、自动模式
      sensors.init({
        is_track_single_page: true,
        is_track_single_page: function (){
          return true 时候,使用默认发送的 $pageview
          return false 时候,不执行默认的 $pageview
          return {} 时候,把对象中的属性,覆盖$pageview里的默认属性
        }
      });
    C、手动模式
      在 react 中可以在全局的 onUpdate 里来调用
        onUpdate: function(){
          sensors.quick('autoTrackSinglePage');
        }
      vue 项目在路由切换的时候调用
        router.afterEach((to,from) => {
          Vue.nextTick(() => {
            sensors.quick("autoTrackSinglePage");
          });
        });
        //注意:vue下因为首页打开时候就会默认触发页面更新,所以需要去掉默认加的 sa.quick('autoTrack')。
        //此方法也可添加自定义属性,sensors.quick("autoTrackSinglePage",{platForm:"H5"});
5、安全合规
  (1)安全,
    本地存储加密,不支持埋点数据加密
      sensors.init({
        encrypt_cookie: true //开启cookie加密配置,默认false
      });
  (2)合规,
    延迟初始化
      if(同意隐私条款){
        sensors.init({});
        sensors.quick('autoTrack'); 
      }
    动态开启/关闭采集,必须在init后调用
      sensors.init({});
      sensors.disableSDK()// 禁用API执行
      sensors.enableSDK()// 恢复API执行
6、用户关联
  (1)简易用户关联,
    方法:sensors.login("登录 ID");
    时机:
      A、用户注册成功时
      B、用户登录成功时
      C、已登录用户每次启动App时
    获取匿名ID
      sensors.quick('isReady',function(){
        var anonymousID = sensors.quick('getAnonymousID');
      });
    修改匿名ID
      sensors.identify(id, true): 会把这个id保存在浏览器的cookie中,该域名下的页面都会默认使用这个id
  (2)全域用户关联
    方法
      sensors.login("登录 ID");
    时机
      A、用户注册成功时
      B、用户登录成功时
    多用户ID关联,
      sensors.bind("$identity_mobile","187****8991"),后续采集的事件,均包含缓存的ID信息
      第1个参数从详细的预置id key列表中获取,https://manual.sensorsdata.cn/sa/latest/zh_cn/tech_sdk_client_web_idm3-109576372.html
      第2个参数为对应的关联用户ID
    多用户ID取消关联,
      sensors.unbind("$identity_mobile","187****8991")
    重置匿名ID,
      sensors.resetAnonymousIdentity();
      sensors.resetAnonymousIdentity('id-xxxxxxx-xxxxx'); // 修改为指定的匿名ID
    获取全域用户的ID,
      sensors.getPresetProperties()
      sensors.quick('isReady',function(){
        sensors.getPresetProperties()
      });
7、插件集成
  (1)使用内置插件,
    内置插件
      Amp、SensorsChannel、Deeplink、PageLeave、PageLoad、RegisterPropertyPageHeight、SiteLinke
    使用
      sensors.use('PageLeave', option);
      sensors.init({
        ...初始化参数
      })
  (2)使用外置插件,
    外置插件
      AesEncryption、Exposure、SessionEvent、SiteLinkerConcatUtm、WechatWebViewChannel、SensorsABTest、Popup(H5版)WebPopup(Web版)、GeneralEncryption、CustomEventsSender、SfInstantEvent、ChannelUtm、SMEncryption
    使用
      import sessionEvent from '/dist/web/plugin/session-event/index.es6.js';
      sensors.use(sessionEvent);
      sensors.init({
        ...初始化参数
      })
8、多域名打通
  (1)示例
    sensors.init({});
    sensors.use('SiteLinker',
      {
        linker: [ // 在神策分析环境中实现用户统一,从而实现跨域打通
          { part_url: 'sensorsdata.cn', after_hash: false },
          { part_url: 'example.com', after_hash: false }
        ],
        re_login: true, //如果从已登录的页面跳转过来,即使当前网页已经登录,当前网页仍然会以之前网页的登录id再次登录
      }
    );
    sensors.quick('autoTrack');
  (2)打通结果
  (3)原理说明
    A、浏览器中的Cookie包含用户id和域名
    B、多域名间共享用户id
    C、只有a标签跳转可以实现跨域打通
  (4)实施方案
    A、linker参数的part_url属性配置‘需要打通的网域’
    B、神策检查本网域中的a标签的链接,如果链接包含part_url,将从Cookie中提取本网域下的Distinct ID拼接到链接上
    C、跳转到目标网域后,目标网域截取网址中的链接器_sasdk参数,用其替换自身的Distinct ID
    D、反过来也一样
9、渠道追踪与广告(以下数据自动生成)
  注意,从搜索引擎的搜索结果向目标网页跳转时,如果搜索引擎不携带相关参数,会导致下列数据的部分字段为空
  (1)用户相关属性
  (2)事件相关属性
  (3)流量来源类型
  (4)搜索引擎关键词
  (5)场景示例
  (6)常见问题
10、曝光采集,
  包含名称、配置、属性
  (1)初始化与注销
    初始化
      import Exposure from '/dist/web/plugin/exposure/index.es6';
      var exposure = sensors.use(Exposure, {
        area_rate: 0,
        stay_duration: 2,
        repeated: true
      });
    注销
      exposure.removeExposureView(document.getElementById('exposure_ele'))
  (2)注册曝光元素
    方案1
      <div
        data-sensors-exposure-event-name="home_top_banner"
        data-sensors-exposure-config-area_rate="1" 
        data-sensors-exposure-config-stay_duration="2"
        data-sensors-exposure-config-repeated="true"
        data-sensors-exposure-property-propA="valueA"
        data-sensors-exposure-property-propB="valueB"
      ></div>
    方案2
      <div id="exposure_div"></div>
      var exposureDiv = document.getElementById("exposure_div");
      exposureDiv.setAttribute("data-sensors-exposure-event-name", "exposure_ele"); //1、名称
      exposureDiv.setAttribute( //设置曝光元素配置及自定义属性
        "data-sensors-exposure-option",
        JSON.stringify({
          config: { //2、配置
            area_rate: 0.5,
            stay_duration: 0,
            repeated: true
          },
          properties: { //3、属性
            d: "abc",
            e: false
          },
          listener: { //4、曝光的回调
            shouldExpose: function (ele, properties) { //是否触发曝光事件
              // ele 为当前曝光的元素
              // properties 为曝光元素的元素信息及该元素的曝光自定义属性
              // 触发曝光事件则返回 true
              // 不触发曝光事件则返回 false
              return true;
            },
            didExpose: function (ele, properties) { //已触发曝光回调
              // ele 为当前曝光的元素
              // properties 为曝光元素的元素信息及该元素的曝光自定义属性
            }
          }
        })
      );
11、谷歌AMP
  (1)说明,AMP提供了可分析用户行为数据的<amp-analytics>元素,通过该元素实现对神策埋点数据的采集
  (2)集成,在head中引入拓展组件<amp-analytics>的脚本
    <script async custom-element="amp-analytics" src="https://cdn.ampproject.org/v0/amp-analytics-0.1.js"></script>
  (3)采集,在body中引入<amp-analytics>标签,标签type="sensorsanalytics",通过编辑标签内部的json配置,来进行行为数据采集
    <amp-analytics type="sensorsanalytics" id="sensorsanalytics1">
      <script type="application/json">
        "requests": {
          "event": "https://jssdkdata.debugbox.sensorsdata.cn/sa?project=liangshuang" //数据接收地址
        },
        "triggers": { //下面的key相当于上面的$WebClick、$WebStay、$pageview
          "trackPageview": { //页面浏览事件采集
            "on": "visible",
            "request": "event",
            "vars":{
              "event":"amp_pageview", //自定义事件名称
              "amp_properties":"%7B%22platform%22%3A%22amp%22%2C%22color%22%3A%22red%22%7D" //自定义属性
            }
          },
          "id_test1": { //自定义事件采集
            "on": "click",
            "selector": "#test1", //点击事件需要监听的元素选择器
            "request": "event",
            "vars":{
              "event":"click_test1", //自定义事件名称
              "amp_properties":"%7B%22platform%22%3A%22amp%22%2C%22color%22%3A%22red%22%7D" //自定义属性
            }
          },
          "class_test2": { //自定义事件采集
            "on": "click",
            "selector": ".test2", //点击事件需要监听的元素选择器
            "request": "event",
            "vars":{
              "event":"click_test2", //自定义事件名称
              "amp_properties":"%7B%22platform%22%3A%22amp%22%2C%22color%22%3A%22red%22%7D" //自定义属性
            }
          }
        }
      </script>
    </amp-analytics>
  (4)amp_properties值的生成步骤
    var prop = {
      platform:'amp',
      color:'red'
    }
    var amp_properties = encodeURIComponent(JSON.stringify(prop))
  (5)AMP网页的三种访问方式
    AMP查看器访问
    代理/缓存访问
    普通浏览器直接访问
  (6)AMP页面和非AMP页面的用户统一
    sensors.init({});
    sensors.use('Amp'); //引入AMP插件
    sensors.quick('autoTrack');
  (7)检测AMP集成是否成功
    触发一条埋点事件,查看network是否有sa.gif的请求发出
12、深度链接deeplink
  (1)需求,客户希望通过H5将用户引流至移动 App,借助于深度链接可以提高活动推广的效果
13、常见问题FAQ
  附、常见问题解答(frequently-asked questions,简称FAQ)是使新用户熟悉规则的一种方法
  (1)单页面的页面标题$title问题
    router.beforeEach((to, from, next) => {
      document.title = '新页面的 title 值';
      next()
    })
  (2)异步集成时,使用“内置(但)未使用插件”
    sensors.quick('isReady',function(){
      sensors.use('PageLeave')
      sensors.quick('autoTrack')
    })
附、采集数据的含义
  注释参考1;https://manual.sensorsdata.cn/sa/latest/zh_cn/%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F-149553285.html
  注释参考2;https://manual.sensorsdata.cn/sa/latest/zh_cn/id-key-150668129.html
  注释参考3;https://manual.sensorsdata.cn/sa/latest/zh_cn/%E9%A2%84%E7%BD%AE%E5%B1%9E%E6%80%A7%E6%80%BB%E8%A1%A8%E6%A0%BC-152305885.html
  注释参考4;https://manual.sensorsdata.cn/sa/latest/zh_cn/kafka-154632771.html
  [web-sdk-log]: {
      "identities": {
          //identities:全域用户关联业务中用户标识字段,可包含多个用户标识,具体可以参考全域用户关联,
          //https://manual.sensorsdata.cn/sa/latest/zh_cn/%E6%A0%87%E8%AF%86%E7%94%A8%E6%88%B7%E2%80%94%E2%80%94%E5%85%A8%E5%9F%9F%E7%94%A8%E6%88%B7%E5%85%B3%E8%81%94-150667578.html
          "$identity_cookie_id": "18dcb37ea531b29-088c1d7f7926fa8-1e525637-2104200-18dcb37ea54bd9"//Web cookie ID
      },
      "distinct_id": "18dcb37ea531b29-088c1d7f7926fa8-1e525637-2104200-18dcb37ea54bd9",//类型是字符串,对用户的标识,对未登录用户,可以填充设备标识、CookieID 等,对于登录用户,则应该填充注册账号;这里的例子,假设是一个匿名用户,所以填充的是一个设备 ID;
      "lib": {//SDK
          "$lib": "js",//SDK 类型
          "$lib_method": "code",//埋点方式
          "$lib_version": "1.26.5"//SDK 版本
      },
      "properties": {//属性
          "$timezone_offset": -480,//时区偏移量
          "$screen_height": 1169,//屏幕高度 
          "$screen_width": 1800,//屏幕宽度
          "$viewport_height": 363,//视区高度
          "$viewport_width": 1752,//视区宽度
          "$lib": "js",//SDK 类型
          "$lib_version": "1.26.5",//SDK 版本
          "$referrer": "http://test.cctv.com/",//前向地址,上一页面的$url信息
          "$url": "http://test.cctv.com/#/home",//页面地址
          "$url_path": "/",//页面路径
          "$title": "智策",//页面标题
          "$latest_referrer": "取值异常",//最近一次站外地址
          "$latest_search_keyword": "取值异常",//最近一次搜索引擎关键词
          "$latest_traffic_source_type": "取值异常",//最近一次流量来源类型
          "$is_first_day": false,//是否首日访问
          "$is_first_time": false,//是否首次触发事件
          "$referrer_host": "test.cctv.com",//前向域名
          "tmzone": 8,//时区
          "b": "Chrome",//浏览器名称
          "o": "MacIntel",//操作系统名称
          "lng": "zh-CN",//操作系统语言
          "ism": "不是移动端",//移动系统
          "param": "#/home",//页面URL #后部分
          "logtype": 1,//1-访问日志,2-交互日志
          "single_page": true,//是否单页面应用
          "show_log": true,//控制台是否显示日志
          "v_id": "121212"//(自定义字段)
      },
      "anonymous_id": "18dcb37ea531b29-088c1d7f7926fa8-1e525637-2104200-18dcb37ea54bd9",//login_id、anonymous_id:类型是字符串,对用户的标识,对未登录用户,只有 anonymous_id,而无 login_id 信息;
      "type": "track",//表示一条数据的具体操作(小结部分会详细介绍),track 表明是记录一个事件 
      "event": "$pageview",//事件名,需是合法的变量名,即不能以数字开头,且只包含:大小写字母、数字、下划线和 $,其中以 $ 开头的表明是系统的预置事件,自定义事件名请不要以 $ 开头,且 event 字段长度最大为 100;//事件($WebClick、$WebStay、$pageview)
      "time": 1709626986084,//类型是数值,事件发生的实际时间戳,精确到毫秒;
      "_track_id": 319126084,//前端 SDK track 的时候上报的随机值,用于去重判断,并不会写入 events 表
      "_flush_time": 1709626986084//发送数据时的时间
  }
  
十八、神策源码解读
  注意,先做项目(了解源码的功能),再看源码(了解功能的实现)
  注意,神策数据分为神策采集和神策分析,sd.use、sd.init、页面事件,三者一起添加监听事件
  来源,https://juejin.cn/post/6974267640797724679
1、客户自有数据有三类
  (1)前端操作,通过插件sensorsdata.full.js,采集、发送数据,该插件适用于PC、安卓、苹果、小程序等一切前端
  (2)后端日志
  (3)业务数据
2、插件采集数据的方式
  (1)代码埋点
  (2)全埋点
  (3)可视化全埋点
  (4)预置属性
3、在项目中使用
  (1)在项目中引入采集插件,即1万多行的sensorsdata.full.js
  (2)在项目中插入采集代码,把采集数据发送给神策数据服务器
  (3)用神策数据的账号、密码登录神策数据网站,查看采集数据
4、存储方式
  (1)cookie
  (2)localStorage
  (3)sessionStorage
5、发送方式
  (1)ajax,通过ajax异步POST请求将数据发送出去
  (2)image,图片发送也是默认的发送方式,创建一个img标签,将数据放在请求的URL中,以问号传参的方式发送
  (3)sendBeacon,浏览器会取消unload事件里的卸载、刷新、跳转逻辑,发出请求可以通过navigator.sendBeacon(导航员.发送信标)或同步ajax
6、全局变量
  var sd = {}; //1,sensors-data
  var ee = {}; //6711,event-emit
  var sdk = new EventEmitter(); //6710,sensors-data-kit
  var _ = extend({}, W, business); //7545
7、ee,与事件相关,
  //以下在全局添加属性
  ee.spa = spa;
  ee.initSystemEvent
  ee.EVENT_LIST
  ee.sdk = sdk; //6715
  ee.sdk.on,监听事件
  ee.sdk.emit,触发事件
8、sd,与框架相关
  //以下在implementCore里添加属性,
  sd._ = _;
  sd.ee = ee;
  sd.on = eventEmitterFacade;//调用时,最终调用ee.sdk.on
  sd.use = use;
  sd.readyState = readyState;
  //以下在use里添加属性
  sd.modules = sd.modules || {}; //存放插件
  //以下暴露自身,并执行sd.init,同时注意区分变量和字符串
  window[sensorsDataAnalytic201505] = sd;
  window['sensorsDataAnalytic201505'] = sd;//11415
  sd.init();
9、真实项目中的配置,默认自动开启日志输出功能方便调试,
  来源,https://manual.sensorsdata.cn/sa/latest/tech_sdk_client_web_policy-109576387.html
  <script>
    window.sensors_data_pre_config = {
      is_compliance_enabled: true //是否同意启用
    }
  </script>
  <script charset='UTF-8' src="./sensorsdata.min.js"></script> //sd.init,在插件同步加载结束时,不传参执行1次
  <script>
    if(isClientAgree){//同意隐私条款
      sensors.init({ //sd.init,启用插件时,传参执行1次
        server_url: 'http://test-syg.datasink.sensorsdata.cn/sa?token=xxxxx&project=xxxxxx',
        is_track_single_page: true, //单页面配置,默认开启,若页面中有锚点设计,需要将该配置删除,否则触发锚点会多触发$pageview事件
        use_client_time: true,
        send_type: 'beacon',
        heatmap: {
          //是否开启点击图,'default'表示开启,自动采集 $WebClick 事件,可以设置'not_collect'表示关闭
          clickmap: 'default',
          //是否开启触达图,'not_collect'表示关闭,不会自动采集 $WebStay 事件,可以设置'default'表示开启
          scroll_notice_map: 'not_collect'
        } 
      });
      sensors.quick('autoTrack'); 
    }
  </script>
10、页面监听与发射
   附、sd.init = function(para) {
      ee.initSystemEvent();
      sd.detectMode();
    };
  (1)监听
    A、sd.init
      a、sd.detectMode();
    B、function detectMode() {//检测模式
      a、trackMode();
    C、function trackMode() {//跟踪模式
      a、listenSinglePage();
    D、function listenSinglePage(trackFn) {//监听单个页面
        if (sd.para.is_track_single_page) {
          spa.on('switch', function(last_url) {//监听(后出现,立即执行)
            var sendData = function(extraData) {
              extraData = extraData || {};
              if (last_url !== location.href) {//浏览器前进或后退(用户触发)
                pageInfo.pageProp.referrer = getURL(last_url);
                var data = extend({
                  $url: getURL(),
                  $referrer: getURL(last_url)
                }, extraData);
                isFunction(trackFn) ? trackFn(data) : sd.quick && sd.quick('autoTrack', data);//执行quick
              }
            };
            if (typeof sd.para.is_track_single_page === 'boolean') {
              sendData();
            } else if (typeof sd.para.is_track_single_page === 'function') {
              var returnValue = sd.para.is_track_single_page();
              if (isObject(returnValue)) {
                sendData(returnValue);
              } else if (returnValue === true) {
                sendData();
              }
            }
          });
        }
      }
    E、function quick() {
      a、commonWays[arg0].apply(commonWays, arg1);//执行autoTrack
    F、autoTrack: function(para, callback) {//自动跟踪
      a、sd.track(
    G、function track(
      a、saEvent.send({
    H、send: function() {
    I、request: function(data, dataKeys) {
    J、function ajax$1(para) {
    K、function ajax(para) {
  (2)发射
    A、sd.init
      a、ee.initSystemEvent();
    B、ee.initSystemEvent = function() {
        addSinglePageEvent(function(url) {
          spa.emit('switch', url);//发射(先出现,不立即执行)
        });
      };
    C、function addSinglePageEvent(callback) {
        var current_url = location.href;
        //以下重定义方法,两种操作时执行,执行发射逻辑
        var historyPushState = window.history.pushState;
        var historyReplaceState = window.history.replaceState;  
        if (isFunction(window.history.pushState)) {
          window.history.pushState = function() {//浏览器前进或后退(用户触发)
            historyPushState.apply(window.history, arguments);
            callback(current_url);//执行spa.emit('switch',但不出现
            current_url = location.href;
          };
        }
        if (isFunction(window.history.replaceState)) {
          window.history.replaceState = function() {
            historyReplaceState.apply(window.history, arguments);
            callback(current_url);
            current_url = location.href;
          };
        } 
        //以下添加事件,五种操作时执行,执行发射逻辑
        var singlePageEvent;
        if (window.document.documentMode) {//IE浏览器
          singlePageEvent = 'hashchange';
        } else {//非IE浏览器
          singlePageEvent = historyPushState ? 'popstate' : 'hashchange'; // html5 : html5以下
        }
        addEvent(window, singlePageEvent, function() {
          callback(current_url);//执行spa.emit('switch',但不出现
          current_url = location.href;
        });
        // (6)两种操作的作用,给“浏览器当前页签”添加(或修改)状态,以下是两种操作,
        //   附、它们是HTML5的一个API
        //   A、pushState
        //   B、replaceState
        // (7)五种操作的作用,给“浏览器当前页签”的“不同历史记录”激活,以下是五种操作
        //   A、浏览器的后退(向左)
        //   B、浏览器的前进(向右)
        //   C、history.back(向左)
        //   D、history.forward(向右)
        //   E、history.go(负左,正右,0当前)
        // (8)两种操作(手动)、五种操作(手动)、onpopstate(自动),三者之间的关系
        //   A、两种操作,只能改变当前页面状态,不会触发window.onpopstate(event)事件
        //   B、五种操作,与两种操作相关与否,都会触发window.onpopstate(event)事件
        //   C、五种操作,与两种操作无关时,还会触发http请求
      }
    D、function addEvent(target, eventName, eventHandler, useCapture) {
        var register_event = function(element, type, handler) {
          if (useCapture === undefined && type === 'click') {
            useCapture = true;
          }
          if (element && element.addEventListener) {
            element.addEventListener(//被五种操作中的一种操作触发
              type,//popstate
              function(e) {
                e._getPath = fixEvent._getPath;
                handler.call(this, e);
              },
              useCapture
            );
          } else {
            var ontype = 'on' + type;
            var old_handler = element[ontype];
            element[ontype] = makeHandler(element, handler, old_handler, type);
          }
        };
        register_event.apply(null, arguments);
      }    
  (3)ajax的使用
    附、定义,function ajax(para) {
    A、function ajax$1(para) {
        return ajax(para);
      }
      a、AjaxSend.prototype.start = function() {
      b、request: function(data, dataKeys) {
      c、var business = {
    B、var SADeepLink = {
        init: function init(sd) {
          this.sd._.ajax({
        },
      };
      a、var index$5 = createPlugin$5(SADeepLink, 'Deeplink', 'sdkReady');
    C、function debugPath(data) {
        _$6.ajax({});
      }
      a、debugPath(JSON.stringify(data));
  附、hashchange,该事件在当前URL的锚部分发生修改时触发。它在本插件的应用可能如下
   a、监听hashchange事件
   b、当url上的hash改变时,触发hashchange事件,向后台发送相关数据    
11、源码摘录
  (1)extend,initPara--extend--each,外部数组遍历用forEach,内部对象遍历用for in,后面属性覆盖前面属性
    var hasOwnProperty$1 = Object.prototype.hasOwnProperty;
    var hasOwnProperty$2 = Object.prototype.hasOwnProperty;
    function initPara(para) {
      extend(sdPara, para || sd.para || {});
      sd.para = sdPara;
    }
    function extend(obj) {
      each(Array.prototype.slice.call(arguments, 1), function(source) {
        for (var prop in source) {
          if (hasOwnProperty$1.call(source, prop) && source[prop] !== void 0) {
            obj[prop] = source[prop];
          }
        }
      });
      return obj;
    }
    function each(obj, iterator, context) {
      if (obj == null) {
        return false;
      }
      if (nativeForEach && obj.forEach === nativeForEach) {
        obj.forEach(iterator, context);//在函数的定义处执行
      } else if (isArray(obj)) {
        for (var i = 0, l = obj.length; i < l; i++) {
          i in obj && iterator.call(context, obj[i], i, obj); // iterator定义里只有一个参数,这里传进去三个参数,在不改变iterator定义的情况下,第二、三个参数是无用的
        }
      } else {
        for (var key in obj) {
          if (hasOwnProperty$2.call(obj, key)) {
            iterator.call(context, obj[key], key, obj);
          }
        }
      }
    }
  (2)与use相关
    //溯源插件,index$d-createPlugin$d-wrapPluginInitFn$d(重写init)
    //执行插件,use(Plugin)->Plugin.init()-sd.on->eventEmitterFacade->ee[splitEvent[0]].on(splitEvent[1],callback)->ee.EVENT_LIST
    //注册拦截器,registerInterceptor
    //用2个for循环对2组插件进行遍历,并将每个插件放进sd.modules中
    var builtinPlugins = [index$1, index$2, index$3, index$4, index$5, index$6, index$7, index$8, index$9, index$a, 
                 index$b, index$c, index$d, index$e, index$f, index$g, index$h, index$i, index$j, index$k]; // 共20项
    var autoUsePlugins = [index, index$d, index$e, index$g, index$f, index$2, index$6, index$3, index$7, index$h, 
                 index$i, index$j, index$k]; // 共13项
    for (var i = 0; i < builtinPlugins.length; i++) {
      var p = builtinPlugins[i];
      if (sd._.isString(p.plugin_name)) {
        sd.modules[p.plugin_name] = p; //不需要执行plugin.init,把插件存入sd.modules
      } else {
        sd._.isArray(p.plugin_name) &&
          sd._.each(p.plugin_name, function(v) {
            sd.modules[v] = p;
          });
      }
    }
    for (i = 0; i < autoUsePlugins.length; i++) { 
      sd.use(autoUsePlugins[i]); //需要执行plugin.init,把插件存入sd.modules
    }
    function use(plugin, option) {
      function initPlugin() {
        !curPlugin.plugin_is_init && curPlugin.init(sd, option); 
        curPlugin.plugin_is_init = true;
        sd.modules = sd.modules || {};
        sd.modules[curPlugin.plugin_name || 'unnamed_' + nonameCount++] = curPlugin; //把插件存入sd.modules
        return curPlugin;
      }
      return initPlugin();
    }
    var index$d = createPlugin$d(utm, 'Utm', 'sdkAfterInitPara'); //utm是plugin的实参
    function createPlugin$d(plugin, name, lifeCycle) { //添加版本
      wrapPluginInitFn$d(plugin, name, lifeCycle);
      plugin.plugin_version = sdkversion_placeholder$e;
      return plugin;
    }
    function wrapPluginInitFn$d(plugin, name, lifeCycle) { //重写init
      if (name) {
        plugin.plugin_name = name;
      }
      if (lifeCycle && plugin.init) {
        var initFn = plugin.init; //存储旧的
        plugin.init = function(sd, option) { //赋值新的
          if ((sd.readyState && sd.readyState.state >= 3) || !sd.on) {
            return initPlugin();
          }
          sd.on(lifeCycle, initPlugin);
          function initPlugin() {
            initFn.call(plugin, sd, option);
          }
        };
      }
      return plugin;
    }
  (3)与sd.init相关
    sd.init = function(para) { //8669
      ee.sdk.emit('beforeInit');
      if (sd.readyState && sd.readyState.state && sd.readyState.state >= 2) {
        return false;
      }
      if (is_compliance_enabled) { //是否同意启用
        implementCore(true); //给sd添加属性
        checkState(); //给sd的方法重新定义
      }
      ee.initSystemEvent(); //初始化系统事件
      sd.setInitVar(); //设置初始化变量
      sd.readyState.setState(2); //设置全局状态
      sd.initPara(para); //约130行,暂未了解
      ee.sdk.emit('initPara');
      ee.sdk.emit('afterInitPara');
      ee.sdk.emit('initAPI');
      ee.sdk.emit('afterInitAPI');
      sd.detectMode(); //检测模式,暂未了解
      iOSWebClickPolyfill(); //IOS系统兼容处理-可能
      ee.sdk.emit('afterInit');
      ee.sdk.emit('ready');
    }; 
    function implementCore(isRealImp) {
      if (isRealImp) {
        sd.events = events;
        sd.bridge = bridge;
        sd.SDKJSBridge = SDKJSBridge;
        sd.JSBridge = DeprecatedJSBridge;
        sd.store = store;
        sd.unlimitedDiv = unlimitedDiv;
        sd.customProp = customProp;
        sd.vtrackcollect = vtrackcollect;
        sd.vapph5collect = vapph5collect;
        sd.detectMode = detectMode;
        sd.registerFeature = registerFeature;
        sd.registerInterceptor = registerInterceptor;
        sd.commonWays = commonWays;
        registerFeature(new CoreFeature(sd));
        registerFeature(new HeatCollectFeature(sd));
        registerInterceptor('viewStage', heatCollectInterceptor);
      }
      var imp = isRealImp ? functions : saEmpty;
      for (var f in imp) {
        sd[f] = imp[f];
      }
      sd._ = _;
      sd.on = eventEmitterFacade;
      sd.ee = ee;
      sd.use = use;
      sd.lib_version = sdkversion_placeholder;
    }
    function checkState() {
      each(methods, function(method) {
        var oldFunc = sd[method];
        sd[method] = function() {
          if (sd.readyState.state < 3) {
            if (!isArray(sd._q)) {
              sd._q = [];
            }
            sd._q.push([method, arguments]);
            return false;
          }
          return oldFunc.apply(sd, arguments);
        };
      });
    } 
    ee.initSystemEvent = function() {
      addSinglePageEvent(function(url) {
        spa.emit('switch', url);
      });
    };
    
十九、aplus.js
附、uri与url的区别
  A、uri(Uniform Resource Identifier):统一资源标识符,用字符串标识某一资源的位置,示例如下
    urn:issn:1535-3613:用于标识出版物的唯一标识号
    tag:example.com,2008:3:用于标识博客文章的特定版本
    外站类型、应用id、频道id、页面id
  B、url(Uniform Resource Locator):统一资源定位符,它是一种特殊类型的uri,标识了资源的具体位置,示例如下
    http://www.example.com/path/file.html:用于标识特定的网络资源。
    ftp://user:password@ftp.example.com/pub/file.txt:用于标识通过FTP协议访问的资源。
  C、uri与url的关系,
    uri是一个广义的定义,它可以标识任何资源;
    url则是一种具体的uri,提供了一种标识资源的方式,特别是那些基于HTTP、HTTPS等协议可以通过浏览器进行访问的资源
附、meta标签
  <html>
    <head>
      <meta charset="UTF-8">
      <meta name="description" content="免费的 Web 教程">
      <meta name="keywords" content="HTML,CSS,JavaScript">
      <meta name="author" content="YK Investment">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta name="my-name" content="name的等号后面是属性值,由开发者确定">
      <meta name="your-name" content="特定的属性值如keywords、viewport">
      <meta name="your-name" content="会被网络爬虫或浏览器识别!">
    </head>
  </html>
  <script>
    //name的等号后面是属性值,由开发者确定,特定的属性值如keywords、viewport,会被网络爬虫或浏览器识别!
    //js获取meta某name的content
    //方法1
    var metas = document.getElementsByTagName('meta');
    for (var i = 0; i < metas.length; i++) {
      if (metas[i].getAttribute('name') === 'my-name') {
        console.log(metas[i].getAttribute('content'));
      }
    }
    //方法2
    var names = document.getElementsByName('your-name');
    for (var i = 0; i < names.length; i++) {
      console.log(names[i].getAttribute('content'));
    }
  </script>
附、通用术语
  (1)PV,页面浏览量或点击量,page view
  (2)UV,独立访客数,unique visitor
  (3)QPS,每秒查询率,Query Per Second
  (4)TPS,每秒吞吐量,Throughput Per Second,系统在单位时间内处理请求的数量
  (5)RT,响应时间,Response Time
1、aplus.js说明
  (1)源码地址,https://js.data.cctv.com/__aplus_plugin_cctv.js,aplus_plugin_aplus_u.js
  (2)使用方法,https://help.aliyun.com/product/194063.html,费了好大劲才找到
  (3)二次封装,__aplus_plugin_cctv.js和aplus_plugin_aplus_u.js,是领导给我的
  (4)相关域名
    A、收数,https://log-api.aplus.emas-poc.com/,aplus.js的服务器
    B、友盟,https://developer.umeng.com/docs/67963/detail/74526
    C、分享,https://blog.naaln.com/2017/08/alibaba-data-track-1/
    D、术语,https://zhuanlan.zhihu.com/p/650155427
2、aplus.js术语
  来源,https://blog.csdn.net/qq_38397338/article/details/125246947
  (1)SPM,全称超级位置模型,Super Position Model,记录和描述用户的具体点击位置信息,如外站类型、应用id、频道id、页面id
    A、spm-cnt,当前页面的SPM编码(spm_id)
    B、spm-url,当前页面的来源位置的SPM编码(url_spm_id)
    C、spm-pre,来源页面的来源位置的SPM编码(pre_spm_id)
    D、由a.b.c.d四段构成,各分段分别代表 
      a:站点/业务
      b:页面
      c:页面区块
      d:区块内点
      其中a,b位是必须具备的,且均需在SPM申请中心,如a1.b1.c1.d1
  (2)SCM,全称超级内容模型,Super Content Model
  (3)Quick Tracking,快速追踪(全域采集与增长分析),阿里云推出的(基于aplus.js的)企业级流量统计分析产品
  (4)黄金令箭,用户按照约定的格式向日志服务器发送请求
    A、系统自动采集的页面浏览(PV, Page View)日志
    B、自定义日志(时间内容自定义),通过调用黄金令箭日志接口主动上报日志
  (5)aplus-auto-exp,曝光时,aplus自动发送黄金令箭
  (6)aplus-auto-clk,点击时,aplus自动发送黄金令箭
  (7)自定义属性,data-spm、data-spmA、data-spmB
  (8)自动点击,spmC、spmD
3、aplus.js采集内容包括
  (1)ID,cookieID、淘宝会员数字id(不一定需要登录)、nickname(若已登录)
  (2)URL,当前页URL、来源页URL
  (3)SPM,当前页SPM、来源页点击位置SPM、来源页的来源页点击位置SPM编码(前提为,已部署了SPM)
  (4)信息,标题、浏览器名称版本、屏幕分辨率、aplus版本
  (5)反作弊验证码
   附、交互行为使用「黄金令箭」统计
4、aplus.js自动埋点,当某个元素出现在可视区域,或者点击某个元素的时候,aplus会自动发送黄金令箭
  来源,https://blog.csdn.net/qq_38397338/article/details/125246947
  (1)html埋点,
    A、标识站点ID,<meta name="data-spm" content="申请的A位编码">
    B、标识页面ID,<body data-spm="注册的B位编码"></body>
    C、标识容器ID,<div data-spm="自定义或注册的C位编码"></div>
    D、标识链接ID,<a data-spm="d开头的自定义编码串" href=""></a>
    E、html埋点,<meta name="aplus-auto-exp" 
      content='[{"logkey":"/abc1","tag":"div","filter":"data-name","props":["name","age","address"]}]'>
      当带有data-name属性的div标签被曝光时,自动采集这个标签的曝光信息,及当前元素的"name",“age”,"address"这几个属性
  (2)js埋点
    A、示例
      handleClick(clkType) {
        var q = (window.goldlog_queue || (window.goldlog_queue = []));
        q.push({
          action: 'aplus.record', //发送日志请求。放在这里调用,是为了避免aplus未完成初始化
          arguments: [
            '/aplus.99.3',//(埋点方案中的事件编码)
            'CLK', 
            'clickType='+clkType, 
            'POST'
          ]
        });
        q.push({
          action: 'aplus.setMetaInfo', //识别需要曝光的元素
          arguments: ['aplus-auto-exp', 
          [
            { //aplus自动曝光,发送黄金令箭
              cssSelector: '.auto-exp-component', //需要曝光的元素class
              positionSelector: '.parent',//如果页面模块是元素内滚动(某个区块内有滚动条)则需要增加positionSelector辅助定位曝光元素
              logkey: 'test_event_id', //事件管理中的事件id
              props: ['data-itemid'], //你要曝光的元素身上自定义属性
            },
            ...
          ], ],
        });
        q.push({
          action: 'aplus.setMetaInfo',
          arguments: ['aplus-auto-exp', 
            JSON.stringify([
              {
                "logkey": "/abc1",
                "tag": "div",
                "filter": "data-name",
                "props": ["name", "age", "address"]
              }
            ])
          ]
        });
        q.push({
          'action':'aplus.sendPV',
          'arguments':[{
            is_auto: false
          }, {
            page_title: "首页", //默认为pageConfig中的值,如果这里设置了,则为这里设置的值 (非必传)
            page_name: "yourCurrentPageName", //默认为pageConfig中的值,如果这里设置了,则为这里设置的值 (非必传)
            //如果您设置了duration参数(单位须为毫秒),QuickTracking会做为分析时的「事件属性-时长(s)」处理
            duration: 1111111,
            //自定义事件属性
            x: 111,
            y: 222
          }]
        });
      }
      <button onClick={this.handleClick('one')}>HJLJ ONE</button>
    B、参数说明,goldlog.record(logkey, gmkey, gokey, req_method)
      logkey{String},即完整的令箭编码,如上例中的 “/a1.jingyao.clicktest”
      gmkey{String},关键业务类型,目前的约定的元值有五个,
        点击类操作"CLK"、
        曝光类事件"EXP"、
        滑屏类事件"SLD"、
        其它事件"OTHER"(特指除点击和曝光事件外的其他自定义事件)、
        也可以为空值,但不建议留空
      gokey{String},附加的自定义kv对,如本例中clicktype=one
      req_method{String},可选值有’GET’(默认)、‘POST’
        如果入参为POST,则令箭请求会优先navigator.sendBeacon post的形式发出,可以保证页面跳转的时候不会断
        如果当前浏览器不支持sendBeacon则降级为get img的形式发出
5、Quick Tracking的基本概念,全域采集与增长分析-产品概述
  来源,https://help.aliyun.com/document_detail/250932.html
  (1)行为采集
    A、系统事件(APP有,小程序有,Web无),应用启动($$_app_start)、应用退出($$_app_end)、分享($$_share)
    B、页面事件,采集页面浏览行为的事件,包含页面事件标识码和页面编码
    C、自定义事件,
    D、属性,包含全局属性和事件属性
  (2)用户标识,包含设备和用户
6、Web可视化分析使用文档-集成,全域采集与增长分析-操作指南-采集管理-可视化埋点
  附、相关概念
   A、可视化分析,利用图形、图像、动画等直观方式来展现数据的分析方法
   B、可视化埋点,用于分析用户在应用程序或网站上的行为和交互的技术
  来源,https://help.aliyun.com/document_detail/281212.html
  (1)第一步:确认SDK支持投屏
    注、支持投屏,意味着一个设备能够将它的画面复制并显示在另一个设备上
    A、用户将发射端SDK集成到自己的APP或软件应用中
    B、配套投屏接收端设备
  (2)第二步:确认SDK已经集成
    A、首先,将以下脚本放在head标签内
      <script>
        (function(w, d, s, q) {
          w[q] = w[q] || [];
          var f = d.getElementsByTagName(s)[0],j = d.createElement(s);
          j.async = true;
          j.id = 'beacon-aplus';
          j.src = '<sdk地址>';
          f.parentNode.insertBefore(j, f);
        })(window, document, 'script', 'aplus_queue');
      </script>
    B、其次,配置必要的meta
      <html>
        <head>
          <meta name="appKey" content="<QuickA+申请应用时颁发的appKey>">
          <meta name="aplus-rhost-v" content="<日志收数域名>">
          <meta name="aplus-vt-cfg-url" content="<已发布的配置地址>">
        </head>
      </html>
    C、说明
      appKey:QuickA+申请应用时颁发的appKey
      aplus-rhost-v:日志收数域名
      aplus-vt-cfg-url:已发布的配置地址
  (3)第三步:配置SPM
    注、必须使用自动点击、自动曝光上报数据
    A、配置页面级别spm(spmB)
      第一种:通过给body标签添加自定义属性data-pagename
        <body data-pagename=${yourPageName}></body>
      第二种:手动调用API设置spmB
        aplus_queue.push({
          action: 'aplus.setPageName',
          arguments: [${yourPageName}]
        });
    B、自动点击配置spm(spmC、spmD)
      注、通过每一自动点击配置项的spmC和spmD属性来配置spm
      aplus_queue.push({
        action:'aplus.setMetaInfo',
        arguments:['aplus-auto-clk',[{
          cssSelector:'.header',
          logkey:'banner-clk',
          spmC:"header",
          spmD:"banner"
        },{
          cssSelector:'.component-category-industry',
          logkey:'category-clk',
          props:['categorytype','title'],
          spmC:"component-category",
          spmD:"industry"
        },{
          cssSelector:'.component-category-common',
          logkey:'category-clk',
          props:['categorytype','title'],
          spmC:"component-category",
          spmD:"business"
        }]]
      });
    C、自动曝光配置spm(spmC、spmD)
      注、通过每一自动曝光配置项的spmC和spmD属性来配置spm
      aplus_queue.push({
        action:'aplus.setMetaInfo',
        arguments:['aplus-auto-exp',[{
          cssSelector:'.header',
          logkey:'banner-exp',
          spmC:"header",
          spmD:"banner"
        },{
          cssSelector:'.component-category-industry',
          logkey:'category-exp',
          props:['categorytype','title'],
          spmC:"component-category",
          spmD:"industry"
        },{
          cssSelector:'.component-category-common',
          logkey:'category-exp',
          props:['categorytype','title'],
          spmC:"component-category",
          spmD:"business"
        }]]
      });
    D、自定义属性配置spm(spmC、spmD)
      <body data-pagename=${yourPageName}>
        <div data-spmc="c111">
          <a href="链接1" data-spmd="d1" />
          <a href="链接2" data-spmd="d2" />
        </div>
        <div data-spmc="c222">
          <a href="链接3" data-spmd="d1" />
          <a href="链接4" data-spmd="d2" />
        </div>
      </body>
      data-pagename:spmB
      data-spmc:spmC
      data-spmd:spmD
7、引入&初始化SDK,全域采集与增长分析-开发参考-SDK参考-Web SDK
  来源,https://help.aliyun.com/document_detail/473456.html
  (1)参数准备
    A、appkey:在应用列表中获取
    B、收数域名:在“管理控制台-采集信息”模块中获取
    C、SDK链接:在“管理控制台-采集信息”模块中获取
  (2)SDK引入&初始化
    A、当您得到集成SDK代码地址以后,在页面head标签内加入集成代码,确保aplus_queue不被污染
      (function(w, d, s, q) {
        w[q] = w[q] || [];
        var f = d.getElementsByTagName(s)[0], j=d.createElement(s);
        j.async = true;
        j.id = 'beacon-aplus';
        j.src = 'SDK链接';
        f.parentNode.insertBefore(j, f);
      })(window, document, 'script', 'aplus_queue');
    B、设置域名和appkey,以下代码紧跟SDK引入代码
      //集成应用的appKey
      aplus_queue.push({
        action: 'aplus.setMetaInfo',
        arguments: ['appKey', '您的appkey']
      });
      //如果是私有云部署还需要在上面那段JS后面紧接着添加日志域名埋点
      //通常私有云日志服务端域名类似于:xxx-web-api.xxx.com.cn, 具体域名在“管理控制台-采集信息”模块中获取
      //2.x版本SDK设置收数域名
      aplus_queue.push({
        action: 'aplus.setMetaInfo',
        arguments: ['trackDomain', '您的收数域名'] 
      });
      //1.x版本SDK设置收数域名
      aplus_queue.push({
        action: 'aplus.setMetaInfo',
        arguments: ['aplus-rhost-v', '您的收数域名']
      });
8、基础功能,全域采集与增长分析-开发参考-SDK参考-Web SDK
  来源,https://help.aliyun.com/document_detail/602428.html
  (1)原理
    A、SDK 提供一种指令形态的埋点调用方式,您通过对 aplus 环境变量的指令队列 aplus_queue发送指令,
    B、由 aplus 环境变量来执行指令,进而完成您的需求,:
    C、指令格式如下
      aplus_queue.push({
        'action': "$APIName",
        'arguments': [$arguments]//arguments为指定API的入参,
      })
  (2)action 参数代表发送指令的 API 名称,其入参为一个字符串,取值为枚举值,可用的枚举值如下
    A、setMetaInfo:覆盖SDK的已有默认设置
    B、appendMetaInfo: 追加SDK的默认配置
    C、getMetaInfo:获取SDK的当前配置
    D、record:发送事件日志
    E、sendPV:发送页面日志
  (3)arguments参数,为action中指定API的入参,格式是一个数组,数组内的元素顺序与API定义的入参顺序一致
  (4)示例
    A、变更SDK的默认设置
      aplus_queue.push({
        action: 'aplus.setMetaInfo',
        arguments: [metaName, metaValue]
      });
    B、获取SDK的当前配置
      aplus.getMetaInfo(metaName);
    C、发送事件日志
      aplus_queue.push({
        action: 'aplus.record',
        arguments: [
          trackerEventCode, //(埋点方案中的事件编码)
          eventType, 
          eventParams
        ]
      });
    D、发送页面日志
      aplus_queue.push({
        action: 'aplus.sendPV',
        arguments: [pageEventConfig, userData]
      });
  (5)日志打印
    aplus_queue.push({
      action: 'aplus.setMetaInfo',
      arguments: ['DEBUG', true]
    });
  (6)应用基础信息配置
    在SDK引入部分,可以修改或者追加一些默认设置
    //集成应用的appKey
    aplus_queue.push({
      action: 'aplus.setMetaInfo',
      arguments: ['appKey', 'xxxxxxx']
    })
    aplus_queue.push({
      action: 'aplus.setMetaInfo',
      arguments: ['aplus-rhost-v', 'quickaplus-Web-api.xxx.com.cn']
    });
    //开启调试模式
    aplus_queue.push({
      action: 'aplus.setMetaInfo',
      arguments: ['DEBUG', true]
    });
9、埋点API,全域采集与增长分析-开发参考-SDK参考-Web SDK
  来源,https://help.aliyun.com/document_detail/602417.html
  (1)设备ID设置
    A、自动生成:默认逻辑,网站的设备ID只有浏览器发生变化或用户主动清除cookie和缓存时,设备ID会发生改变
    B、手动上传:上传方式为赋值给"_dev_id",上传的长度要在24-36字符
    C、示例
      a、如采集用户ID是异步行为,需要先阻止SDK上报,设置BLOCK埋点
        aplus_queue.push({
          action: 'aplus.setMetaInfo',
          arguments: ['_hold', 'BLOCK']
        });
      b、设置_dev_id
        aplus_queue.push({
          action: 'aplus.setMetaInfo',
          arguments: ['_dev_id', '自定义设备ID']
        });
      c、因为采集用户ID是异步行为,故需要先设置BLOCK,再设置START
       设置_hold=START后,事先被block住的日志会携带上用户信息逐条发出
        aplus_queue.push({
          action: 'aplus.setMetaInfo',
          arguments: ['_hold', 'START']
        });
  (2)账号ID设置
    A、在用户登录时,以及登录态进入H5时,都需要设置账号ID
    B、因为设置后的每一条日志都将携带账号ID,但退出H5再进入后触发的事件不会携带账号ID
    C、所以需要在用户登录时,以及登录态进入H5时设置账号ID
    D、示例
      a、用户登录时,获取到用户登录账号信息 or 用户已登录,通过cookie或者localstorage获取用户登录账号
        function demoLogin() {
          /*************************如果同步场景***********************************/
          aplus_queue.push({
            action: 'aplus.setMetaInfo',
            arguments: ['_user_id', '用户的账号id']
          });
          /******************如果是异步场景,并且日志必须依赖用户账号***********************/
          //先通过设置_hold=BLOCK阻塞采集上报
          aplus_queue.push({
            action: 'aplus.setMetaInfo',
            arguments: ['_hold', 'BLOCK']
          });
          ...
          function callback() {
            //获取异步回调结果中的用户账号id
            aplus_queue.push({
              action: 'aplus.setMetaInfo',
              arguments: ['_user_id', '用户的账号id']
            });
            //再通过设置_hold=START允许采集上报
            aplus_queue.push({
              action: 'aplus.setMetaInfo',
              arguments: ['_hold', 'START']
            });
          };
          ...
        };
      b、用户登出时,重置用户账号id
        function demoLogOff() {
          aplus_queue.push({
            action: 'aplus.setMetaInfo',
            arguments: ['_user_id', '']
          });
        };
  (3)设备ID和账号ID获取
    A、设备ID的获取
      a、SDK自动生成的设备ID,获取方式如下:
        在当前域名的cookie下存储名为cna的字段,可以通过解析document.cookie获取
      b、通过_dev_id方式自定义上传的设备ID,获取方式如下:
        开发者通过setMetaInfo设置_dev_id可以自定义设备ID,可通过aplus.getMetaInfo('_dev_id')读取
    B、账号ID的获取
      a、开发者通过setMetaInfo设置_user_id可以自定义用户账号ID,可通过aplus.getMetaInfo('_user_id')获取
  (4)设置用户属性
    A、通过预制事件编码 $$_user_profile 上报用户属性,事件类型为其他事件
    B、在上报用户属性之前,需要先设置_user_id上报用户账号
    C、示例
      aplus_queue.push({
        'action':'aplus.record',
        'arguments':[
          '$$_user_profile(埋点方案中的事件编码)', 
          'OTHER', //特指除点击和曝光事件外的其他自定义事件
          { //此外内容不可改变
            name: 'sss', //用户属性1
            gender: 'male', //用户属性2    
            class: '3', //用户属性3
          }
        ]
      });
10、常见场景与埋点建议,全域采集与增长分析-开发参考-SDK参考-Web SDK
  来源,https://help.aliyun.com/document_detail/603464.html
  (1)页面跳转前的事件发送
    A、当用户在网页「点击href属性www.xxxx.com的a标签」时,触发的点击事件可能会因为页面立刻跳转而未发送出去,
    B、若希望该场景下尽量保证数据的发送,可以进行页面延迟跳转,
    C、事例代码如下
      //点击链接
      function targetLinkCLK(url) {
        // 延迟页面跳转,给SDK预留发数时间
        setTimeout(function(){
          window.location.href = url;
        }, 500);
        aplus_queue.push({
          action: 'aplus.record',
          arguments: [
            'track_alink_clk', //(埋点方案中的事件编码)
            'CLK', 
            {
              param1: xxxx,
              param2: xxxx
            }
          ]
        });
      }
 
二十、aplus.js源码阅读
1、外框
  ! function(e) { //e,总参数
    function t(a) { //公函数,
      if (o[a]) return o[a].exports;
      var n = o[a] = { 
        exports: {}, 
        id: a,
        loaded: !1
      };
      return e[a].call(n.exports, n, n.exports, t), n.loaded = !0, n.exports //公函数执行,参数的某项执行
    }
    var o = {}; //总数据
    return t.m = e, t.c = o, t.p = "", t(0) //公函数执行
  }([function(e, t, o) { //对象,属性,公函数。公函数执行,参数的某项执行
    "use strict";
    ! function() {
      var e = window.goldlog || (window.goldlog = {});
      e._aplus_plugin_cctv || (
        e._aplus_plugin_cctv = {
          status: "complete"
        }, o(1).run()
      )
    }()
  }, function(e, t, o) {
    "use strict";
    function a() {
      var e = l.getCookie("userSeqId");
      if (e) {
        var t = document.getElementById("tb-beacon-aplus") || document.getElementById("beacon-aplus");
        if (t) {
          var o = t.getAttribute("exparams"),
            a = "uidaplus=" + e;
          o = o ? o.replace(/&aplus&/, "&" + a + "&aplus&") : a + "&aplus&sidx=aplusSidex", t.setAttribute("exparams", o)
        }
      }
      return e
    }
    function n() {
      var e = {};
      try {
        var t = goldlog.getMetaInfo("aplus-rhost-g-map");
        "string" == typeof t ? e = JSON.parse(t) : "object" == typeof t && (e = t)
      } catch (t) {
        e = {}
      }
      return e
    }
    function r(e, t) {
      var o = n();
      return o && o[t] ? "//" + o[t] + t : e
    }
    var s = o(2),
      l = o(3);
    t.run = function() {}
  }]);
2、功能扩展-单页面重新获取信息
  来源,https://help.aliyun.com/document_detail/473456.html?spm=a2c4g.602417.0.0.b24b256fiPDTu3
  (1)当您得到集成SDK代码-地址-以后,在页面head标签内加入集成代码,确保aplus_queue不被污染
    (function(w, d, s, q) { 
      w[q] =w[q] || []; 
      var f=d.getElementsByTagName(s)[0],j=d.createElement(s); 
      j.async=true; 
      j.id='beacon-aplus'; 
      j.src='SDK链接'; 
      f.parentNode.insertBefore(j, f);
    })(window, document, 'script', 'aplus_queue');
    //扩充功能-应该-写在这里
  (2)扩充功能 
    function pushData(){
      // var aaa = window.goldlog.getMetaInfo("aplus-auto-exp")
      window.goldlog_queue.push({
        action: 'aplus.sendPV',//发送页面日志
        arguments:[
          {
            is_auto: true
          }, {
            page_title: "首页", //默认为pageConfig中的值,如果这里设置了,则为这里设置的值 (非必传)
            page_name: "yourCurrentPageName", //默认为pageConfig中的值,如果这里设置了,则为这里设置的值 (非必传)
            //如果您设置了duration参数(单位须为毫秒),QuickTracking会做为分析时的「事件属性-时长(s)」处理
            duration: 1000000000,
            //自定义事件属性
            x: 111,
            y: 222
          }
        ]
      });
    }
    function isFunction(myFunction){
      return Object.prototype.toString.call(myFunction) === "[object Function]"
    } 
    function addEveryPageEvent() {
      var historyPushState = window.history.pushState;
      var historyReplaceState = window.history.replaceState;  
      if (isFunction(window.history.pushState)) {
        window.history.pushState = function() {
          console.log( 'pushState' );
          historyPushState.apply(window.history, arguments);
          pushData()
        };
      }
      if (isFunction(window.history.replaceState)) {
        window.history.replaceState = function() {
          console.log( 'replaceState' );
          historyReplaceState.apply(window.history, arguments);
          pushData()
        };
      }
      if (window.addEventListener) {
        window.addEventListener('popstate', function(){
          console.log( 'popstate' );
          pushData()
        }, false);
      }
    }
    addEveryPageEvent() 
  (3)扩充功能不应该写在这里  
    e.init = function() {
      i.initLoad.init_watchGoldlogQueue("metaQueue"), n(90)(function() {
      })
      //扩充功能-不应该-写在这里
    }
3、使用说明
  来源,https://help.aliyun.com/document_detail/473456.html?spm=a2c4g.602417.0.0.b24b256fiPDTu3
  (1)在QuickTracking后台,为每一个Web应用生成了专属的集成代码,可以根据产品内的引导进行集成
  (2)参数准备
    A、appkey,在应用列表中获取
    B、收数域名,在“管理控制台-采集信息”模块中获取
    C、SDK链接,在“管理控制台-采集信息”模块中获取

  

posted @ 2020-01-08 17:30  WEB前端工程师_钱成  阅读(6808)  评论(0编辑  收藏  举报