写点什么

周爱民:捡豆吃豆的学问(下)

用户头像
极客时间
关注
发布于: 2020 年 04 月 20 日
周爱民:捡豆吃豆的学问(下)

你好,我是周爱民,在上篇文章中讨论了《JavaScript 核心原理解析》所学的内容“到底是什么”。接下来,我们再来看看“怎么学这门课程”。

教的方法


我先来说说这个课的教法。有没有简单、明晰的授课方法呢?有的,你在极客时间上也好,学校的课程里也好,常见的一个教法套路便是:


  • 开篇立个题,把问题抛出来,说我们今天要讲什么什么,关键点有一二三四;

  • 接下来就讲这一二三四,分析也好,解说也罢,趣谈也可,总之让你听得开心有味;

  • 最后收纳主题,我们讲了一二三四,你看看你听懂没有。


听不听得懂?能!你认真听下来,只要老师不差,绝对能懂,如果正好是你没听过的内容,还感觉耳目一开,受用无穷。


但是你仔细想想,你至多知道了老师所讲的,还能知道别的什么吗?几乎不能。清楚明白、无有疏漏,但也毫无差别,你听到的跟别人听到的,你学到的跟别人学到的,完全一样。


所以,这也就是“一般的”知识。


如同我刚才说过的:你学的跟人家一样,听到的跟人家一样,知道的跟人家一样,充其量学了个跟老师讲的一模一样的,可不就是“水平一般”么?


核心原理可不可以这样讲?你可不可以这样学?答案是,其实也是可以的。我如果是把这个课程当成一门功法,一一二二地讲给你听,你全然听去了,一字不落下,那这个核心原理课也可以讲得如同流水清风,让你很是舒坦。


但是你可记得,我在这门课的开篇词里说到的,学这门课的目的是什么呢?我说过,我希望你能够构建自己的“语言学习体系”。体系性,才是所有学习中最难得的。如果你有自己学习的体系,甚至是构建体系的体系,那么学这门课又算什么难事呢?这门课真的在讲什么高深的佛法,玄妙的奥义么?都不是的,它是在讲“另一个体系”下的东西!


准确地说,这门课程的讲法,与你过去二三十年来的学习方法是两个不同的体系;这门课程的内容,与你从业经历中所熟悉的语言,也分属于不同的知识体系!既然如此,你怎么可能指望用你现在的法子,在你的体系中去理解这些知识,又或者为这些知识构建“自己的语言学习体系”呢?


所以这门课程,一开始的讲法就大不相同。


这门课程的“标题”是一行代码,它通常很奇怪,有可能很有用,也有可能根本就不能用,它本身或许就难解,就是一个“问题”。然而,你需要知道,这个“标题”,或者这个标题中的“问题”,其实一点儿也不重要,我讲课不去奔着这个问题去,你学习中也不必奔着这个问题来。“求解这个问题”根本就是一件没有什么意义的事情。


所以在从一开始听课,一直听到现在的同学中,还有一些是困于第 1 讲《delete 0:JavaScript 中到底有什么是可以销毁的》的“delete 0”这行代码的,希望明白这行代码在讲什么、有什么用的同学,可以暂时地收收你的思想,因为——解决这个问题,其实没有什么大用。


既然“主题没什么用”,那么我怎么讲呢?其实我每一讲的开始,就无非是拿这个标题做个引子,然后无所谓背景、历史、相关的知识点,以及各种各样的问题纷纷地抛出来,貌似东讲讲、西讲讲,一直都绕开了这个“标题中的代码”在走。


事实上,我把这整个的过程称为“撒豆子”。


怎么“撒”呢?整个课程大概 2/3 甚至 4/5 的内容,就是一大把豆子!一股脑儿地撒出来,没什么章法,也没什么技巧,也没有什么道理。只有一个原则,这些豆子,都是围绕“标题”的这个话题来的,它们或是互有相关性,或者是彼此有相似性,或者……等等等等。


总之,简单地说:它们是“同一个系统”下的东西。


这是我组织每一节课程的基本原则,这个原则就是:在标题之下,东拉西扯,直到一地豆子,四处乱滚。


最后我告诉你,学习这门课程的终极秘密:


把这些碎纸片捡起来,捡起来的,就是你的体系。


学的方法


所以,这门课的听法也就不同。


你非要去盯着每节课的标题,把它弄得一清二楚,知道它怎么来的,怎么解释,以及怎么去应用到项目中,老实说,也无不可,也会有所得。但终究是“捡了芝麻丢了西瓜”。我既然说了,这“大西瓜”就是这一地的豆子,关键是在于你怎么捡,而不是在于我怎么讲。


