JavaScript中作用域与闭包深入解析

函数中的作用域

对这些问题的最常见的回答是,JavaScript 拥有基于函数的作用域。也就是,你声明的每一个函数都为自己创建了一个气泡,而且没有其他的结构可以创建它们自己的作用域气泡。但是就像我们一会儿将会看到的,这不完全正确。

但首先,让我们探索一下函数作用域和它的含义。

考虑这段代码:

function foo(a) {var b = 2;// 一些代码function bar() {// ...}// 更多代码var c = 3;
}

在这个代码段中,foo(..) 的作用域气泡包含标识符 abc 和 bar。一个声明出现在作用域 何处 是 无关紧要的,不管怎样,变量和函数属于包含它们的作用域气泡。在下一章中我们将会探索这到底是如何工作的。

bar(..) 拥有它自己的作用域气泡。全局作用域也一样,它仅含有一个标识符:foo

因为 abc,和 bar 都属于 foo(..) 的作用域气泡,所以它们在 foo(..) 外部是不可访问的。也就是,接下来的代码都会得到 ReferenceError 错误,因为这些标识符在全局作用域中都不可用:

bar(); // 失败console.log( a, b, c ); // 3个都失败

然而,所有这些标识符(abc,和 bar)在 foo(..) 内部 都是可以访问的,而且在 bar(..) 内部也都是可用的(假定在 bar(..) 内部没有遮蔽标识符的声明)。

函数作用域支持着这样的想法:所有变量都属于函数,而且贯穿整个函数始终都可以使用和重用(而且甚至可以在嵌套的作用域中访问)。这种设计方式可以十分有用,而且肯定可以完全利用 JavaScript 的“动态”性质 —— 变量可以根据需要接受不同种类型的值。

另一方面,如果你不小心提防,跨越整个作用域存在的变量可能会导致一些意料之外的陷阱。

隐藏于普通作用域

考虑一个函数的传统方式是,你声明一个函数,并在它内部添加代码。但是相反的想法也同样强大和有用:拿你所编写的代码的任意一部分,在它周围包装一个函数声明,这实质上“隐藏”了这段代码。

其实际结果是在这段代码周围创建了一个作用域气泡,这意味着现在在这段代码中的任何声明都将绑在这个新的包装函数的作用域上,而不是前一个包含它们的作用域。换句话说,你可以通过将变量和函数围在一个函数的作用域中来“隐藏”它们。

为什么“隐藏”变量和函数是一种有用的技术?

有多种原因驱使着这种基于作用域的隐藏。它们主要是由一种称为“最低权限原则”的软件设计原则引起的note-leastprivilege,有时也被称为“最低授权”或“最少曝光”。这个原则规定,在软件设计中,比如一个模块/对象的API,你应当只暴露所需要的最低限度的东西,而“隐藏”其他的一切。

这个原则可以扩展到用哪个作用域来包含变量和函数的选择。如果所有的变量和函数都在全局作用域中,它们将理所当然地对任何嵌套的作用域来说都是可访问的。但这回违背“最少……”原则,因为你(很可能)暴露了许多你本应当保持为私有的变量和函数,而这些代码的恰当用法是不鼓励访问这些变量/函数的。

例如:

function doSomething(a) {b = a + doSomethingElse( a * 2 );console.log( b * 3 );
}function doSomethingElse(a) {return a - 1;
}var b;doSomething( 2 ); // 15

在这个代码段中,变量 b 和函数 doSomethingElse(..) 很可能是 doSomething(..) 如何工作的“私有”细节。允许外围的作用域“访问” b 和 doSomethingElse(..) 不仅没必要而且可能是“危险的”,因为它们可能会以种种意外的方式,有意或无意地被使用,而这也许违背了 doSomething(..) 假设的前提条件。

一个更“恰当”的设计是讲这些私有细节隐藏在doSomething(..)的作用域内部,比如:

function doSomething(a) {function doSomethingElse(a) {return a - 1;}var b;b = a + doSomethingElse( a * 2 );console.log( b * 3 );
}doSomething( 2 ); // 15

现在,b 和 doSomethingElse(..) 对任何外界影响都是不可访问的,而是仅仅由 doSomething(..) 控制。它的功能和最终结果不受影响,但是这种设计将私有细节保持为私有的,这通常被认为是好的软件。

避免冲突

