最近有点流年不利,不论是大环境还是个人的境遇。
好的方面是又可以静下心来,读书,学习新的东西。要一直保持积极乐观的心态啊!
今天我们继续读《你不知道的js》的最后一卷。开始吧。
深入编程
代码
程序常被称为源码或代码,它是一组特定的指令,用来指示计算机要执行哪些任务。
指令的格式和组合规则被称为计算机语言,有时也被称为语法,这非常类似于英语中告诉你如何拼写单词以及如何使用单词和标点符号来构造有效的句子。
表达式
语句由一个或多个表达式组成。
一个表达式是对一个变量或值的引用,或者是一组值和变量与运算符的组合。
举例来说,a = b * 2;这个语句中有四个表达式。
- 2是一个字面值表达式。
- b是一个变量表达式,表示获取它的当前值。
- b * 2是一个算术表达式,表示进行乘法运算。
- a = b * 2是一个赋值表达式,意思是将表达式b * 2的结果赋值给变量a。
程序需要被执行,我们也将这一过程称为运行程序。
a = b * 2这样的语句便于开发者读写,但实际上计算机并不能直接理解这种形式。
因此,需要通过计算机上一个专门的工具(解释器或编译器)将你编写的代码翻译成计算机可以理解的命令。
对某些计算机语言来说,在程序被执行时,对命令的翻译通常是自上而下逐行执行的,这通常被称为代码解释。
对另外一些语言来说,这种翻译是预先进行的,被称为代码编译,这样一来,当执行程序时,实际上运行的是已经编译好的、可以执行的计算机指令。
基本上可以说JavaScript是解释型的,因为每次执行JavaScript源码时都需要进行处理。
但这么说并不完全精确。JavaScript引擎实际上是动态编译程序,然后立即执行编译后的代码。
实践
学习编程的最好方法就是编写代码!
运算符
- 赋值=,如a = 2就表示将值2保存在变量a中。
- 算术+(加)、-(减)、*(乘)、/(除),如a * 3。
- 复合赋值+=、-=、*=和/=是复合运算符,可以将算术运算符与赋值组合起来,比如,a += 2等同于a = a + 2。
- 递增/递减++表示递增,–表示递减,比如a++就类似于a = a + 1。
- 对象属性访问如console.log()中的.。
- 相等==(粗略相等)、===(严格相等)、! =(粗略不等)和!==(严格不等),如a == b
- 比较<(小于)、>(大于)、<=(小于或粗略等于)和>=(大于或粗略等于),如a <= b
- 逻辑&&(与)和||(或),如a || b就表示a或者b
值与类型
在编程术语中,对值的不同表示方法称为类型
代码注释
有关如何编写注释良好的代码有很多种观点;我们确实无法定义绝对的普遍标准。但是以下这些观察结论和指导原则是很有用的。
- 没有注释的代码不是最优的。
- 过多注释(比如每行一个)可能是拙劣代码的征兆。
- 代码应该解释为什么,而非是什么。如果编写的代码特别容易令人迷惑的话,那么注释也可以解释一下实现原理。
变量
在程序中实现这一点的最简单方法是将值赋给一个符号容器,这个符号容器称为变量,使用这个名字是因为这个容器中的值是可以变化的。
在某些编程语言中,你需要声明一个变量(容器)用于存放指定类型的值(如数字或字符串)。通过避免不想要的值转换,人们认为这种静态类型(也称为类型强制)提高了程序的正确性。
其他语言强调的是值的类型而不是变量的类型。弱类型(也称为动态类型)允许一个变量在任意时刻存放任意类型的值。这种方式允许一个变量在程序的逻辑流中的任意时刻代表任意类型的值,人们认为这样可以提高程序的灵活性。
JavaScript采用了后一种机制——动态类型,这也就是说,变量可以持有任意类型值而不存在类型强制。
块
我们常常需要将在代码中的一系列语句组织到一起,这些语句通常被称为块。
在JavaScript中,使用一对大括号{ .. }在一个或多个语句外来表示块。
条件判断
程序中有很多种方法可以用于表示条件判断(也就是决策)。
最常用的是if语句。本质上就是在表达“如果这个条件是真的,那么进行后续这些……”。
循环
重复一系列动作,直到不满足某个条件,换句话说,重复只发生在满足条件的情况下,这就是程序循环的工作。
循环包括测试条件以及一个块(通常就是{ .. })。循环块的每次执行被称为一个迭代。
函数
程序也几乎总是需要将代码的任务分割成可复用的片段,而不是一直重复编码。
实现这一点的方法就是定义一个函数。
深入 JavaScript
值与类型
对象
1 | var obj = { |
内置类型方法
内置类型和子类型拥有作为属性和方法暴露出来的行为,这是非常强大有力的功能。
举例来说:
1 | var a = 'hello world' |
值的比较
1.类型转换
JavaScript中有两种类型转换:显式的类型转换与隐式的类型转换
1 | // 显式 |
2.真与假
JavaScript中“假”值的详细列表如下:
- “”(空字符串)
- 0、-0、NaN(无效数字)
- null、undefined
- false
3.相等
==检查的是允许类型转换情况下的值的相等性,而===检查不允许类型转换情况下的值的相等性;
因此,===经常被称为“严格相等”。
4.不等关系
运算符<, >、<=和>=用于表示不等关系,在规范中被称为“关系比较”。
变量
1.提升
1 | var a = 2 |
2.嵌套作用域
1 | function foo() { |
c在bar()的内部是不可访问的,因为它只声明在内层baz()作用域,b在foo()中是不可访问的,也是同样的原因。
条件判断
在JavaScript中,条件判断的另一种形式是“条件运算符”,通常被称为“三进制运算符”。
它更像是单个if..else语句的紧凑版,如下所示:
1 | var a = 42 |
如果条件表达式(这里是a > 41)求值为真,那么就会返回第一个子句(”hello”);否则,结果就是第二个子句(”world”),不论结果是什么,都会赋给b。
条件运算符并不一定要用在赋值上,但这肯定是最常见的用法。
严格模式
遵循严格模式也更容易让引擎优化你的代码。
严格模式是代码的一次重大突破,你应该在自己的程序中一直使用。
使用严格模式的一个关键区别(改进!)是,不允许省略var的隐式自动全局变量声明
作为值的函数
不仅你可以向函数传入值(参数),函数本身也可以作为值赋给变量或者向其他函数传入,又或者从其他函数传出。
因此,应该将函数值视为一个表达式,与其他的值或者表达式类似。
立即调用函数表达式
1 | (function() { |
闭包
1 | function makeAdder(x) { |
每次调用外层makeAdder(..)
返回的、指向内层add(..)
函数的引用能够记忆传入makeAdder(..)
的x
值。
现在,我们来使用makeAdder(..)
:
1 | var plusOne = makeAdder(1) |
我们来详细说明一下这段代码是如何执行的。
(1)调用makeAdder(1)
时得到了内层add(..)
的一个引用,它会将x记为1。我们将这个函数引用命名为plusOne()
。
(2)调用makeAdder(10)
时得到了内层add(..)
的另一个引用,它会将x记为10,我们将这个函数引用命名为plusTen()
。
(3)调用plusOne(3)
时,它会向1(记住的x)加上3(内层y),从而得到结果4。
(4)调用plusTen(13)
时,它会向10(记住的x)加上13(内层y),从而得到结果23。
模块
在JavaScript中,闭包最常见的应用是模块模式。
模块允许你定义外部不可见的私有实现细节(变量、函数),同时也可以提供允许从外部访问的公开API。
this标识符
关于如何设置this有4条规则,上述代码中的最后4行展示了这4条规则。
(1) 在非严格模式下,foo()最后会将this设置为全局对象。在严格模式下,这是未定义的行为,在访问bar属性时会出错——因此”global”是为this.bar创建的值。
(2) obj1.foo()将this设置为对象obj1。
(3) foo.call(obj2)将this设置为对象obj2。
(4) new foo()将this设置为一个全新的空对象。
底线:为了搞清楚this指向什么,你必须检查相关的函数是如何被调用的。
调用方式会是以上4种之一,这也会回答“this是什么”这个问题。
原型
当引用对象的某个属性时,如果这个属性并不存在,那么JavaScript会自动使用对象的内部原型引用找到另外一个对象来寻找这个属性。
你可以将这点看作是属性缺失情况的备用模式。
旧与新
polyfilling
单词“polyfill”是由Remy Sharp发明的一个新术语,用于表示根据新特性的定义,创建一段与之行为等价但能够在旧的JavaScript环境中运行的代码。
ES6定义了一个名为Number.isNaN(..)的工具,用于提供一个精确无bug的NaN值检查,取代原来的isNaN(..)。但对这个工具进行兼容处理很容易,这样一来,无论终端用户是否使用ES6浏览器,你都能够开始使用它。
1 | if(!Number.isNaN){ |
或许更好的办法是,使用一个已有的、可信任的polyfilling版本,比如由ES5-Shim和ES6-Shim提供的版本。
transpiling
语言中新增的语法是无法进行polyfilling的。
新语法在旧版JavaScript引擎上会抛出未识别/无效错误。
因此,更好的方法是,通过工具将新版代码转换为等价的旧版代码。
这个过程通常被称为“transpiling”。它是由transforming(转换)和compiling(编译)组合而成的术语。
有几点重要原因使得transpiling值得被关注。
- 语言中新添加的语法的设计目的是让代码更容易阅读和维护。等价的旧版本通常更加繁复。你应该编写更新、更简洁的语法,这不只是为你自己,同时也是为开发组中的所有其他成员着想。
- 如果只是为旧版本进行编译转换,对新版本应用新语法,那么你就得到了新语法浏览器性能优化的好处。这也使得浏览器开发者可以拥有更真实的代码,以便测试它们的实现和优化。
- 越早使用新语法,就可以越早在现实世界中更健壮地测试这些语法,也就可以越早地为JavaScript委员会(TC39)提供反馈。如果能够很早就发现问题,那么就能够在这些语言设计错误被固化前对其进行修改/修复。
以下是transpiling的一个简单示例。ES6新增了一个名为“默认参数值”的新特性。如下所示:
1 | function foo(a=2) { |
很简单,对不对?但也非常有用!
然而这个新语法在ES6前的引擎中是无效的。
那么transpiler是如何改变这段代码,从而让其能够在旧环境下运行的呢?
1 | function foo() { |
正如你可以看到的,它会检查arguments[0]的值是否为void 0(也就是undefined),如果是的话就提供默认值2;否则就使用传入值。
除了能够在旧版浏览器中使用更好的新语法,编译转换后的代码实际上也更好地表达了编程意图。
单看这段ES6版本的代码,你可能不会意识到undefined是唯一一个无法作为默认值参数显式传入的值。
而编译转换后的代码就更清楚地展示了这一点。
有很多很棒的transpiler可供选择。
以下是编写本部分时几个很好的选择:
· Babel 从ES6+编译转换到ES5
· Traceur 将ES6、ES7及后续版本转换到ES5
非JavaScript
你将遇到的最常见的非JavaScript就是DOM API。举例来说:
1 | var e1 = document.getElementById('foo') |
当你的代码在浏览器中运行时,变量document作为一个全局变量存在。
它既不是由JavaScript引擎提供的,也不由JavaScript标准控制。
它的存在形式看起来非常类似于普通的JavaScript对象,但实际上并不完全是这样。
它是一个特殊的对象,通常被称为宿主对象。
深入“你不知道的JavaScript”系列
作用域和闭包
JavaScript引擎在执行前(有时是执行中!)就编译了代码。
因此,通过深入理解编译器对代码的处理方式,我们可以尝试理解它是如何找到并处理变量和函数声明的。
沿着这条路,我们看到了JavaScript变量作用域管理的常见方式——“提升”。
闭包可能是JavaScript所有概念中最重要的一个,但如果你没有深刻了解作用域的工作原理,那么很可能就无法理解闭包。
正如我们在第2章中简单提到的那样,闭包的一个重要应用就是模块模式。
模块模式可能是JavaScript所有代码组织模式中最普遍的方法;深入理解模块模式应该是你最高优先级的任务之一。
this和对象
关键词this是根据相关函数的执行方式而动态绑定的,事实证明,可以通过4条简单的规则理解并完全确定this绑定。
与this紧密关联的是对象原型机制,这种机制是一个属性查找链,与寻找词法作用域变量的方式类似。
类型和语法
到底类型转换的哪些部分是出乎意料的,哪些部分在花费精力学习后则是完全可以理解的。
这不仅仅只是声称类型转换是合理的、可学习的;我想表明的是,类型转换是非常有用且被低估了的工具,你应该在自己的代码中使用它。
在我看来,如果能够正确使用的话,类型转换不仅能够工作,而且也会让你的代码质量更高。
异步和性能
“作用域和闭包”“this和对象原型”以及“类型和语法”关注的都是语言的核心机制,而“异步和性能”则稍微偏重于在语言机制之上处理异步编程的模式。
异步不只是对应用的性能至关重要,而且正在慢慢成为代码易写性和可维护性方面的关键因素。
promise是对“未来值”的与时间无关的封装,使得不管这个值是否已经可用,你都可以推导和组合使用它们。
另外,通过一种可信任的、可组合的promise机制,分发回调它们也有效地解决了IoC信任问题。
生成器为JavaScript函数引入了一种新的执行模式,其中生成器可以暂停在yield点上,并在之后被异步继续。
暂停与继续的能力使得生成器中同步的、看似连续的代码可以在后台异步执行。
通过这种方式,我们解决了回调的非线性、非局部跳转引发的代码混乱问题,因而让我们的异步代码看似同步,更容易追踪。
编写高效的JavaScript代码意味着,你编写的代码可以打破不同浏览器和环境的壁垒,达到动态运行。
这要求大量复杂而详细的计划和努力,只有这样,才能让程序从“可以运行”到“可以很好地运行”。
- 本文作者: Jambo
- 版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!