阅读《Javascript 编程精解》的一些思考

CodeingBoy 2月 24, 2017

最近在恶补前端知识,正在阅读《Javascript 编程精解》作为 Javascript 入门第二本的教材(第一本是《Javascript DOM 编程艺术》),阅读之中难免有难题和思考,因此记录下来。

值、类型和运算符

==与null的思考

第 9 页中提到:

如果运算符两侧存在 null 或者 undefined,那么只有两侧均为  null 或  undefined 时结果才为true。
上文提及的最后一点其实非常有用,当我们想确定一个值是否为真,而非 null 或  undefined 时,直接使用“==”(或“!==”)运算符来进行比较即可。

第一行的特性的使用场景有点不是很直观……

经过思考之后,发现它的使用场景其实与 === 运算符的含义是一致的:严格判定值为 null 或 undefined。

结合一下代码例子:

所以,在使用 == 与 null 或 undefined 比较的时候,应当是可以理解和互换为 === 的含义的。

函数

关于闭包中局部变量的思考

第 32 页中提到了闭包,即在 A 函数中返回 B 函数,并且这个 B 函数是可以访问 A 函数中的变量的,甚至是跳出 A 函数后。

传统的编程语言(如 C、C++)的局部变量都有一点,即离开了函数作用域后,局部变量就会被回收,因此不能返回局部变量的指针或引用(因为这时候局部变量已经被回收,指向被回收的局部变量其实就是指向一处未知的地方)。

但由于 Javascript 的变量回收机制其实是基于引用,因此只要存在对变量的引用,即使离开函数作用域,就不会被回收。

那么闭包函数如何访问局部变量就不难想到了:创建闭包函数时,闭包函数包含了对该局部变量的引用,因此在离开上一级函数作用域时,该局部变量也不会被回收,并可以通过闭包函数中的引用进行访问。

第五章中的一些不理解的地方

两句不太理解的话

但是,现在循环体位于函数的内部,即包含在 forEach 函数的括号之中。因此我们要在整个语句之后添加右大括号和右括号。(第60页)

不是很理解这里说的意思。添加右大括号和右括号是理所当然的,右大括号用于终止函数的定义,右括号用于终止参数列表。

其中内部函数与语句块之间的主要区别在于,在内部函数中声明的变量不会因为外部函数执行结束而丢失。(第62页)

应该是和“关于闭包中局部变量的思考”里面我理解的是一回事……吧。

TODO: 需要继续思考并对比差异

map函数的理解

对于 65 页介绍的 map 函数的作用理解并不是很直观,尤其是“映射”的概念。

简单来说,map 函数会根据你传入的函数以及遍历时传入的对象,“生成”一个新值,并按原数组顺序存放在一个数组中返回。

说到“映射”,结合这个图应该会好理解一点。

x 你可以看成原数组中的对象们,U 正是生成的新值们。新值是如何产生的?根据映射 T (也就是传入的函数)生成的。

不过这幅图有一点不贴切:没有按原顺序存放。在 Javascript 中,U 数组中的值应该为 [2, 8, 16, 4]。

bind 方法的理解

函数的 bind 方法用于绑定好已经预先决定的参数,就是预先指定参数,但是这种解释有点怪怪的。

Mozilla Developers Network 上的一段解释好像还不错:

bind() 函数会创建一个新函数(称为绑定函数),新函数与被调函数(绑定函数的目标函数)具有相同的函数体(在 ECMAScript 5 规范中内置的 call属性)。当目标函数被调用时 this 值绑定到 bind() 的第一个参数,该参数不能被重写。绑定函数被调用时,bind() 也接受预设的参数提供给原函数。一个绑定函数也能使用 new操作符创建对象:这种行为就像把原函数当成构造器。提供的 this 值被忽略,同时调用时的参数被提供给模拟函数。

也就是说,bind 使得函数可以“附着”于一个对象上,因而函数可以使用 this 访问这个函数。

例如 MDN 上对应的示例代码:

最初,getX() “附着”于 module 对象上,因此它可以访问 module 中的 x 属性。但是如果从 module 对象提取出来,函数试图访问 this.x 时就会尝试访问全局作用域中的 x。而将其与 module 再次绑定后,“附着”于 module 上的函数就可以读取 module.x 了。

深入理解对象

关于对象原型的思考

第 76 页提到:

对于构造函数来说(实际上,对所有函数适用),都会自动获得一个名为 prototype 的属性。

这里的“所有函数”让我不好理解,难道我写一个:

只要我调用 a(),返回来的变量(上面的例子是 b)都会带有 prototype 属性吗?

然而经过测试,并不是。

上面那一句话是对于函数而言的,对于每一个函数(无论是构造函数还是非构造函数),函数对象(别忘了,函数也是对象)都会带有 prototype 这个属性。

因此:

可以通过这样来查看函数的 prototype 属性。而关于函数的 prototype 属性都有些什么,请自行 Google。

关于获取原型的两种方法

第 76 页还提到:

我们需要注意两种方法使用上的区别,第一种是使用构造函数建立对象和原型之间关联(直接通过 prototype 属性来获取),第二种是将原型对象作为对象的属性(使用 Object.getPrototypeOf 获取属性)。

在理解了上面通过构造函数的 prototype 属性来获取原型信息后,这句话也就不难理解了。

如果我们要获取原型信息,第一,可以使用构造函数的 prototype 属性进行获取:

但假如我们不知道一个对象的构造函数该怎么办呢,这时候就可以使用第二种方法——调用 Object.getPrototypeOf() 并传入对象来获取原型信息:

不使用 new 而直接调用构造函数会怎样?

由于构造函数不返回任何对象,因此会返回 undefined。

总结

这一章的内容比较重要,也有点难度,因此稍微总结了一下:

  • 可以使用构造函数构造新的对象,使用方法为 new XXX(),XXX 为构造函数
  • 对象还有对象原型,当对象自身没有一个属性时,会到原型中进行嵌套查找
  • 从构造函数获取原型信息可以访问函数对象的 prototype 属性
  • 获取一个对象的原型可以使用 Object.getPrototypeOf()
  • 可以在对象中对原型的同名属性进行覆盖
  • 属性分为可枚举的及不可枚举的,不可枚举的属性将不会在使用 var b in a 遍历时出现,但会在 b in a 中返回 true。要定义不可枚举的属性,使用 Object.defineProperty()
  • 要查询对象自身的属性,使用对象的 hasOwnProperty()
  • 可以通过 Object.create() 中传入原型来实例化一个原型,也可以传入 null 创建一个无原型的对象
  • 可以定义 Getter 和 Settter, 同时在对象外部通过普通访问属性的方法调用 Getter 和 Setter
  • 定义了 Getter 但未定义 Setter  时,会忽略所有的属性修改操作
  • 设 A 是 B 的父类,则可以通过在 B 类构造函数调用 A.call(this, argv) 来达到继承的目的
  • 可以使用 instanceOf 运算符获取对象是否继承自某个构造函数的信息

项目实战:构建电子生态系统

关于函数中 this 的作用域的思考

94 页提到:

值得注意的是对于传递给 forEach 方法的函数来说,其作用域已经超出了构造函数作用域的范围。每个函数调用都会将自己作为 this 引用,因此内部函数的 this 与外部函数(构造函数)的 this 不同,不会指向新创建的对象。实际上,如果我们没有以方法的形式来调用某个函数,该函数内部的 this 会指向全局对象。

也就是说,如果:

第一句 log 语句会返回 Object ,第二句则直接返回全局对象 Window。

注意,这时候的 this 并不指向迭代的元素自身。因为默认的函数没有“附着”在任何对象上,因此 this 默认为全局对象。(参见上面的“bind 方法的理解”)

即使是在 arr.forEach 中进行调用,但传入的函数仍然是普通函数而不是一个对象的方法,因此 this 仍然为全局对象。

如果还是不理解,将上面的代码切换为下面的等价代码试试:

对于在普通函数中设置 this,有几种方法:

  1. 调用函数对象的 bind() 方法
  2. 在函数体的上一级函数作用域中设置一局部变量,透过该局部变量访问 this
  3. 在一些高阶函数传入特定值
  4. 调用函数对象的 call() 方法

正则表达式回溯(124页)

replace方法重写替换函数的参数问题(126页)

require函数:只能使用exports吗?第二个问题的意思?(139页)

本文采用 CC BY-NC-SA 3.0 协议进行许可,在您遵循此协议的情况下,可以自由共享与演绎本文章。
本文链接:https://blog.codeingboy.me/reflections-about-eloquent-javascript/

发表评论

电子邮件地址不会被公开。 必填项已用*标注