海屿文章列表

《少有人走的路》书摘

b4858a561b6d634632f4ecf344be9f72.jpg

  1. 人可以拒绝任何东西,但绝对不可以拒绝成熟。拒绝成熟,实际上就是在规避问题、逃避痛苦。规避问题和逃避痛苦的趋向,是人类心理疾病的根源,不及时处理,你就会为此付出沉重的代价,承受更大的痛苦。
  2. 人生是一个面对问题并解决问题的过程。问题能启发我们的智慧,激发我们的勇气;问题是我们成功与失败的分水岭。为解决问题而付出努力,能使思想和心智不断成熟。
  3. 规避问题和逃避痛苦的倾向,是人类心理疾病的根源。
  4. 所谓自律,是以积极而主动的态度,去解决人生痛苦的重要原则,主要包括四个方面:推迟满足感、承担责任、尊重事实、保持平衡。
  5. 推迟满足感,意味着不贪图暂时的安逸,重新设置人生快乐与痛苦的次序:首先,面对问题并感受痛苦;然后,解决问题并享受更大的快乐,这是唯一可行的生活方式。
  6. 父母习惯用严厉的体罚教训孩子,本质上不是教育,而是发泄怨气和不满。
  7. “我是个有价值的人”,有了这样宝贵的认知,便构成了健全心理的基本前提,也是自律的根基。
  8. 和那些缺乏耐心、想让问题马上解决的态度相比,另一种解决问题的态度更低级、也更具有破坏性,那就是希望问题自行消失。
  9. 直面问题会使人感觉痛苦。问题通常不可能自行消失,若不解决,就会永远存在,阻碍心智的成熟。
  10. 曾经有位久经沙场的将军告诉我:“军队最大的问题在任何组织和结构中都同样存在,那就是大堆问题,迟迟无法做出决定,更谈不上实际的行动了,似乎顶上几天几夜,问题就会自行消失一样。”
  11. 他们推卸责任时,可能感觉舒服和痛快,但心智却无法成熟,常常成为集体、社会的负担。
  12. 我们力图把责任推给别人或组织,就意味着我们甘愿出于附属地位,把自由和权力拱手交给命运、社会、政府、独裁者、上司。埃里克 弗洛姆将其所著的讨论纳粹主义和集权主义的专论命名为《逃避自由》,可谓恰如其分。为逃离责任带来的痛苦,数不清的人甘愿放弃权力,实则是在逃避自由。
  13. 献身真理,意味着敢于接受其他制图者—外界的之一和挑战,由此确定地图是否与事实相符合。不然,我们就将生活在封闭的系统里—就像是单间牢房,我们“反复呼吸着自己释放的恶臭空气”—如同塞尔维亚 普拉斯(美国女诗人)的比喻,沉湎在个人幻想里。修订地图带来的痛苦,使我们更容易选择逃避,不容许别人质疑我们的地图的有效性。
  14. 故步自封,逃避挑战,可说是人性的基本特征之一。
  15. 只有让接受挑战成为习惯,心里治疗才能够真正成功。…接受挑战,才可以带来真正的安慰;心里接受长期的、甚至经常碰壁的自律,才可能使治疗成功。
  16. 敢于面对事实的人,能够心胸坦荡地生活在天地间,也可借此摆脱良心的折磨和恐惧的威胁。
  17. 保持平衡,意味着要确立富有弹性的约束机制。不妨以生气为例。我们心里或生理上受到侵犯,或者说,某个人、某件事令我们伤心和失望,我们就会感到生气。要获得正常的生存,生气时一种必不可少的反击方式。从来不会生气的人,注定终生遭受欺凌和压制,直至被摧毁和消灭。必要的生气,可以使我们更好地生存。我们受到侵犯,不见得是侵犯者对我们怀有敌意。有时候,即使他们果真有意而为,我们也要适当约束情绪,正面冲突只会使处境更加不利。大脑的高级中枢—判断力,必须约束低级中枢—情绪,提醒后者稍安勿躁。在这个复杂多变的世界里,想使人生顺遂,我们不但要有生气的能力,还要具备即便生气、也可抑制其爆发的能力。我们还要善于以不同的方式,恰当表达生气的情绪:有时需要委婉,有时需要直接;有时需要心平气和,有时不妨火冒三丈。表达生气,还要注意时机和场合。我们必须建立一整套灵活的情绪系统,提高我们的“情商”。
  18. 要使心智成熟,就须在彼此冲突的需要、目标、责任之间,取得微妙的平衡,这就要求我们利用机遇,不断自我调整。保持平衡的最高原则就是“放弃”。…我想不管是谁,经过人生旅途的急转弯,都必须放弃某些快乐,放弃属于自己的一部分。回避放弃只有一个办法,那就是永远停在原地,不让双脚踏上旅途。
  19. 放弃某种心爱的失误—至少是自身熟悉的事物,肯定让人痛苦,但适当放弃过去的自我,才能使心智成熟。…人生各个阶段,会出现各种各样的危机,只有放弃过去过时的观念和习惯,才能顺利进入人生下一阶段。
  20. 天地不仁,以万物为刍狗。
  21. 假使人生的目标就是逃避痛苦,你完全可以得过且过,不必寻找更高层次的精神和意识的进步。因为不经痛苦和折磨,就无法实现灵魂的超越。而且,即便达到很高的精神境界,但彼时的痛苦之烈,可能远远超过你的想象,让你最终无法承受。
  22. 自律,包含具有积极意义的四种人生原则,目标都是解决问题,而不是回避痛苦。综上所述,这四种原则包括:推迟满足感、承担责任、尊重事实、保持平衡。
  23. 我的定义是:爱,是为了促进自我和他人心智成熟,而具有的一种自我完善的意愿。
  24. 首先,坠入情网,通常涉及与性有关的欲望。…只有意识和潜意识的性冲动,才会使我们陷入情网。…坠入情网,意味着自我界限的某一部分突然崩溃,使我们的“自我”和他人的“自我”合而为一。
  25. 坠入情网不是真正的爱,不过是一种幻觉而已。情侣只有脱离情网,才能够真正相爱。真爱的基础不是恋爱,甚至没有恋爱的感觉,也无须以之为基础。…坠入情网不是真正的爱,其本质究竟是什么呢?仅仅是自我界限暂时的崩溃吗?在我看来,它与人的“力比多”(性的需求和原动力)有关,或与受基因支配的生物交配本能有关。坠入情网,是人类内在性的需求和外在性的刺激,产生的典型生理和心理的反应,意义在于增加人类生殖的机会,促进物种繁衍和生存。
  26. 对于某种事物长期的爱,使我们生活在精神贯注的境界里,自我界限开始延伸。延伸到一定程度就会归于消失,而我们的心智就会成熟,爱不断释放,自我与世界的区别也越来越模糊,我们与外在世界融为一体。
  27. 还有一种最常见的对爱的误解,就是将依赖性当成真正的爱。…他们痛苦地说:“我不想再活下去了!我没有了丈夫(妻子、男朋友、女朋友)或者还有什么乐趣?我是多么爱他(她)啊?”你描述的不是爱,而是过分的依赖感。确切的说,那是寄生心理。没有别人就无法生存,意味着你是个寄生者,而对方是记寄主。你们的关系和感情,没有自由的成分。你们是因为需要而不是爱,才结合在一起的。真正的爱是自由的选择。真正相爱的人,不一定非要生活在一起,充其量只是选择一起生活罢了。
  28. 人人都有依赖的需求和渴望,都希望有更强大、更有力的人关心自己。不管我们看起来多么强壮,也不管我们花多大的心思,竭力做出无须关心的样子,但从内心深处,我们都渴望过依赖他人的感觉。
  29. 消极性依赖人格失调患者的典型特征—他们不在乎依赖的对象是谁,只要有人可以依赖,就心满意足。只要通过与别人的关系,让他们获得某种身份或角色,他们就会感觉舒适,至于那是什么身份,对他们并不重要。
  30. 长期以来,她饱受寂寞、空虚的趋势,有了感情就紧抓不放,这只能使感情更快地走向毁灭。学会了自我约束,及时调整心态,使他更有机会发挥特长,从事有价值的事业,最终走出病态性依赖的阴影。
  31. 只想获取却不愿付出,心智就会永远停留在婴儿期,这只会对人生构成限制和舒服,只会给人际关系造成破坏,而不会使情感走向完满,也会使卷入其中的人跟着遭殃。
  32. 合理而健康的嗜好,是培养自尊自爱的必要手段。
  33. 真正的爱的本质之一,就是希望对方拥有独立自主的人格。我们豢养宠物,只是希望它们永远不要长大,乖乖地陪着我们。我们看中的,是宠物对我们的依赖性。
  34. 真正的爱,不是单纯的给予,还包括适当的拒绝、及时的赞美、得体的批评、恰当的争论、必要的鼓励、温柔的安慰、有效地敦促。
  35. 真正的爱,能够使人发生改变,在本质上是一种自我扩充,而非纯粹的自我牺牲。真正的爱,能使自我更为完善。爱,在某种意义上是自私的,最终追求的则是自我完善。当然,自私与否,不是判定爱的标准,唯一的判断标准时:爱—永远追求心智的成熟,除此之外,都不是真正的爱。
  36. 真正的爱不是忘乎所以,而是深思熟虑,是奉献全部身心的重大决定。把真正的爱与爱的感觉混为一谈,只能是自欺欺人。
  37. 扩充自我界限,意味着摆脱惰性,直面内心的恐惧,这就是说,爱可以使我们勇气倍增。所以,爱也是获得勇气的一种特殊的形式。
  38. 爱最重要的的体现形式,就是关注。我们爱某个人,一定会关注对方,进而帮助对方成长。我们必须把成见放到一边,调整心理状态,满足对方的需要。我们对对方的关注,是出自自我意愿、摒弃惰性的行为。
  39. 不论年龄有多大,孩子都需要父母的关注和倾听。
  40. 不敢正视死亡,就无法获得人生的真谛,无法理解什么是爱,什么是生活。万物永远处在变化中,死亡是一种正常现象,不肯接受这一事实,我们就永远无法体味生命宏大的意义。
  41. 成长的过程极为缓慢,除了大步跳跃以外,还包括进入未知天地的无数次小规模跨越—例如,八岁的孩子第一次独自骑车到遥远的郊区商店购物;十七八岁的孩子第一次与异性约会等。…即使是心里最健康的孩子,他们初次步入成人世界,除了兴奋和激动,想必也不乏迟疑而胆怯。他们不时想回到熟悉、安全的环境中,想做回当初那个凡事依赖别人的幼儿。成年人也会经历类似的矛盾心理,年龄越大,越难以摆脱久已熟悉的事物。…很多人从未有过大规模跳跃,也就无法实现真正意义的成长。
  42. 至高境界的爱,必然是自由状态下的资助选择,而不是步步亦趋、墨守成规,不是被动而消极地抗拒心灵的呼唤。
  43. 你这么多礼没有必要。从你的表现看,就像是个缺乏自信、以为自己不受欢迎的客人。
  44. 性爱过程是自我释放,是肉体的体验、精神的探索、情感的宣泄。
  45. 要实现自我完善,享受良好的人际关系带来的快乐,进而使真正的爱成为人生的重心,就必须无所畏惧,敢于做出改变,而不是墨守成规。
  46. 真心爱别人,就会承认对方是与自己不同的、完全独立的个体。
  47. 冲突或者批评,是人际关系中特殊的控制权,如果恰当地运用,就可以改进人际关系进程,继而改变所爱的人的一生。
  48. 真正以爱为出发点的人,总是致力于自我完善,让自己具备起码的道德和智慧,然后才会行使批评权。
  49. 爱的重要特征之一,在于爱着与被爱着都不是对方的附属品。付出真爱的人,应该永远把爱的对象视为独立的个体,永远尊重对方的独立和成长。
  50. 就我看来,妻子的意义和价值,是尽可能满足她自己的需要,尽可能使她的心智获得成熟。这不仅对我有好处,也是为了她本人乃至上帝的荣耀。
  51. 诗人纪伯伦曾这样谈到婚姻中的“寂寞的智慧”:你们的结合需要保留空隙,让天堂的风在你们中间舞动。彼此相爱,但不要制造爱的枷锁,在你们灵魂的两岸之间,让爱成为涌动的海洋。倒满彼此的酒杯,但不可只从一个杯子啜饮,分享你们的面包,但不可只把同一块面包享用。
  52. 缺乏爱,是导致心理疾病的主要原因,爱,则是推动心里治疗的重要元素。

