理解Vue源码,从0开始撸了一个简版Vue

vue 的双向绑定、虚拟dom、diff算法等等面试常见问题你可能在几年前就学过了,其中有些人可能看过Vue的源码,了解过Vue是如何实现数据监听和数据绑定这些技术的。不过让从零开始实现一个 vue,你可以吗?

模板语法其实早就存在,在Vue发布之前就有了。Vue除了具备基本的模板编译功能外,新增了很多功能属性,比如数据集data、方法集methods、组件集components等等,当然还具备了数据的响应式功能,具备生命周期函数……
我想如果能够从编译功能开始逐步增加这些功能属性,是否可以体会到尤大当初开发Vue的心路历程?或者至少能够更加清楚的理解Vue源码吧。

简易版Vue基本实现思路
简易版Vue基本实现思路

在这个背景下,我按照自己的理解决定从0开始,开发一个简版的Vue:SSvue。

从类的创建开始

创建一个类,参数为对象options,里面是Vue的各种数据集。这里采用es6语法,出于兼容性考虑的话,可以使用babel做处理。

class SSvue {
    constructor(options) {
    
    }
}

具备数据集data、方法集methods和挂载el

class SSvue {
    constructor(options) {
        const  { data,method, el } = options
        // 数据集
        this.data = data;
        // 方法集
        this.methods = methods;
        // 挂载
        this.el = el
    }
}

具备一定编译功能

遵循单一职责原则,编译功能单独拿出来,创建编译类。这里的编译可以处理Mustache语法(双大括号)以及事件指令。

class SSvue {
    constructor(options) {
        const  { data,methods, el } = options
        // 数据集
        this.data = data;
        // 方法集
        this.methods = methods;
        // 挂载
        this.el = el
        // 编译功能
        new Compile(this)
    }
}

编译类实现。编译类实现了对元素节点和文本节点处理,能够处理其上的Mustache语法和事件指令。

class Compile {
  constructor(vm) {
    this.vm = vm
    this.vm.el = document.querySelector(vm.el);
    this.compile();
  }

  compile() {
    this.replaceData(this.vm.el);
    const documentFragment = this.nodeToFragment(this.vm.el)
    this.vm.el.appendChild(documentFragment);
  }
  
  nodeToFragment(el) {
    let fragment = document.createDocumentFragment();
    let child;
    while (child = el.firstChild) {
      // 将Dom元素移入fragment中
      fragment.appendChild(child);
    }
    return fragment;
  }
  
  replaceData(frag) {
      Array.from(frag.childNodes).forEach(node => {
        let txt = node.textContent;
        let reg = /\{\{(.*?)\}\}/g;
  
        if (this.isTextNode(node) && reg.test(txt)) {
          let replaceTxt = () => {
            node.textContent = txt.replace(reg, (matched, placeholder) => {
              return placeholder.split('.').reduce((val, key) => {
                return val[key];
              }, this.vm);
            });
          };
          replaceTxt();
        }
  
        if (this.isElementNode(node)) {
          Array.from(node.attributes).forEach(attr => {
            if (attr.name.startsWith('@')) {
              const eventName = attr.name.slice(1);
              const methodName = attr.value;
              if (methodName in this.vm.methods) {
                node.addEventListener(eventName, this.vm.methods[methodName].bind(this.vm));
              }
            }
          });
          if (node.childNodes && node.childNodes.length) {
            this.replaceData(node);        
          }
        }
      });
    }
  // 元素节点
  isElementNode(node) {
    return node.nodeType == 1
  }
  // 文本节点
  isTextNode(node) {
    return node.nodeType == 3
  }
}

注意:这个时候使用SSvue,访问data中数据时,需要使用this.data[attr]方式。如果要解决这个问题需要加一层代理,访问代理。在SSvue中添加访问代理方法proxyKeys。

