前端沙箱浅析

news2024/11/25 8:22:01

前言

沙箱,即sandbox

通常解释为:沙箱是一种安全机制,为运行中的程序提供隔离环境。常用于执行未经测试或者不受信任的程序或代码,它会为待执行的程序创建一个独立的执行环境,内部程序的执行不会影响外部程序的运行。

常见的一些沙箱使用场景:

  • 在线代码编辑器,如codesandboxleetcode
  • jsonp请求的数据,未知的第三方js的测试执行等
  • vue服务端渲染等、模板中表达式计算等

通用概念的简单描述:

  • 主应用:在微前端方案中,主应用通常负责全局资源加载、分配、控制,也称为基座。如用户的登录和全局状态管理等
  • 子应用:在微前端方案中,子应用通常是一个独立运行的web应用,也称为微应用
  • qiankun:一款开源解决方案

实现方式

基于技术方案,大致有如下几种实现:

  • 仅作为demo参考,非完整解决方案

  • 基于属性diff实现

  • 基于iframe实现

  • 基于Proxy实现

  • 基于ShadowRealm实现

部分内容官方实现比较繁复,我们这里尽量简化说明。本次不会涉及到太多关于微前端相关的知识。下面来看详细介绍。

JS沙箱

IIFE

我们知道在JavaScript中目前有三种作用域(scope):全局作用域(global scope)、函数作用域(function scope)、块级作用域(block scope)。

通常可以通过把一段代码封装到一个函数中实现作用域的隔离。这种方式是基于IIFE(Immediately Invoked Function Expression)立即执行函数来实现。

简单示例

(function testFn(){
    const a = 1;
    console.log(a);// 1
})();

console.log(a) // Uncaught ReferenceError: a is not defined

经典jquery实现

(function (window) {
    var jQuery = function (selector, context) {
        return new jQuery.fn.init(selector, context);
    }
    jQuery.fn = jQuery.prototype = function () {
        //原型上的方法,即所有jQuery对象都可以共享的方法和属性
    }
    jQuery.fn.init.prototype = jQuery.fn;
    window.jQeury = window.$ = jQuery; // 暴露到全局的方法
})(window);

需要注意的是 IIFE 只能实现一个简易的沙箱,并不算一个独立的运行环境。虽然外部不能访问函数内部,但函数内部可以访问外部的全局变量,有污染全局的风险。

eval

eval() 函数会将传入的字符串当做 JavaScript 代码进行执行。

eval的副作用是非常大的,官方反复声明这一点。一般出于安全和性能方面的考虑,我们不建议在实际业务代码中使用eval

简单示例

console.log(eval("1 + 2")); // 3

const person = eval("({name:'张三'})");
console.log(person.name); // "张三"

由于eval 执行的代码可以访问闭包和全局,因此会导致代码注入等安全问题,如:

console.log(eval( this.window === window )); // true 

关于eval还有一个比较有意思的地方:直接调用和间接调用,详细的用法这里推荐一篇文章:eval 的一些不为人知道的用法 。下面看一个示例

function testEval() {
    var x = 1, y = 2;
    // 直接调用
    console.log(eval("x + y")); // 3
    // 间接调用1
    var copyEval = eval;
    console.log(copyEval("x + y")); // ReferenceError: x is not defined
    // 间接调用2
    (0, eval)("x + y"); // ReferenceError: x is not defined
}
testEval();

使用eval实现沙箱:沙箱中执行的程序,所访问的变量应该来源于沙箱环境,而非全局环境,因此最简单的做法就是给待执行程序添加上下文环境。但这要求程序中获取变量要添加一个执行上下文环境的前缀,显然是不友好的。

// 执行上下文环境
const ctx = {
  func: (v) => {
    console.log(v);
  },
  foo: "foo",
};

function sandbox(code, ctx) {
  eval(code); // 为执行程序构造了一个函数作用域
}

