JavaScript秘密花园 - Type Casting,undefined,eval,setTimeout,Auto Semicolon Insertion

JavaScript 秘密花园.pdf 立即下载

类型转换

JavaScript 是弱类型语言,所以会在任何可能的情况下应用强制类型转换。

// 下面的比较结果是:true
new Number(10) == 10; // Number.toString() 返回的字符串被再次转换为数字

10 == '10';           // 字符串被转换为数字
10 == '+10 ';         // 同上
10 == '010';          // 同上
isNaN
(null) == false; // null 被转换为数字 0
                     
// 0 当然不是一个 NaN(译者注:否定之否定)

// 下面的比较结果是:false
10 == 010;
10 == '-10';

ES5 提示: 以 0 开头的数字字面值会被作为八进制数字解析。而在 ECMAScript 5 严格模式下,这个特性被移除了。

为了避免上面复杂的强制类型转换,强烈推荐使用严格的等于操作符。虽然这可以避免大部分的问题,但 JavaScript 的弱类型系统仍然会导致一些其它问题。

内置类型的构造函数(Constructors of built-in types)

内置类型(比如 Number 和 String)的构造函数在被调用时,使用或者不使用 new 的结果完全不同。

new Number(10) === 10;     // False, 对象与数字的比较
Number(10) === 10;         // True, 数字与数字的比较
new Number(10) + 0 === 10; // True, 由于隐式的类型转换

使用内置类型 Number 作为构造函数将会创建一个新的 Number 对象,而在不使用 new 关键字的 Number 函数更像是一个数字转换器。

另外,在比较中引入对象的字面值将会导致更加复杂的强制类型转换。

最好的选择是把要比较的值显式的转换为三种可能的类型之一。

转换为字符串(Casting to a string)

'' + 10 === '10'; // true

将一个值加上空字符串可以轻松转换为字符串类型。

转换为数字(Casting to a number)

+'10' === 10; // true

使用一元的加号操作符,可以把字符串转换为数字。

译者注:字符串转换为数字的常用方法:

+'010' === 10
Number('010') === 10
parseInt
('010', 10) === 10  // 用来转换为整数

+'010.2' === 10.2
Number('010.2') === 10.2
parseInt
('010.2', 10) === 10

转换为布尔型(Casting to a boolean)

通过使用 否 操作符两次,可以把一个值转换为布尔型。

!!'foo';   // true
!!'';      // false
!!'0';     // true
!!'1';     // true
!!'-1'     // true
!!{};      // true
!!true;    // true

undefined 和 null

JavaScript 有两个表示 空 的值,其中比较有用的是 undefined。

`undefined`的值(The value `undefined`)

undefined 是一个值为 undefined 的类型。

这个语言也定义了一个全局变量,它的值是 undefined,这个变量也被称为 undefined。但是这个变量不是一个常量,也不是一个关键字。这意味着它的值可以轻易被覆盖。

ES5 提示: 在 ECMAScript 5 的严格模式下,undefined 不再是 可写的了。但是它的名称仍然可以被隐藏,比如定义一个函数名为 undefined。

下面的情况会返回 undefined 值:

  • 访问未修改的全局变量 undefined。
  • 由于没有定义 return 表达式的函数隐式返回。
  • return 表达式没有显式的返回任何内容。
  • 访问不存在的属性。
  • 函数参数没有被显式的传递值。
  • 任何被设置为 undefined 值的变量。

处理 `undefined` 值的改变(Handling changes to the value of `undefined`)

由于全局变量 undefined 只是保存了 undefined 类型实际值的副本,因此对它赋新值不会改变类型 undefined 的值。

然而,为了方便其它变量和 undefined 做比较,我们需要事先获取类型 undefined 的值。

为了避免可能对 undefined 值的改变,一个常用的技巧是使用一个传递到匿名包装器的额外参数。在调用时,这个参数不会获取任何值。

var undefined = 123;
(function(something, foo, undefined) {
   
// 局部作用域里的 undefined 变量重新获得了 `undefined` 值

})('Hello World', 42);

另外一种达到相同目的方法是在函数内使用变量声明。

var undefined = 123;
(function(something, foo) {
   
var undefined;
   
...

})('Hello World', 42);