class SSvue {
    constructor(options) {
        const  { data,methods, el } = options
        
        // 数据集
        this.data = data;
        // 数据集代理
        Object.keys(this.data).forEach(key => {
          this.proxyKeys(key);
        });
        // 挂载
        this.el = el
        // 方法集
        this.methods = methods;
        // 编译功能
        new Compile(this)
    }
    
   // 访问代理
   proxyKeys(key) {
    Object.defineProperty(this, key, {
      enumerable: false,
      configurable: true,
      get: function proxyGetter() {
        return this.data[key];
      },
      set: function proxySetter(newVal) {
        this.data[key] = newVal;
      }
    });
  }
}

具备数据的响应式功能

增加Dep类:用于实现订阅发布模式,订阅Watcher对象,发布Watcher对象

增加Observe类:对数据集data数据进行拦截,在拦截过程中,get保存或订阅Watcher对象,set触发或者发布的Watcher对象

增加observe方法:用于对对象数据深层级进行拦截处理

增加Watcher类:用于触发Observe类数据拦截操作,然后以Dep.target为媒介,将当前Watcher对象保存到Dep对象中

总结:通过Observe类实现了对数据集的拦截,创建Watcher时触发get方法,此时Dep类订阅Watcher;设置数据集数据时,触发set方法,此时Dep类发布Watcher,触发update方法,触发回调函数,触发更新

class SSvue {
  constructor(options) {
    ...
    this.data = options.data;
    
    Object.keys(this.data).forEach(key => {
      this.proxyKeys(key);
    });
    new Observe(this.data)
    ...
  }
}
class Dep {
  constructor() {
    this.subs = [];
  }

  addSub(sub) {
    this.subs.push(sub);
  }

  notify() {
    this.subs.forEach(sub => sub.update());
  }
}

class Watcher {
  constructor(vm, exp, fn) {
    this.fn = fn;
    this.vm = vm;
    this.exp = exp;
    Dep.target = this;
    let arr = exp.split('.');
    let val = vm;
    // 手动获取一次data里面的数据 执行Observe添加方法
    arr.forEach(key => {
      val = val[key];
    });
    Dep.target = null;
  }

  update() {
    let arr = this.exp.split('.');
    let val = this.vm;
    arr.forEach(key => {
      val = val[key];
    });
    this.fn(val);
  }
}

class Observe {
  constructor(data) {
    let dep = new Dep();
    for (let key in data) {
      let val = data[key];
      observe(val);
      Object.defineProperty(data, key, {
        get() {
          Dep.target && dep.addSub(Dep.target);
          return val;
        },
        set(newVal) {
          if (val === newVal) {
            return;
          }
          val = newVal;
          observe(newVal);
          dep.notify();
        }
      });
    }
  }
}

function observe(data) {
  if (!data || typeof data !== 'object') return;
  return new Observe(data);
}

编译类Compile增加Watcher,更新函数作为Watcher对象的回调函数

class Compile {
  ...
  replaceData(frag) {
    Array.from(frag.childNodes).forEach(node => {
      let txt = node.textContent;
      let reg = /\{\{(.*?)\}\}/g;

      if (this.isTextNode(node) && reg.test(txt)) {
        let replaceTxt = () => {
          node.textContent = txt.replace(reg, (matched, placeholder) => {
            // 增加Watcher
            new Watcher(this.vm, placeholder, replaceTxt);
            return placeholder.split('.').reduce((val, key) => {
              return val[key];
            }, this.vm);
          });
        };
        replaceTxt();
      }

      if (this.isElementNode(node)) {
        Array.from(node.attributes).forEach(attr => {
          if (attr.name.startsWith('@')) {
            const eventName = attr.name.slice(1);
            const methodName = attr.value;
            if (methodName in this.vm.methods) {
              node.addEventListener(eventName, this.vm.methods[methodName].bind(this.vm));
            }
          }
        });
        if (node.childNodes && node.childNodes.length) {
          this.replaceData(node);        
        }
      }
    });
  }
}

