写点什么

有趣的 JavaScript 运算符用法

  • 2019-08-26
  • 本文字数:8915 字

    阅读完需:约 29 分钟

有趣的JavaScript运算符用法

JavaScript 提供了几种运算符,可以用来对简单的值执行一些基本操作,如算术运算、赋值、逻辑运算、按位运算等。


JavaScript 代码中赋值、算术和逻辑三种运算符经常会混合使用,但按位运算符就用得没那么多了。

JavaScript 按位运算符

  1. ~ - 按位非(NOT)

  2. & - 按位与(AND)

  3. | - 按位或(OR)

  4. ^ - 按位异或(XOR)

  5. << - 左移

    • 符号传播(有符号)右移

    • 零填充(无符号)右移


这篇教程会逐一介绍 JavaScript 的按位运算符,并总结它们的运算机制。我们还会研究一些实际的 JavaScript 程序案例。这里还要了解一下 JavaScript 按位运算符是怎样将其操作数表示为有符号的 32 位整数的。

按位非(~)

~运算符是一个一元运算符,只需要一个操作数。~运算符对其操作数的每一比特位都执行一次非(NOT)运算。非运算的结果称为反码。一个整数的反码就是整数的每个比特位都反转的结果。


对于给定的整数,比如 170,可以使用~运算符计算反码,如下所示:


// 170 => 00000000000000000000000010101010// --------------------------------------// ~ 00000000000000000000000010101010// --------------------------------------// = 11111111111111111111111101010101// --------------------------------------// = -171 (decimal)
console.log(~170); // -171
复制代码


JavaScript 按位运算符将其操作数转换为补码格式的 32 位有符号整数。因此对整数使用~运算符时,结果值是整数的补码。整数 A 的补码由-(A + 1)给出。


~170 => -(170 + 1) => -171
复制代码


关于 JavaScript 按位运算符使用的 32 位有符号整数有几点注意事项:


  • 最高位(最左边)称为符号位。正整数的符号位始终为 0,负整数的符号位始终为 1。

  • 除符号位之外的剩余 31 位用于表示整数。因此可以表示的最大 32 位整数是(2^32 - 1),即 2147483647,而最小整数是 -(2^31),即-2147483648。

  • 对于超出 32 位有符号整数范围的整数,最高位将被逐一丢弃,直到整数落在范围内。


以下是一些重要数字的 32 位序列表示:


          0 => 00000000000000000000000000000000         -1 => 11111111111111111111111111111111 2147483647 => 01111111111111111111111111111111-2147483648 => 10000000000000000000000000000000
复制代码


从上面的表示法中可以看出:


          ~0 => -1         ~-1 => 0 ~2147483647 => -2147483648~-2147483648 => 2147483647
复制代码

找到的索引

大多数 JavaScript 内置对象(如数组和字符串)都带有一些方法,可用于查找数组中的项目或字符串中的子字符串。下面是其中一些方法:


  • Array.indexOf()

  • Array.lastIndexOf()

  • Array.findIndex()

  • String.indexOf()

  • String.lastIndexOf()

  • String.search()


这些方法都返回项目或子字符串的从零开始的索引(如果找得到);否则会返回-1。例如:


const numbers = [1, 3, 5, 7, 9];
console.log(numbers.indexOf(5)); // 2console.log(numbers.indexOf(8)); // -1
复制代码


如果我们对找到的项或子字符串的索引不感兴趣,则可以使用布尔值,这样未找到项目或子字符串时返回 false 来代替-1,而找到其他值时返回 true。代码如下:


function foundIndex (index) {  return Boolean(~index);}
复制代码


在上面的代码中,~运算符在-1 上使用时计算结果为 0,这是一个虚值。因此使用 Boolean()将虚值转换为布尔值,返回 false。对于其他索引值则返回 true。因此上面的代码段可以修改如下:


const numbers = [1, 3, 5, 7, 9];
console.log(foundIndex(numbers.indexOf(5))); // trueconsole.log(foundIndex(numbers.indexOf(8))); // false
复制代码

按位与(&)

&运算符对其操作数的每对比特位执行与(AND)运算。仅当两个位都为 1 时,&运算符才返回 1;否则它返回 0。因此,与操作的结果相当于将每对比特位相乘。


对于一对比特位来说,与运算可能的值如下所示。


