徜徉在知识海洋的一群鲸鱼
eloquentjavascript阅读
eloquentjavascript阅读

eloquentjavascript阅读

在线阅读这本书: https://eloquentjavascript.net/

阅读之前,我觉得还是先体验一下游戏,这样当我们阅读的时候会觉得这是一个很快乐的过程,带着我们的好奇心来阅读探索,这样我们才能真心感受到那种探索的乐趣。

这里是这本书本身的代码游戏:https://bituo123.github.io/game/game2

整体分为三大部分,语言,浏览器,nodejs

语言这里目录是,变量,类型,运算符,程序结构,函数,数据结构:对象和数组,高阶函数,对象的生命周期,机器人项目,bug,正则表达式,模块,异步编程,项目:一个编程语言

浏览器这里是,js和浏览器,document对象,处理事件,项目:一个平台游戏,在canvas上面画画,http和表单,项目:像素编辑器,

nodejs是,nodejs,项目:技术分享网站,

语言:

整数,小数,运算,正负无穷,NaN(也是数字,无意义的数字,0/0这种),字符串,字符的+,拼接数字运算${},typeof单向运算符,大于,小于,不等于,双等于,等于,逻辑运算符,and,or,not,也就是并或非,并&&,或||,非!,优先级:||的优先级最低,然后是&&,然后是比较运算符(>,==,等等),然后是其他。三元运算符,自动类型转换,其中逻辑运算的转换需要注意,||或会转换两边为布尔进行比较,而结果会根据比较结果来展示,这是一个坑,逻辑运算符的转换

程序结构:绑定,函数,函数就是专门绑定的一部分程序,最后返回的值,控制流,循环,条件,

函数:定义函数,三种函数定义规范,赋值,自定义,箭头函数,回调地狱,参数,可以定义多个参数,可以默认参数,闭合,函数里面返回函数,a里面还有函数,b=a(2),b()最后会带上a上次传的参数。递归,不加限制条件的话就像两面镜子对照。增长函数,举了一个例子,关于给动物前面加数字的例子,他把功能拆分了,导致可以兼容更多的需求,我认为这相当于一个实现SDK来调用API,创建值的函数比直接执行副作用的函数更容易以新的方式组合。

数据结构:对象和数组:数据集,属性,方法,对象,Object.keys返回键的数组,Object.assign复制属性,变化性,赋值和传递,给一个对象一个值,这是独立的,而把A传递给B,就是简单的A=B,这样A变,B也变,矩阵,字符串和属性,数学对象。

高阶函数:抽象,比如做饭3次是一个函数,那做某件事3次范围大了,那重复做某事n次范围又大了,关于map,过滤数组,通过数组的值相加减少数组,字符串和字符编码,识别文本。

对象生命周期:介绍了面向对象,封装,然后兔子对象的speak函数,还有一种表达方式speak.call(hungryRabbit, “Burp!”);本来对象调用方法,现在可以用函数直接调用对象这样,普通函数不能用this,箭头函数能用this,原型,祖先模型Object.prototype.你可以使用Object.create来创建一个具有特定原型的对象。

let protoRabbit = {
  speak(line) {
    console.log(`The ${this.type} rabbit says '${line}'`);
  }
};
let killerRabbit = Object.create(protoRabbit);
killerRabbit.type = "killer";
killerRabbit.speak("SKREEEE!");
// → The killer rabbit says 'SKREEEE!'

类,构造函数就是为了保证实例有个初始类一样的属性,

function makeRabbit(type) {
  let rabbit = Object.create(protoRabbit);
  rabbit.type = type;
  return rabbit;
}

也可以自己修改属性,

function Rabbit(type) {
  this.type = type;
}
Rabbit.prototype.speak = function(line) {
  console.log(`The ${this.type} rabbit says '${line}'`);
};

let weirdRabbit = new Rabbit("weird");

类别符号,class,

