Vue源码分析拓展 - 响应式系统搭建

news2024/12/25 10:35:08

Vue里面如何追踪变化

当你把一个普通的JavaScript对象传入Vue实例作为data选项,Vue将遍历此对象所有的属性,并使用Object.defineProperty把这些属性全部转为getter/setter.

这些getter/setter.对用户来说是不可见的,但是在内部他们让Vue能够追踪依赖,在属性被访问和修改时通知变更。

每个组件实例都对应一个watcher实例,它会在组件渲染的过程中把”接触“过的数据属性记录为依赖。之后当依赖项的setter触发时,会通知watcher,从而使它关联的组件重新渲染。

做性能优化

响应原理图

 demo.html


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vue.2.5.1.生命周期</title>
</head>
<body>
  <div id="app"></div>
  <script src="vue.2.5.1.生命周期.js"></script>
  <script>
    // count 自定义策略处理   parentVal == Vue.options.count = undefined, childVal === 1 
    // Vue.config.optionMergeStrategies.count = function (parentVal, childVal, vm) {
    //   return childVal >= 10 ? childVal : 10
    // }
    // Vue 构造函数
    var vm = new Vue({
      el: "#app",
      props: [
        'root'
      ],
      data: { // 对象 !== function mergedInstanceDataFn
        root: "max" // 不代理 $ _ 开头的key,为了避免自身属性的冲突
      },
      // methods: {
      //   root: function () { }
      // },
    });
    // 组件的这些选项,组件时可复用的Vue的实例data,computed,watch,methods以及生命周期钩子等,仅有的例外是el这样根实例特有的选项
    //
    console.log(vm.$options)
    // 响应式系统的搭建
    // Vue.extend({
    //   data: { // data的选项必须是函数  创建组件
    //   }
    // })
  </script>
</body>
</html>

index.html


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>vue-v2.6.14</title>
</head>
<body>
  <div id="app">
    <!-- <componen-a></componen-a> -->
  </div>
  <script src="vue-v2.6.14.js"></script>
  <script>
    // 组件选项的对象——注册局部组件的方式
    var ComponentA = {
      name: "ComponentA",
      template: "<div>123</div>"
    }
    // 属性优先级  data > methods   props > data > methods
    // 他们三个都会成为vm的代理属性
    var vm = new Vue({
      name: 'App',
      el: "#app",
      props: ['root'],
      data: { // 对象 !== function mergedInstanceDataFn
        _root: "JN"
      },
      // methods: {
      //   root: function () { }
      // }
    })
    console.log(vm._root)// 代理形式访问 undefined
    console.log(vm._data._root)// 数据对象上的 _root   JN
    // console.log(vm.$options)
    // json 对象    组件选项对象  Vue.extend()  创建一个子类  实例化子类(createComponentInstanceForVnode) => 组件
    // createComponentInstanceForVnode 实例化组件
    // 组件系统
    // 数据响应系统主要解决的问题:
    // 1.数据观察
    // 2.依赖收集
    // 3.触发更新
    // 4.优化
  </script>
</body>
</html>

Vue.生命周期+data处理.js