(0 & 0) === 0 // 0 x 0 = 0(0 & 1) === 0 // 0 x 1 = 0(1 & 0) === 0 // 1 x 0 = 0(1 & 1) === 1 // 1 x 1 = 1
复制代码

关闭位

&运算符在位掩码应用中通常用来对特定的位序列关闭某些位。因为对于任意给定的位 A 来说:


  • (A&0 = 0) - 对应的位是 0 时,该位关闭。

  • (A&1 = A) - 对应的位是 1 时,该位不变。


例如我们有一个 8 位整数,我们希望关闭右数前 4 位(设置为 0),就可以用 &运算符实现此目的:


  • 首先创建一个位掩码,其效果是关闭 8 位整数的右数前 4 位。这个位掩码为 0b11110000。注意位掩码的右数前 4 位设置为 0,其他位设置为 1。

  • 接下来对 8 位整数和刚创建的位掩码执行 &操作:


const mask = 0b11110000;
// 222 => 11011110
// (222 & mask)// ------------// 11011110// & 11110000// ------------// = 11010000// ------------// = 208 (decimal)
console.log(222 & mask); // 208
复制代码

检查设置位

&运算符还有其他一些位掩码用途。一种用途是检查给定的位序列是否设置了一个或多个位。例如我们要检查右数第五个比特位是否设置为某个十进制数字,就可以使用 &运算符来执行此操作:


  • 首先创建一个位掩码,用于检查目标位(这里是第五位)是否设置为 1。位掩码上的其他位都设置为 0,但目标位设置为 1。二进制数字字面量如下:


const mask = 0b10000;
复制代码


  • 接下来,使用十进制数和位掩码作为操作数执行 &操作,并将结果与​​位掩码对比。如果所有目标位都设置为这个十进制数,则 &操作的结果将等于位掩码。注意,由于 A&0 = 0,位掩码中的 0 位将关闭十进制数中的对应位。


// 34 => 100010// (34 & mask) => (100010 & 010000) = 000000console.log((34 & mask) === mask); // false
// 50 => 110010// (50 & mask) => (110010 & 010000) = 010000console.log((50 & mask) === mask); // true
复制代码

偶数或奇数

进一步扩展上面的用途,可以用 &运算符检查给定的十进制数是偶数还是奇数。这里的位掩码是 1(以确定最右位是否设置过了)。


对于整数来说,可以使用最低位(最右位)来确定该数字是偶数还是奇数。如果最低位被打开了(设置为 1),则该数字为奇数;否则就是偶数。


function isOdd (int) {  return (int & 1) === 1;}
function isEven (int) { return (int & 1) === 0;}
console.log(isOdd(34)); // falseconsole.log(isOdd(-63)); // trueconsole.log(isEven(-12)); // trueconsole.log(isEven(199)); // false
复制代码

实用等式

以下是一些 &运算的实用等式(对于任何有符号的 32 位整数 A 来说):


(A & 0) === 0(A & ~A) === 0(A & A) === A(A & -1) === A
复制代码

按位或(|)

| 运算符对其操作数的每对比特位执行或(OR)运算。只有当两个位都为 0 时|运算符才返回 0;否则它返回 1。


对于一对比特位来说,或运算的可能结果如下:


(0 | 0) === 0(0 | 1) === 1(1 | 0) === 1(1 | 1) === 1
复制代码

打开位

针对位掩码用途,| 运算符可用来打开一个位序列中的某些位(设置为 1)。因为对于任意给定的位 A 来说:


  • (A | 0 = A) - 对应的位是 0 时,该位保持不变。

  • (A | 1 = 1) - 对应的位是 1 时,该位打开。


例如我们有一个 8 位整数,我们希望所有偶数位(第二、四、六、八位)都打开(设置为 1)。于是可以用| 运算符实现此目的:


  • 首先创建一个位掩码,其效果是打开 8 位整数的每个偶数位。这个位掩码是 0b10101010。注意位掩码的偶数位设置为 1,其他位设置为 0。

  • 接下来对 8 位整数和刚创建的位掩码执行| 操作:


const mask = 0b10101010;
// 208 => 11010000
// (208 | mask)// ------------// 11010000// | 10101010// ------------// = 11111010// ------------// = 250 (decimal)
console.log(208 | mask); // 250
复制代码

实用等式

以下是一些|的实用等式(对于任何有符号的 32 位整数 A 来说):


(A | 0) === A(A | ~A) === -1(A | A) === A(A | -1) === -1
复制代码

按位异或(^)