这里唯一的区别是,在压缩后并且函数内没有其它需要使用 var 声明变量的情况下,这个版本的代码会多出 4 个字节的代码。

译者注:这里有点绕口,其实很简单。如果此函数内没有其它需要声明的变量,那么 var 总共 4 个字符(包含一个空白字符)就是专门为 undefined 变量准备的,相比上个例子多出了 4 个字节。

使用 `null`(Uses of `null`)

JavaScript 中的 undefined 的使用场景类似于其它语言中的 null,实际上 JavaScript 中的 null 是另外一种数据类型。

它在 JavaScript 内部有一些使用场景(比如声明原型链的终结 Foo.prototype = null),但是大多数情况下都可以使用 undefined 来代替。

为什么不要使用 `eval`

eval 函数会在当前作用域中执行一段 JavaScript 代码字符串。

var foo = 1;
function test() {
   
var foo = 2;
   
eval('foo = 3');
   
return foo;
}
test
(); // 3
foo
; // 1

但是 eval 只在被直接调用并且调用函数就是 eval 本身时,才在当前作用域中执行。

var foo = 1;
function test() {
   
var foo = 2;
   
var bar = eval;
    bar
('foo = 3');
   
return foo;
}
test
(); // 2
foo
; // 3

译者注:上面的代码等价于在全局作用域中调用 eval,和下面两种写法效果一样:

// 写法一:直接调用全局作用域下的 foo 变量
var foo = 1;
function test() {
   
var foo = 2;
    window
.foo = 3;
   
return foo;
}
test
(); // 2
foo
; // 3

// 写法二:使用 call 函数修改 `eval` 执行的上下文为全局作用域
var foo = 1;
function test() {
   
var foo = 2;
   
eval.call(window, 'foo = 3');
   
return foo;
}
test
(); // 2
foo
; // 3

在任何情况下我们都应该避免使用 eval 函数。99.9% 使用 eval 的场景都有不使用 eval 的解决方案。

伪装的 `eval`(`eval` in disguise)

定时函数 setTimeout 和 setInterval 都可以接受字符串作为它们的第一个参数。这个字符串总是在全局作用域中执行,因此 eval 在这种情况下没有被直接调用。

安全问题(Security issues)

eval 也存在安全问题,因为它会执行任意传给它的代码,在代码字符串未知或者是来自一个不信任的源时,绝对不要使用 eval 函数。

结论(In conclusion)

绝对不要使用 eval,任何使用它的代码都会在它的工作方式,性能和安全性方面受到质疑。如果一些情况必须使用到 eval 才能正常工作,首先它的设计会受到质疑,这不应该是首选的解决方案,一个更好的不使用 eval 的解决方案应该得到充分考虑并优先采用。

`setTimeout` 和 `setInterval`

由于 JavaScript 是异步的,可以使用 setTimeout 和 setInterval 来计划执行函数。

注意: 定时处理不是 ECMAScript 的标准,它们在 DOM 被实现。

function foo() {}
var id = setTimeout(foo, 1000); // 返回一个大于零的数字

当 setTimeout 被调用时,它会返回一个 ID 标识并且计划在将来大约 1000 毫秒后调用 foo 函数。 foo 函数只会被执行一次。

基于 JavaScript 引擎的计时策略,以及本质上的单线程运行方式,所以其它代码的运行可能会阻塞此线程。因此没法确保函数会在 setTimeout 指定的时刻被调用。

作为第一个参数的函数将会在全局作用域中执行,因此函数内的 `this` 将会指向这个全局对象。

function Foo() {
   
this.value = 42;
   
this.method = function() {
       
// this 指向全局对象
        console
.log(this.value); // 输出:undefined
   
};
    setTimeout
(this.method, 500);
}
new Foo();

注意: setTimeout 的第一个参数是函数对象,一个常犯的错误是这样的 setTimeout(foo(), 1000),这里回调函数是 foo 的返回值,而不是foo本身。大部分情况下,这是一个潜在的错误,因为如果函数返回 undefined,setTimeout 也不会报错。

`setInterval` 的堆调用(Stacking calls with `setInterval`)

