自学SLAM(5)《第三讲:李群和李代数》作业

news2024/11/24 19:38:02

前言

在这里插入图片描述

小编研究生的研究方向是视觉SLAM,目前在自学,本篇文章为初学高翔老师课的第三次作业。

文章目录

  • 前言
  • 1.群的性质
  • 2.验证向量叉乘的李代数性质
  • 3.推导 SE(3) 的指数映射
  • 4.伴随
  • 5.轨迹的描绘
  • 6.* 轨迹的误差(附加题)


1.群的性质

课上我们讲解了什么是群。请根据群定义,求解以下问题:

  1. {Z, +} 是否为群?若是,验证其满⾜群定义;若不是,说明理由。
  2. {N, +} 是否为群?若是,验证其满⾜群定义;若不是,说明理由。
    其中 Z 为整数集, N 为⾃然数集。

在这里我再写一下群的定义,满足“凤姐咬你”的就是群,也就是四个性质,如图:
注意:
图中吧集合记作A,运算记作·(·不代表乘法),群可以记作G=(A,·)
对于整数的逆,进行的是乘法它的逆就是它的倒数 进行的是加法,它的逆就是它的相反数
在这里插入图片描述
对于 {Z, +} ,设a1∈Z,a2∈Z,a3∈Z
①封闭性:对于任意的a1∈Z,a2∈Z都有a1+a2∈Z,满足封闭性。
②结合性:对于任意的a1∈Z,a2∈Z,a3∈Z,都有(a1+a2)+a3=a1+(a2+a3),满足结合性。
③幺元:Z中存在0∈Z,对于任意的a∈Z,有a+0=a+0=a,因此满足幺元。
④逆:对于任意的a∈Z,有-a∈Z,a+(-a)=0,任何整数加上它的相反数等于幺元0,所以逆元素是其相反数,因此满足逆。

对于 {N, +}
①封闭性:两个自然数相加依然是自然数,封闭性成立。
②结合性:两个自然数相加可以互换位置,结合性成立。
③幺元:任何自然数与0相加仍然是自然数本身,幺元成立。
④逆: 自然数都是非负数(加法中,自然数的逆已经不属于自然数了),所以两个大于等于0的数相加不可能为0,逆不成立。

2.验证向量叉乘的李代数性质

我们说向量和叉乘运算构成了李代数,现在请你验证它。书中对李代数的定义为:李代数由⼀个集合V,⼀个数域 F 和⼀个⼆元运算 [,]组成。如果它们满⾜以下⼏条性质,称 (V, F, [, ]) 为⼀个李代数,记作g。
注意:自反性是指自己与自己的运算为零。
在这里插入图片描述

解题过程如下:
在这里插入图片描述

3.推导 SE(3) 的指数映射

课上给出了 SO(3) 的指数映射推导,但对于 SE(3),仅介绍了结论,没有给出详细推导。请你完成 SE(3)指数映射部分,有关左雅可⽐的详细推导。
在这里插入图片描述

解题过程如下:
在这里插入图片描述
在这里插入图片描述

4.伴随

在 SO(3) 和 SE(3) 上,有⼀个东西称为伴随(Adjoint)。下⾯请你证明 SO(3)伴随的性质。在这里插入图片描述

解题过程如下:
在这里插入图片描述
完整的 SO(3) 和 SE(3) 性质见下图
在这里插入图片描述
在这里插入图片描述

5.轨迹的描绘

我们通常会记录机器⼈的运动轨迹,来观察它的运动是否符合预期。⼤部分数据集都会提供标准轨迹以供参考,如 kitti、 TUM-RGBD 等。这些⽂件会有各⾃的格式,但⾸先你要理解它的内容。记世界坐标系为 W,机器⼈坐标系为 C,那么机器⼈的运动可以⽤ TWC 或TCW来描述。现在,我们希望画出机器⼈在世界当中的运动轨迹,请回答以下问题:
在这里插入图片描述

解题过程如下:
世界坐标系W(world),机器人坐标系也就是相机坐标系C(camera)