⚛ Atomic 原子操作与多线程

何谓原子操作?

所谓原子操作,就是多线程程序中“最小的且不可并行化的”操作。对于在多个线程间共享的一个资源而言,这意味着同一时刻,多个线程中有且仅有一个线程在对这个资源进行操作,即互斥访问。

JS 中的 Atomic

Chrome 67+实现了SharedArrayBuffer和Atomics。

共享阵列缓冲区的概念是,您将消息发布给工作线程,但不是复制数组的内容,而是发送对它的引用,以便多个工作线程可以共享内存块。

原子的概念是它们提供可以“一次”发生的操作。

这是为什么原子如此重要的一个示例:执行加法时,您将发出多个操作。

a = a + 3;
// ↑ is equivalent to the following:
let reg = a;
reg += 3;
a = reg;

在普通的JS中这不是问题,因为您被授予在每个给定时间仅执行一个函数。

引入Workers和共享内存会打破这种假设,因此a在您递增时reg,有人可能会更改的值,最终您会覆盖其值。要解决此问题,可以使用Atomic.add。

Atomic 的 wait() 和 notify() 方法采用的是 Linux 上的 futexes 模型(“快速用户空间互斥量”),可以让进程一直等待直到某个特定的条件为真,主要用于实现阻塞。

sleep 问题