具备计算属性功能

SSvue增加用于处理计算属性功能。

实际就是将计算属性数据集computed打平,将所有计算属性添加到SSvue实例对象上,同时进行拦截。使用计算属性数据时,执行get方法,执行计算属性函数。这里只是实现了基本的计算属性功能。
打平操作也说明了Vue的计算属性computed和数据集data不能有同名属性。

class SSvue {
  constructor(options) {
    ....
    const { computed } = options
    this.computed = computed;
    Object.keys(this.computed).forEach(key => {
      Object.defineProperty(this, key, {
        get: () => {
          return this.computed[key].call(this);
        }
      });
    });
    ...
}

具备watch功能

SSvue增加用以处理watch数据集的功能。

遍历watch集合,创建Watcher对象,实际就是前面的发布订阅模式。不同的是此时的回调函数是watch里面的方法。这里也只是实现了基本功能。

class SSvue {
  constructor(options) {
    ...
    const { watch } = options
    this.watch = watch;
    // 处理watch
    this.initWatch()
  }
  initWatch() {
    for (let key in this.watch) {
      new Watcher(this, key, this.watch[key]);
    }
  }
}

具备过滤器功能

增加过滤器功能。

过滤器功能就比较简单了,可以说是一种语法糖或者面向切面编程。需要拦截双大括号,判断是否有过滤器标识。然后在编译类更新内容函数replaceTxt里面添加部分代码。

class Compile {
  ...
  replaceData(frag) {
    Array.from(frag.childNodes).forEach(node => {
      let txt = node.textContent;
      let reg = /\{\{(.*?)\}\}/g;

      if (this.isTextNode(node) && reg.test(txt)) {
        let replaceTxt = () => {
          node.textContent = txt.replace(reg, (matched, placeholder) => {
            // 判断过滤器是否存在
            let key = placeholder.split('|')[0].trim();
            let filter = placeholder.split('|')[1];
            if (filter) {
              let filterFunc = this.vm.filters[filter.trim()];
              if (filterFunc) {
                new Watcher(this.vm, key, replaceTxt);
                return filterFunc.call(this.vm, key.split('.').reduce((val, k) => {
                  return val[k];
                }, this.vm));
              }
            } else {
              // 增加Watcher
             new Watcher(this.vm, placeholder, replaceTxt);
             return placeholder.split('.').reduce((val, key) => {
                return val[key];
             }, this.vm);
            }
          });
        };
        replaceTxt();
      }
    });
  }
}

具备组件注册功能

遵循单一职责原则,增加组件类。

组件本质上就是一个特殊的SSvue实例。这里参照Vue,在SSvue中创建静态方法extend,用以生成创建组件的构造函数。

class SSvue {
  constructor(options) {
    this._init(options)
  }
  
  ...
  _init(options){
    if(!options){
     return
    }
    const { data, methods, el, computed, components, watch, filters, template } = options
    this.data = data;
    this.methods = methods;
    this.computed = computed;
    this.watch = watch;
    this.filters = filters;
    this.components =components;
    this.template = template
    this.el = el
    Object.keys(this.data).forEach(key => {
      this.proxyKeys(key);
    });
    // 注意如果没有计算属性会报错
    this.computed && Object.keys(this.computed).forEach(key => {
      Object.defineProperty(this, key, {
        get: () => {
          return this.computed[key].call(this);
        }
      });
    });
    new Observe(this.data)
    this.initWatch()
    // 创建编译模板以及编译赋值
    new Compile(this);
  }
   