①Twc指的是从世界坐标系原点到相机中心的平移向量,(机器人(相机)坐标系的原点在世界坐标系中的坐标)
世界坐标系是不随相机运动变化的,因此可以认为Twc是机器人相对于原点坐标在移动, 移动可视化在观察者眼中就是机器人的运动轨迹。

如果我们假设机器人坐标系的原点为Oc,此时的Ow就是这个原点在世界坐标系下的坐标:
Ow=TwcOc=twc
这正是Twc的平移部分。因此,可以从Twc中直接看到相机在何处,这也就是我们所说的Twc更为直观。因此在可视化程序里,轨迹文件储存的是Twc而不是Tcw

我想这也是第一问问我们Twc而不是问·Tcw的原因。


首先我们需要安装Sophus

    git clone https://github.com/strasdat/Sophus.git
    cd Sophus
    git checkout a621ff
    mkdir build
    cd build
    cmake ..
    make
    sudo make install

但是我们会编译失败,按照如下操作修改后,重新编译即可。
解决方法:打开 Sophus/sophus/so2.cpp文件修改报错内容

//将
SO2::SO2() 
{ 
 unit_complex_.real() = 1.; 
 unit_complex_.imag() = 0.; 
 }
//改为
SO2::SO2() 
{ 
   unit_complex_.real(1.); 
   unit_complex_.imag(0.); 
 }

这时候我们就可以安装成功Sophus了
在这里插入图片描述

draw_trajectory.cpp对应代码如下:

#include "sophus/so3.h"
#include "sophus/se3.h"
#include <string>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <Eigen/Core>
// need pangolin for plotting trajectory
#include <pangolin/pangolin.h>
using namespace std;
// path to trajectory file
string trajectory_file = "/home/zhe/1/lianxi/3/plotTrajectory/trajectory.txt";
class SE3d;
void DrawTrajectory(vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>>);
int main(int argc, char **argv) {
    vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> poses;
    //文件读取器
    ifstream fin(trajectory_file);
    if (!fin) {
        cerr << "trajectory " << trajectory_file << " not found." << endl;
    }
    //如果eof()返回0,就没读完
    while (!fin.eof()) {
        //按照时间  平移 四元素的顺序定义并读取
        double time, tx, ty, tz, qx, qy, qz, qw;
        fin >> time >> tx >> ty >> tz >> qx >> qy >> qz >> qw;
        Sophus::SE3 p1(Eigen::Quaterniond(qw, qx, qy, qz), Eigen::Vector3d(tx, ty, tz));
        poses.push_back(p1);
    }
    fin.close();
    // end your code here
    // draw trajectory in pangolin
    DrawTrajectory(poses);
    return 0;
}
/************************************************************************/
void DrawTrajectory(vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> poses) {
    if (poses.empty()) {
        cerr << "Trajectory is empty!" << endl;
        return;
    }
    // create pangolin window and plot the trajectory
    pangolin::CreateWindowAndBind("Trajectory Viewer", 1024, 768);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    pangolin::OpenGlRenderState s_cam(
            pangolin::ProjectionMatrix(1024, 768, 500, 500, 512, 389, 0.1, 1000),
            pangolin::ModelViewLookAt(0, -0.1, -1.8, 0, 0, 0, 0.0, -1.0, 0.0)
    );
    pangolin::View &d_cam = pangolin::CreateDisplay()
            .SetBounds(0.0, 1.0, pangolin::Attach::Pix(175), 1.0, -1024.0f / 768.0f)
            .SetHandler(new pangolin::Handler3D(s_cam));
    while (pangolin::ShouldQuit() == false) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        d_cam.Activate(s_cam);
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
        glLineWidth(2);
        for (size_t i = 0; i < poses.size() - 1; i++) {
            glColor3f(1 - (float) i / poses.size(), 0.0f, (float) i / poses.size());
            glBegin(GL_LINES);
            auto p1 = poses[i], p2 = poses[i + 1];
            glVertex3d(p1.translation()[0], p1.translation()[1], p1.translation()[2]);
            glVertex3d(p2.translation()[0], p2.translation()[1], p2.translation()[2]);
            glEnd();
        }
        pangolin::FinishFrame();
        usleep(5000);   // sleep 5 ms
    }
}