如果我们想让一段程序休眠一段时间该怎么做?
也许会想到 setTimeout 但是他只会暂停回调函数,不会阻止事件循环的进行,所以其他异步函数还是会执行的。

可以利用类似自旋锁的方式,用一段空转函数占用CPU运行,来实现暂停:

自旋锁不会让出CPU,是一种忙等待状态,自旋锁其实就是:死循环等待锁被释放

function sleep(ms) {
  const target = Date.now() + Number(ms);
  while (target > Date.now()) {}
}

Atomics.wait 方法,该方法会监听一个Int32Array 对象的给定下标下的值,若值未发生改变,则一直等待(阻塞event loop),直到发生超时(由ms参数决定定):

const nil = new Int32Array(new SharedArrayBuffer(4))

function sleep (ms) {

  // 参数校验相关代码略去

  Atomics.wait(nil, 0, 0, Number(ms))
}

另外,模块还对低版本的 js 运行时做了兼容,如果不支持 Atomics,则改用一个占用CPU运行的方式:

function sleep(ms) {

  // 参数校验相关代码略去

  const target = Date.now() + Number(ms);
  while (target > Date.now()) {}
}

尽管Atomic有很强,但它们从来都不容易使用。此外,原子是非常低级的API,并且要表达高级行为,通常需要使多个Atomic操作相互交互。