// 待执行程序
const code = `
    ctx.foo = 'bar'
    ctx.func(ctx.foo)
`;

sandbox(code, ctx); // bar
new Function

Function构造函数创建一个新的Function 对象。直接调用这个构造函数可以动态创建函数。

new Function ([arg1[, arg2[, ...argN]],] functionBody) 

简单示例

const sum = new Function('a', 'b', 'return a + b'); 
console.log(sum(1, 2)); //3 

new Function默认被创建于全局环境,因此运行时只能访问全局变量和自身的局部变量。不能访问它被创建时所在作用域的变量。

let a = 1;
function testFunc() {
    let a = 2;
    return new Function('return a;');
}
console.log(testFunc()); // 1

new Function eval更好的替代方案。但还是没有解决访问全局作用域的问题。

with

with 一般用于扩展一个语句的作用域链。它是半沙箱模式。什么是半沙箱模式?with将某个对象添加到作用域链的顶部,如果在沙箱中有某个未使用命名空间的变量,跟作用域链中的某个属性同名,则这个变量将指向这个属性值。如果沒有同名的属性,则拋出 ReferenceError异常。当然在严格模式下是禁止使用with语句的

简单示例

function foo(obj) {
	with (obj) {
		a = 2;
	}
}
var o1 = {
	a: 3
};
var o2 = {
	b: 3
}

foo(o1);
console.log(o1.a);// 2

foo(o2);
console.log(o2.a);// underfined
console.log(a);// 2,a被泄漏到全局作用域上

由于with是半沙箱模式,会优先从沙箱提供的执行上下文中查找变量,但同时,当提供的执行上下文环境中没有找到某变量时,会沿着作用域链向上查找(在非严格模式下,会自动在全局作用域创建一个全局变量),有可能会对外部环境产生影响。

// 执行上下文环境
const ctx = {
  func: (v) => {
    console.log(v);
  },
  foo: "foo",
};
function sandbox(code, ctx) {
  with (ctx) {
    // 将 ctx 添加到作用域顶端
    eval(code);
  }
}

// 待执行程序
const code = `
    foo = 'bar'
    func(foo)
`;
sandbox(code, ctx); // bar
with + new Function

配合 with用法可以稍加限制沙箱作用域,当提供的执行环境中找不到某一变量时,还是会去上一级作用域链中进行遍历查找,污染或篡改全局环境。

const ctx = {
  func: (v) => {
    console.log(v);
  },
  foo: "foo",
};
function sandbox(code) {
  code = "with (ctx) {" + code + "}";
  return new Function("ctx", code);
}

// 待执行程序
const code = `
    foo = 'bar'
    func(foo)
`;
sandbox(code)(ctx); // bar
基于diff实现的快照沙箱(SnapshotSandbox)

此方式实现较为简单,主要用于某些不支持 proxy 的低版本浏览器中,原理是基于 diff 方式实现的。

简单来说,存在两个变量: windowSnapshot 保存 window 上面的快照信息,modifyPropsMap 保存沙箱环境与外部环境不同的快照信息。

当沙箱激活后,将 window 的全部属性存储到 windowSnapshot,同时将 modifyPropsMap 存储的沙箱环境加载到 window 上;退出沙箱后,利用 windowSnapshot 恢复 window 环境,将发生变化的属性存储到 modifyPropsMap

但由此会产生一个问题:沙箱启用过程有可能会存在部分新增属性,如示例中的 window.age ,当沙箱关闭后,虽然还原后 window.age 值为 undefined,但该属性依旧存在,污染了全局环境。

这种方式无法支持多实例,因为运行期间所有的属性都是保存在window上的。

源码参考:https://github.com/umijs/qiankun/blob/master/src/sandbox/snapshotSandbox.ts

请添加图片描述

下面以qiankun中的snapshotSandbox源码作为简单案例分析。