// 整体结构是立即执行函数。
// 带两个参数,一个global用作全局变量存放,一个factory工厂函数制造一些初始化方法。
// typeof判断变量类型,以此校验执行环境
// 需要理解逻辑运算符之间的优先级,
// 其中用到了几个不理解的变量,属性和方法,分别是 module、module.exports、define、define.amd
(function (global, factory) {// global = window, factory
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
      (global = global || self, global.Vue = factory());
  // 暴露出factory工厂函数
}(this, function () {
  // ASSET_TYPES 常量复用
  // Vue.options.components
  // Vue.component..   Vue.directive..
  var ASSET_TYPES = [
    'component',
    'directive',
    'filter'
  ];
  // 生命周期钩子常量组
  var LIFECYCLE_HOOKS = [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'beforeDestroy',
    'destroyed',
    'activated',
    'deactivated',
    'errorCaptured',
    'serverPrefetch'
  ];
  var noop = function () { }
  // Vue 全局配置对象
  // 最终config会暴露为Vue.config
  var config = {
    /**
     * 选项合并策略(用于core/util/options)
     */
    //$flow-disable-line 禁用行
    optionMergeStrategies: Object.create(null),
  }
  /**
     * 检查对象是否具有该属性
     */
  var hasOwnProperty = Object.prototype.hasOwnProperty;
  function hasOwn(obj, key) {
    return hasOwnProperty.call(obj, key)
  }
  // 类型检测 对象Object
  function isPlainObject(obj) {
    return toString.call(obj) === '[object Object]'
  }
  /**
 * 检查字符串是否以$or开头_
 */
  function isReserved(str) {
    var c = (str + '').charCodeAt(0); // 获取Unicode编码 0~65535
    return c === 0x24 || c === 0x5F // 十六进制的Unicode 编码 $=== 0x24  _=== 0x5F
  }
  /** 自定义策略
   * 选项覆盖策略是处理
   * 如何合并父选项值和子选项
   * 将值转换为最终值。
   */
  // 自定义处理用在了很多地方,如el,props,watch....
  var strats = config.optionMergeStrategies;
  // 自定义策略处理
  strats.data = function (parentVal, childVal, vm) {
    // 组件的基本原理
    // 聚焦到vm,判别是根实例,还是组件
    if (!vm) { // 组件,不是根实例
      if (childVal && typeof childVal !== "function") {
        console.error("data选项应该为函数 返回组件中每个实例的值");
        return parentVal
      }
      // 处理子组件data的选项
      return mergeDataOrFn(parentVal, childVal)
    }
    // 处理根实例data的选项
    return mergeDataOrFn(parentVal, childVal, vm)
  };
  // 处理根data的选项
  function mergeDataOrFn(
    parentVal,
    childVal,
    vm
  ) {
    if (!vm) { // 子组件data选项
      // 在Vue.extend合并中,这两个函数都应该是函数
      // if (!childVal) {
      //   return parentVal
      // }
      // if (!parentVal) {
      //   return childVal
      // }
      //当parentVal和childVal都存在时,
      //我们需要返回一个函数,该函数返回
      //两个函数的合并结果。。。没必要
      //此处检查parentVal是否为函数,因为
      //它必须是传递先前合并的函数。
      // return function mergedDataFn() {
      //   return mergeData(
      //     typeof childVal === 'function' ? childVal.call(this, this) : childVal,
      //     typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal
      //   )
      // }
    } else { // 根实例data选项
      return function mergedInstanceDataFn() {
        // 实例合并
        return typeof childVal === 'function'
          ? childVal.call(vm, vm)
          : childVal;
      }
    }
  }
  // 所有钩子函数的自定义策略   parentVal === undefined    childVal === function(){}
  function mergeHook(parentVal, childVal) {
    return childVal
      ? parentVal // 数组
        ? parentVal.concat(childVal) // 合并parentVal、childVal
        : Array.isArray(childVal) // 是不是数组
          ? childVal
          : [childVal] // [function(){}]
      : parentVal;
  }
  LIFECYCLE_HOOKS.forEach(hook => {
    strats[hook] = mergeHook;
  })
  /**
   * "所有"选项默认策略
   */
  var defaultStrat = function (parentVal, childVal) {
    return childVal === undefined
      ? parentVal
      : childVal
  };
  function mergeOptions(parent, child, vm) {
    /* 选项规范检测 Components Props Inject Directives */
    var options = {};
    var key;
    for (key in parent) {
      //parent-> 'components', 'directives', 'filters'
      mergeField(key);
    }
    for (key in child) {
      if (!hasOwn(parent, key)) { // 父组件没有这个key,才会扩展key
        mergeField(key);
      }
    }
    // 选项的策略处理;要考虑el,data,生命周期的钩子函数...
    // 自定义策略挂载在(strats对象)  默认策略
    function mergeField(key) { // 组件也会调用,组件包含vue的除根实例特有的el等外的实例
      // console.log(key);
      // 属性值 || 默认策略
      var strat = strats[key] || defaultStrat;
      // 给options扩展key属性
      options[key] = strat(parent[key], child[key], vm, key);
    }
    return options;
  }
  // 执行钩子函数方法
  function callHook(vm, hook) {
    var handlers = vm.$options[hook];
    if (handlers) {
      for (var i = 0, j = handlers.length; i < j; i++) {
        handlers[i].call(vm)
      }
    }
  }
  var sharedPropertyDefinition = {
    enumerable: true, // 可枚举的
    configurable: true, // 可配置的
    get: noop,
    set: noop
  };
  // 代理数据
  // target === vm, sourceKey === "_data", key === key 属性名称
  function proxy(target, sourceKey, key) {
    sharedPropertyDefinition.get = function proxyGetter() {
      return this[sourceKey][key] // this === target === vm;vm._data.root
    };
    sharedPropertyDefinition.set = function proxySetter(val) {
      this[sourceKey][key] = val; // vm._data.root = val
    };
    // vm._data 数据对象
    // vm root    监听 vm.root === vm._data.root; sharedPropertyDefinition 加的钩子
    Object.defineProperty(target, key, sharedPropertyDefinition);
  }
  // 初始化状态值
  function initState(vm) {
    var opts = vm.$options;
    if (opts.data) { // data == mergedInstanceDataFn
      initData(vm);
    } else {
      observe(vm._data = {}, true /* asRootData */);
    }
  }
  // 初始化data
  function initData(vm) {
    // 校验数据对象data是否是一个纯对象
    var data = vm.$options.data;// data = mergedInstanceDataFn
    // 因为beforeCreate钩子可以修改data,所以要再判断一次data当前值是否是函数
    data = vm._data = typeof data === 'function'
      ? data(vm, vm)
      : data || {};
    if (!isPlainObject(data)) {
      data = {};
      console.error(
        'data functions should return an object:\n' +
        'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
        vm
      );
    }
    // 校验data是否与props、methods冲突了【key】
    // 实例上的代理数据
    var keys = Object.keys(data); // 所有key的属性
    var props = vm.$options.props;
    var methods = vm.$options.methods;
    var i = keys.length;
    while (i--) {
      var key = keys[i];
      {
        if (methods && hasOwn(methods, key)) {
          console.error(
            // methods 对象上的 key 属性,已经被定义为 data 数据对象属性
            ("Method \"" + key + "\" has already been defined as a data property."),
            vm
          );
        }
      }
      if (props && hasOwn(props, key)) {
        console.error(
          // data的数据属性 key 因为成为props 的prop;prop是该属性的默认值。
          "The data property \"" + key + "\" is already declared as a prop. " +
          "Use prop default value instead.",
          vm
        );
      } else if (!isReserved(key)) {
        // 数据代理的时候是否有不合理的属性
        // vm._data === 以获取数据对象的引用
        proxy(vm, "_data", key);
      }
    }
    // 观察数据,开启响应式系统之路
    // observe(data, true /* asRootData */);
  }
  function initMixin(Vue) {
    Vue.prototype._init = function (options) {
      var vm = this;
      // a uid
      // 选项合并,给$options插入方法,参数
      vm.$options = mergeOptions(
        // 返回vue选项的引用
        Vue.options, // Vue.options
        options || {}, // options
        vm // Vue
      );
      // 执行钩子函数beforeCreate
      callHook(vm, 'beforeCreate') // 这里可以修改data
      initState(vm); // 数据初始化 data
    };
  }
  // config ,Vue全局配置对象
  function initGlobalAPI(Vue) {
    var configDef = {};
    configDef.get = function () { return config; };
    {
      configDef.set = function (newVal) {
        console.error('不要尝试修改Vue.config的引用',
          'Do not replace the Vue.config object, set individual fields instead.'
        );
      };
    }
    // 让Vue可访问config
    // 不直接写Vue.config而是监听,就是防止Vue.config引用被篡改
    Object.defineProperty(Vue, 'config', configDef);// 监听你对Vue.config属性的访问,暴漏出自定义策略接口
  }
  function initExtend(Vue) {
    /**
      *每个实例构造函数(包括Vue)都有一个唯一的
      *cid。这使我们能够创建包装的“子对象”
      *“构造函数”用于原型继承并缓存它们。
      */
    //  用于原型继承   缓存构造函数
    Vue.cid = 0;
    var cid = 1;
    /**
     * 类继承
     */
    Vue.extend = function (extendOptions) {
      extendOptions = extendOptions || {};
      var Super = this; // Super === Vue
      var SuperId = Super.cid;
      // 缓存检测 cachedCtors
      var cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {});
      // 缓存处理cachedCtors[0] = 子类的引用
      if (cachedCtors[SuperId]) {
        return cachedCtors[SuperId]
      }
      var name = extendOptions.name || Super.options.name;
      if (name) {
        // validateComponentName(name); // 组件名称规范检查
      }
      // 子类 构造函数
      var Sub = function VueComponent(options) {
        this._init(options);
      };
      // 把Vue的原型引用赋给Sub
      // {}.__proto__ = Super.prototype = Vue.prototype
      Sub.prototype = Object.create(Super.prototype);
      Sub.prototype.constructor = Sub;
      Sub.cid = cid++;
      // 组件在初始化 mergeOptions 选项的合并 => 规范的检测 => 策略的处理
      Sub.options = mergeOptions(
        Super.options, // Vue.options
        extendOptions // 组件的选项对象
      );
      Sub['super'] = Super;
      //对于props和computed属性,我们在
      //在扩展原型上扩展时的Vue实例。这
      //避免为创建的每个实例调用Object.defineProperty。
      if (Sub.options.props) {
        initProps$1(Sub);
      }
      if (Sub.options.computed) {
        initComputed$1(Sub);
      }
      // 允许进一步扩展/混合/插件使用 extension/mixin/plugin
      // Sub.extend = Super.extend;
      // Sub.mixin = Super.mixin;
      // Sub.use = Super.use;
      //创建资产寄存器,以便扩展类
      //也可以拥有他们的私人资产。
      // Super == Vue Vue.component【注册全局组件的一种方法】 不等于  Vue.options.components【挂载内置抽象组件】
      ASSET_TYPES.forEach(function (type) {
        Sub[type] = Super[type];
      });
      //启用递归自查找
      // if (name) {
      //   Sub.options.components[name] = Sub;
      // }
      //在扩展时保留对超级选项的引用。
      //稍后在实例化时,我们可以检查Super的选项是否有
      //已更新。
      // Sub.superOptions = Super.options;
      // Sub.extendOptions = extendOptions;
      // Sub.sealedOptions = extend({}, Sub.options);
      // 缓存构造函数
      cachedCtors[SuperId] = Sub;
      return Sub
    };
  }
  // 拿到空对象的引用
  Vue.options = Object.create(null);
  // 添加内置组件'components',指令'directives',过滤器'filters',他们都加一个空对象的引用
  ASSET_TYPES.forEach(function (type) {
    // ASSET_TYPES 常量复用
    Vue.options[type + 's'] = Object.create(null);// Vue.options.components
  });
  function Vue(options) {
    if (!(this instanceof Vue)) { // 得new一个Vue,不然就报错
      console.error('Vue is a constructor and should be called with the `new` keyword');
    }
    // 初始化
    this._init(options);
  }
  initMixin(Vue);
  initGlobalAPI(Vue);
  initExtend(Vue)
  return Vue;
}));