CmakeLists.txt对应代码如下:

cmake_minimum_required(VERSION 2.8)
project(draw_trajectory)
set( CMAKE_BUILD_TYPE "Release" )
set( CMAKE_CXX_FLAGS "-std=c++11 -O3" )
set( CMAKE_BUILD_TYPE "Debug" )

find_package(Pangolin REQUIRED)
find_package(Sophus REQUIRED)
include_directories("/usr/include/eigen3")
include_directories(
        ${Pangolin_INCLUDE_DIRS}
        ${Sophus_INCLUDE_DIR}
)
add_executable(trajectory draw_trajectory.cpp)
target_link_libraries(trajectory
        ${Pangolin_LIBRARIES}
        ${Sophus_LIBRARIES}
        )

然后

cd  SLAM4track//自己建的文件夹
cat CMakeLists.txt
cd build
cmake ..
make
./trajectory

在这里插入图片描述
该图中:轨迹首尾颜色不一样,通过观察,发现是着色函数设置的颜色随位置变化.

6.* 轨迹的误差(附加题)

本题为附加题。 除了画出真实轨迹以外,我们经常需要把 SLAM 估计的轨迹与真实轨迹相⽐较。下⾯说明⽐较的原理,请你完成⽐较部分的代码实现。
在这里插入图片描述

CMakeLists.txt对应代码

cmake_minimum_required(VERSION 2.8)
project(wucha)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

#set(CMAKE_CXX_STANDARD 11)
#set(CMAKE_BUILD_TYPE "Release")
#set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/bin)
#set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib)
#添加库
#sophus
#  为使用 sophus,需要使用find_package命令找到它并赋给Sophus_INCLUDE_DIRS
find_package(Sophus REQUIRED)
include_directories(${Sophus_INCLUDE_DIRS})
#Pangolin生成一个libPangolin动态链接库
find_package(Pangolin REQUIRED)
include_directories(${Pangolin_INCLUDE_DIRS})

include_directories("/usr/include/eigen3")
#编译
add_executable(plotError compare_tra.cpp)
#链接
#target_link_libraries(plotError Sophus::Sophus)
target_link_libraries(plotError ${Sophus_LIBRARIES} )

target_link_libraries(plotError ${Pangolin_LIBRARIES})

compare_tra.cpp对应代码:

#include "sophus/so3.h"
#include "sophus/se3.h"
#include <string>
#include <iostream>
#include <fstream>
#include <unistd.h>
#include <Eigen/Core>
#include <Eigen/Geometry>
// need pangolin for plotting trajectory
#include <pangolin/pangolin.h>
using namespace std;
// path to trajectory file
string gt_file = "../groundtruth.txt";
string est_file = "../estimated.txt";
vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> gt_poses,est_poses;
// function for plotting trajectory, don't edit this code
// start point is red and end point is blue
void DrawTrajectory(vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> gt_poses,
                    vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> est_poses);