setTimeout 只会执行回调函数一次,不过 setInterval - 正如名字建议的 - 会每隔 X 毫秒执行函数一次。但是却不鼓励使用这个函数。

当回调函数的执行被阻塞时,setInterval 仍然会发布更多的回调指令。在很小的定时间隔情况下,这会导致回调函数被堆积起来。

function foo(){
   
// 阻塞执行 1 秒
}
setInterval
(foo, 100);

上面代码中,foo 会执行一次随后被阻塞了一分钟。

在 foo 被阻塞的时候,setInterval 仍然在组织将来对回调函数的调用。因此,当第一次 foo 函数调用结束时,已经有 10 次函数调用在等待执行。

处理可能的阻塞调用(Dealing with possible blocking code)

最简单也是最容易控制的方案,是在回调函数内部使用 setTimeout 函数。

function foo(){
   
// 阻塞执行 1 秒
    setTimeout
(foo, 100);
}
foo
();

这样不仅封装了 setTimeout 回调函数,而且阻止了调用指令的堆积,可以有更多的控制。 foo 函数现在可以控制是否继续执行还是终止执行。

手工清空定时器(Manually clearing timeouts)

可以通过将定时时产生的 ID 标识传递给 clearTimeout 或者 clearInterval 函数来清除定时,至于使用哪个函数取决于调用的时候使用的是 setTimeout 还是 setInterval。

var id = setTimeout(foo, 1000);
clearTimeout
(id);

清除所有定时器(Clearing all timeouts)

由于没有内置的清除所有定时器的方法,可以采用一种暴力的方式来达到这一目的。

// 清空"所有"的定时器
for(var i = 1; i < 1000; i++) {
    clearTimeout
(i);
}

可能还有些定时器不会在上面代码中被清除(译者注:如果定时器调用时返回的 ID 值大于 1000),因此我们可以事先保存所有的定时器 ID,然后一把清除。

隐藏使用 `eval`(Hidden use of `eval`)

setTimeout 和 setInterval 也接受第一个参数为字符串的情况。这个特性绝对不要使用,因为它在内部使用了 eval。

注意: 由于定时器函数不是 ECMAScript 的标准,如何解析字符串参数在不同的 JavaScript 引擎实现中可能不同。事实上,微软的 JScript 会使用 Function构造函数来代替 eval 的使用。

function foo() {
   
// 将会被调用
}

function bar() {
   
function foo() {
       
// 不会被调用
   
}
    setTimeout
('foo()', 1000);
}
bar
();

由于 eval 在这种情况下不是被直接调用,因此传递到 setTimeout 的字符串会自全局作用域中执行;因此,上面的回调函数使用的不是定义在 bar 作用域中的局部变量 foo。

建议不要在调用定时器函数时,为了向回调函数传递参数而使用字符串的形式。

function foo(a, b, c) {}

// 不要这样做
setTimeout
('foo(1,2, 3)', 1000)

// 可以使用匿名函数完成相同功能
setTimeout
(function() {
    foo
(a, b, c);
}, 1000)

注意: 虽然也可以使用这样的语法 setTimeout(foo, 1000, a, b, c),但是不推荐这么做,因为在使用对象的属性方法时可能会出错。(译者注:这里说的是属性方法内,this 的指向错误)

结论(In conclusion)

绝对不要使用字符串作为 setTimeout 或者 setInterval 的第一个参数,这么写的代码明显质量很差。当需要向回调函数传递参数时,可以创建一个匿名函数,在函数内执行真实的回调函数。

另外,应该避免使用 setInterval,因为它的定时执行不会被 JavaScript 阻塞。

自动分号插入

尽管 JavaScript 有 C 的代码风格,但是它不强制要求在代码中使用分号,实际上可以省略它们。

JavaScript 不是一个没有分号的语言,恰恰相反上它需要分号来就解析源代码。因此 JavaScript 解析器在遇到由于缺少分号导致的解析错误时,会自动在源代码中插入分号。

var foo = function() {
} // 解析错误,分号丢失
test
()

自动插入分号,解析器重新解析。

var foo = function() {
}; // 没有错误,解析继续
test
()

自动的分号插入被认为是 JavaScript 语言最大的设计缺陷之一,因为它能改变代码的行为。

工作原理(How it works)