  // 增加extend方法
  static extend(options) {
    const Super = this;
    
    // 闭包保存options
    const Sub = (function (){
      return function VueComponent() {
        let instance = new Super(options);
        Object.assign(this, instance);
      }
    })()
    // 创建一个基于SSvue的构造函数
    Sub.prototype = Object.create(Super.prototype);
    Sub.prototype.constructor = Sub;
    // 合并参数
    Sub.options = Object.assign({}, Super.options, options);
    return Sub;
  }
}

编译类Compile增加处理自定义组件功能。

着重说明一下,这里自定义组件使用的模板是template属性。

SSvue本身没有使用template属性,而采用的是查询挂载el下面的dom结构。所以自定义组件的template需要单独处理。增加单独处理方法handleTemplate。然后replaceData方法里增加创建组件功能。

class Compile {
  constructor(vm) {
    this.vm = vm
    // 兼容处理组件
    this.vm.el = this.handleTemplate(vm)
    this.compile();
  }
  handleTemplate(vm){
    if(vm.el && typeof vm.el === 'string') {
      return document.querySelector(vm.el)
    }
    // 将字符串转为dom
    const div = document.createElement('div')
    div.innerHTML = vm.template
    return div.firstChild
  }
  ...
  replaceData(frag) {
    Array.from(frag.childNodes).forEach(node => {

      ...
      if (this.isElementNode(node)) {
        ...
        let nodeName = node.nodeName.toLowerCase();
        
        // 如果存在components 则创建组件
        if (this.vm.components && this.vm.components[nodeName]) {
          let ComponentConstructor = this.vm.components[nodeName];
          let component = new ComponentConstructor();
          node.parentNode.replaceChild(component.el, node);
        }
        if (node.childNodes && node.childNodes.length) {
          this.replaceData(node);        
        }
      }
    });
  }
  // 元素节点
  isElementNode(node) {
    return node.nodeType == 1
  }
  // 文本节点
  isTextNode(node) {
    return node.nodeType == 3
  }
}

组件注册功能给我的启发比较大。之前一直不理解为什么Vue可以做到局部更新。写了简版的Vue,明白组件实际是特殊的Vue实例,组件本身就有一套更新机制,组件本身就是局部更新。

具备Vuex功能

Vuex本质上Vue的实例属性,而且只能是Vue而不能是组件的,否则就不能全局使用了。将其独立出来,创建Store类。

class Store {
  constructor(options) {
    this.state = options.state;
    this.mutations = options.mutations;
    this.actions = options.actions;
  }

  commit(type, payload) {
    this.mutations[type](this.state, payload);
  }

  dispatch(type, payload) {
    this.actions[type]({ commit: this.commit.bind(this), state: this.state }, payload);
  }
}

使用时创建实例,作为SSvue实例的参数。同时需要将store中的state数据加入Observe类中,让其具备响应式特性。

let store = new Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  actions: {
    increment(context) {
      context.commit('increment');
    }
  }
});

...
class SSvue {
  constructor(options) {
    this._init(options)
  }
  _init(options){
    if(!options){
     return
    }
    const { data, methods, el, computed, components, watch, filters, template, store } = options
    ...
    this.store = store

    Object.keys(this.data).forEach(key => {
      this.proxyKeys(key);
    });
    this.computed && Object.keys(this.computed).forEach(key => {
      Object.defineProperty(this, key, {
        get: () => {
          return this.computed[key].call(this);
        }
      });
    });
    new Observe(this.data)
    // 响应式功能
    this.store && new Observe(this.store.state)

    this.initWatch()
    // 创建编译模板以及编译赋值
    new Compile(this);
  }
}

具备插件注册功能

SSvue增加静态方法use,用于接收插件。实际就是运行插件里的install方法,将不同种类的插件加到SSvue上,以原型的形式、静态数据形式或其他。

class SSvue {
  constructor(options) {
    // ...
    this.plugins = [];
  }

  static use(plugin) {
    plugin.install(this);
  }
}
const MyPlugin = {
  install(ssvue) {
    // 在这里添加你的插件代码
    // 你可以添加全局方法或属性
    ssvue.myGlobalMethod = function () {
      console.log('This is a global method');
    }
    // 添加实例方法
    ssvue.prototype.$myMethod = function (methodOptions) {
      // 一些逻辑……
    }
  }
}