function iter(obj, callbackFn) {
    for (const prop in obj) {
        if (obj.hasOwnProperty(prop)) {
            callbackFn(prop);
        }
    }
}
/**
 * 基于 diff 方式实现的沙箱,用于不支持 Proxy 的低版本浏览器
 */
class SnapshotSandbox {
    constructor() {
        this.proxy = window;
        this.type = 'Snapshot';
        this.sandboxRunning = true;
        this.windowSnapshot = {};
        this.modifyPropsMap = {};
        this.active();
    }
    //激活
    active() {
        // 记录当前快照
        this.windowSnapshot = {};
        iter(window, (prop) => {
            this.windowSnapshot[prop] = window[prop];
        });

        // 恢复之前的变更
        Object.keys(this.modifyPropsMap).forEach((p) => {
            window[p] = this.modifyPropsMap[p];
        });

        this.sandboxRunning = true;
    }
    //还原
    inactive() {
        this.modifyPropsMap = {};
        iter(window, (prop) => {
            if (window[prop] !== this.windowSnapshot[prop]) {
                // 记录变更
                this.modifyPropsMap[prop] = window[prop];
              	// 还原window
                window[prop] = this.windowSnapshot[prop];
            }
        });
        this.sandboxRunning = false;
    }
}

// 这里我们可以简单测试一下
window.name = '李四';
let sandbox = new SnapshotSandbox();
let proxy = sandbox.proxy;
proxy.name = '张三';
proxy.age = 18;
console.log(window.name, window.age); // 张三,18
sandbox.inactive();
console.log(window.name, window.age); // 李四,undefined
sandbox.active();
console.log(window.name, window.age); // 张三,18
基于 proxy 的单例沙箱 (legacySandbox)

此方式类似于快照实现。新增了三个变量:用于记录沙箱新增的全局变量addedPropsMapInSandboxInSandbox、用于记录沙箱修改的全局变量 modifiedPropsOriginalValueMapInSandbox、用于记录有变动(包含新增和修改,方便在任意时刻做snapshot)的全局变量 currentUpdatedPropsValueMap

当沙箱激活后,根据 currentUpdatedPropsValueMap 还原window;退出沙箱后,利用 modifiedPropsOriginalValueMapInSandbox 恢复被修改的变量,利用addedPropsMapInSandboxInSandbox删除新增的变量。

legacySandbox 依旧会对 window 进行操作,造成一定的污染,但不会对 window 对象进行遍历,性能优于快照沙箱。

这种实现方式无法支持多实例,否则全局会有多个沙箱更新,造成变量冲突。

源码参考:https://github.com/umijs/qiankun/blob/master/src/sandbox/legacy/sandbox.ts

请添加图片描述

下面以qiankunsingular模式下的legacySandbox源码作为简单案例分析。

function setWindowProp(prop, value, toDelete) {
  if (value === undefined || toDelete) {
    delete window[prop];
  } else {
    window[prop] = value;
  }
};

/**
 * 基于 Proxy 实现的单例沙箱
 */
class SingularProxySandbox {
  constructor() {
    this.proxy = null;
    this.tyep = 'LegacyProxy';
    this.sandboxRunning = true;
    // 存放新增的全局变量
    this.addedPropsMapInSandbox  = new Map(); 
    // 存放沙箱期间更新的全局变量
    this.modifiedPropsOriginalValueMapInSandbox = new Map();
    // 存在新增和修改的全局变量,在沙箱激活的时候使用
    this.currentUpdatedPropsValueMap = new Map();
    const {
      addedPropsMapInSandbox,
      modifiedPropsOriginalValueMapInSandbox,
      currentUpdatedPropsValueMap
    } = this;

    const rawWindow = window;
    //Object.create(null)的方式,传入一个不含有原型链的对象
    const fakeWindow = Object.create(null); 

    const proxy = new Proxy(fakeWindow, {
      set: (_, p, value) => {
        if (this.sandboxRunning) {
          // 如果 window 没有该属性,代表发生了新增,记录到新增属性里
          if (!rawWindow.hasOwnProperty(p)) {
            addedPropsMapInSandbox.set(p, value);
          } else if (!modifiedPropsOriginalValueMapInSandbox.has(p)) {
            // 如果当前window对象有该属性,且未更新过,则记录该属性在window上的初始值
            const originalValue = rawWindow[p];
            modifiedPropsOriginalValueMapInSandbox.set(p, originalValue);
          }
		  // 记录修改属性以及修改后的值
          currentUpdatedPropsValueMap.set(p, value);
          // 必须重新设置 window 对象保证下次 get 时能拿到已更新的数据
          rawWindow[p] = value;
          return true;
        }
        // strict-mode
        return true;
      },

      get(_, p) {
        return rawWindow[p];
      },
    });
    this.proxy = proxy;
  }