将变量和函数“隐藏”在一个作用域内部的另一个好处是,避免两个同名但用处不同的标识符之间发生无意的冲突。冲突经常导致值被意外地覆盖。

例如:

function foo() {function bar(a) {i = 3; // 在外围的for循环的作用域中改变`i`console.log( a + i );}for (var i=0; i<10; i++) {bar( i * 2 ); // 噢,无限循环!}
}foo();

bar(..) 内部的赋值 i = 3 意外地覆盖了在 foo(..) 的for循环中声明的 i。在这个例子中,这将导致一个无限循环,因为 i 被设定为固定的值 3,而它将永远 < 10

bar(..) 内部的赋值需要声明一个本地变量来使用,不论选用什么样的标识符名称。var i = 3; 将修复这个问题(并将为 i 创建一个前面提到的“遮蔽变量”声明)。一个 另外的 选项,不是代替的选项,是完全选择另外一个标识符名称,比如 var j = 3;。但是你的软件设计也许会自然而然地使用相同的标识符名称,所以在这种情况下利用作用域来“隐藏”你的内部声明是你最好/唯一的选择。

全局“名称空间”

变量冲突(很可能)发生的一个特别强有力的例子是在全局作用域中。当多个库被加载到你的程序中时,如果它们没有适当地隐藏它们的内部/私有函数和变量,那么它们可以十分容易地互相冲突。

这样的库通常会在全局作用域中使用一个足够独特的名称来创建一个单独的变量声明,它经常是一个对象。然后这个对象被用作这个库的一个“名称空间”,所有要明确暴露出来的功能都被作为属性挂在这个对象(名称空间)上,而不是将它们自身作为顶层词法作用域的标识符。

例如:

var MyReallyCoolLibrary = {awesome: "stuff",doSomething: function() {// ...},doAnotherThing: function() {// ...}
};
模块管理

另一种回避冲突的选择是通过任意一种依赖管理器,使用更加现代的“模块”方式。使用这些工具,没有库可以向全局作用域添加任何标识符,取而代之的是使用依赖管理器的各种机制,要求库的标识符被明确地导入到另一个指定的作用域中。

应该可以看到,这些工具并不拥有可以豁免于词法作用域规则的“魔法”功能。它们简单地使用这里讲解的作用域规则,来强制标识符不会被注入任何共享的作用域,而是保持在私有的,不易冲突的作用域中,这防止了任何意外的作用域冲突。

因此,如果你选择这样做的话,你可以防御性地编码,并在实际上不使用依赖管理器的情况下,取得与使用它们相同的结果。关于模块模式的更多信息参见第五章。

函数作为作用域

我们已经看到,我们可以拿来一段代码并在它周围包装一个函数,而这实质上对外部作用域“隐藏”了这个函数内部作用域包含的任何变量或函数声明。

例如:

var a = 2;function foo() { // <-- 插入这个var a = 3;console.log( a ); // 3} // <-- 和这个
foo(); // <-- 还有这个console.log( a ); // 2

虽然这种技术“可以工作”,但它不一定非常理想。它引入了几个问题。首先是我们不得不声明一个命名函数 foo(),这意味着这个标识符名称 foo 本身就“污染”了外围作用域(在这个例子中是全局)。我们要不得不通过名称(foo())明确地调用这个函数来使被包装的代码真正运行。

如果这个函数不需要名称(或者,这个名称不污染外围作用域),而且如果这个函数能自动地被执行就更理想了。

幸运的是,JavaScript 给这两个问题提供了一个解决方法。

var a = 2;(function foo(){ // <-- 插入这个var a = 3;console.log( a ); // 3})(); // <-- 和这个console.log( a ); // 2

让我们分析一下这里发生了什么。

首先注意,与仅仅是 function... 相对,这个包装函数语句以 (function... 开头。虽然这看起来像是一个微小的细节,但实际上这是一个重大改变。与将这个函数视为一个标准的声明不同的是,这个函数被视为一个函数表达式。

注意: 区分声明与表达式的最简单的方法是,这个语句中(不仅仅是一行,而是一个独立的语句)“function”一词的位置。如果“function”是这个语句中的第一个东西,那么它就是一个函数声明。否则,它就是一个函数表达式。

这里我们可以观察到一个函数声明和一个函数表达式之间的关键不同是,它的名称作为一个标识符被绑定在何处。

比较这前两个代码段。在第一个代码段中,名称 foo 被绑定在外围作用域中,我们用 foo() 直接调用它。在第二个代码段中,名称 foo 没有被绑定在外围作用域中,而是被绑定在它自己的函数内部。

换句话说,(function foo(){ .. }) 作为一个表达式意味着标识符 foo 仅能在 .. 代表的作用域中被找到,而不是在外部作用域中。将名称 foo 隐藏在它自己内部意味着它不会没必要地污染外围作用域。

匿名与命名

你可能对函数表达式作为回调参数再熟悉不过了,比如:

setTimeout( function(){console.log("I waited 1 second!");
}, 1000 );

这称为一个“匿名函数表达式”,因为 function()... 上没有名称标识符。函数表达式可以是匿名的,但是函数声明不能省略名称 —— 那将是不合法的JS程序。

匿名函数表达式可以快速和很容易地键入,而且许多库和工具往往鼓励使用这种代码惯用风格。然而,它们有几个缺点需要考虑:

  1. 在栈轨迹上匿名函数没有有用的名称可以表示,这可能会使得调试更加困难。

  2. 没有名称的情况下,如果这个函数需要为了递归等目的引用它自己,那么就需要很不幸地使用 被废弃的 arguments.callee 引用。另一个需要自引用的例子是,当一个事件处理器函数在被触发后想要把自己解除绑定。

  3. 匿名函数省略的名称经常对提供更易读/易懂的代码很有帮助。一个描述性的名称可以帮助代码自解释。

内联函数表达式 很强大且很有用 —— 匿名和命名的问题并不会贬损这一点。给你的函数表达式提供一个名称就可以十分有效地解决这些缺陷,而且没有实际的坏处。最佳的方法是总是命名你的函数表达式:

setTimeout( function timeoutHandler(){ // <-- 看,我有一个名字!console.log( "I waited 1 second!" );
}, 1000 );

立即调用函数表达式

var a = 2;(function foo(){var a = 3;console.log( a ); // 3})();console.log( a ); // 2

得益于包装在一个 () 中,我们有了一个作为表达式的函数,我们可以通过在末尾加入另一个 () 来执行这个函数,就像 (function foo(){ .. })()。第一个外围的 ( ) 使这个函数变成表达式,而第二个 () 执行这个函数。

这个模式是如此常见,以至于几年前开发者社区一致同意给它一个术语:IIFE,它表示“立即被调用的函数表达式”(Immediately Invoked Function Expression)。

当然,IIFE不一定需要一个名称 —— IIFE的最常见形式是使用一个匿名函数表达式。虽然少见一些,但与匿名函数表达式相比,命名的IIFE拥有前述所有的好处,所以它是一个可以采用的好方式。

var a = 2;(function IIFE(){var a = 3;console.log( a ); // 3})();console.log( a ); // 2

传统的IIFE有一种稍稍变化的形式,一些人偏好这样:(function(){ .. }())。仔细观察不同之处。在第一种形式中,函数表达式被包在 ( ) 中,然后用于调用的 () 出现在它的外侧。在第二种形式中,用于调用的 () 被移动到用于包装的 ( ) 内侧。

这两种形式在功能上完全相同。这纯粹是一个你偏好的风格的选择。

IIFE的另一种十分常见的变种是,利用它们实际上只是函数调用的事实,来传入参数值。

例如:

var a = 2;(function IIFE( global ){var a = 3;console.log( a ); // 3console.log( global.a ); // 2})( window );console.log( a ); // 2

我们传入 window 对象引用,但是我们将参数命名为 global,这样我们对于全局和非全局引用就有了一个清晰的文体上的划分。当然,你可以从外围作用域传入任何你想要的东西,而且你可以将参数命名为任何适合你的名称。这几乎仅仅是文体上的选择。

这种模式的另一种应用解决了一个小问题:默认的 undefined 标识符的值也许会被不正确地覆盖掉,而导致意外的结果。通过将参数命名为undefined,同时不为它传递任何参数值,我们就可以保证在一个代码块中 undefined 标识符确实是是一个未定义的值。

undefined = true; // 给其他的代码埋地雷!别这么干!(function IIFE( undefined ){var a;if (a === undefined) {console.log( "Undefined is safe here!" );}})();

IIFE 还有另一种变种,它将事情的顺序倒了过来,要被执行的函数在调用和传递给它的参数 之后 给出。这种模式被用于 UMD(Universal Module Definition —— 统一模块定义)项目。一些人发现它更干净和易懂一些,虽然有点儿繁冗。

var a = 2;(function IIFE( def ){def( window );
})(function def( global ){var a = 3;console.log( a ); // 3console.log( global.a ); // 2});

def 函数表达式在这个代码段的后半部分被定义,然后作为一个参数(也叫 def)被传递给在代码段前半部分定义的 IIFE 函数。最后,参数 def(函数)被调用,并将 window 作为 global 参数传入。

块儿作为作用域

虽然函数是最常见的作用域单位,而且当然也是在世面上流通的绝大多数 JS 中最为广泛传播的设计方式,但是其他的作用域单位也是可能的,而且使用这些作用域单位可以导致更好、对于维护来说更干净的代码。

JavaScript 之外的许多其他语言都支持块儿作用域,所以有这些语言背景的开发者习惯于这种思维模式,然而那些主要在 JavaScript 中工作的开发者可能会发现这个概念有些陌生。

但即使你从没用块儿作用域的方式写过一行代码,你可能依然对 JavaScript 中这种极其常见的惯用法很熟悉:

for (var i=0; i<10; i++) {console.log( i );
}

我们在 for 循环头的内部直接声明了变量 i,因为我们意图很可能是仅在这个 for 循环内部的上下文环境中使用 i,而实质上忽略了这个变量实际上将自己划入了外围作用域中(函数或全局)的事实。

这就是有关块儿作用域的一切。尽可能靠近地,尽可能局部地,在变量将被使用的位置声明它。另一个例子是:

var foo = true;if (foo) {var bar = foo * 2;bar = something( bar );console.log( bar );
}

我们仅在 if 语句的上下文环境中使用变量 bar,所以我们将它声明在 if 块儿的内部是有些道理的。然而,当使用 var 时,我们在何处声明变量是无关紧要的,因为它们将总是属于外围作用域。这个代码段实质上为了代码风格的原因“假冒”了块儿作用域,并依赖于我们要管好自己,不要在这个作用域的其他地方意外地使用 bar

从将信息隐藏在函数中,到将信息隐藏在我们代码的块儿中,块儿作用域是一种扩展了早先的“最低 权限 暴露原则”note-leastprivilege的工具。

再次考虑这个for循环的例子:

for (var i=0; i<10; i++) {console.log( i );
}

为什么要用仅将(或者至少是,仅 应当)在这个 for 循环中使用的变量 i 去污染一个函数的整个作用域呢?

但更重要的是,开发者们也许偏好于 检查 他们自己来防止在变量预期的目的之外意外地(重)使用它们,例如如果你试着在错误的地方使用变量会导致一个未知变量的错误。对于变量 i 的块儿作用域(如果它是可能的话)将使 i 仅在 for 循环内部可用,使得如果在函数的其他地方访问 i 将导致一个错误。这有助于保证变量不会被糊涂地重用或者难于维护。

但是,悲惨的现实是,表面上看来,JavaScript 没有块儿作用域的能力。

更确切地说,直到你再深入一些才有。

with

我们在第二章中学习了 with。虽然它是一个使人皱眉头的结构,但它确实是一个(一种形式的)块儿作用域的例子,它从对象中创建的作用域仅存在于这个 with 语句的生命周期中,而不在外围作用域中。

try/catch

一个鲜为人知的事实是,JavaScript 在 ES3 中明确指出在 try/catch 的 catch 子句中声明的变量,是属于 catch 块儿的块儿作用域的。

例如:

try {undefined(); //用非法的操作强制产生一个异常!
}
catch (err) {console.log( err ); // 好用!
}console.log( err ); // ReferenceError: `err` not found

如你所见,err 仅存在于 catch 子句中,并且在你试着从其他地方引用它时抛出一个错误。

注意: 虽然这种行为已经被明确规定,而且对于几乎所有的标准JS环境(也许除了老IE)来说都是成立的,但是如果你在同一个作用域中有两个或多个 catch 子句,而它们又各自用相同的标识符名称声明了它们表示错误的变量时,许多 linter 依然会报警。实际上这不是重定义,因为这些变量都安全地位于块儿作用域中,但是 linter 看起来依然会恼人地抱怨这个事实。

为了避免这些不必要的警告,一些开发者将他们的 catch 变量命名为 err1err2,等等。另一些开发者干脆关闭 linter 对重复变量名的检查。

catch 的块儿作用域性质看起来像是一个没用的,只有学院派意义的事实,但是参看附录B来了解更多它如何有用的信息。

let

至此,我们看到 JavaScript 仅仅有一些奇怪的小众行为暴露了块儿作用域功能。如果这就是我们拥有的一切,而且许多许多年以来这 确实就是 我们拥有的一切,那么块作用域对 JavaScript 开发者来说就不是非常有用。

幸运的是,ES6 改变了这种状态,并引入了一个新的关键字 let,作为另一种声明变量的方式伴随着 var

let 关键字将变量声明附着在它所在的任何块儿(通常是一个 { .. })的作用域中。换句话说,let 为它的变量声明隐含地劫持了任意块儿的作用域。

var foo = true;if (foo) {let bar = foo * 2;bar = something( bar );console.log( bar );
}console.log( bar ); // ReferenceError

使用 let 将一个变量附着在一个现存的块儿上有些隐晦。它可能会使人困惑 —— 在你开发和设计代码时,如果你不仔细注意哪些块儿的作用域包含了变量,并且习惯于将块儿四处移动,将它们包进其他的块儿中,等等。

为块儿作用域创建明确的块儿可以解决这些问题中的一些,使变量附着在何处更加明显。通常来说,明确的代码要比隐晦或微妙的代码好。这种明确的块儿作用域风格很容易达成,而且它与块儿作用域在其他语言中的工作方式匹配得更自然:

var foo = true;if (foo) {{ // <-- 明确的块儿let bar = foo * 2;bar = something( bar );console.log( bar );}
}console.log( bar ); // ReferenceError

我们可以在一个语句是合法文法的任何地方,通过简单地引入一个 { .. } 来为 let 创建一个任意的可以绑定的块儿。在这个例子中,我们在 if 语句内部制造了一个明确的块儿,在以后的重构中将整个块儿四处移动可能会更容易,而且不会影响外围的 if 语句的位置和语义。

注意: 另一个明确表达块儿作用域的方法,参见附录B。

在第四章中,我们将讲解提升(hoisting),它讲述关于声明在它们所出现的整个作用域中都被认为是存在的。

然而,使用 let 做出的声明将 不会 在它们所出现的整个块儿的作用域中提升。如此,直到声明语句为止,声明将不会“存在”于块儿中。

{console.log( bar ); // ReferenceError!let bar = 2;
}
垃圾回收

块儿作用域的另一个有用之处是关于闭包和释放内存的垃圾回收。我们将简单地在这里展示一下,但是闭包机制将在第五章中详细讲解。

考虑这段代码:

function process(data) {// 做些有趣的事
}var someReallyBigData = { .. };process( someReallyBigData );var btn = document.getElementById( "my_button" );btn.addEventListener( "click", function click(evt){console.log("button clicked");
}, /*capturingPhase=*/false );

点击事件的处理器回调函数 click 根本不 需要 someReallyBigData 变量。这意味着从理论上讲,在 process(..) 运行之后,这个消耗巨大内存的数据结构可以被作为垃圾回收。然而,JS引擎很可能(虽然这要看具体实现)仍会将这个结构保持一段时间,因为click函数在整个作用域上拥有一个闭包。

块儿作用域可以解决这个问题,使引擎清楚地知道它不必再保持 someReallyBigData 了:

function process(data) {// 做些有趣的事
}// 运行过后,任何定义在这个块中的东西都可以消失了
{let someReallyBigData = { .. };process( someReallyBigData );
}var btn = document.getElementById( "my_button" );btn.addEventListener( "click", function click(evt){console.log("button clicked");
}, /*capturingPhase=*/false );

声明可以将变量绑定在本地的明确的块儿是一种强大的工具,你可以把它加入你的工具箱。

let 循环

一个使 let 闪光的特殊例子是我们先前讨论的 for 循环。

for (let i=0; i<10; i++) {console.log( i );
}console.log( i ); // ReferenceError

在 for 循环头部的 let 不仅将 i 绑定在 for 循环体中,而且实际上,它会对每一次循环的 迭代 重新绑定 i,确保它被赋予来自上一次循环迭代末尾的值。

这是描绘这种为每次迭代进行绑定的行为的另一种方式:

{let j;for (j=0; j<10; j++) {let i = j; // 每次迭代都重新绑定console.log( i );}
}

这种为每次迭代进行的绑定有趣的原因将在第五章中我们讨论闭包时变得明朗。

因为 let 声明附着于任意的块儿,而不是外围的函数作用域(或全局),所以在重构代码时可能会有一些坑需要额外小心:现存的代码拥有对函数作用域的 var 声明有隐藏的依赖,但你想要用 let 来取代 var

考虑如下代码:

var foo = true, baz = 10;if (foo) {var bar = 3;if (baz > bar) {console.log( baz );}// ...
}

这段代码可以相当容易地重构为:

var foo = true, baz = 10;if (foo) {var bar = 3;// ...
}if (baz > bar) {console.log( baz );
}

但是,当使用块儿作用域变量时要小心这样的变化:

var foo = true, baz = 10;if (foo) {let bar = 3;if (baz > bar) { // <-- 移动时不要忘了`bar`console.log( baz );}
}

附录B介绍了一种块作用域的(更加明确的)替代形式,它可能会在这些场景下提供更易于维护/重构的更健壮的代码。

const

除了 let 之外,ES6 还引入了 const,它也创建一个块儿作用域变量,但是它的值是固定的(常量)。任何稍后改变它的企图都将导致错误。

var foo = true;if (foo) {var a = 2;const b = 3; // 存在于包含它的`if`作用域中a = 3; // 没问题!b = 4; // 错误!
}console.log( a ); // 3
console.log( b ); // ReferenceError!

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

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

相关文章

vue创建项目报错Fail to check for updates

网上查了文章说更换淘宝镜像地址啥的 改了地址后依然报错显示Fail to check for updates 并且装包时报错Failed to get response from https://registry.npmmirror.com/binary-mirror-config 既然又是淘宝镜像问题&#xff0c;直接干脆不用淘宝的地址 npm config set regis…

iPhone的iOS系统:定义移动智能体验,引领科技潮流之巅

来自&#xff1a;dlshuhua.com/post/83721.html 在移动智能设备领域&#xff0c;iPhone一直以其出色的性能和独特的用户体验脱颖而出。而这一切的背后&#xff0c;离不开其强大的操作系统——iOS。iOS系统不仅为iPhone提供了强大的性能支持&#xff0c;更通过不断创新和升级&a…

蓝桥杯备考随手记: 数位分解

1. 什么是数位分解 数位分解是将一个数拆分成它的各个数位的过程。每个数位代表了数字在该位上的权重。 例如&#xff0c;对于整数12345&#xff0c;数位分解可以得到以下结果&#xff1a; 万位&#xff1a;1千位&#xff1a;2百位&#xff1a;3十位&#xff1a;4个位&#…

产品经理的自我修养

点击下载《产品经理的自我修养》 1. 前言 在产品领域取得成功的关键在于持续的激情。只有保持热情不减,我们才能克服各种困难,打造出卓越的产品。 如果你真心渴望追求产品之路,我强烈建议你立即行动起来,亲自参与实际的产品创作。无论是建立一个网站、创建一个社群,还是…

Dubbo 负载均衡算法说明

https://cn.dubbo.apache.org/zh-cn/overview/core-features/load-balance/ 在集群负载均衡时&#xff0c;Dubbo 提供了多种均衡策略&#xff0c;缺省为 weighted random 基于权重的随机负载均衡策略。 具体实现上&#xff0c;Dubbo 提供的是客户端负载均衡&#xff0c;即由 …

【前端学习——js篇】4.浅拷贝与深拷贝

具体可见https://github.com/febobo/web-interview 4.浅拷贝与深拷贝 ①栈内存与堆内存 栈内存&#xff08;Stack Memory&#xff09; 栈内存用于存储基本类型的变量和引用类型的变量引用&#xff08;即指向堆内存中实际数据的指针&#xff09;。当一个函数被调用时&#xf…

Mysql的日志管理,备份与回复

目录 一、Mysql日志管理 1、日志的默认位置及配置文件 2、日志分类 2.1错误日志 2.2通用查询日志 2.3二进制日志 2.4慢查询日志 2.5中继日志 3、日志配置 4、日志查询 4.1查询通用日志是否开启 4.2查询二进制日志是否开启 4.3查看慢查询日志是否开启 4.4查询慢查…

Vivado Lab Edition

Vivado Lab Edition 是完整版 Vivado Design Suite 的独立安装版本 &#xff0c; 包含在生成比特流后对赛灵思 FPGA 进行编程和 调试所需的所有功能。通常适用于在如下实验室环境内进行编程和调试&#xff1a; 实验室环境中的机器所含磁盘空间、内存和连 接资源较少。Vivad…

python数据实时传给unity工程并绘制出来

python # 服务器端代码 import socket import random import struct import time# 创建一个服务器Socket server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM)# 监听的地址和端口 host 127.0.0.1 port 12345# 绑定地址和端口 server_socket.bind((host, port…

纯分享万岳外卖跑腿系统客户端源码uniapp目录结构示意图

系统买的是商业版&#xff0c;使用非常不错有三端uniapp开源代码&#xff0c;自从上次分享uniapp后有些网友让我分享下各个端的uniapp下的各个目录结构说明 我就截图说以下吧&#xff0c;

【Java - 框架 - Lombok】(1) 普通Java项目通过Lombok+Logback完成日志的创建使用 - 快速上手

普通Java项目通过"Lombok""Logback"完成日志的创建使用 - 快速上手&#xff1b; 步骤A 说明 创建"Maven"项目&#xff1b; 图片 步骤B 说明 添加相关依赖项&#xff1b; 图片 代码 <!-- "Lombok"依赖项--> <dependency>&…

c++核心学习--继承2

4.6.7多继承语法 4.6.8菱形继承 利用虚继承解决菱形继承的问题&#xff1a;继承之前加上关键字virtual变为虚继承

经纬恒润RTaW-Pegase:车载网络通信建模与时间特性分析工具

▎RTaW简介 RTaW-Pegase是由法国国家信息与自动化研究所&#xff08;INRIA&#xff09;旗下的RTaW公司开发的产品。它主要用于构建和优化汽车、航空航天以及工业领域的通信网络&#xff0c;包括时间敏感网络&#xff08;TSN&#xff09;、CAN&#xff08;FD&#xff0c;XL&…

math模块篇(六)

文章目录 math.log(x[, base])math.log1p(x)math.log2(x)math.log10(x)math.pow(x, y)math.sqrt(x)math.acos(x)math.asin(x)math.atan(x)math.atan2(y, x)math.cos(x) math.log(x[, base]) math.log(x[, base]) 是 Python 中 math 模块的一个函数&#xff0c;用于计算一个数的…

react-navigation:

我的仓库地址&#xff1a;https://gitee.com/ruanjianbianjing/bj-hybrid react-navigation&#xff1a; 学习文档&#xff1a;https://reactnavigation.org 安装核心包: npm install react-navigation/native 安装react-navigation/native本身依赖的相关包: react-nativ…

开源 | 电动自行车充换电解决方案,从智能硬件到软件系统,全部自主研发

文章目录 一、产品功能部分截图1.手机端&#xff08;小程序、安卓、ios&#xff09;2.PC端 二、小程序体验账号以及PC后台体验账号1.小程序体验账号2.PC后台体验账号关注公众号获取最新资讯 三、产品简介&#xff1f;1. 充电桩云平台&#xff08;含硬件充电桩&#xff09;&…

Codeforces Round 841 (Div. 2) C. Even Subarrays

题目 思路&#xff1a; #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e6 5, inf 1e9, maxm 4e4 5; co…

Java学习之方法

目录 方法 方法声明格式&#xff1a; 调用方式&#xff1a; 详细说明 示例 --方法的声明及调用 语句块 练习 方法的重载(overload) 构成条件 示例 --方法重载 递归结构 缺陷 方法 方法(method)&#xff1a;一段用于完成特定功能的代码片段&#xff0c;类似于其他语…

--内部类

目录 1. 成员内部类 2. 静态内部类 3. 局部内部类 4. 匿名内部类 在Java中&#xff0c;内部类是定义在其他类内部的类。内部类有以下几种形式&#xff1a;成员内部类、静态内部类、局部内部类和匿名内部类。 1. 成员内部类 概念 成员内部类是定义在外部类的成员位置上的类。…

Zigbee技术在智能农业领域的应用研究

Zigbee技术在智能农业领域的应用研究 **摘要&#xff1a;**随着现代信息技术的飞速发展&#xff0c;智能农业已成为当今农业发展的新趋势。Zigbee技术作为一种低功耗、低成本的无线通信技术&#xff0c;在智能农业领域具有广泛的应用前景。本文深入分析了Zigbee技术的原理和特…