Blog

JavaScript构造私有成员

Content #

有JavaScript代码如下:

function cls(){
    this.a = 100;
    return {
        getValue:() => this.a
    }
}
var o = new cls;
o.getValue(); //100

其中a=100只能通过o.getValue()来得到,无法通过o.a来得到,请问是什么原因?

我们大致可以认为,construct的执行过程如下:

  1. 以 Object.prototype 为原型创建一个新对象;
  2. 以新对象为 this,执行函数的call;
  3. 如果call的返回值是对象,那么,返回这个对象,否则返回第一步创建的新对象。

这样的规则造成了个有趣的现象,如果我们的构造器返回了一个新的对象,那么 new 创建的新对象就变成了一个构造函数之外完全无法访问的对象,这一定程度上可以实现“私有”。

Viewpoint #

From #

正则断言(Assertion)

正则断言(Assertion)指的是什么?常见的断言有哪三种? #

\d{11} 能匹配上 11 位数字,但这 11 位数字可能是 18 位身份证号中的一部分。再比如,去查找一个单词,我们要查找 tom,但其它的单词,比如 tomorrow 中也包含了 tom。

在有些情况下,我们对要匹配的文本的位置也有一定的要求。为了解决这个问题,正则中提供了一些结构,只用于匹配位置,而不是文本内容本身,这种结构就是断言。常见的断言有三种:

  1. 单词边界
  2. 行的开始
  3. 结束以及环视。

Viewpoint #

From #

回路错误(round-trip fallacy)

什么是回路错误(round-trip fallacy)? #

某个人观察了火鸡前1000天的生活(但没有看到第1001天令人震惊的事件),他会理所当然地对你说,没有证据表明会发生大事,即黑天鹅事件。

但是,你会把这一说法理解为证据表明黑天鹅事件不会发生,尤其在你不仔细考虑的时候。这两种说法之间的逻辑差距实际上是非常大的,但这种差距在你的思维中看上去却很小,所以二者可以相互替代。

从现在起10天后,即使你还记得第一种说法,你也一定宁愿倾向于第二种说法(不确切的说法),即证据表明没有黑天鹅现象。我把这种混淆称为回路错误(round-trip fallacy),因为两种说法是不可互换的。

Viewpoint #

From #

基金经理是一只基金的灵魂

在“基金经理是一只基金的灵魂”这个前提下,我们就可以得到哪两条非常有价值的推论? #

  1. 新的基金尽量不要购买。如果要一定要购买的话,就必须先调研清楚这只新基金的经理的历史业绩,并且要重点查看这位基金经理是不是操盘过同类的基金。
  2. 更换了基金经理的基金等同于一只新基金。如果一只基金更换了基金经理,那么它所有的历史评级、历史业绩都不具备很强的参考价值,几乎可以看成一只新基金。

Viewpoint #

From #

FLP不可能性

Content #

FLP不可能性(FLP Impossibility,F、L、P三个字母分别代表三个作者Fischer、 Lynch和Patterson名字的首字母)是分布式领域中一个非常著名的定理,该定理给出了什么结论?

No completely asynchronous consensus protocol can tolerate even a single unannounced process death.

这里的“unannounced process death”指的是一个进程发生了故障,但其他节点并不知道,继续认为这个进程还没有处理完成或发生消息延迟了,要强于“fail-stop Failure”。

Viewpoint #

From #

能否通过亲合力来判断是获取者还是付出者

Content #

付出和获取行为,与我们的性格是否具有亲和力之间,有何种关系?人们一般的见解是怎样的?

亲和力强的人看上去更加愿意合作、礼貌待人——他们希望与他人保持和谐的关系,给人留下温暖、和蔼和热情的印象。亲和力弱的人更有竞争性、挑剔和强硬——他们更习惯于制造冲突,给人留下多疑和挑衅的印象。

我们倾向于刻板地认为,亲和力强的人是付出者,而亲和力弱的人是获取者。如果我们刚认识的某个人表现得和蔼可亲,我们会很自然地得出结论,认为他有良好的意图。如果他表现出冷漠或是对抗性,这似乎就传递了一种信号,表明他不会关心我们的利益。在做出这些判断时,我们太过于关注这个人行为举止的外壳,却忽视了壳里装的究竟是珍珠,还是蛤蜊。

付出和获取行为是基于我们的动机和价值观而做出的个人选择,与我们的性格是否具有亲和力没有关系。正如我们在第一章中介绍过的那位最初拒绝了戴维·霍尼克开出的合同条款的连续创业者丹尼·夏德尔所解释的:“你是不是一个和蔼可亲的人,这和你究竟关注自己还是关注他人是两码事。它们是相互独立的,不是相反的两极。”如果你把外在的表现和内在的动机结合起来,亲和力强的付出者和亲和力弱的获取者只是世界上存在的四种组合中的两种。

Viewpoint #

From #

查看C语言表达式的抽象语法树

查看C语言表达式的抽象语法树 #