new SSvue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
});

SSvue.use(MyPlugin);

具备生命周期函数

生命周期就简单了,生命周期是切面编程的体现。只需要在对应时机或者位置加上生命周期函数就可以了。

SSvue类增加处理生命周期函数方法_callHook,以及SSvue实例增加对应的生命周期方法beforeCreate和mounted。

class SSvue {
  constructor(options) {
    this._init(options)
  }

  _callHook(lifecycle) {
    this.$options[lifecycle] && this.$options[lifecycle].call(this);
  }
  
    _init(options){
    if(!options){
     return
    }
    ...
    // 创建编译模板以及编译赋值
    this._callHook('beforeCreate');
    new Compile(this);
    this._callHook('mounted')
  }
}

编译类Compile增加created生命周期

class Compile {
  constructor(vm) {
    ...
    this.compile();
    this.vm._callHook('created');
  }
}

测试用例和所有功能代码

测试用例

let MyComponent = SSvue.extend({
    template: '<div>这是一个组件{{message}}</div>',
    data:{
      message: 'Hello, Component!'
    }
    // ...
  })
  let store = new Store({
    state: {
      count: 0
    },
    mutations: {
      increment(state) {
        state.count++;
      }
    },
    actions: {
      increment(context) {
        context.commit('increment');
      }
    }
  });
  const ssvue = new SSvue({
    el: '#app',
    store,
    data: {
      name: 'canf1oo'
    },
    components: {
      'my-component': MyComponent
    },
    computed: {
      computedName(){
        return  this.name + '我是计算属性'
      }
    },
    filters: {
      addSSvue(val){
        return val + 'SSvue'
      }
    },
    watch: {
      name(){
        console.log('测试室测试试试')
      },
      computedName(){
        console.log('测试室测试试试12232323')
      }
    },
    methods: {
      clickMe() {
        this.name = 'click me'
        this.store.commit('increment');
        this.$plugin()
      }
    },
    beforeCreate() {
      console.log('beforeCreate')
    },
    created() {
      console.log('created')
    },
    mounted() {
      console.log('mounted')
    },
  });
  const MyPlugin = {
    install(ssvue) {
      // 在这里添加你的插件代码
      // 你可以添加全局方法或属性
      ssvue.myGlobalMethod = function () {
        console.log('This is a global method');
      }
      // 添加实例方法
      ssvue.prototype.$plugin = function (methodOptions) {
        // 一些逻辑……
        console.log('我是插件')
      }
    }
  }

  SSvue.use(MyPlugin)

SSvue测试模板

<div id="app">
    <button @click="clickMe">{{name}}</button>
    <button @click="clickMe">{{name | addSSvue}}</button>

    <button>{{computedName}}</button>
    <span>{{store.state.count}}</span>
    <my-component></my-component>
  </div>

代码地址:github

整体上没有路由功能,所以是一个静态的非单页面的简版Vue。距离真实的Vue还差很远,比如props,比如render函数,比如插槽,比如作用域插槽,比如vdom等等。感兴趣的可以继续添加。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

03-学成在线内容管理模块之课程查询

课程查询 需求分析 教学机构人员点击课程管理按钮进入课程查询界面,在课程列表页面输入查询条件查询课程的信息 当不输入查询条件时默认会全部课程信息,输入查询条件会查询符合条件的课程信息,约束条件是本教学机构查询本机构的课程信息 数据模型(model工程) 课程查询功能…

MAT工具定位分析Java堆内存泄漏问题方法

原创/朱季谦 一、MAT概述与安装 MAT&#xff0c;全称Memory Analysis Tools&#xff0c;是一款分析Java堆内存的工具&#xff0c;可以快速定位到堆内泄漏问题。该工具提供了两种使用方式&#xff0c;一种是插件版&#xff0c;可以安装到Eclipse使用&#xff0c;另一种是独立版…