所以,我再来讲讲这个“捡豆子”的方法。


1.设问,列问题


我可能在讲课中会“问”一些问题,但多数情况下,那是为了讲课的上下文连贯,那些问题本身并没有太明确的指向性。而且,即使是“有指向性”,又能如何呢?你求解了,也不过是多解了一个问题,于你无益。


真正有用的,是你自己学会“提问题”


  • 找一张纸,列一下这个标题给你带来的问题;

  • 列一下在这个主题下面你不知道的,或者你想知道的问题;

  • 列一下听课过程中发现的不解的、难解的问题;

  • 列一下你的理解跟我所讲的内容之间,那些貌似“不可调和”的概念问题。


这些仍然不够重要。更重要的设问是:


  • 为什么会有这些问题?

  • 这些问题指向哪个“黑暗未知的方向”?(这个方向是你的知识盲点)

  • 老师为什么要撒这些豆子?(这些豆子有内在的相关性,而这就是我撒他们的目的)

  • 为什么会存在跟既有知识的矛盾?

  • 为什么在 JavaScript 语言的层面“看不到这些问题”?

  • 为什么……要问上面这些问题?

  • ……


总之,带着问题来学习,学会从你的问题中求解。这个过程,就已经与你之前的学习方法不同了。


是你接受“我所讲的知识”好呢?还是你“找到自己的答案”好呢?


2.求解,在知识域中找答案


既然我在每一个大段落中划了一个知识域,那么上面这些问题也就应该在这个知识域里去求解。


比如说你有人生、事业、理想的困惑了,那么你该去找知心小姐姐,非得在这么二十讲的课程中去寻答案,你肯定是找不到的。所以,上面你可以尽量宽泛地设问,到了这个求解的时候,却应该把它限定到我们讲的这个问题域里面来。这二十讲一共有四个大主题,每个大主题是一个领域。所以你得想想,你的问题可以放在哪个领域里,为什么这么放,为什么是这个领域,为什么不在其他的领域范围内。


  • 这件事跟主题有什么关系?

  • 这个东西是哪方面跟其他东西“有关系”?

  • 怎么表达这种关系?

  • 如何把它们放在同一个体系下(逻辑下或者抽象概念下)来解释?

  • ……


总之,多问几个为什么。


求解、答案都可以是错的,没关系,先做着,直到你能得到一个“貌似可能的解”。


3.推翻,找到反例,精化抽象


有了“貌似可能的解”只是个开头,如果你止步于此,那之前的努力就全部白费了。这跟“一般的学法”并没有什么不同,甚至还远远不如别的老师的教法,直接给你来个“三段式”的立题求解。真正对整个学起提到提升效果的,正是这第三步的“推翻”。


问题是你提出的,答案是你找到的,而推翻也由你来行使。


正是因为你提出问题,所以你知道“源起”;正是因为你找的答案,所以你知道“经由”。你知道一件事情的源起与经由,那么要找到这件事情的关键处,其实只需要看看那些“自相矛盾”的地方,就好了。你找到你的逻辑的、过程的、结果的任何一处反例,进而重新按上述过程来思考,重新找到“貌似可能的(第二个)解”。


如此往复,最终你就看了一些事物最初的,以及最终的面貌。


有了这个面貌,你为它命个名字,抽出个概念,于是就得到了一个“抽象”。有了抽象概念,你就可以在概念的层面上描述事物,以及进行事物的推演。而这,就是架构的基本功。有了体系性,有了概念抽象,有了推演过程,你做的,就是体系架构的工作,而不是“写代码”。代码是你架构的表现方式,仅此而已。


我想这个过程,以及这个过程的可能的结果已经超出了多数同学的“需要”。是的,暂时的,你并不需要变成“架构师”,我这门课也并不是要教你“做一个用 JavaScript 的架构师”。


最佳实践


但是,正因为这个最后“收官”的过程比较抽象、比较虚。所以,我给你在第 1 讲的时候就留了个伏笔,你回顾一下,我在第 1 讲的结束的时候,提过一个问题:


delete x 中,如果 x 根本不存在,会发生什么?


这个问题在“潇潇雨歇”同学的答案后面(他的答案是正确的)。在他的答案里面,我又提了两个潜在的问题。其一是:


在(如果 x 根本不存在,delete x 什么也不做,返回 true)的这种情况下,x 是什么呢?它显然是语法可以识别的东西,但如果这样,在语法上它是什么,且在执行环境中它又是什么?