watch.html


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Watch——vue-v2.6.14</title>
</head>
<body>
  <div id="app">
  </div>
  <!-- <script src="vue-v2.6.14.js"></script> -->
  <script>
    // var vm = new Vue({
    //   el: "#app",
    //   data: {
    //     age: 30,
    //   },
    // })
    // vm.$watch("age", function () {
    //   console.log("age被篡改了")
    // })
    // vm.age = 31
    // data 数据对象
    // $watch 方法
    // 数据观测
    // data.list.a  data.list.b   数组数据观测 ,observe【原型观测】
    var data = {
      root: "max",
      age: 30,
      nb: 'min',
      arr: [1, 2, 3, 4],
      list: {
        a: 1,
        b: 2
      }
    }
    // 数据观察   依赖收集
    function observe(data) {
      // for中使用var 会变量提升,导致var的值都是最后赋的值
      for (let key in data) {
        let deps = [];// 依赖收集存储的地方
        let val = data[key];// 原有值,
        if (toString.call(val) === "[object Object]") {
          observe(val)
        }
        Object.defineProperty(data, key, {
          get: function () {
            deps.push(target);
            console.log(key)
            return val;
          },
          set: function (newValue) {// 新值
            if (newValue === val) { return }
            val = newValue;
            deps.forEach(function (dep) {
              dep();
            })
          }
        })
      }
    }
    observe(data)
    console.log(data)
    var target = null;
    function $watch(str, fn) {
      target = fn;
      var arr, obj = data; // 不改变数据源本身
      if (typeof str === "function") {
        str();
        return;
      }
      if (/\./.test(str)) { // 正则判断str里有没有.
        arr = str.split(".");
        arr.forEach(function (key) { // list a
          obj = obj[key];// 1: data.list = {}  2: list.a = 1;
        })
        return;
      }
      data[str]
      // console.log(data[str]);
    }
    // 依赖
    // $watch("list.a", function () {
    //   console.log("list.a 发生了篡改")
    // });
    // 触发更新
    // 函数  render 渲染函数
    function render() {
      with (data) { // 作用域绑定 data.root   data.age   (get钩子函数)
        console.log("姓名" + root + "年龄" + age)//重复依赖收集   数组做处理
      }
    }
    $watch(render, render);
  </script>