  active() {
    if (!this.sandboxRunning) {
      this.currentUpdatedPropsValueMap.forEach((v, p) => setWindowProp(p, v));
    }

    this.sandboxRunning = true;
  }

  inactive() {
    //删除添加的属性,修改已有的属性
    this.modifiedPropsOriginalValueMapInSandbox.forEach((v, p) => setWindowProp(p, v));
    this.addedPropsMapInSandbox.forEach((_, p) => setWindowProp(p, undefined, true));

    this.sandboxRunning = false;
  }
}

// 这里我们可以简单测试一下
window.name = '李四';
const sandbox = new SingularProxySandbox();
const proxy = sandbox.proxy;
proxy.name = '张三';
proxy.age = 18;
console.log(window.name, window.age); // 张三,18
sandbox.inactive();
console.log(window.name, window.age); // 李四,undefined
sandbox.active();
console.log(window.name, window.age); // 张三,18
基于Proxy实现的多例沙箱(ProxySandbox)

在上述单实例的场景中,fakeWindow是一个空对象,没有存储变量的功能,微应用创建的变量最终都是挂载在window上的,这就限制了在同一时刻不能激活多个微应用。

为了支持多沙箱同时运行,我们需要让fakeWindow发挥作用。即对 fakeWindow 进行代理,在激活沙箱后,先找自己沙箱环境的fakeWindow,如果找不到,则去外部环境rawWindow进行查找;当对沙箱内部的 window 对象赋值的时候,会直接操作 fakeWindow,而不会影响到 rawWindow

源码参考:https://github.com/umijs/qiankun/blob/master/src/sandbox/proxySandbox.ts

请添加图片描述

下面以qiankun中的proxySandbox源码作为简单案例分析。

// 复制一份fakeWindow
function createFakeWindow(globalContext) {
  var propertiesWithGetter = new Map();
  var fakeWindow = {};
  // copy the non-configurable property of global to fakeWindow
  // 处理过程省略...
  return {
    fakeWindow,
    propertiesWithGetter,
  }
}
/**
 * 基于 Proxy 实现的多例沙箱
 */
class ProxySandbox {
  active() {
    this.sandboxRunning = true;
  }
  inactive() {
    this.sandboxRunning = false;
  }
  constructor() {
    this.proxy = null;
    this.sandboxRunning = true;
    const rawWindow = window;
    const fakeWindow = createFakeWindow(window).fakeWindow;
    // 代理 fakeWindow
    const proxy = new Proxy(fakeWindow, {
      set: (target, prop, value) => {
        // 只有沙箱开启的时候才操作 fakeWindow
        if (this.sandboxRunning) {
          target[prop] = value;
          return true;
        }
        // strict-mode
        return true;
      },
      get: (target, prop) => {
        // 先查找 fakeWindow,找不到再寻找 rawWindow
        let value = prop in target ? target[prop] : rawWindow[prop];
        return value;
      },
    });
    this.proxy = proxy;
  }
}