class Rabbit {
  constructor(type) {
    this.type = type;
  }
  speak(line) {
    console.log(`The ${this.type} rabbit says '${line}'`);
  }
}

let killerRabbit = new Rabbit("killer");
let blackRabbit = new Rabbit("black");

重构,map,map的键可以是对象,同时对象有自己的原型属性,为了不让对象有原型属性,可以create一个null,然后给他设置属性,Object.keys可以忽略原型只看自己的属性,同时hasOwnProperty也是判断自己有没有而不是原型,多态性,这是不是就是java的封装,继承,多态?多态就是表示你可以重写方法,类型,String调用的是对象的toString,你可以自己写,符号,符号可以作为一个属性名称,定义一个变量,可以用字符串作为值新建,比如:

const toStringSymbol = Symbol("toString");
Array.prototype[toStringSymbol] = function() {
  return `${this.length} cm of blue yarn`;
};

console.log([1, 2].toString());
// → 1,2
console.log([1, 2][toStringSymbol]());
// → 2 cm of blue yarn

符号还可以这样用

let stringObject = {
  [toStringSymbol]() { return "a jute rope"; }
};
console.log(stringObject[toStringSymbol]());
// → a jute rope

迭代器接口,请注意,next、value和done属性的名称是纯字符串,而不是符号。只有Symbol.iterator,可能会被添加到很多不同的对象中,是一个实际的符号。

let okIterator = "OK"[Symbol.iterator]();
console.log(okIterator.next());
// → {value: "O", done: false}
console.log(okIterator.next());
// → {value: "K", done: false}
console.log(okIterator.next());
// → {value: undefined, done: true}

java类的get,set和静止属性,

有时你想把一些属性直接附加到你的构造函数上,而不是附加到原型上。这样的方法不会访问类的实例,但可以,例如,用来提供额外的方法来创建实例。

在一个类的声明中,在其名称前写有static的方法被存储在构造函数上。所以温度类允许你写Temperature.fromFahrenheit(100)来创建一个使用华氏度的温度。

class Temperature {
  constructor(celsius) {
    this.celsius = celsius;
  }
  get fahrenheit() {
    return this.celsius * 1.8 + 32;
  }
  set fahrenheit(value) {
    this.celsius = (value - 32) / 1.8;
  }

  static fromFahrenheit(value) {
    return new Temperature((value - 32) / 1.8);
  }
}

继承extends,

机器人项目:本章中我们的项目是建立一个自动机,一个在虚拟世界中执行任务的小程序。我们的自动机将是一个邮递员机器人,它负责收取和放下包裹。Meadowfield村不是很大。它由11个地方组成,它们之间有14条道路。它可以用这样的道路阵列来描述。

const roads = [
  "Alice's House-Bob's House",   "Alice's House-Cabin",
  "Alice's House-Post Office",   "Bob's House-Town Hall",
  "Daria's House-Ernie's House", "Daria's House-Town Hall",
  "Ernie's House-Grete's House", "Grete's House-Farm",
  "Grete's House-Shop",          "Marketplace-Farm",
  "Marketplace-Post Office",     "Marketplace-Shop",
  "Marketplace-Town Hall",       "Shop-Town Hall"
];

村子里的道路网络形成了一个图。图是一个点(村里的地方)和它们之间的线(道路)的集合。这个图将是我们的机器人所移动的世界。

字符串阵列不是很容易处理。我们感兴趣的是我们可以从一个特定的地方到达的目的地。让我们把道路列表转换成一个数据结构,对于每个地方,告诉我们从那里可以到达什么。

给定一个边的数组,buildGraph创建一个map对象,为每个节点存储一个连接节点的数组。

它使用分割方法从道路字符串(具有 “开始-结束 “的形式)到包含作为独立字符串的开始和结束的两元素数组。

我们的机器人将在村子里移动。各个地方都有包裹,每个包裹都是寄到其他地方的。机器人在遇到包裹时就会捡起它们,在到达目的地时就会把它们送到。