线程同步

互斥体

互斥体是允许只有一个线程可以同时进入关键部分的原语。关键部分是 lock 和 unlock调用之间的代码。

详细说明:

  • 互斥锁有2种状态:解锁和锁定。
  • 调用lock()互斥锁应将其转换为锁定状态。
  • lock()如果该互斥锁已被其他人锁定,则调用该互斥锁应该会阻塞并等待。
  • 调用unlock()互斥锁应将其转换为解锁状态。
  • 调用unlock()未锁定的互斥锁不是有效操作。

为此,我们需要以下原子方法:

  • Atomics.compareExchange
  • Atomics.wait
  • Atomics.notify
const locked = 1;
const unlocked = 0;

class Mutex {
  /**
   * Instantiate Mutex.
   * If opt_sab is provided, the mutex will use it as a backing array.
   * @param {SharedArrayBuffer} opt_sab Optional SharedArrayBuffer.
   */
  constructor(opt_sab) {
    this._sab = opt_sab || new SharedArrayBuffer(4);
    this._mu = new Int32Array(this._sab);
  }

  /**
   * Instantiate a Mutex connected to the given one.
   * @param {Mutex} mu the other Mutex.
   */
  static connect(mu) {
    return new Mutex(mu._sab);
  }

  lock() {
    for(;;) {
      if (Atomics.compareExchange(this._mu, 0, unlocked, locked) == unlocked) {
        return;
      }
      Atomics.wait(this._mu, 0, locked);
    }
  }