下面的代码没有分号,因此解析器需要自己判断需要在哪些地方插入分号。

(function(window, undefined) {
   
function test(options) {
        log
('testing!')

       
(options.list || []).forEach(function(i) {

       
})

        options
.value.test(
           
'long string to pass here',
           
'and another long string to pass'
       
)

       
return
       
{
            foo
: function() {}
       
}
   
}
    window
.test = test

})(window)

(function(window) {
    window
.someLibrary = {}

})(window)

下面是解析器"猜测"的结果。

(function(window, undefined) {
   
function test(options) {

       
// Not inserted, lines got merged
        log
('testing!')(options.list || []).forEach(function(i) {

       
}); // <- 插入分号

        options
.value.test(
           
'long string to pass here',
           
'and another long string to pass'
       
); // <- 插入分号

       
return; // <- 插入分号, 改变了 return 表达式的行为
       
{ // 作为一个代码段处理

           
// a label and a single expression statement
            foo
: function() {}
       
}; // <- 插入分号
   
}
    window
.test = test; // <- 插入分号

// The lines got merged again
})(window)(function(window) {
    window
.someLibrary = {}; // <- 插入分号

})(window); //<- 插入分号

注意: JavaScript 不能正确的处理 return 表达式紧跟换行符的情况,虽然这不能算是自动分号插入的错误,但这确实是一种不希望的副作用。

解析器显著改变了上面代码的行为,在另外一些情况下也会做出错误的处理。

前置括号(Leading parenthesis)

在前置括号的情况下,解析器不会自动插入分号。

log('testing!')
(options.list || []).forEach(function(i) {})

上面代码被解析器转换为一行。

log('testing!')(options.list || []).forEach(function(i) {})

log 函数的执行结果极大可能不是函数;这种情况下就会出现 TypeError 的错误,详细错误信息可能是 undefined is not a function。

结论(In conclusion)

建议绝对不要省略分号,同时也提倡将花括号和相应的表达式放在一行,对于只有一行代码的 if 或者 else 表达式,也不应该省略花括号。这些良好的编程习惯不仅可以提到代码的一致性,而且可以防止解析器改变代码行为的错误处理。

OBS技术深度剖析:从开源直播工具到专业制作引擎的进化之路 本文深入剖析了开源直播软件OBS的技术架构与发展历程。从2012年诞生至今,OBS已成长为直播领域的事实标准,其成功源于模块化设计和开源协作模式。文章详细解析了OBS的核心技术:1) 分层架构与插件系统,2) GPU加速的渲染管线,3) 智能音频处理,4) 多平台编码优化。同时探讨了虚拟摄像头、浏览器源等高级功能的实现原理,并提供了性能优化方案和硬件配置建议。随着AI、WebRTC等新技术发展,OBS将持续引领实时内容创作工具的创新,为开发者和创作者提供更强大的技术支持。 阅读详情

相关推荐

别再乱设阻尼了!Abaqus动力分析中瑞利阻尼的α和β到底怎么算?

本文深入解析Abaqus动力分析中瑞利阻尼参数α和β的科学计算方法,帮助工程师准确设置阻尼参数以提升仿真精度。详细介绍了基于模态阻尼比的经典计算方法和多模态优化法,并结合工程案例演示参数设置流程,避免常见的阻尼设置误区。

weixin_42634811的博客 731

JavaScript - Type Casting,undefined,eval,setTimeout,Auto Semicolon Insertion

JavaScript Garden - 原文 类型转换 JavaScript 是弱类型语言,所以会在任何可能的情况下应用强制类型转换。 // 下面的比较结果是:truenew Number(10) == 10; // Number.toString() 返回的字符串被再次转换为数字10 == '10'; // 字符串被转换为数字10 == '+10 '; /...

iteye_6378的博客 196

别再死记硬背了!用一张时序图彻底搞懂Setup和Hold检查的本质

本文深入解析数字IC设计中的Setup和Hold时序检查原理,通过波形图直观展示数据在触发器间的传递过程。详细讲解Setup检查为何关注前一周期的数据,Hold检查为何关注同一周期的数据,并提供实际设计中的考量因素和修复策略,帮助工程师彻底理解时序检查的本质。