// 这里我们可以简单测试一下
window.name = '王五';
window.age = 18;
const sandbox1 = new ProxySandbox();
const sandbox2 = new ProxySandbox();
const proxy1 = sandbox1.proxy;
const proxy2 = sandbox2.proxy;
console.log("====激活时====");
proxy1.name = '张三';
proxy1.age = 12;
proxy2.name = '李四';
console.log("沙箱1:", proxy1.name, proxy1.age); // 张三 12
console.log("沙箱2:", proxy2.name, proxy2.age); // 李四 18,取不到自己的值,取全局
console.log("全局值:", window.name, window.age); //王五 18

console.log("====销毁====");
sandbox1.inactive();
sandbox2.inactive();
proxy1.name = '张三三';
proxy2.name = '李四四';
window.name = '王五五';
console.log("沙箱1:", proxy1.name); //张三
console.log("沙箱2:", proxy2.name); // 李四
console.log("全局值:", window.name); //王五五
天然沙箱iframe

iframe 可以创建一个独立的浏览器原生级别的运行环境,这个环境由浏览器实现与主环境的隔离。iframe 是天然的沙箱,也是当前主流的沙箱实现方案之一。

html5iframe 新增了一个sandbox属性,可以实现带有额外限制的沙箱模式,配合使用postMessage实现子应用与主应用之间的通信。基础用法和属性值可参考 sandbox 属性。

当然,iframe并不是万能的,如果一个页面存在多个沙箱,每个子应用都要与主应用共享一些全局变量,那么实现起来是非常困难的,或者子应用需要与主应用共享路由是无法直接实现的。

其它实现
  • ShadowRealm API :此方案还在提案阶段,不做详述

  • NodeJS中的VMVM2Safeify等,不做详述

CSS沙箱

上面介绍的主要是针对js沙箱,那么对于css,有对应的沙箱吗?严格意义上的css沙箱是不存在的,当前解决方案主要是利用(作用域)隔离。

namespace

@namespace是定义要在CSS 样式表中使用的XML名称空间的规则。已定义的名称空间可用于限制通用类型和属性选择器,以仅选择该名称空间内的元素。

语法

/* Default namespace */
@namespace url(XML-namespace-URL);
@namespace "XML-namespace-URL";

/* Prefixed namespace */
@namespace prefix url(XML-namespace-URL);
@namespace prefix "XML-namespace-URL";
Dynamic StyleSheet

动态样式表,实现方式是通过JS运行时动态加载/卸载微应用样式表来避免样式的冲突。缺点是对于主应用本身与子应用之间会存在样式冲突,多个子应用之间也会有冲突。

css in js

CSS-IN-JS 是将 CSS代码写在JavaScript 代码中,而不是独立的.css文件。这样就可以使用一些JS相关的变量声明,函数判断等方式,实现自由化。优点是可以防止各个组件的样式冲突(自动局部css作用域),缺点是会自动添加选择器前缀,复杂度提升。

常见的CSS-IN-JS 库:Styled-componentsRadiumEmotion

Shadow DOM

这里推荐先查看Web Components和shadow DOM相关文档。

Shadow DOM可以让一个组件拥有自己的影子DOM树,这个DOM树不能在主文档中被访问,拥有局部样式规则和其他特性,具有良好的密封性。Shadow DOM 允许将隐藏的DOM树附加到常规的DOM树中,它以 shadow root 节点为起始根节点,在这个根节点的下方,可以是任意元素,和普通的DOM元素一样。因为这个特点,单个空标签才能渲染出各种各样的复杂内容。

我们可以通过开启show user agent shadow DOM来显示这部分内容。

video为例,我们只需要设置视频地址,就可以实现播放、进度条、全屏等功能,实际上这部分内容在shadow dom中。

请添加图片描述

基础概念

  • Shadow host:一个常规 DOM 节点,Shadow DOM 会被附加到这个节点上(宿主元素)
  • Shadow treeShadow DOM 内部的 DOM
  • Shadow rootShadow tree 的根节点

基础用法:

我们可以使用Element.attachShadow来将一个shadow root附加到任意元素上。