^运算符对其操作数的每对比特位执行异或(XOR)运算。如果两个位相同(0 或 1),^运算符返回 0;否则它返回 1。


对于一对比特位来说,异或运算的可能结果如下。


(0 ^ 0) === 0(0 ^ 1) === 1(1 ^ 0) === 1(1 ^ 1) === 0
复制代码

翻转位

针对位掩码用途,^运算符通常用于翻转位序列中的某些位。因为对于任何指定的位 A 来说:


  • 其对应的位是 0 时,该位保持不变。(A ^ 0 = A)

  • 对应的位是 1 时,该位始终翻转。


(A ^ 1 = 1) - 如果 A 为 0


(A ^ 1 = 0) - 如果 A 是 1


例如我们有一个 8 位整数,我们希望每个位都被翻转,最低位和最高位除外。这里可以用^运算符实现,如下所示:


  • 首先创建一个位掩码,其效果是翻转 8 位整数除最低位和最高位之外的每个位。这个位掩码是 0b01111110。注意,要翻转的位设置为 1,其他位设置为 0。

  • 接下来对 8 位整数和刚创建的位掩码执行^操作:


const mask = 0b01111110;
// 208 => 11010000
// (208 ^ mask)// ------------// 11010000// ^ 01111110// ------------// = 10101110// ------------// = 174 (decimal)
console.log(208 ^ mask); // 174
复制代码

实用等式

下面是一些^运算的实用等式(对于任何有符号的 32 位整数 A 来说):


(A ^ 0) === A(A ^ ~A) === -1(A ^ A) === 0(A ^ -1) === ~A
复制代码


如上所示,对 A 和-1 执行异或运算相当于对 A 执行非运算。因此之前的 foundIndex()函数也可以这样写:


function foundIndex (index) {  return Boolean(index ^ -1);}
复制代码

左移(<<)

左移(<<)运算符需要两个操作数。第一个操作数是整数,而第二个操作数是第一个操作数要向左移位的位数。右面空出来的位用 0 填充,左边移出去的位会被丢弃。


例如对整数 170 来说,假设我们想要向左移三位,对其使用<<运算符如下所示:


// 170 => 00000000000000000000000010101010
// 170 << 3// --------------------------------------------// (000)00000000000000000000010101010(***)// --------------------------------------------// = (***)00000000000000000000010101010(000)// --------------------------------------------// = 00000000000000000000010101010000// --------------------------------------------// = 1360 (decimal)
console.log(170 << 3); // 1360
复制代码


可以使用以下 JavaScript 表达式定义左移位运算符(<<):


(A << B) => A * (2 ** B) => A * Math.pow(2, B)
复制代码


套用前面的例子:


(170 << 3) => 170 * (2 ** 3) => 170 * 8 => 1360
复制代码

颜色转换:RGB 到十六进制

左移(<<)运算符的一个常见用途是将颜色从 RGB 表示转换为十六进制表示。


RGB 颜色的每个分量的值在 0 到 255 之间。简单来说,每个颜色值刚好能用 8 位表示。


  0 => 0b00000000 (binary) => 0x00 (hexadecimal)255 => 0b11111111 (binary) => 0xff (hexadecimal)
复制代码


因此颜色可以用 24 位(红色,绿色和蓝色各 8 位)完美表示。右数前 8 位表示蓝色分量,接下来的 8 位表示绿色分量,最后的 8 位表示红色分量。


(binary) => 11111111 00100011 00010100
(red) => 11111111 => ff => 255 (green) => 00100011 => 23 => 35 (blue) => 00010100 => 14 => 20
(hex) => ff2314
复制代码


现在我们已经知道如何将颜色表示为 24 位序列了,下面探讨如何用各个分量值组成 24 位颜色。假设我们有一个由 rgb(255,35,20)表示的颜色。下面是组合方法:


  (red) => 255 => 00000000 00000000 00000000 11111111 (green) => 35 => 00000000 00000000 00000000 00100011  (blue) => 20 => 00000000 00000000 00000000 00010100
// 重新排列位,补上必要的0// 使用左移运算符
(red << 16) => 00000000 11111111 00000000 00000000 (green << 8) => 00000000 00000000 00100011 00000000 (blue) => 00000000 00000000 00000000 00010100
// 使用或 (|) 运算符组合起来// ( red << 16 | green << 8 | blue )
00000000 11111111 00000000 00000000| 00000000 00000000 00100011 00000000| 00000000 00000000 00000000 00010100// ----------------------------------------- 00000000 11111111 00100011 00010100// -----------------------------------------
复制代码