这个问题其实问得很深,也正是我们这里说的:如果你找到了“貌似可能的解”,那么就进一步地找一下反例,进一步地“精化抽象”。


为什么呢?


其实啊,我们得问一个很深层的、有些哲学性的问题:不知,是不是“知”的一种?


对于 JavaScript 来说,如果一个标识符 x“根本不存在”,那么就是真正的“不知道它存在”吗?不是的,JavaScript 必须知道——“这里一个有未知的标识符”。在 JavaScript 引擎来说,我不能确定它是什么,我的整个引擎中都找不到它,但是我必须把它“标识”出来,只有把它标识出来,我才能处理它!


所以,在语法概念上,词法记号(Tokens)是比标记(Identifer)更底层的抽象概念——也就是更“精化”的抽象。


但在 JavaScript 中,不需要理解所谓“词法记号”,因为它不需要在这种引擎层面的“对代码文本的理解”。而在引擎层面,是将代码文本解析成词法记号序列的:它认为,所有这样的序列——也就是一串字符,要么能解释成标识符,要么就是一个不能识别的序列。


当“不能识别的序列”出现的时候,就是语法解析期错误,简单地说,就是代码错了。接下来,当词法记号是有效的标记时,它可能是能识别的、环境中有的,也就是说它是能被引擎从环境中发现(Resolve)到的引用,因此它就称为“可发现引用(ResolvableReference)”,反之——例如上面提到的“未声明的 x”,就称为“不可发现的引用(IsUnresolvableReference)”。


注意,这些概念不是我生造的,你在读 ECMAScript 规范时就会看到这些概念名词。只是 ECMAScript 并不解释这些概念的由来,以及它们之间的抽象关系。


所以,引擎必须能识别“不能识别的标识符”。能识别才能处理,即使这个处理“仅仅是”抛出一个异常。


你想想,要是不能识别、不能抛出异常,那么这个引擎就该出现完全未知的逻辑了,这种情况下,引擎的更外层,例如宿主程序,又例如操作系统就会无法处理了,就会中止进程。引擎要么抛出一个异常,然后退出程序;要么操作系统直接将引擎杀死,连异常也没有。


我们都是有经验的程序员,上面哪种处理更好,是一目了然的事情。而上溯整个处理过程,就在于在“精化抽象”的过程中,有没有处理“不可发现的引用”,又或者说,“未发现”是不是被当成了一个需要处理的抽象概念。


少了一个抽象概念,少了一个处理逻辑,你的程序就“莫名其妙”地退出了。如果这是一个框架,或者这是一个库,一个平台系统,这个抽象概念一少掉,那么就没有人会去使用它了。因为,你知道的,系统中怕的不是出错,而是,出了错却不知道。


“知未知”,就是这个概念系统中最顶层的抽象了。


这是一个在“概念完整性”方面的实践。


对于一个体系来说,概念完整性是很重要的,如果缺乏关键概念,那么这个体系构建就会出现漏洞。习惯性上,人们用“概念对称性”来解决这个完整性的问题,例如“能发现的 vs. 不能发现的”,这两个概念在抽象层面上,就是指“所有的”;又例如,索引数组对应连续存储,而关联数组对应非连续存储,所以“连续的 vs. 非连续的”,就意味着“数组能处理所有存储(的数据)”。


别担心,还有


到这里,可能就有同学说了,这个讲课的方法是很新颖,学习的方法看起来也可行,但是我就是这么做的呀,问题我想不到“有效的解”啊。


对啊,如果你一次两次就能想到有效的解了,一遍两遍就学成收工了,那也只能说明这个东西还是“一般的东西”,这个方法也就是“一般的方法”,而照着这个路子做下去,你也就还是个“一般的你”。


所以,不要担心,你没学明白也正常,上面的做法找不到“有效的解”也正常,这门课听到现在,以及后面要听的内容,都无非是给你一个“使用这种学习方法”的训练营,你在这个过程中,多练多试,多出错多反思,就成了。


学习要“知味”,你一旦从这个过程中得到了收获,你就如同食髓,乐此不疲了。所以,不要气馁,放松心态,坚持就好了。


发布于: 2020 年 04 月 20 日阅读数: 66
用户头像

极客时间

关注

极客时间,一种新的学习方式 2018.10.30 加入

面向 IT 领域的知识服务产品,通过好内容、好产品、好服务,帮助用户轻松学习、高效学习。 APP/ 公众号 / 微博 / B站 / 知乎 :@极客时间

评论

发布
暂无评论
周爱民:捡豆吃豆的学问(下)