/* mode
open: 可以通过页面内的 JavaScript 方法来获取 Shadow DOM
closed: 相反,即不可以~
*/
let shadow = elementRef.attachShadow({mode: 'open'});
let shadow = elementRef.attachShadow({mode: 'closed'});

请添加图片描述

CSS Module

CSS的规则都是全局的,任何一个组件的样式规则,都对整个页面有效。产生局部作用域的唯一方法,就是使用一个独一无二的类名,不会与其他选择器重名。

这可能是最为常见的处理方式。通过hash实现类似于命名空间的方法,类名是动态生成且是唯一的。我们(基于webpack)以VueReact为例简单说明,当然我们只从局部作用域分析,其它功能的实现,如全局作用域、组合、定制、继承、变量等规则就不逐一介绍,各配置和用法自行查阅资料。

Vue

Vue Loader支持Scoped CSSCSS Module,我们这里不讨论Scoped CSS

示例如下,我们在两个组件中使用相同的类名,编译结果是不同的。

请添加图片描述

React

示例如下,我们在两个组件中使用相同的类名,编译结果是不同的。

请添加图片描述

沙箱逃逸

“沙箱于作者而言是一种安全策略,但于使用者而言可能是一种束缚”。从沙箱诞生之初,开发者都在尝试通过各种途径摆脱这种束缚,称为“沙箱逃逸”。在实际项目中,受限于技术或场景需求,我们一般要求部分内容处于沙箱之中,部分内容处于沙箱之外,这显然是对原有框架的“破坏”;

我们以qiankun为例(使用的是Dynamic StyleSheet),其中一个常见的css问题是:弹窗/下拉等组件通常为了避免出现定位问题,会优先选择插入到body下,我们加入一些样式后,在子应用独立运行时表现是正常的,而在基座中运行时会发现自定义的样式失效了。

为何出现这种情况,我们简单分析一下。

  • 原则上各个应用之间应该是严格沙箱模式,即应用A的变量和样式不能影响到应用B的变量和样式,这个设计是没问题的
  • 子应用被激活时,所有css会被统一加上前缀div[data-qiankun="micro-xxx"]保证其生效作用域
  • 子应用被激活时,其本身的htmlbody等标签会被替换成div(标准中要保证这些标签全局唯一),此操作会导致append to body插入到文档根节点,而非我们认知的子应用的根节点。此时这些Dom其实已经“逃逸”出子应用了,而css还保留在原有的子应用局部作用域

那么如何解决呢

  • 官方提供一个方案:将所有需要“逃离”的样式单独写在一个文件中,在激活子应用时,告知主应用不对css处理,即保证这些样式是全局生效的。但是维护成本比较高,且不符合书写习惯
  • 手动修改append to body的位置,使其插入到子应用的根节点,而非文档根节点

补充说明

  • Proxy API:在ES6中提出,可以创建一个对象的代理,实现自定义的拦截和操作。了解Vue3响应式原理的话,对Proxy/Reflect的用法应该很清楚

  • 对于Module Federation(某种意义上更为轻量级的微前端实现方案),可以让跨应用间做到模块共享(真正的插拔式的便捷使用)。比如应用A想使用应用B中的组件C,通过模块联邦可以直接在A中import('B/C')。相关内容大家可以着重学习一下,很有意思

  • 现代沙箱机制,大多是通过模拟、代理来实现环境隔离,因此必须要考虑一些特殊语法(如变量提升等)会导致作用域发生变化,诸如此类,需要特别注意

  • 在实际项目中,会遇到当前方案没有完全解决的问题,需要我们自己根据所使用的技术、场景去特殊处理。如样式逃逸,在ElementUI中需要手动更改插入body的时机和位置,而对于Ant Design来说可以通过设置getContainer达到目的

  • 一个相对完善的解决方案,必然是多种方案的组合。没有任何一个框架能独立解决所有问题,所以我们需要了解每种方案能够解决什么样的问题,并加以整合

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

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