</body>
</html>

献上一张AI生成图~

 

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

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

相关文章

Nginx配置汇总

一、Nginx概念 Nginx是目前负载均衡技术中的主流方案&#xff0c;几乎绝大部分项目都会使用它&#xff0c;Nginx是一个轻量级的高性能HTTP反向代理服务器&#xff0c;同时它也是一个通用类型的代理服务器&#xff0c;支持绝大部分协议&#xff0c;如TCP、UDP、SMTP、HTTPS等。…

Java028——Runtime 类

一、Runtime 类介绍 Runtime 类是JDK 提供的运行时类&#xff0c;该类为 Java 程序提供了与当前运行环境相连接的一个通道,Java 程序可以利用该类对当前的运行环境执行一些简单的操作。 二、Runtime 对象的创建 Runtime 类对象不能使用 new 关键字创建&#xff0c;只能通过 …

【LeetCode: 167. 两数之和 II - 输入有序数组 | 双指针专题 】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

C++之final关键字用法(一百六十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

Loki+promtail+Grafana监控docker容器日志

目标&#xff1a;监控docker容器的日志&#xff0c;适用于生成环境 效果&#xff1a; 需要的工具&#xff1a;Loki&#xff0c;promtail&#xff0c;Grafana 通过安装promtail容器收集日志&#xff0c;并把日志发送给loki存储处理&#xff0c;由Grafana展示日志。 参考官网的…

[SSM]MyBatis的注解式开发与PageHelper

目录 十五、MyBatis使用PageHelper 15.1 limit分页 15.2PageHelper插件 第一步&#xff1a;引入依赖pom.xml 第二步&#xff1a;在mybatis-config.xml文件中配置插件 第三步&#xff1a;编写Java代码 十六、MyBatis的注解式开发 16.1Insert 16.2Delete 16.3Update 1…

Java设计模式之结构型-外观模式(UML类图+案例分析)

目录 一、基础概念 二、UML类图 三、角色设计 四、案例分析 五、总结 一、基础概念 外观模式&#xff0c;为子系统中的一组接口提供一个一致的界面&#xff0c;此模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用。 二、UML类图 三、角色设计 角…

自动驾驶与智能网联场地测试一体化装备应用

自动化驾驶层级与结构 L1:能够辅助驾驶员玩车某些驾驶任务制动防抱死系统 (ABS),车身电子稳定系统 (ESP)等,这些配置就是L1级别的运用。 L2:部分自动化,在L2的级别里,必须要具备的是自适应巡航系统,主动车道保持系统自动刹车辅助系统以及自动泊车系统等系统。 L3:有条件…

JavaWeb(2)——HTML、CSS、JS 快速入门

一、JavaScript快速入门 一个完整的JavaScript实现由3个不同部分组成&#xff1a;核心&#xff08;ECMAScript&#xff09;、文档对象模型&#xff08;DOM&#xff09;和浏览器对象模型&#xff08;BOM&#xff09;&#xff0c;如图所示。 ECMAScript是一种通过ECMA-262标准化…

iview table选中项显示在上方tag标签并可以取消

如图表格多选功能选中项显示在table的上方并且支持跨页&#xff0c;table上方加tag标签 <spanclass"select_tips"><Tagv-for"item in selection":key"item.id":name"item.id"closableon-close"handleClose">{{…

Python GUI编程利器:Tkinker中的消息对话框(13)

小朋友们好&#xff0c;大朋友们好&#xff01; 我是猫妹&#xff0c;一名爱上Python编程的小学生。 和猫妹学Python&#xff0c;一起趣味学编程。 今日目标 学习Tkinter中的消息对话框的使用&#xff0c;实现如下效果&#xff1a; 文本消息对话框 可以通过showinfo()创建文…

AtcoderABC258场

A - When? A - When? 题目大意 给定一个整数K&#xff0c;表示从日本标准时间21:00开始经过的分钟数。要求将该时间转换为24小时制的时间&#xff08;HH:MM格式&#xff09;。 思路分析 可直接分时间打印。关于格式&#xff0c;填充0&#xff0c;打印时间&#xff0c;题解…

DP83TG720RWRHARQ1汽车以太网PHY,NTHL020N120SC1 通孔 N-CH 1200V 103A(MOSFET)

DP83TG720RWRHARQ1汽车以太网PHY是一款符合IEEE 802.3bp和Open Alliance标准的汽车以太网物理层收发器。该器件通过屏蔽/屏蔽单双绞线提供传输和接收数据所需的所有物理层功能。该器件支持RGMII与MAC连接。 应用&#xff1a; 远程信息处理控制单元&#xff08;TCU、TBOX&#x…

接口测试 [分享] 自动化测试与持续集成方案--Jmeter 测试接口及性能

目录 前言&#xff1a; 一、什么是接口测试&#xff1f; 二、接口测试的流程 三、编写接口测试脚本 四、接口持续集成 补上性能测试报告&#xff1a; 前言&#xff1a; 接口测试是软件测试中的重要环节&#xff0c;它用于验证系统的不同组件之间的通信和数据传输是否正常…

一起学SF框架系列5.7-模块Beans-BeanDefinition使用

SF如何使用BeanDefinition达成其目标IoC&#xff0c;我们通过跟踪BeanDefinition使用来了解。 使用起点 跟踪SF初始化过程&#xff0c;第一个点在&#xff1a;DefaultListableBeanFactory.preInstantiateSingletons。如下图&#xff1a; RootBeanDefinition是运行时Spring B…

前端白屏检测方案

早期因为浏览器、技术、兼容性等诸多问题&#xff0c;导致网页的显示效果非常的单一&#xff0c;基本都是静态页&#xff0c;后续随着Angular、React、Vue等前端框架的出现&#xff0c;采用SPA单页面应用的方案越来越多。 用户和企业对于页面的稳定性、性能有了更高的诉求&…

openssl为什么从1.1跳跃到3.0,为什么没有2.0版本?

OpenSSL在版本号上从1.1跳跃到3.0是因为在其发展过程中发生了一些特定的情况和变化&#xff0c;导致开发团队做出了这样的决定。以下是一些可能的原因&#xff1a; 历史背景&#xff1a;OpenSSL的版本号体系并不是连续递增的&#xff0c;而是根据项目的发展和变化进行调整。在过…

UFS 15 - UFS RPMB操作

UFS 15 - UFS RPMB操作 1 Request Type Message Delivery&#xff08;请求类型消息传递&#xff09;2 Response Type Message Delivery&#xff08;响应类型消息传递&#xff09;3 Authentication Key Programming3.1 Authentication Key Programming3.2 报文示例3.2.1 Authent…

【工具使用】使用J-link离线下载芯唐MCU固件

一&#xff0c;简介 本文主要介绍如何使用J-link工具&#xff0c;离线下载M483的程序。 二&#xff0c;操作步骤 主要分为以下三个步骤&#xff1a; 1&#xff0c;使用SWD接口连接硬件&#xff1b; 2&#xff0c;配置上位机工程&#xff1b; 3&#xff0c;下载程序到芯片&am…

LiveGBS流媒体平台GB/T28181功能-作为上级平台对接海康大华华为宇视等下级平台监控摄像机NVR硬件执法仪等GB28181设备

LiveGBS作为上级平台对接海康大华华为宇视等下级平台监控摄像机NVR硬件执法仪等GB28181设备 1、背景说明2、部署国标平台2.1、安装使用说明2.2、服务器网络环境2.3、信令服务配置 3、监控摄像头设备接入3.1、海康GB28181接入示例3.2、大华GB28181接入示例3.3、华为IPC GB28181接…