  unlock() {
    if (Atomics.compareExchange(this._mu, 0, locked, unlocked) != locked) {
      throw new Error("Mutex is in inconsistent state: unlock on unlocked Mutex.");
    }
    Atomics.notify(this._mu, 0, 1);
  }
}

当lock被调用时,它将尝试将互斥锁转换为locked状态并检查是否成功。这是通过来完成的compareExchange,这意味着:“加载值并locked在且仅当为时才与之交换unlocked”。compareExchange返回变量的旧值,可用来检查是否成功获取了互斥量。

WaitGroup

WaitGroup通常用于等待所有Worker完成其工作,然后读取累积输出。

  • 调用add(n)WaitGroup应该通过添加给定值来更改计数器
  • 调用doneWaitGroup等效于调用add(-1)
  • 调用waitWaitGroup应该暂停执行,直到计数器等于0
  • 当心在工作开始之前add应该始终调用它,这就是为什么我在构造函数中添加了初始值。召集呼叫add的工作人员done会导致比赛条件,并可能导致意外的早退wait。

为了实现它,我使用了以下原语:

  • Atomics.add
  • Atomics.load
  • Atomics.wait
  • Atomics.notify

这是代码:

class WaitGroup {
  constructor(initial, opt_sab) {
    this._sab = opt_sab || new SharedArrayBuffer(4);
    this._wg = new Int32Array(this._sab);
    this.add(initial);
  }

  static connect(wg) {
    return new WaitGroup(0, wg._sab);
  }

  add(n) {
    let current = n + Atomics.add(this._wg, 0, n);
    if (current < 0) {
      throw new Error('WaitGroup is in inconsistent state: negative count.');
    }
    if (current > 0){
      return;
    }
    Atomics.notify(this._wg, 0);
  }

  done() {
    this.add(-1);
  }

  wait() {
    for (;;) {
      let count = Atomics.load(this._wg, 0);
      if (count == 0){
        return;
      }
      if (Atomics.wait(this._wg, 0, count) == 'ok') {
        return;
      }
    }
  }
}

首先,add实现将计数器增加给定值。由于add返回了旧值,因此我们将其递增n,然后对其进行推理,就好像它被及时冻结了一样。我们只加载一次计数器,这使该调用保持一致性。

在notify被称为与服务员的默认量,这是无穷大。

我们不检查计数器是否0处于等待唤醒状态的原因是,仅在添加期间计数器达到0的情况下才调用通知。

参考文章:

https://zhuanlan.zhihu.com/p/112126898
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Atomics
https://blogtitle.github.io/using-javascript-sharedarraybuffers-and-atomics/
https://www.cnblogs.com/accordion/p/12533305.html

Enzyme 中 Shallow, Mount 和 render 的区别

