Dan Abramov脑中的JS知识图谱

2022-05-13 22:48:17 浏览数 (2)

作者:Dan Abramov 原文链接:What Is JavaScript Made Of? 译者:Yodonicc 译者注:Dan Abramov是Redux的作者、也是React核心开发成员,在React社区中享有很高的声望。本文中,他讲述了自己脑中的JS知识图谱。

在我使用JavaScript的头几年里,我觉得自己是个骗子。尽管我可以用框架建立网站,但还是缺少一些东西。我惧怕JavaScript的工作面试,因为我对基础知识掌握得不够牢固。

多年来,我形成了一个关于JavaScript的心智模型,给了我信心。在这里,我分享的是它的一个非常压缩的版本。它的结构像一个词汇表,每个主题都有几句话。

当你读完这篇文章时,试着在心理上记下你对每个主题的自信程度。如果其中有不少是没有信心的,我也不会对你进行评判。在这篇文章的末尾,有一些东西可能对这种情况有帮助。


  • 。值的概念是有点抽象的。它是一个 "东西"。对于JavaScript来说,值就像数字之于数学,或者点之于几何学一样。当你的程序运行时,它的世界充满了值。像1、2和420这样的数字是数值,但其他一些东西也是,比如这句话——"牛哞哞叫"。但并不是所有的东西都是一个值。一个数字是一个值,但一个if语句不是。下面我们来看看几种不同类型的值。
    • 值的类型。有几种不同的值的 "类型"。例如,像420这样的数字,像 "牛哞哞叫 "这样的字符串,对象,以及其他一些类型。你可以通过在某个值前面加上typeof来了解它的类型。例如,console.log(typeof 2)打印出 "number"
    • 原始值。有些值的类型是 "原始的"。它们包括数字、字符串和其他一些类型。原始值的一个特殊之处在于,你不能创建更多的原始值,也不能以任何方式改变它们。例如,每次你写2,你都会得到相同的值2。你不能在你的程序中 "创造 "另一个2,或使2的值 "变成 "3。
    • nullundefined。这是两个特殊的值。它们之所以特殊,是因为有很多事情你不能用它们来做--它们经常导致错误。通常情况下,null表示某些值是故意丢失的,undefined表示某个值是无意丢失的。然而,什么时候使用这两种情况是由程序员决定的。它们的存在是因为有时操作失败比带着一个缺失的值进行操作要好。
  • 相等。和 "值 "一样,相等是JavaScript的一个基本概念。我们说两个值是相等的,当它们......事实上,我从来不会这么说。如果两个值相等,这意味着它们是同一个值。不是两个不同的值,而是一个!例如,"Cows go moo" === "Cows go moo"2 === 2,因为2就是2。 注意,我们用三个等号来表示JavaScript中的这个相等概念。
    • 严格的相等。与上述相同。
    • 参考性相等。和上面一样。
    • 松散的相等性。哦,这个是不同的! 松散相等是指我们使用两个等号(==)。事物可以被认为是松散相等的,即使它们指的是看起来相似的不同值(比如2"2")。为了方便起见,它很早就被添加到了JavaScript中,并且从那时起就造成了无尽的混乱。这个概念并不基本,但却是一个常见的错误来源。你可以学习它的工作原理未雨绸缪,但很多人都尽量避免它。
  • 字面量。字面量是指你通过在程序中写下一个值来引用它。例如,2是一个数字字面量,而 "Banana "是一个字符串字面量。
  • 变量。变量让你用一个名字来指代某个值。例如,let message = "Cows go moo"。现在你可以写message,而不是在你的代码中每次都重复同样的句子。你以后可以改变message来指向另一个值,比如message = "I am the walrus"。注意这并不改变值本身,而只是改变消息指向的地方,就像一条"线"。它指向的是"Cows go moo",而现在它指向的是"I am the walrus"
    • 作用域。如果整个程序中只能有一个message变量,那就糟糕了。相反,当你定义一个变量时,它在你的程序的某个部分是可用的。这一部分被称为 "作用域"。作用域是有规则的,但通常你可以在你定义变量的地方寻找最接近的{和}括号。那个代码 "块 "就是它的作用域。
    • 赋值。当我们写message = "I am the walrus "时,我们改变message变量,使其指向"I am the walrus "的值。这就是所谓的赋值、写、或设置变量。
    • let vs const vs var: 通常情况下,你需要let。如果你想禁止对这个变量进行赋值,你可以使用 const。(有些代码库和同事很迂腐,强迫你在只有一个赋值时使用 const) 。尽量避免使用var,因为它的范围规则很混乱。
  • 对象。对象是JavaScript中一种特殊的值。对象最酷的地方在于,它们可以与其他的值有联系。例如,一个{flavor: "vanilla"}对象有一个指向 "vanilla "值的flavor属性。把对象看作是 "你自己的 "值,并给外界提供了一条“线”。
    • 属性。一个属性就像一条从对象中伸出来的 "线",指向某个值。它可能会让你想起一个变量:它有一个名字(像flavor)并指向一个值(像 "vanilla ")。但与变量不同,属性 "生活 "在对象本身,而不是在你的代码中的某个地方(范围)。一个属性被认为是对象的一部分--但它所指向的值却不是。
    • 对象字面量。对象字面量是通过在你的程序中写下它来创建一个对象值,比如{}{flavor: "vanilla"}。在{}里面,我们可以有多个property: value键值对,用逗号分隔。这让我们可以设置属性的 "线 "从我们的对象指向哪里。
    • 对象ID。我们在前面提到,2等于2(换句话说,2===2),因为每当我们写2的时候,我们 "召唤 "的是同一个值。但是每当我们写{}时,我们总是会得到一个不同的值! 所以{}不等于另一个{}。在控制台中试试这个。{}==={}(结果为false)。当计算机在我们的代码中遇到2时,它总是给我们相同的2值。然而,对象字面量是不同的:当计算机遇到{}时,它会创建一个新的对象,这总是一个新的值。那么,什么是对象ID呢?它是相等的另一个术语,或者说是值的同一性。当我们说 "a和b有相同的ID "时,我们的意思是 "a和b指向相同的值"(a === b)。当我们说 "a和b有不同的ID "时,我们的意思是 "a和b指向不同的值"(a !==b)。
    • 点运算符。当你想从一个对象中读取一个属性或向其赋值时,你可以使用点(.)符号。例如,如果一个变量iceCream指向一个对象,该对象的属性flavor指向 "巧克力",那么写出iceCream.flavor就可以得到"巧克力"
    • 方括号运算符。有时你并不事先知道你想读取的属性名称。例如,也许有时你想读取iceCream.flavor,有时你想读取iceCream.taste。当属性的名称本身是一个变量时,括号([])符号可以让你读取该属性。例如,let ourProperty = 'flavor'。那么iceCream[ourProperty]将给我们"巧克力"。奇怪的是,我们在创建对象时也可以使用它:{ [ourProperty]: "vanilla" }
    • 突变。当有人把一个对象的属性改成不同的值时,我们就说这个对象被突变了。例如,如果我们声明let iceCream = {flavor: "vanilla"},我们以后可以用iceCream.flavor = "chocolate "来突变它。注意,即使我们用const来声明iceCream,我们仍然可以突变iceCream.flavor。这是因为const只会阻止对iceCream变量本身的赋值,但我们却突变了它所指向的对象的一个属性(flavor)。有些人发誓不再使用const,因为他们认为这太容易引起误解了。
    • 数组。数组是一个表示东西列表的对象。当你写一个像["香蕉"、"巧克力"、"香草"]这样的数组字面时,你基本上创建了一个对象,其属性0指向 "香蕉 "字符串值,属性1指向 "巧克力 "值,而属性2指向 "香草 "值。写{0: ..., 1: ..., 2: ...}会很烦人,这就是为什么数组很有用。还有一些对数组进行操作的内置方法,如mapfilterreduce。如果reduce看起来很混乱,不要绝望——它对每个人来说都是混乱的。
    • 原型。如果我们读取一个不存在的属性,会发生什么?例如,iceCream.taste(但我们的属性叫 flavor)。简单的答案是我们会得到特殊的未定义值。更细微的答案是,JavaScript中的大多数对象都有一个 "原型"。你可以把原型想象成每个对象上的一个 "隐藏 "属性,它决定了 "下一步要看哪里"。因此,如果冰激凌上没有味道属性,JavaScript会在它的原型上寻找味道属性,然后在该对象的原型上寻找,以此类推,如果它到达这个 "原型链 "的末端而没有找到.taste,才会给我们未定义。你很少会与这种机制直接互动,但它解释了为什么我们的冰激凌对象有一个我们从未定义过的toString方法——它来自原型。
  • 函数。一个函数是一个特殊的值,有一个目的:它代表你程序中的一些代码。如果你不想多次编写相同的代码,函数就很方便。"调用 "一个像sayHi()这样的函数,告诉计算机运行里面的代码,然后再回到程序中的位置。在JavaScript中,有很多方法来定义一个函数,它们的作用略有不同。
    • 参数。参数让你从你调用函数的地方传递一些信息给你的函数:sayHi("Amelie")。在函数内部,它们的作用类似于变量。它们要么被称为 "arguments",要么被称为 "parameters",这取决于你阅读的是哪一方(函数定义或函数调用)。然而,这种术语上的区别是迂腐的,在实践中这两个术语是可以互换使用的。
    • 函数表达式。以前,我们将一个变量设置为一个字符串值,比如让message = "I am the walrus"。事实证明,我们也可以将一个变量设置为一个函数,比如let sayHi = function() { }。这里的=后面的东西被称为函数表达式。它给了我们一个特殊的值(一个函数),代表我们的这段代码,所以如果我们想的话,以后可以调用它。
    • 函数声明。每次都写类似let sayHi = function() { }的东西会很累,所以我们可以用一个更简短的形式来代替:function sayHi() { }。这就是所谓的函数声明。我们不用在左边指定变量名,而是把它放在函数关键字后面。这两种风格大多是可以互换的。
    • 函数提升。通常情况下,你只能在用letconst声明后使用一个变量。这对于函数来说可能很烦人,因为它们可能需要互相调用,而且很难跟踪哪个函数被其他函数使用,需要先定义。为了方便起见,当(也只有当!)你使用函数声明语法时,它们的定义顺序并不重要,因为它们被 "提升"。这是一种花哨的说法,从概念上讲,它们都被自动移到了作用域的顶部。当你调用它们时,它们都已经被定义了。
    • this。可能是最被误解的JavaScript概念,this就像一个函数的特殊参数。你不会自己把它传给一个函数。相反,JavaScript自己会传递它,这取决于你如何调用函数。例如,使用点.运算符的调用——如iceCream.eat()——会从.前面的东西(在我们的例子中,是iceCream)获得一个特殊的this值。在一个函数中this的值取决于该函数的调用方式,而不是它的定义位置。像.bind、.call和.apply这样的抓手让你对this的值有更多控制。
    • 箭头函数。箭头函数类似于函数表达式。你像这样声明它们:let sayHi = () => { }。它们很简明,经常用于单行代码。箭头函数比普通函数更有局限性——例如,它们没有任何 "this "的概念。当你在一个箭头函数里面写这个时,它使用上面最接近的 "常规 "函数的this。这类似于如果你使用一个只存在于上面一个函数中的参数或变量会发生什么。实际上,这意味着人们在使用箭头函数时,希望在其内部 "看到 "与周围代码中相同的this。
    • 函数绑定。通常,将一个函数f绑定到一个特定的this值和参数上意味着创建一个新的函数,用这些预定义的值调用f。JavaScript有一个内置的辅助工具来做这件事,叫做.bind,但你也可以用手来做。绑定是一种流行的方式,可以使嵌套函数 "看到 "与外层函数相同的this值。但现在这个用例是由箭头函数处理的,所以绑定并不经常使用。
    • 调用栈。调用一个函数就像进入一个房间。每次我们调用一个函数,它里面的变量都要重新初始化。因此,每次函数调用就像用它的代码构建一个新的 "房间 "并进入其中。我们的函数的变量 "住 "在那个房间里。当我们从函数中返回时,那个 "房间 "和它的所有变量一起消失了。你可以把这些房间想象成一个垂直的房间堆栈——一个调用堆栈。当我们退出一个函数时,我们会回到它在调用栈中 "下面 "的函数。
    • 递归。递归是指一个函数从自身内部调用自己。当你想在你的函数中再次重复你刚才做的事情时,这是非常有用的,但要针对不同的参数。例如,如果你在写一个抓取网页的搜索引擎,你的collectLinks(url)函数可能首先收集一个页面的链接,然后为每个链接调用自己,直到访问所有的页面。递归的隐患在于,很容易写出永远无法完成的代码,因为一个函数永远在调用自己。如果发生这种情况,JavaScript会用一个叫做 "堆栈溢出 "的错误来阻止它。之所以这样叫,是因为它意味着我们的调用栈中堆积了太多的函数调用,而且它实际上已经溢出了。
    • 高阶函数。高阶函数是指通过接收其他函数作为参数或返回这些参数来处理其他函数的函数。这起初可能看起来很奇怪,但我们应该记住,函数是值,所以我们可以把它们传递出去——就像我们对数字、字符串或对象所做的那样。这种风格可能会被过度使用,但适度地使用是非常有表现力的。
    • 回调。回调并不是一个真正的JavaScript术语。它更像是一种模式。它是指你把一个函数作为参数传递给另一个函数,期望它稍后能回调你的函数。你在期待一个 "回调"。例如,setTimeout接收一个回调函数,然后......在超时后回调你。但回调函数并没有什么特别之处。它们是普通的函数,当我们说 "回调 "时,我们只是在谈论我们的期望。
    • 闭包:通常情况下,当你退出一个函数时,它的所有变量都会 "消失"。这是因为没有什么需要它们了。但是,如果你在一个函数里面声明一个函数呢?那么内部函数仍然可以在以后被调用,并读取外部函数的变量。在实践中,这是很有用的!但要做到这一点,外层函数的变量需要 "停留 "在某个地方。所以在这种情况下,JavaScript负责 "保持变量的活力",而不是像通常那样 "忘记 "它们。这就是所谓的 "闭包"。虽然闭包经常被认为是一个被误解的JavaScript特性,但你可能每天都会不知不觉地使用它们很多次。

JavaScript就是由这些概念组成的,而且还有更多。在我能够建立一个正确的心智模型之前,我对自己的JavaScript知识感到非常焦虑,我希望能够帮助下一代的开发者更早地弥补这个差距。

如果你想和我一起深入研究这些主题,我有东西给你。Just JavaScript是我提炼出来的关于JavaScript如何工作的心智模型,它将以惊人的Maggie Appleton的视觉插图为特色。与这篇文章不同的是,它以较慢的速度进行,所以你可以跟随每个细节。

Just JavaScript还处于非常早期的阶段,所以它只能以一系列电子邮件的形式提供,没有经过润色或编辑。如果这个项目听起来很有趣,你可以注册,通过电子邮件接收免费的草稿。我将感谢你的反馈。谢谢你!"。

注:特别感谢技术指导dazhao(赵达)对本文翻译的审阅指正

0 人点赞