void readData(string filepath);
void ErrorTrajectory();
int main(int argc, char **argv) {
    readData(gt_file);
    readData(est_file);
    ErrorTrajectory();
    // draw trajectory in pangolin
    DrawTrajectory(gt_poses,est_poses);//打印两条轨迹
    return 0;
    /// implement pose reading code
    // start your code here (5~10 lines)
}
/*******************************************************************************************/
void readData(string filepath){
    vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> poses;
    ifstream infile(filepath);
    double t1,tx,ty,tz,qx,qy,qz,qw;
    string line;
    if(infile)
    {
        while(getline(infile,line))
        {
            stringstream record(line);    //从string读取数据
            record>>t1>>tx>>ty>>tz>>qx>>qy>>qz>>qw;
            Eigen::Vector3d t(tx,ty,tz);
            Eigen::Quaterniond q = Eigen::Quaterniond(qw,qx,qy,qz).normalized();  //四元数的顺序要注意
            Sophus::SE3 SE3_qt(q,t);
            poses.push_back(SE3_qt);
        }
    }
    else
    {
        cout<<"没找到这个文件"<<endl;
    }
    if(filepath==gt_file){
        gt_poses = poses;
    }else if( filepath==est_file ){
        est_poses = poses;
    }else{ cout<<"读文件出错"<<endl;}
    infile.close();
}
/*******************************************************************************************/
void ErrorTrajectory()
{
    double RMSE = 0;
    Eigen::Matrix<double ,6,1> se3;
    vector<double> error;
    for(int i=0;i<gt_poses.size();i++){
        se3=(gt_poses[i].inverse()*est_poses[i]).log();  //这里的se3为向量形式,求log之后是向量形式
        //cout<<se3.transpose()<<endl;
        error.push_back( se3.squaredNorm() );  //二范数
        // cout<<error[i]<<endl;
    }
    for(int i=0; i<gt_poses.size();i++){
        RMSE += error[i];
    }
    RMSE /= double(error.size());
    RMSE = sqrt(RMSE);
    cout<<RMSE<<endl;
}
/*******************************************************************************************/
void DrawTrajectory(vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> gt_poses,
                    vector<Sophus::SE3, Eigen::aligned_allocator<Sophus::SE3>> est_poses) {
    if (gt_poses.empty()) {
        cerr << "groundtruth is empty!" << endl;
        return;
    }
    if (est_poses.empty()) {
        cerr << "estimated is empty!" << endl;
        return;
    }
    // create pangolin window and plot the trajectory
    pangolin::CreateWindowAndBind("Trajectory Viewer", 1024, 768);
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    pangolin::OpenGlRenderState s_cam(
            pangolin::ProjectionMatrix(1024, 768, 500, 500, 512, 389, 0.1, 1000),
            pangolin::ModelViewLookAt(0, -0.1, -1.8, 0, 0, 0, 0.0, -1.0, 0.0)
    );
    pangolin::View &d_cam = pangolin::CreateDisplay()
            .SetBounds(0.0, 1.0, pangolin::Attach::Pix(175), 1.0, -1024.0f / 768.0f)
            .SetHandler(new pangolin::Handler3D(s_cam));
    while (pangolin::ShouldQuit() == false) {
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        d_cam.Activate(s_cam);
        glClearColor(1.0f, 1.0f, 1.0f, 1.0f);//窗口,rgba
        glLineWidth(2);
        for (size_t i = 0; i < est_poses.size() - 1; i++) {
            glColor3f(1 - (float) i / est_poses.size(), 0.0f, (float) i / est_poses.size());
            glBegin(GL_LINES);
            auto p1 = est_poses[i], p2 = est_poses[i + 1];
            glVertex3d(p1.translation()[0], p1.translation()[1], p1.translation()[2]);
            glVertex3d(p2.translation()[0], p2.translation()[1], p2.translation()[2]);
            glEnd();
        }
        for (size_t i = 0; i < gt_poses.size() - 2; i++) {
            glColor3f(0.f, 0.8f, 0.f);//绿色
            glBegin(GL_LINES);
            auto p3 = gt_poses[i], p4 = gt_poses[i + 1];//只显示tx,ty,tz
            glVertex3d(p3.translation()[0], p3.translation()[1], p3.translation()[2]);
            glVertex3d(p4.translation()[0], p4.translation()[1], p4.translation()[2]);
            glEnd();
        }
        pangolin::FinishFrame();
        usleep(5000);   // sleep 5 ms
    }
}

然后运行,运行命令如下:

cd track_compare
cd build
cmake ..
make
./plotError

在这里插入图片描述
本人自学SLAM,如果错误,还请见谅留言提醒
希望的我的博客对你有帮助!

在这里插入图片描述

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

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

相关文章

Linux--进程替换