原文:https://gist.github.com/fokusferit/e4558d384e4e9cab95d04e5f35d4f913

Shallow

真正的单元测试 (隔离,子组件不会渲染)

Simple shallow

Calls:

  • constructor
  • render

Shallow + setProps

Calls:

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render

Shallow + unmount

Calls:

  • componentWillUnmount

Mount

测试 componentDidMount 和 componentDidUpdate 的唯一方式。会渲染包括子组件在内的所有组件。
需要 DOM (jsdom, domino)。
执行时更稳定。
如果在JSDOM之前包含react,则可能需要一些技巧:

require('fbjs/lib/ExecutionEnvironment').canUseDOM = true;

Simple mount

Calls:

  • constructor
  • render
  • componentDidMount

Mount + setProps

Calls:

  • componentWillReceiveProps
  • shouldComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

Mount + unmount

Calls:

  • componentWillUnmount

Render

仅调用render,但渲染所有子组件。

所以我的经验法则是:

  • 总是从浅处开始
  • 如果应该测试componentDidMount或componentDidUpdate,使用mount
  • 如果要测试组件的生命周期和子行为,使用mount
  • 如果要以比挂载少的开销测试子级渲染,并且对生命周期方法不感兴趣,使用render

There seems to be a very tiny use case for render. I like it because it seems snappier than requiring jsdom but as @ljharb said, we cannot really test React internals with this.

I wonder if it would be possible to emulate lifecycle methods with the render method just like shallow ?
I would really appreciate if you could give me the use cases you have for render internally or what use cases you have seen in the wild.

I'm also curious to know why shallow does not call componentDidUpdate.

Jest 模拟定时器

因为定时器依赖于实时时间,所以在测试时不是很方便。
Jest 可以将定时器换成允许我们自己控制时间的功能。

定时器模拟

jest.useFakeTimers();   // 使用标准计时器函数的模拟版本
jest.useRealTimers()    // 使用标准计时器函数的真实版本
jest.clearAllTimers();  // 从计时器系统中删除任何挂起的计时器

通常每次测试之前手动调用或者在 beforeEach 函数中调用 useFakeTimers。不这样做会导致未重置内部使用计数器。

runAllTimers

通常在测试时我们不想等待那么长时间才真正执行回调。Jest 提供 jest.runAllTimers函数来运行所有等待中的 timer。

jest.useFakeTimers();

it('closes some time after being opened.', (done) => {
  // An automatic door that fires a `closed` event.
  const autoDoor = new AutoDoor();
  autoDoor.on('closed', done);
  autoDoor.open();
  // 直接运行所有等待中的 timer
  jest.runAllTimers();
});

runTimersToTime

如果不想一次性运行所有计时器怎么办?runTimersToTime 可以只模拟时间流逝。

jest.useFakeTimers();

it('announces it will close before closing.', (done) => {
  const autoDoor = new AutoDoor();
  // The test passes if there's a `closing` event.
  autoDoor.on('closing', done);
  // The test fails if there's a `closed` event.
  autoDoor.on('closed', done.fail);
  autoDoor.open();
  // Only move ahead enough time so that it starts closing, but not enough that it is closed.
  jest.runTimersToTime(autoDoor.TIME_BEFORE_CLOSING);
});

runOnlyPendingTimers

只执行当前挂起的宏任务(即,只包括到目前为止由setTimeout()或setInterval()排队的任务。如果当前挂起的任何宏任务调度了新的宏任务,那么这个调用将不会执行这些新任务。

例如递归地调用setTimeout(),在这个场景中使用 runOnlyPendingTimers 能够一次只向前运行一步。

advanceTimersByTime

把计时器都提前指定的毫秒。将执行已经通过setTimeout()或setInterval()排队并且将在此时间帧期间执行所有待处理"宏任务"。此外,如果这些宏任务调度将在同一时间帧内执行的新宏任务,那么将执行这些宏任务,直到队列中不再有宏任务应该在msToRun毫秒内运行。