相关文章

Go第 7 章:数组与切片

Go第 7 章:数组与切片 7.1 为什么需要数组 7.2 数组介绍 数组可以存放多个同一类型数据。数组也是一种数据类型,在 Go 中,数组是值类型。 7.3 数组的快速入门 我们使用数组的方法来解决养鸡场的问题. 7.4 数组定义和内存布局 对上图的总…

QA | 关于信号发生器的扫频功能,您了解多少?

在上期文章中,我们介绍了可编程信号发生器使用中的相关问题,那么关于便携式信号发生器的扫频功能您是否有很多问题呢,今天我们将围绕信号源扫频功能详细解答大家感兴趣的几个问题,快来看看吧!Q1:信号源是否…

Linux操作系统--文件管理(保姆级教程)

文件系统类型的含义 文件系统类型式指文件在存储介质上存放及存储的组织方法和数据结构。 Linux采用虚拟文件系统技术(virtual file system)-VFS 一个世纪的文件系统想要被Linux支持,就必须提供一个符合VFS标准的接口,才能与VFS协同工作&am…

线程的创建与同步

线程的创建与同步线程的概念与实现方式线程的概念进程线程的区别线程使用线程相关的接口函数多线程代码线程并发线程的实现方式线程的同步信号量互斥锁读写锁条件变量线程的安全线程与fork线程的概念与实现方式 线程的概念 进程是正在执行的程序。线程是进程内部的一条执行路…

MXNet的Faster R-CNN(基于区域提议网络的实时目标检测)《4》

这篇主要了解语义分割(semantic segmentation),语义分割是分类中的一个核心知识点,而且这些语义区域的标注和预测都是像素级的。在语义分割中有两个很相似的重要问题,需要注意下:图像分割(image segmentation):将图像分…

一文解决用C语言实现一个链表(全都是细节)

目录前言单链表1.链表中的结点2.链表中的常见操作(1)相关声明格式(2)常见操作的实现(定义)(5)测试前言 链表是指数据使用一个一个的结点连接起来的数据结构,这样的数据结…

(框架)Deepracer Local - 001: 搭建本地环境

Deepracer - 阿里云1. 安装环境2. 预安装脚本3. 从 github 下载 deepracer 代码 并初始化4. 首次运行deepracer1. 安装环境 推荐本地环境: Ubuntu (如果windowns必要的话,就装双系统,我的台式机就是双系统) 云环境: 阿里云,配置如下&#xf…

python简单介绍及基础知识(二)

♥️作者:小刘在这里 ♥️每天分享云计算网络运维课堂笔记,疫情之下,你我素未谋面,但你一定要平平安安,一 起努力,共赴美好人生! ♥️夕阳下,是最美的,绽放,…

Codeforces Round #839 (Div. 3)(A~F)

A. AB?给出长度为3的字符串&#xff0c;计算字符串表示的表达式的值。思路&#xff1a;。AC Code&#xff1a;#include <bits/stdc.h>typedef long long ll; const int N 2e5 5; int t; std::string s;int main() {std::ios::sync_with_stdio(false);std::cin.tie(0);…

立即放弃 TypeScript 的 17 个理由

如果你和我一样&#xff0c;你可能会因为被迫而使用 Typescript。你的公司决定它会成为未来的语言&#xff0c;所以你被迫学习它。起初&#xff0c;您很高兴使用 Typescript。你知道它有很大的潜力&#xff0c;可以帮助你制作更强大的应用程序。但在使用了一段时间后&#xff0…

3.深度学习前的预备知识

3.预备知识 目录 数据操作 N维数组创建数组访问元素 数据预处理读取数据集 处理缺失值转换为张量格式小结 练习线性代数 标量向量矩阵张量张量算法的基本性质降维非降维求和点积矩阵-向量积矩阵-矩阵乘法范数范数和目标 微积分 导数和微分偏导数梯度链式法则 自动微分 一个简…