weixin_29927965的博客 365

JavaScript 基础之 JavaScript 秘密花园

JavaScript 基础之 JavaScript 秘密花园

qq_43682537的博客 725

Windows安装WSL子系统及docker,以及WSL和docker配置、使用及问题解决

在Windows操作系统中,Ubuntu子系统(也称为Windows Subsystem for Linux, WSL)为开发者提供了一个在Windows环境下运行Linux环境的平台。然而,有时用户在按照Ubuntu子系统或者使用WSL时,可能会遇到各种问题,下面总结一下解决方式。想要在Windows上安装Docker(实际上是基于Hyper-V或者WSL2这两项虚拟化技术),需要开启虚拟化功能,所以不管是对于系统还是硬件都有一定的要求。

sym的博客 9786

金属粘结剂喷射3D打印技术:工艺流程与后处理关键

金属粘结剂喷射(Binder Jetting)是一种创新的增材制造技术,通过逐层喷射粘合剂将金属粉末粘结成型,再经过脱脂和烧结等后处理工序获得最终零件。这项技术的核心优势在于无需支撑结构、可实现批量生产,并且材料利用率高。在3D打印领域,金属BJ技术特别适合中小尺寸、结构复杂且表面质量要求高的零件生产。其工艺流程包括前期数据处理、打印生胚阶段、拆包与清粉,以及关键的渗透、脱脂和烧结后处理环节。后处理工艺的控制直接决定了零件的机械性能和尺寸精度,其中烧结工艺尤为关键,需要精确控制温度曲线和气氛环境。金属BJ打

weixin_35751194的博客 85

《JavaScript 秘密花园》—一些奇奇怪怪的知识

读《JavaScript 秘密花园》,根据自己知识缺漏摘抄部分内容,以及实际代码执行结果

qq_40029828的博客 1327

【自适应滤波】自适应UKF,基于新息自适应调节Q和R,场景为雷达跟踪与滤波、MATLAB实现

本文提出了一种基于新息协方差匹配的自适应UKF(CM-AUKF)算法,用于解决噪声统计未知或时变场景下的目标跟踪问题。通过实时调整过程噪声协方差Q和观测噪声协方差R,CM-AUKF显著提升了跟踪精度和稳定性。仿真结果表明,相比传统UKF,CM-AUKF在位置和速度估计误差上分别降低了约50%和30%,且无需人工调参。该算法适用于无人机导航、雷达跟踪等动态环境,MATLAB代码可直接运行复现结果。自适应曲线显示算法能有效收敛到真实噪声参数,验证了其自适应能力。

985工科博士毕业,专攻定位、导航和滤波等算法研究。从业10年,主要使用MATLAB 330

JavaScript秘密花园

译文地址 bonsaiden.github.io/JavaScript-Garden/zh/#intro.authors 之前被人问到JS一些概念性的东西,感觉很模糊,可能层次比较浅,偏理论的东西实践得较少,发现一处花园, 采点小蜜。 1 数字不是对象的误解 2.toString();报语法错误的原因是因为试图将 点号解析为浮点数的一部分。 (2).toString()可行的。...

weixin_30472035的博客 145

图机器学习(2)——图的可视化

图结构可通过图形化方式直观呈现。节点通常用圆形表示,边则用连接线表示。然而当节点和边数量增加时,绘制清晰的图形表示可能会变得相当困难,这主要源于节点在二维坐标系中的布局问题。对于包含数百个节点的图,手动指定每个节点的坐标显然不切实际。在本节中,我们将介绍如何在不指定每个节点坐标的情况下绘制图形。我们将利用两种不同的解决方案:networkx 和 Gephi。

盼小辉丶的博客 8799

Ambari 3.0+BigTop 3.3集群部署实战

Ambari 3.0+BigTop 3.3集群部署

docsz的博客 612

JavaScript 的秘密花园

1、对象: JS中所有的变量都是对象,

咸鱼的星空 1326

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

[翻译]JavaScript秘密花园 - Type Casting,undefined,eval,setTimeout,Auto Semicolon Insertion - 全部完成PDF打包下载