1.什么是进程替换 在fork函数之后&#xff0c;父子进程各自执行代码的一部分&#xff0c;但是如果子进程想要执行一份全新的程序呢&#xff1f; 通过进程替换来完成&#xff0c;进程替换就是父子进程代码发生写时拷贝&#xff0c;子进程执行自己的功能。 程序替换就是通过特定的…

GPT出大错了!原来GPT不是万能的!这就是人类存在的意义!

目录 前言 测试方式 测试 问题一 问题二 问题三 问题四 结尾 前言 Chat GPT大家应该都听说过&#xff0c;但是它真的有想象中的那么强大吗&#xff1f;这篇文章带你深入了解。 这几个月GPT非常火爆&#xff0c;我也是经常用到。GPT是一个只能聊天机器人&#xff0c;能…

高等数学前置知识——二次函数

文章目录 二次函数1.1 二次函数1.2 二次函数的图像1.2.1 a > 0 时1.2.2 a < 0 时1.2.3 二次函数的平移1.2.4 普通二次型函数图像总结 1.3 其他形式的二次函数1.3.1 顶点式1.3.2 交点式 1.4 二次函数与直线的交点 二次函数 1.1 二次函数 二次函数的定义&#xff1a;y a…

贪心算法学习——最长单调递增子序列

目录 ​编辑 一&#xff0c;题目 二&#xff0c;题目接口 三&#xff0c;解题思路和代码 一&#xff0c;题目 给你一个整数数组 nums &#xff0c;找到其中最长严格递增子序列的长度。 子序列 是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组…

STM32F4X SDIO(二) SDIO协议

上一节简单介绍了SD卡的分类&#xff0c;本节将会介绍SD卡的通信协议&#xff0c;也就是SDIO协议。 STM32F4X SDIO&#xff08;二&#xff09;SDIO协议 SD 卡管脚和寄存器SD卡管脚分布SD卡通信协议SD卡寄存器SD卡内部结构 SDIO总线SDIO总线拓扑SDIO总线协议SDIO协议的基本结构…

手写RPC框架

文章目录 什么是RPC框架RPC框架中的关键点通信协议序列化协议动态代理和反射 目前已有的RPC框架手写RPC框架介绍项目框架项目执行流程项目启动 什么是RPC框架 RPC&#xff08;Remote Procedure Call&#xff0c;远程过程调用&#xff09;, 简单来说遵循RPC协议的就是RPC框架. …

编程实例:洗车店会员管理系统软件一卡多项目管理编程

编程实例&#xff1a;洗车店会员管理系统软件一卡多项目管理编程 编程系统化课程总目录及明细&#xff0c;点击进入了解详情。 https://blog.csdn.net/qq_29129627/article/details/134073098?spm1001.2014.3001.5502 1、会员可以直接用手机号&#xff0c;并可以绑定车牌号 2…

进阶JAVA篇- Java 综合基本语法实践(习题一)

路漫漫其修远兮&#xff0c;吾将上下而求索。—— 屈原 目录 第一道题&#xff1a;集合的灵活运用 第二道题&#xff1a;基础编程能力 第三道题&#xff1a; 手写 ArrayList 集合&#xff08;模拟实现 ArrayList 核心API&#xff09; 第四道题&#xff1a;二分查找的应用 第五道…

设置Oracle数据库默认为spfle启动,并且设置数据库SGA大小和PGA大小

本次目标&#xff1a;设置数据库默认为spfle启动&#xff0c;并且数据库SGA大小为2G&#xff0c;PGA大小为200M 1、环境检查 Oracle 9i以后启动的时候默认使用的初始化文件是spfile&#xff0c;但是为了万一还是进行查看一下 首先&#xff0c;确认您的数据库当前是否使用pfi…

事件循环的学习、执行上文、this、执行栈和任务队列

事件循环 对于js运行中的任务&#xff0c;js有一套处理收集&#xff0c;排队&#xff0c;执行的特殊机制&#xff0c;我们称之为事件循环(Even Loop). &#x1f355;js一大特点就是单线程&#xff0c;同一个时间只能做一件事。为了协调事件、用户交互、脚本、UI渲染和网络处理…

FPGA_状态机工作原理