机器人必须决定,在每个点上,下一步去哪里。当所有的包裹都被送达时,它就完成了任务。

为了能够模拟这个过程,我们必须定义一个能够描述它的虚拟世界。这个模型告诉我们机器人的位置和包裹的位置。当机器人决定移动到某个地方时,我们需要更新这个模型以反映新的情况。

如果你在思考面向对象的编程,你的第一个冲动可能是开始为世界上的各种元素定义对象:一个机器人的类,一个包裹的类,也许一个地方的类。然后,这些对象可以持有描述其当前状态的属性,例如某个地点的包裹堆,我们可以在更新世界时改变这些属性。

这不对哦,

至少,它通常是这样。听起来像一个对象的事实并不自动意味着它应该是你程序中的一个对象。反射性地为你的应用程序中的每一个概念编写类,往往会给你留下一个相互关联的对象的集合,每个对象都有自己的内部,不断变化的状态。这样的程序往往难以理解,因此容易被破坏。

相反,让我们把村庄的状态压缩到定义它的最小值集合。这里有机器人的当前位置和未交付的包裹集合,每个包裹都有一个当前位置和一个目的地地址。就这样了。

当我们在做这件事的时候,让我们在机器人移动时不改变这个状态,而是为移动后的情况计算一个新的状态。

移动方法是行动发生的地方。它首先检查是否有一条从当前位置到目的地的道路,如果没有,它将返回旧状态,因为这不是一个有效的移动。

然后,它创建一个新的状态,将目的地作为机器人的新位置。但它也需要创建一个新的包裹集–机器人携带的包裹(在机器人的当前位置)需要被移动到新的地方。而那些被送到新地方的包裹需要被送达–也就是说,它们需要从未送达的包裹集中被移除。对地图的调用负责移动,而对过滤器的调用则负责交付。

当包裹对象被移动时,它们并没有被改变,而是被重新创建。移动方法给了我们一个新的村庄状态,但完全保留了旧的状态。

永久数据Object.freeze({value: 5});不管如何改变都不会导致这个属性发生改变。

一个送货机器人看着这个世界,并决定它要向哪个方向移动。因此,我们可以说,机器人是一个函数,它接收一个VillageState对象并返回附近的地方的名称。

因为我们希望机器人能够记住一些东西,这样他们就可以制定和执行计划,所以我们也把他们的记忆传递给他们,并允许他们返回一个新的记忆。因此,机器人返回的东西是一个包含它想要移动的方向和一个内存值的对象,这个内存值将在下一次被调用时反馈给它。

考虑一个机器人要做什么来 “解决 “一个给定的状态。它必须通过访问每一个有包裹的地点来拾取所有的包裹,并通过访问每一个有包裹的地点来运送包裹,但只能在拾取包裹之后。

什么是可能成功的最愚蠢的策略?机器人可以每转一圈都向一个随机的方向行走。这意味着,很有可能,它最终会碰到所有的包裹,然后也会在某一时刻到达应该交付包裹的地方。

请记住,Math.random()返回一个介于0和1之间的数字,但总是低于1。用这样的数字乘以数组的长度,然后对其应用Math.floor,就可以得到数组的随机索引。

由于这个机器人不需要记住任何东西,它忽略了它的第二个参数(记住,JavaScript函数可以用额外的参数来调用而不会产生不良影响),并且在它返回的对象中省略了内存属性。

为了让这个复杂的机器人开始工作,我们首先需要一种方法来创建一个带有一些包裹的新状态。一个静态方法(在这里通过直接给构造函数添加一个属性来编写)是放置该功能的一个好地方。

由于机器人没有很好地提前计划,它需要花很多时间来运送包裹。我们将很快解决这个问题。

要想从更愉快的角度看模拟,你可以使用本章编程环境中的runRobotAnimation函数。这将运行模拟,但不是输出文本,而是向你展示机器人在村庄地图上的移动。