JavaScript Garden - 原文 JavaScript Garden - 中文翻译 PDF打包下载 类型转换 JavaScript 是弱类型语言,所以会在任何可能的情况下应用强制类型转换。 // 下面的比较结果是:truenew Number(10) == 10; // Number.toString() 返回的字符串被再次转换为数字10 == '10';      

三生石上 1068

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

[翻译]JavaScript秘密花园 - Type Casting,undefined,eval,setTimeout,Auto Semicolon Insertion - 全部完成PDF打包下载...

JavaScript Garden - 原文 JavaScript Garden - 中文翻译 PDF打包下载 类型转换 JavaScript 是弱类型语言,所以会在任何可能的情况下应用强制类型转换。 // 下面的比较结果是:truenew Number(10) == 10; // Number.toString() 返回的字符串被再次转换为数字10 == '10';    ...

weixin_34380781的博客 137

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

JavaScript - scope, namespace, constructor, equality and comparisons

JavaScript Garden - 原文 作用域与命名空间 尽管 JavaScript 支持一对花括号创建的代码段,但是并不支持块级作用域; 而仅仅支持函数作用域。 function test() { // 一个作用域for(var i = 0; i &lt; 10; i++) { // 不是一个作用域// count} console.log(i); // 10} ...

iteye_6378的博客 234

javascript秘密花园

大家请参考http://www.jb51.net/onlineread/JavaScript-Garden-CN/,真的很好

wudiazu的博客 1097

JavaScript 秘密花园 学习心得

目的   记录一下学习心得,便于以后复习,内容是比较基础的...但是很多内容我还是不知道... 对象 对象使用和属性 1.JavaScript 中所有变量都可以当作对象使用,除了两个例外 null和dundefined。 数字其实也可以当做对象,只是因为.会被当成小数点,所以要这么写(2).toString(),即下一个括号 2.删除属性的唯一方法是使用dele...

ajunkui4460的博客 142

JavaScript 秘密花园

JavaScript 秘密花园  对象 o 对象使用和属性 o 原型 o hasOwnProperty 函数 o for in 循环  函数 o 函数声明与表达式 o this 的工作原理 o 闭包和引用 o arguments 对象 o 构造函数 o 作用域与命名空间  数组 o 数组遍历与属性 o Array 构造函数  类型 o 相等与比较 o typeo...

weixin_30832351的博客 124

《Javascript秘密花园》学习笔记(下)

JS核心在任何情况下我们都应该避免使用 eval 函数。99.9% 使用 eval 的场景都有不使用 eval 的解决方案。JavaScript 有两个表示‘空’的值,分别是null与undefined,其中比较有用的是 undefined。undefined 是一个值为 undefined 的类型。 下面的情况会返回 undefined 值: 访问未修改的全局变量 undefined。 由于没有

在前端路上 633

《JavaScript秘密花园》— 笔记

前言本文是笔者阅读了《JavaScript秘密花园》之后的一些总结,主要是一些平常容易忽略的小知识点。数字的字面值如何当做对象使用2.toString(); // 出错:SyntaxError, JS解析器将数字与点操作符视为一体 // 解决办法 2..toString(); // 以小数形式表示 2 .toString(); // 中间留空 (2).toString(); // 利用圆括号的优...

weixin_34202952的博客 442

js的强制类型转换 type casting

ECMASCRIPT有三种强制类型转换 Boolean(value) 把给定的值转成Boolean型  Number(value)把给定的值转换成数字(可以是整数或浮点数)  String(value) 把给定的值转换成字符串

1493

容易遗忘的JS知识点整理

1.hasOwnProperty相关 为了判断一个对象是否包含自定义属性而不是原型链上的属性,我们需要使用继承自 Object.prototype 的 hasOwnProperty方法。hasOwnProperty 是 JavaScript 中唯一一个处理属性但是不查找原型链的函数。 // 修改Object.prototype Object.prototype.bar = 1; va...

weixin_34352449的博客 160
上一篇: JavaScript秘密花园 - Array, Array Constructor, for in loop, typeof, instanceOf
下一篇: JavaScript秘密花园 - hasOwnProperty,Functions,this
迷渡
迷渡 领域专家: 前端开发技术领域 领域专家: 前端开发技术领域
博客等级 码龄19年 2405粉丝 124原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值