对表达式的求值过程,实际上就是根据运算符的优先级和结合性,来对表达式和它所包含的子表达式进行递归求值的过程。从编译的角度来看,这个过程中所涉及到的操作数的实际求值顺序会在语法分析阶段被确定,并体现在源码对应的抽象语法树(AST,Abstract Syntax Tree)上。为了方便进一步观察,我将这个表达式整合到了下面的 C 代码里,并保存在文件 main.c 中:

int foo(void) {
  return (1 + 2) * 3 + 4 / 5;
}

然后,借助 Clang 编译器提供的 “-ast-dump” 选项,我们可以编译并打印出这段 C 代码对应的 AST 结构。完整的编译命令如下:

clang -Xclang -ast-dump -fsyntax-only main.c

上面的命令执行完毕后,部分输出结果如下图所示:

`-FunctionDecl 0x616600 <main.c:1:1, line:3:1> line:1:5 foo 'int (void)'
  `-CompoundStmt 0x616838 <col:15, line:3:1>
    `-ReturnStmt 0x616828 <line:2:7, col:32>
      `-BinaryOperator 0x616808 <col:14, col:32> 'int' '+'
        |-BinaryOperator 0x616788 <col:14, col:24> 'int' '*'
        | |-ParenExpr 0x616748 <col:14, col:20> 'int'
        | | `-BinaryOperator 0x616728 <col:15, col:19> 'int' '+'
        | |   |-IntegerLiteral 0x6166e8 <col:15> 'int' 1
        | |   `-IntegerLiteral 0x616708 <col:19> 'int' 2
        | `-IntegerLiteral 0x616768 <col:24> 'int' 3
        `-BinaryOperator 0x6167e8 <col:28, col:32> 'int' '/'
          |-IntegerLiteral 0x6167a8 <col:28> 'int' 4
          `-IntegerLiteral 0x6167c8 <col:32> 'int' 5

AST 作为用于表示源代码语法结构的一种树形数据结构,语法分析器会将表达式中操作数的整体求值顺序映射到树的结构上。因此,当我们以后序遍历(LRD)的方式遍历这棵树时,便可以直接得到正确的表达式求值顺序。

...

类型转换与数据的实际存储

类型转换与数据的实际存储 #

计算机不会区分数据的符号性,符号性的差异仅由计算机指令如何使用数据而定。比如在 C 语言中,当对某类型变量进行强制类型转换时,其底层存储的数据并不会发生实质的变化,而仅是程序对如何解读这部分数据的方式发生了改变。比如下面这个例子:

#include <stdio.h>
int main(void) {
  signed char x = -10;
  unsigned char y = (unsigned char)x;
  printf("%d\n", y);  // output: 246.
  return 0;
}

其中,有符号整型变量 x 会按照位模式 1111 0110 的补码形式存放有符号数 -10,而如果将该序列按照无符号整数的位模式进行解读,则可得到如程序运行输出一样的结果,即无符号整数值 246。 总之,程序在进行强制类型转换时,不会影响其底层数据的实际存储方式。

在 C 语言中,关于数据使用还有一个值得注意的问题:变量类型的隐式转换(Implicit Type Conversion)。C 语言作为一种相对而言的“弱类型”语言,其一大特征就是在某些特殊情况下,变量的实际类型会发生隐式转换。

在下面这个例子中,定义的两个变量 x 与 y 分别为有符号整数和无符号整数,且变量 x 的值明显小于变量 y,但程序在实际运行时却会进入到 x >= y 的分支中,这就是因为发生了变量类型的隐式转换。

#include <stdio.h>
int main(void) {
  int x = -10;
  unsigned int y = 1;
  if (x < y) {
    printf("x is smaller than y.");
  } else {
    printf("x is bigger than y.");   // this branch is picked!
  }
  return 0;
}

实际上,在上面的代码中,程序逻辑在真正进入到条件语句之前,变量 x 的类型会首先被隐式转换为 unsigned int ,即无符号整型。而根据数据类型的解释规则,原先存放有 -10 补码的位模式会被解释为一个十分庞大的正整数,而这个数则远远大于 1。

...

为什么叫补码

为什么叫补码 #

补码的英文名称是 Two’s-complement ,可直译为“对数字 2 的补充”,那为什么会叫这个名字呢?你可以先停下来思考一下,然后再来看看我的理解:

首先,我们来计算一下有符号整数 3 对应的四位补码,可以得到一个二进制序列 “0011”。将该二进制序列与上述 -3 对应的二进制序列相加,通过进位可以得到序列 “10000”,该序列可以表示无符号正整数 16。

因此,我们可以得到这样一个结论:对于非负数 x,我们可以用 \(2^w − x\) 来计算−x 的 w 位表示。套用在上述的例子中,可以得到“在四位补码的情况下,对于非负数 3,可以用无符号数 13 (即 16−3) 的位模式来表示有符号数 -3 的位模式”这个结论,即两者位模式相同。而补码的英文名称正是对 x、−x 和 \(2^w\) 三者之间的关系进行的总结。

Viewpoint #

From #

02|程序基石:数据与量值是如何被组织的?