论文浅尝 | 用于开放式文本生成的事实增强语言模型

笔记整理&#xff1a;李煜&#xff0c;东南大学硕士&#xff0c;研究方向为知识图谱 链接&#xff1a;https://proceedings.neurips.cc/paper_files/paper/2022/hash/df438caa36714f69277daa92d608dd63-Abstract-Conference.html 1. 动机 生成式语言模型&#xff08;例如 GPT-3…

【图论】最小生成树(python和cpp)

文章目录 一、声明二、简介三、代码C代码Python代码 一、声明 本帖持续更新中如有纰漏望指正&#xff01; 二、简介 &#xff08;a&#xff09;点云建立的k近邻图&#xff08;b&#xff09;k近邻图上建立的最小生成树 最小生成树 (Minimum Spanning Tree&#xff0c;简称 M…

使用Tauri开发桌面应用

本文是对视频 Tauri入门教程[1]的学习与记录 Tauri官网[2] 对 node版本有要求 创建项目及目录介绍: 项目的目录结构如下 可以安装推荐的插件 执行npm run tauri build出错,根据 https://github.com/tauri-apps/tauri/issues/7430 执行 yarn add -D tauri-apps/cli && y…

缺陷预测(一)——论文复现

运行CGCN文件 问题一&#xff1a;CNN输入维度的问题出现的问题解决问题原因 问题二&#xff1a;mix时&#xff0c;输入的train_in和train_gen.inputs数据格式不一致出现的问题解决问题 最终结果 问题一&#xff1a;CNN输入维度的问题 出现的问题 数据集改好之后&#xff0c;出…

Python Flask: 构建轻量级、灵活的Web应用

大家好&#xff0c;我是涛哥&#xff0c;今天为大家分享 Python Web开发框架 Flask&#xff0c;文章3400字&#xff0c;阅读大约15分钟&#xff0c;大家enjoy~~ Flask是一个流行的Python Web框架&#xff0c;以其轻量级、灵活和易学的特性受到开发者的喜爱。本文将深入探讨Flas…

微服务简单理解与快速搭建

分布式和微服务 含义 微服务架构 微服务架构风格是一种将一个单一应用程序开发为一组小型服务的方法&#xff0c;每个服务运行在自己的进程中&#xff0c;服务间通信采用轻量级通信机制(通常用HTTP资源API)。这些服务围绕业务能力构建并且可通过全自动部署机制独立部署。这些服…

C语言进阶之指针(2)

✨ 猪巴戒&#xff1a;个人主页✨ 所属专栏&#xff1a;《C语言进阶》 &#x1f388;跟着猪巴戒&#xff0c;一起学习C语言&#x1f388; 目录 前情回顾 1.数组参数&#xff0c;指针参数 1.1一维数组传参 1.2二维数组传参 1.3一级指针传参 1.4二级指针传参 思考&#xf…

算法萌新闯力扣:同构字符串

力扣题&#xff1a;同构字符串 开篇 对于字符串相关的题目&#xff0c;哈希表经常会使用到&#xff0c;这道题更是如此&#xff0c;还用到了两个哈希表。拿下它&#xff0c;你对字符串题目的理解就会更上一层楼。 题目链接:205.同构字符串 题目描述 代码思路 看完题目后&a…

Django实战项目-学习任务系统-任务完成率统计

接着上期代码内容&#xff0c;继续完善优化系统功能。 本次增加任务完成率统计功能&#xff0c;为更好的了解哪些任务完成率高&#xff0c;哪些任务完成率低。 该功能完成后&#xff0c;学习任务系统1.0版本就基本完成了。 1&#xff0c;编辑urls配置文件&#xff1a; ./mysi…

原论文一比一复现 | 更换 RT-DETR 主干网络为 【ResNet-50】【ResNet-101】【ResNet-152】| 对比实验必备