我们应该能够比随机机器人做得更好。一个简单的改进是,从现实世界的邮件投递方式中得到提示。如果我们找到一条能经过村子里所有地方的路线,机器人可以把这条路线跑两遍,这时就能保证完成任务。这里有一条这样的路线(从邮局开始)。

为了实现遵循路线的机器人,我们需要利用机器人的内存。机器人将其余的路线保存在其内存中,每转一圈就丢掉第一个元素。

尽管如此,我也不会真的把盲目地遵循固定路线称为智能行为。如果机器人根据需要完成的实际工作调整自己的行为,它可以更有效地工作。

要做到这一点,它必须能够有意地朝着一个给定的包裹或必须交付包裹的地点移动。要做到这一点,即使目标在一个以上的地方,也需要某种寻路的功能。

在一个图中寻找路线的问题是一个典型的搜索问题。我们可以知道一个给定的解决方案(路线)是否是一个有效的解决方案,但我们不能像计算2+2那样直接计算出解决方案。相反,我们必须不断创造潜在的解决方案,直到我们找到一个有效的解决方案。

通过一个图的可能路线的数量是无限的。但在搜索从A到B的路线时,我们只对以A为起点的路线感兴趣。我们也不关心那些两次访问同一地点的路线,这些路线绝对不是最有效的路线。所以这就减少了路线搜索器需要考虑的路线数量。

事实上,我们主要对最短的路线感兴趣。所以我们要确保在看长的路线之前先看短的路线。一个好的方法是,从起点开始 “增长 “路线,探索每一个还没有被访问过的可到达的地方,直到路线到达目标。这样一来,我们就只会探索那些可能感兴趣的路线,并找到通往目标的最短路线(或者最短路线之一,如果有多条的话)。

这里有一个函数可以做到这一点。

探索必须按照正确的顺序进行–先到达的地方必须先被探索。我们不能一到达一个地方就立即进行探索,因为这意味着从那里到达的地方也会立即被探索,以此类推,即使可能有其他更短的路径尚未被探索。

因此,该函数保留了一个工作列表。这是一个下一步应该探索的地方的数组,以及让我们到达那里的路线。它开始时只有起始位置和一个空的路线。

然后,搜索的操作是取列表中的下一个项目,并对其进行探索,这意味着从那个地方出发的所有道路都会被查看。如果其中一条是目标,就可以返回一条完整的路线。否则,如果我们之前没有看过这个地方,就会在列表中添加一个新的项目。如果我们以前看过这个地方,因为我们先看的是短的路线,所以我们已经找到了一条去那个地方的较长的路线,或者正好和现有的路线一样长,我们不需要再去探索它。

你可以直观地想象,这是一张由已知路线组成的网,从起点位置爬出来,在四面八方均匀地生长(但绝不会纠缠在一起)。一旦第一条线到达目标地点,这条线就会被追溯到起点,从而得到我们的路线。

我们的代码不会处理工作列表上没有更多工作项目的情况,因为我们知道我们的图是连接的,这意味着每个位置都可以从所有其他位置到达。我们总是能够在两点之间找到一条路线,而且搜索不会失败。

这个机器人把它的记忆值作为一个移动方向的列表,就像跟随路线的机器人一样。每当这个列表为空时,它就必须想出下一步该怎么做。它选择集合中第一个未交付的包裹,如果该包裹还没有被取走,它就绘制出一条走向它的路线。如果包裹已经被取走了,它仍然需要被送达,所以机器人就会创建一条朝向送货地址的路线。

bug:

正则表达式:正则表达式是一种描述字符串数据模式的方法。它们形成了一种小型的、独立的语言,是JavaScript和许多其他语言和系统的一部分。但它们是检查和处理字符串的强大工具。

包和模块:

异步编程:鸟类通讯作为例子,回调,setTimeout函数,会等待一个给定的毫秒数(一秒钟是一千毫秒),然后调用一个函数。鸟类的信息通讯,发送函数包含发送的内容,以及对方发送成功后的返回操作,承诺,