FPGA_状态机介绍和工作原理 状态机工作原理Mealy 状态机模型Moore 状态机模型状态机描述方式代码格式 总结 状态机工作原理 状态机全称是有限状态机&#xff08;Finite State Machine、FSM&#xff09;&#xff0c;是表示有限个状态以及在这些状态之间的转移和动作等行为的数学…

2023年正版win10/win11系统安装教学(纯净版)

第一步&#xff1a;准备一个8G容量以上的U盘。 注意&#xff0c;在制作系统盘时会格式化U盘&#xff0c;所以最好准备个空U盘&#xff0c;防止资料丢失。 第二步&#xff1a;制作系统盘。 安装win10 进入windows官网 官网win10下载地址&#xff1a;https://www.microsoft.c…

【精选】VMware部署ESXI6.5 vCenter Server详解

VMware部署ESXI6.5 vCenter Server 一、ESXi主机介绍1、虚拟机的好处2、为什么要使用虚拟机 二、虚拟化服务器概述1、VSphere物理架构2、体系架构3、VMware vSphere 组件 三、ESXi安装环境1、安装步骤2、使用VMware新建ESXi主机3、初始环境安装 四、创建虚拟机五、安装部署VMwa…

kr 第三阶段(五)32 位逆向

如何寻找 main 函数 对于低版本的 VC 编译器&#xff08;VC 6.0&#xff09;&#xff0c;main 函数在 PE 入口点 mainCRTStartup 函数中是倒数第 3 个函数调用&#xff0c;且参数个数为 3 个&#xff08;wmain 函数为 4 个参数&#xff09;。 对于高版本的 VC 编译器 程序入口…

DTI-ALPS处理笔记

DTI-ALPS处理笔记 前言: 前段时间刚好学习了一下DTI-ALPS处理(diffusion tensor image analysis along the perivascular space ),记录一下,以便后续学习。ALPS是2017年发表在《Japanese Journal of Radiology》的一篇文章首次提出的 (文章地址),主要用于无创评估脑内淋…

锐捷Smartweb管理系统 默认开启Guest账户漏洞

通过弱口令进行登录 guest/guest 文笔生疏&#xff0c;措辞浅薄&#xff0c;望各位大佬不吝赐教&#xff0c;万分感谢。 免责声明&#xff1a;由于传播或利用此文所提供的信息、技术或方法而造成的任何直接或间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c; 文章…

ce从初阶到大牛(两台主机免密登录)

一、配置ssh远程连接 实现两台linux主机之间通过公钥验证能够互相实现免密登陆 1.确认服务程序是否安装 rpm -qa | grep ssh 2.是否启动 ps -aux | grep ssh 3.生成非对称公钥 ssh-keygen -t rsa 4.公钥发送到客户端 cd /root/.ssh/ ssh-copy-id root192.168.170.134 因为…

Apollo 快速上手指南:打造自动驾驶解决方案

快速上手 概述云端体验登录云端仿真环境 打开DreamView播放离线数据包PNC Monitor 内置的数据监视器cyber_monitor 实时通道信息视图福利活动 主页传送门&#xff1a;&#x1f4c0; 传送 概述 Apollo 开放平台是一个开放的、完整的、安全的平台&#xff0c;将帮助汽车行业及自…

搭建帮助中心系统的关键注意事项

帮助中心系统是现代企业不可或缺的一部分。它们提供了一个集中管理和发布信息的平台&#xff0c;使企业能够向客户提供准确、及时且易于访问的帮助文档和知识库。在搭建帮助中心系统之前&#xff0c;务必要仔细考虑这些关键注意事项。 | 选择合适的帮助中心系统软件 考虑功能…

​Vue2响应式原理

目录 初始化 initProps()&#xff1a;父组件传的 props 列表&#xff0c;proxy() 把属性代理到当前实例上 vm._props.xx 变成 vm.xx initData()&#xff1a;判断data和props、methods是否重名&#xff0c;proxy() 把属性代理到当前实例上 this.xx observe()&#xff1a;给…