这样流程就很清楚了。这里用了一个简单的函数,其将颜色的 RGB 值作为输入数组,并根据上述过程返回对应的十六进制颜色表示:


function rgbToHex ([red = 0, green = 0, blue = 0] = []) {  return `#${(red << 16 | green << 8 | blue).toString(16)}`;}
复制代码

符号传播右移(>>)

符号传播右移(>>)运算符需要两个操作数。第一个操作数是一个整数,而第二个操作数是第一个操作数需要向右移的位数。


向右移多出来的位会被丢弃,左边空出来的位用符号位(最左位)的副本填充。结果整数的符号不变。这就是这个运算符名称的来历。


例如给定整数 170 和-170。假设我们想要向右移三位。我们可以使用>>运算符操作如下:


// 170 => 00000000000000000000000010101010// -170 => 11111111111111111111111101010110
// 170 >> 3// --------------------------------------------// (***)00000000000000000000000010101(010)// --------------------------------------------// = (000)00000000000000000000000010101(***)// --------------------------------------------// = 00000000000000000000000000010101// --------------------------------------------// = 21 (decimal)
// -170 >> 3// --------------------------------------------// (***)11111111111111111111111101010(110)// --------------------------------------------// = (111)11111111111111111111111101010(***)// --------------------------------------------// = 11111111111111111111111111101010// --------------------------------------------// = -22 (decimal)
console.log(170 >> 3); // 21console.log(-170 >> 3); // -22
复制代码


符号传播右移位运算符(>>)可以通过以下 JavaScript 表达式来描述:


(A >> B) => Math.floor(A / (2 ** B)) => Math.floor(A / Math.pow(2, B)
复制代码


套回前面的例子:


(170 >> 3) => Math.floor(170 / (2 ** 3)) => Math.floor(170 / 8) => 21(-170 >> 3) => Math.floor(-170 / (2 ** 3)) => Math.floor(-170 / 8) => -22
复制代码

颜色提取

右移(>>)运算符的一个常见用途是从颜色中提取 RGB 颜色值。当颜色以 RGB 表示时很容易区分红色、绿色和蓝色分量值。但对于十六进制的颜色表示来说就没那么直观了。


在上一节中,我们知道了从各个分量(红色、绿色和蓝色)组成颜色是怎样的过程。这个过程倒过来就能用来提取颜色的各个分量的值。下面来试一试。


假设我们有一个由十六进制表示为 #ff2314 的颜色。以下是这个颜色的有符号 32 位表示:


(color) => ff2314 (hexadecimal) => 11111111 00100011 00010100 (binary)
// 32-bit representation of color00000000 11111111 00100011 00010100
复制代码


为了获得各个分量,我们将颜色位右移 8 的倍数,直到右数前 8 位是我们需要的分量为止。由于 32 位颜色的最高位是 0,我们可以安全地使用符号传播右移(>>)运算符。


color => 00000000 11111111 00100011 00010100
// Right shift the color bits by multiples of 8// Until the target component bits are the first 8 bits from the right
red => color >> 16 => 00000000 11111111 00100011 00010100 >> 16 => 00000000 00000000 00000000 11111111
green => color >> 8 => 00000000 11111111 00100011 00010100 >> 8 => 00000000 00000000 11111111 00100011
blue => color >> 0 => color => 00000000 11111111 00100011 00010100
复制代码


现在右起前 8 位就是我们的目标分量,我们需要一种方法来屏蔽掉前 8 位之外的位数。这里又要使用与(&)运算符。记住 &运算符可用于关闭某些位。


先来创建所需的位掩码。如下所示:


mask => 00000000 00000000 00000000 11111111     => 0b11111111 (binary)     => 0xff (hexadecimal)
复制代码


位掩码准备就绪后,我们可以用它对先前右移操作的各个结果执行与(&)运算,以提取目标分量。


red   => color >> 16 & 0xff      =>   00000000 00000000 00000000 11111111      => & 00000000 00000000 00000000 11111111      => = 00000000 00000000 00000000 11111111       =>   255 (decimal)
green => color >> 8 & 0xff => 00000000 00000000 11111111 00100011 => & 00000000 00000000 00000000 11111111 => = 00000000 00000000 00000000 00100011 => 35 (decimal)
blue => color & 0xff => 00000000 11111111 00100011 00010100 => & 00000000 00000000 00000000 11111111 => = 00000000 00000000 00000000 00010100 => 20 (decimal)
复制代码


如上所示。这是一个简单的函数,它将十六进制颜色字符串(带有六个十六进制数字)作为输入,并返回对应的 RGB 颜色分量值数组。


function hexToRgb (hex) {  hex = hex.replace(/^#?([0-9a-f]{6})$/i, '$1');  hex = Number(`0x${hex}`);
return [ hex >> 16 & 0xff, // red hex >> 8 & 0xff, // green hex & 0xff // blue ];}
复制代码

零填充右移(>>>)

零填充右移(>>>)运算符的行为很像符号传播右移(>>)运算符。它们的关键区别在于左边填充的位数。


顾名思义,这里左边空出来的位都是用 0 填充的。因此>>>运算符始终返回无符号的 32 位整数,因为结果的符号位始终为 0。对于正整数来说,>>和>>>将始终返回相同的结果。


例如对于整数 170 和-170 来说,假设我们要向右移 3 位,可以使用>>>运算符操作如下:


// 170 => 00000000000000000000000010101010// -170 => 11111111111111111111111101010110
// 170 >>> 3// --------------------------------------------// (***)00000000000000000000000010101(010)// --------------------------------------------// = (000)00000000000000000000000010101(***)// --------------------------------------------// = 00000000000000000000000000010101// --------------------------------------------// = 21 (decimal)
// -170 >>> 3// --------------------------------------------// (***)11111111111111111111111101010(110)// --------------------------------------------// = (000)11111111111111111111111101010(***)// --------------------------------------------// = 00011111111111111111111111101010// --------------------------------------------// = 536870890 (decimal)
console.log(170 >>> 3); // 21console.log(-170 >>> 3); // 536870890
复制代码

配置标志

最后讨论另一个非常常见的按位运算符和位掩码应用:配置标志(flag)。


假设我们有一个函数,其接受一些 boolean 选项,这些选项可用于控制函数的运行方式或返回的值类型。一种方法是将所有选项作为参数传递给函数,可能还有一些默认值,如下所示:


function doSomething (optA = true, optB = true, optC = false, optD = true, ...) {  // 这个函数做一些事情……}
复制代码


当然这样不太方便。下面两种情况下这种方法会变得非常复杂:


  • 假如有超过 10 个布尔选项。我们无法使用那么多参数定义我们的函数。

  • 假如我们只想为第五个和第九个选项指定新的值,其他选项则保留其默认值。此时我们需要调用该函数,对其他选项的参数传递默认值,对第五个和第九个选项则传递指定的值。


解决这个问题的一种方法是使用一个对象作为配置选项,如下所示:


const defaultOptions = {  optA: true,  optB: true,  optC: false,  optD: true,  ...};
function doSomething (options = defaultOptions) { // 做一些事情……}
复制代码


这种方法非常优雅,最为常见。但使用这种方法时 options 参数始终是一个对象,如果只是用来配置选项的话未免杀鸡用牛刀了。


如果所有选项都采用布尔值,我们可以使用整数而不是对象来表示选项。在这种情况下,整数的特定位将映射到指定的选项。如果某个位被打开(设置为 1),则指定选项的值为 true;否则为 false。


举一个简单的例子。假设我们有一个函数来规范化包含数字的数组列表的项目并返回规范化数组。返回的数组可以通过三个选项控制,即:


  • fraction:将数组中的每个项目除以数组中的最大项目。

  • unique:从数组中删除重复的项目。

  • sorted:从最低到最高排序数组的项目。


我们可以使用 3 位整数来表示这些选项,每个位都映射到一个选项。下面的代码是选项标志:


const LIST_FRACTION = 0x1; // (001)const LIST_UNIQUE = 0x2; // (010)const LIST_SORTED = 0x4; // (100)
复制代码


要激活一个或多个选项时,可以使用| 运算符根据需要组合对应的标志。例如我们可以创建一个激活所有选项的标志,如下所示:


const LIST_ALL = LIST_FRACTION | LIST_UNIQUE | LIST_SORTED; // (111)
复制代码


假设我们只希望默认激活 fraction 和 sorted 选项。我们可以使用| 运算符操作如下:


const LIST_DEFAULT = LIST_FRACTION | LIST_SORTED; // (101)
复制代码


只有三个选项时看起来还不错,但选项变多时就会变得乱七八糟,还需要激活很多默认选项。在这种情况下,更好的方法是使用^运算符停用不需要的选项:


const LIST_DEFAULT = LIST_ALL ^ LIST_UNIQUE; // (101)
复制代码


这里我们用 LIST_ALL 标志来激活所有选项。然后我们使用^运算符停用某些选项,其他选项则保持激活状态。


现在我们已经准备好了选项标志,就可以继续定义 normalizeList()函数:


function normalizeList (list, flag = LIST_DEFAULT) {  if (flag & LIST_FRACTION) {    const max = Math.max(...list);    list = list.map(value => Number((value / max).toFixed(2)));  }  if (flag & LIST_UNIQUE) {    list = [...new Set(list)];  }  if (flag & LIST_SORTED) {    list = list.sort((a, b) => a - b);  }  return list;}
复制代码


要检查选项是否已激活,我们使用 &运算符检查选项的对应位是否已打开(设置为 1)。&操作是使用传递给函数的 flag 参数和选项的对应标志来执行的,如下所示:


// Checking if the unique option is activated// (flag & LIST_UNIQUE) === LIST_UNIQUE (activated)// (flag & LIST_UNIQUE) === 0 (deactivated)
flag & LIST_UNIQUE
复制代码

小结

文章比较长,读起来有些枯燥,恭喜你坚持看完了。


你也看到了,虽然 JavaScript 按位运算符用得不多,但它有一些非常有趣的用例。希望大家能在实际编程工作中用到本文学到的内容。


编程快乐!


英文原文:https://blog.logrocket.com/interesting-use-cases-for-javascript-bitwise-operators/


2019-08-26 14:412954

评论

发布
暂无评论
发现更多内容

sap.ui.require in SAP UI5 and require in nodejs

Jerry Wang

nodejs SAP SAP UI5

使用ABAP Push Channel(APC)开发的乒乓球游戏,可双打

Jerry Wang

SAP abap APC

架构实战营 模块4 作业

CR

你真的会用ABAP, Java和JavaScript里的constructor么?

Jerry Wang

JavaScript CRM SAP abap

ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较

Jerry Wang

JavaScript nodejs CRM SAP C4C

千万级学生管理系统的考试试卷存储方案

Lane

S4HANA和CRM Fiori应用的搜索分页实现

Jerry Wang

CRM SAP Fiori SAP UI5 S/4HANA

S4CRM和C4C的技术比较

Jerry Wang

CRM SAP ERP abap Cloud for Customer

架构实战营模块 4 作业

梦寻解语花

架构实战营

千万级学生管理系统的考试试卷存储方案

王瑞强

架构实战营

架构实战营 模块四作业

Dylan

架构实战营

SAP UI5, CRM, S/4HANA 和 C4C里的Association, Composition and Aggregation

Jerry Wang

JavaScript CRM SAP SAP UI5

如何计算并测量ABAP及Java代码的环复杂度Cyclomatic complexity

Jerry Wang

Java SAP abap

过拟合 - DAY13

Qien Z.

5月日更 过拟合

架构实战营 模块四作业

夏日

架构实战营

如何实现一个简易版的 Spring - 如何实现 AOP(上)

mghio

Java 技术 后端 基础知识 spring aop

思考题太难了

Nydia

mongodb 修改字段类型

xiaolu

mongodb

架构实战营模块四作业

冷大大

作业 架构实战营 模块四

SAP UI5和CRM WebUI的View和Controller是如何绑定的

Jerry Wang

CRM SAP abap WebClient UI SAP UI5

架构训练营模块四作业

Neil43

架构训练营

华仔架构训练营作业(模块四)

不听不听王八念晶

设计千万级学生管理系统的考试试卷存储方案

thewangzl

Dubbo 令牌验证和优雅停机

青年IT男

dubbo

第四课作业

杰语

架构师训练营模块4作业

歲月鎏金😈

架构营作业-模块4

大师兄

如何查看某个用户指定时间段的ABAP开发记录

Jerry Wang

SAP abap SAPGUI

今天社区团购了吗?

lenka

5月日更

作业 - 设计千万级学生管理系统的考试试卷存储方案

sN0wpeak

架构实战营

身份认证

escray

学习 极客时间 安全 5月日更 安全攻防技能30讲

有趣的JavaScript运算符用法_语言 & 开发_Glad Chinda_InfoQ精选文章