万字讲解!进阶指针!

今天我们来看进阶指针&#xff0c;还没有看过初阶指针的话建议先看看初阶 (3条消息) 初阶指针---从入门到入坟_KLZUQ的博客-CSDN博客 目录 1. 字符指针 2. 指针数组 3. 数组指针 3.1 数组指针的定义 3.2 &数组名VS数组名 3.3 数组指针的使用 4. 数组参数、指针参数 …

使用Sivarc使PLC程序标准化

前言 由于公司最近做的项目都是同样的&#xff0c;并且都采用S7-1500/S7-1200 与G120 系列做为主控系统&#xff0c;所以我一直在思考一个问题&#xff1a;如何标准化并且快速的编程调试。这样可以极大的缩短项目的调试周期&#xff0c;减少公司工程成本&#xff0c;同时也免去…

英伟达发布528.02 WHQL 新驱动,支持4070 Ti

昨日&#xff0c;英伟达正式发布RTX 4070 Ti显卡&#xff0c;并马不停蹄发布了528.02 WHQL 驱动&#xff0c;支持4070 Ti&#xff0c;新硬件新驱动一次性齐活。 RTX 4070 Ti显卡在光线追踪游戏中的性能表现良好&#xff0c;在现代游戏如《瘟疫传说:安魂曲》&#xff0c;《战锤…

php如何接收支付宝应用网关发送的post请求

php如何接收支付宝应用网关发送的POST请求方式,参数又是GET请求的数据格式配置支付宝应用网关如何接收支付宝异步通知(应用网关接收请求)将&连接的参数分割成数组实例&#xff1a;难点配置支付宝应用网关 首先要在服务器上写一个接口,然后将接口的访问地址设置在支付宝应用…

Java中常用API总结(2)—— System类(含实例解读)

System类一、前言二、概述1.API帮助文档2.概述3.使用方式三、常用方法1.获取当前时间所对应的毫秒值1️⃣格式2️⃣实例3️⃣具体应用2.终止当前正在运行的Java虚拟机1️⃣格式2️⃣实例3.进行数值元素copy1️⃣格式2️⃣实例3️⃣注意事项四、结语一、前言 本文将讲述System类…

springsecurity认证流程

Authentication AuthenticationManager : 认证管理器 实现类&#xff1a; ProviderManager AuthenticationProvider &#xff1a; 实现类: DaoAuthenticationProviderRememberMeAuthenticationProvider 方法: authenticate()supports() : 判断当前AuthenticationProvider是…

dubbo学习笔记2(小d课堂)

dubbo核心架构及流程 企业中dubbo常见的多种开发方式 详解dubbo服务注册中心 Dubbo整合zookeeper 我们主要是改这部分&#xff1a; 然后我们启动本地的zookeeper&#xff0c;再去启动它&#xff1a; 会报错&#xff0c;这是说我们缺少响应的jar包&#xff1a; 就可以了。 我们提…

〖Python 数据库开发实战 - Python与Redis交互篇⑨〗- 利用 redis-py 实现模拟商品秒杀活动案例

文章目录 ❤️‍&#x1f525; 为什么要引入线程池技术 ❤️‍&#x1f525; 通过案例加深线程池技术原理的理解 ❤️‍&#x1f525; 实现多线程模拟商品秒杀案例 - 思路 ❤️‍&#x1f525; 实现多线程模拟商品秒杀案例 - 代码 今天的这一章节我们将来实现 “模拟商品秒杀活…

ES6 课程概述②

文章目录更好的 Unicode 支持更多的字符串 API3-3. [扩展]正则中的粘连标记模板字符串3-5. [扩展]模板字符串标记4-1. 参数默认值使用[扩展]对 arguments 的影响[扩展]留意暂时性死区4-2. 剩余参数4-3. 展开运算符对数组展开 ES6对对象展开 ES7函数柯里化4-5. 明确函数的双重用…