本专栏内容均为博主独家全网首发,未经授权,任何形式的复制、转载、洗稿或传播行为均属违法侵权行为,一经发现将采取法律手段维护合法权益。我们对所有未经授权传播行为保留追究责任的权利。请尊重原创,支持创作者的努力,共同维护网络知识产权。 更深层的神经网络更难训练。…

香港科技大学广州|机器人与自主系统学域博士招生宣讲会—电子科技大学专场!!!(暨全额奖学金政策)

在机器人和自主系统领域实现全球卓越—机器人与自主系统学域 硬核科研实验室&#xff0c;浓厚创新产学研氛围&#xff01; 教授亲临现场&#xff0c;面对面答疑解惑助攻申请&#xff01; 一经录取&#xff0c;享全额奖学金1.5万/月&#xff01; &#x1f559;时间&#xff1a;…

基于谐波参数空间的卷积神经网络自动三维牙齿分割

论文连接&#xff1a;https://www.sciencedirect.com/science/article/abs/pii/S1524070320300151 机构&#xff1a; a英国卡迪夫大学计算机科学与信息学院 b中国科学院大学北京 c中国科学院计算技术研究所北京 d深圳大数据研究院&#xff0c;深圳518172 代码链接&#x…

4路光栅尺磁栅尺编码器解码转换5MHz高速差分信号转Modbus TCP网络模块 YL97-RJ45

特点&#xff1a; ● 光栅尺磁栅尺解码转换成标准Modbus TCP协议 ● 光栅尺5V差分信号直接输入&#xff0c;4倍频计数 ● 模块可以输出5V的电源给光栅尺供电 ● 高速光栅尺磁栅尺计数&#xff0c;频率可达5MHz ● 支持4个光栅尺同时计数&#xff0c;可识别正反转 ● 可网…

Looking for downloadable pre-built shared indexes关闭

这个功能很烦,把他关闭就行了 PyCharm的“Looking for downloadable pre-built shared indexes”是指PyCharm IDE中的一个功能&#xff0c;用于搜索和下载可共享的预构建索引。 这个功能的主要用途是帮助开发人员在开发过程中快速地获取和使用预构建的索引&#xff0c;以提高…

算法笔记-第七章-链表(未完成)

算法笔记-第七章-链表 链表的遍历链表结点的个数链表的头插法!链表删除元素链表反转例题思路一:原地反转思路二:头插法链表去除重复元素(有些复杂了)思路题目一题目二链表的遍历 #include<cstdio> const int N = 100; struct Node {int data, next;//表示的是当前数据和…

基于K7的PXIPXIe数据处理板(Kintex-7 FMC载板)

基于PXI&PXIe总线架构的高性能数据预处理FMC 载板&#xff0c;板卡具有 1 个 FMC&#xff08;HPC&#xff09;接口&#xff0c;1 个 X8 PCIe 和1个PCI主机接口&#xff1b;板卡采用 Xilinx 的高性能 Kintex-7 系列 FPGA 作为实时处理器&#xff0c;实现 FMC 接口数据的采集…

网络安全-学习手册

前言 前几天发布了一篇 网络安全&#xff08;黑客&#xff09;自学 没想到收到了许多人的私信想要学习网安黑客技术&#xff01;却不知道从哪里开始学起&#xff01;怎么学 今天给大家分享一下&#xff0c;很多人上来就说想学习黑客&#xff0c;但是连方向都没搞清楚就开始学习…

【postgresql】CentOS7 安装Pgweb

Pgweb Pgweb是PostgreSQL的一个基于web的数据库浏览器&#xff0c;用Go编写&#xff0c;可在Mac、Linux和Windows机器上运行。以零依赖性的简单二进制形式分布。非常易于使用&#xff0c;并具有适当数量的功能。简单的基于web和跨平台的PostgreSQL数据库浏览器。 特点 跨平台…