二、核心 JavaScript
JavaScript 是一种好语言,也是一种坏语言。JavaScript 有一些非常糟糕的部分,也有一些非常优秀的部分。总的来说,JavaScript 是一种动态的、松散类型的、解释的脚本语言。从整体上来看,这种语言的研究方法是以道格拉斯·克洛克福特所描述的好的部分为重点,因为 JavaScript 的坏的部分有多坏:它们是真正的雷区。Crockford 对现在如何使用 JavaScript 产生了深远的影响;足以让名为 AngularJS:坏的部分的博客文章立即,清晰,完整地反映了什么样的事情将被痛苦地详细剖析。
本章涵盖的主题包括:
- 严格模式:隐式用于包含 ECMAScript 6 模块的代码中
- 变量和赋值:任何语言编程的一些基本构件
- 点评:有两种风格;我们关注其中一个,而不是另一个,因为通过让注释真正开始或结束于程序员不希望的地方,很容易产生意想不到的行为
- 流程控制:对 if-then、if-then-else 和 switch 语句的基本观察
- 值与 NaN 的注释:真与毒的注释不是数字T5 的值
- 函数:JavaScript 函数的插图,这是该语言最好的部分之一
- 关于 ECMAScript 6 的几点简要说明:多年来第一次真正彻底改变核心 JavaScript
严格模式
"use strict";
因为文件或函数的第一行会导致某些会导致无数问题的事情(例如在没有声明变量的情况下赋值)以一个被调用的行号干净利落地出错,而不是让你从许多可能的结果中找出线索。"use strict";
也可以是功能的第一行,在这种情况下,该功能处于严格模式。
Perl 用户将了解-w
标志,可能是与该语言相关的最著名的标志,以及它的后继者,Perl 的使用警告 pragma。文档中说,打开已知错误列表,使用警告暗示的行为不是强制性的。
JavaScript 的严格模式本身可能与 Perl 的使用警告 pragma 不相上下,但它至少能让你养成使用的习惯。
变量和赋值
变量应该是用var
关键字声明的。任何在函数外声明的变量,或者任何没有声明就使用的变量,都是全局变量,全局变量是个大问题;他们在默认 JavaScript 中的位置是主要的设计缺陷之一。
当有一个主要的公共关系推动 Java 时,JavaScript 被命名为在 Java 的提携下运行,并且做出了某些决定来使 JavaScript 代码看起来像 Java。这些决定是不幸的。JavaScript 在形式上是一种类似 C 的语言,它与 Java 或 C#最近的共同祖先是 C,而不是 C++或 Java。JavaScript 被描述成 C 的服装中的 Scheme。Lisp 是一种与包括 Scheme、Common Lisp、Clojure 和 clojures script 在内的一系列语言相关联的语法,追求最佳 JavaScript 的函数式越来越强的重点来自 clojures script,这是有争议的。在 JavaScript 中,没有单独的整数和浮点类型;数字是 64 位浮点值,在一定的长范围内使用时表现得像整数。然而,它们有时确实会给新程序员带来惊喜;例如,0.1 + 0.2 不等于 0.3,这是历史原因,也困扰着无数其他语言。
基本变量赋值可能看起来像 C:
var x;
var y = 12 + 2;
如果一个变量在没有赋值的情况下被声明,如前面例子中的x
,它的值将是未定义的。
以下是等效的:
y = y + 1;
y += 1;
++y;
我们将避免使用前面例子中列出的三个中的最后一个,因为它不被认为是好的部分之一。道格拉斯·克洛克福特在他的一个视频中讲述了一个故事,他为++y
用法做了一个小时的雄辩辩护,然后进行了一个没完没了的调试环节,在这个环节中++y
的微妙之处刺痛了他。与前面例子中的前两个选项不同,可以给它赋值,++y
和y++
不一样。克罗克福德随后大度地放弃了他先前的职位。
评论
大多数语言都支持某种形式的注释。除了对代码的解释,它们还用于暂时停用代码。JavaScript 具有看起来像 Java 的语言应该有的注释;但是,Javadoc 注释本身并不特殊(已经提出了各种解决方案,如 JSDoc 来填补这一空白)。
JavaScript 支持这两类 C++注释。前三行只包含注释,没有可执行代码,最后一行有一行代码,然后是注释,直到行尾:
/*
* Multiline comments are legal.
*/
if (x) // In this case, we ...
我们将避免多行注释。星号和斜线在正则表达式中经常出现,以至于多行注释可能会出错,并且正则表达式可能是上下文所必需的,或者是由其他人在代码中编写的,仍然会出现意外的效果。内联评论本质上不太容易受到意外效果的影响。
流量控制
If-then 和 if-then-else 的工作原理如下面的示例代码所述,其中一个在数不为零时执行某项操作,另一个在一个数不为零时执行某项操作,如果为 false 则执行另一项操作。在 0(和 NaN)中使用真值都是假的,而任何其他值都是真的:
if (books_remaining) {
print_title();
}
var c = 1;
if (c) {
c += 2;
} else {
c -= 1;
}
关于值和 NaN 的一个注记
所有值都可以在布尔上下文中使用并测试真实性。值undefined
、null
、''
、0
、[]
、false
、NaN
(非数字)为falsy
,其他值均为truthy
。
NaN
尤其是一个特例,它的行为不像其他实数值。NaN
有毒;任何包含NaN
的计算都会给出NaN
的结果。再者,NaN
虽然是falsy
,但并不等于什么,包括NaN
本身。检查NaN
的通常方式是通过isNaN()
功能。如果你发现NaN
潜伏在意想不到的地方,你可能会给出代码的调试日志语句,导致你检测到NaN
的地方;在NaN
最初生成的地方和你观察到它破坏你的结果的地方之间可能有一些距离。
功能
在一个函数中,默认情况下,控制从开始到结束流动,函数返回undefined
。可选地,在结束之前可以有一个返回语句,函数将停止执行并返回有问题的值。下面的示例说明了一个带有返回的函数:
var add = function(first, second) {
return first + second;
}
console.log(add(1, 2));
// 3
前面的函数接受两个参数,尽管函数可以比声明指定的参数少(没有错误或错误消息)或多(T6)个参数。如果它们被声明有值,这些值将作为局部变量出现(在前面的例子中,first
和second
)。在任何情况下,参数也可以在名为arguments
的类似数组的对象中使用,该对象有一个.length
方法(数组有一个.length
方法,该方法比一个项目的最高位置大一个位置),但不能使用数组的其他特征。这里我们做了一个函数,可以取任意数量的数字参数,并通过使用arguments
伪数组返回它们的(算术)平均值。
var average_arbitrarily_many() {
if (!arguments.length) {
return 0;
}
var count = 0;
var total = 0;
for(var index = 0; index < arguments.length; index += 1) {
total += arguments[i];
}
return total / arguments.length;
}
基本数据类型包括数字、字符串、布尔值、符号、对象、空值和未定义。对象包括函数、数组、日期和正则表达式。
对象包含函数这一事实意味着函数是值,可以作为值传递。这有助于更高阶的函数,或者将函数作为值的函数。
举一个高阶函数的例子,我们将包括一个sort
函数,它对一个数组进行排序,并可选地接受一个比较器。这建立在函数定义的基础上,实际上在函数定义中包含一个函数定义(这在任何地方都是完全合法的),然后是快速排序的实现,其中值被分为比较小于第一个元素、比较等于第一个元素、比较大于第一个元素,这三个中的第一个和最后一个被递归排序。为了避免无限递归,我们在排序前检查非空长度。经典快速排序算法的实现如下,这里实现为更高级的函数:
var sort = function(array, comparator) {
if (typeof comparator === 'undefined') {
comparator = function(first, second) {
if (first < second) {
return -1;
} else if (second < first) {
return 1;
} else {
return 0;
}
}
}
var before = [];
var same = [];
var after = [];
if (array.length) {
same.push(array[0]);
}
for(var i = 1; i < array.length; ++i) {
if (comparator(array[i], array[0]) < 0) {
before.push(array[i]);
} else if (comparator(array[i], array[0]) > 0) {
after.push(array[i]);
} else {
same.push(array[i]);
}
}
var result = [];
if (before.length) {
result = result.concat(sort(before, comparator));
}
result = result.concat(same);
if (after.length) {
result = result.concat(sort(after, comparator));
}
return result;
}
评论
关于该功能,有几个基本特征和观察需要注意,其目的不是突破极限,而是演示如何很好地覆盖标准基底:
- 我们使用
var sort = function()...
而不是允许的function sort()...
。当在函数中使用时,它将函数存储在变量中,而不是全局定义。请注意,在调试中包含函数的名称,var sort = function sort()...
可能会有所帮助,以便仅通过变量访问函数,并让调试器在第二个变量上继续运行。例如:sort
而不是匿名引用函数。注意,使用var sort = function()
,变量声明被挂起,而不是初始化;借助function sort()
,函数值被提升,在当前范围内的任何地方都可以使用。 -
This is a standard way of checking to see if only one of the two arguments has been specified, that is if an array has been provided without a sort function. If not, a default function is provided. We run a few trial runs of sorting:
js console.log(sort([1, 3, 2, 11, 9])); console.log(sort(['a', 'c', 'b'])); console.log(sort(['a', 'c', 'b', 1, 3, 2, 11, 9]);
这给了我们:
js [1, 2, 3, 9, 11] ["a", "b", "c"] ["a", 1, 3, 2, 11, 9, "b", "c"]
这给了我们一个调试的机会。现在,假设我们添加了以下内容:
js console.log('Before: ' + before); console.log('Same: ' + same); console.log('After: ' + after);
在宣布结果之前,我们得到:
js [1, 2, 3, 9, 11] Before: Same: a After: c,b Before: b Same: c After: Before: Same: b After: ["a", "b", "c"] Before: Same: a,1,3,2,11,9 After: c,b Before: b Same: c After: Before: Same: b After: ["a", 1, 3, 2, 11, 9, "b", "c"]
显示Same: a,1,3,2,11,9
的输出看起来可疑,一个Same
桶应该有相同的值,所以一个合适的输出可能是Same: 2,2,2
或者仅仅是Same: 3
,其中Same
列表有五个值,没有两个是相同的。这不可能是我们想要的行为。似乎整数被归类为与首字母“a”相同,其余部分正在排序。一点调查证实“"a" < 1
”和“"a" > 1
”都是假的,所以我们的比较器可以改进。
我们对它们的类型进行字典排序。首先按照类型的字母顺序进行排序,然后按照类型默认值中的默认排序顺序进行排序,这有点武断,因为这可能会被另一个比较器覆盖。这是可以用来对数组进行排序的另一种比较器的示例:这种比较器不同于前一种比较器,它对不同类型的项目(如按顺序排序的数字)进行分段,并将出现在按顺序排序的字符串之前:
var comparator = function(first, second) {
if (typeof first < typeof second) {
return -1;
} else if (typeof second < typeof first) {
return -1;
} else if (first < second) {
return -1;
} else if (second < first) {
return 1;
} else {
return 0;
}
}
typeof
函数返回一个包含类型描述的字符串;因此typeof
可以提供扩展类型。使用类似于此的比较器功能,可以有意义地比较对象,例如保存名字、姓氏和地址的记录。
对象可以通过大括号符号来声明。代码块和对象都可以使用花括号,但这是两个不同的东西。下面带大括号的代码不是一个带有要执行的语句的代码块;它定义了一个包含键和值的字典:
var sample = {
'a': 12,
'b': 2.5
};
除非某个键是保留字或包含特殊字符,如'strange.key'
所做的(此处为句点),否则键周围的引号是可选的。JSON 格式,为了有一个简单和一致的规则,在所有情况下都需要引号,特别是双引号,而不是单引号。
下面显示了一个具有名字、姓氏和电子邮件地址的记录示例,可能通过 JSON 填充。这个例子不是 JSON,因为它没有遵循 JSON 关于对所有键和所有字符串进行双重引用的规则,但是它说明了一个记录数组,该数组可以有其他字段,并且可以更长。按距离或资历排序可能是有意义的(此处未显示填充字段):有一大堆可能的比较器可以用于记录。
var records = [{
first_name: 'Alice',
last_name: 'Spung',
email: 'a.spung@yahoo.com'
}, {
first_name: 'Robert',
last_name: 'Hendrickson',
email: 'Bob.Hendrickson@gmail.com'
}
];
请注意,尾随逗号不仅在 JavaScript 中不合适(在几乎所有用逗号分隔的条目的最后一个条目之后),而且它还有一些奇怪的、意想不到的行为,这可能非常难以调试。
JavaScript 被设计成在语句的末尾有分号,这可能是可选的。这是一个有争议的决定,并且与一个让普通非程序员可以在不涉及信息技术人员的情况下使用的流行语言的决定联系在一起,这也是设计 SQL 的一个因素。我们应该在适当的时候提供分号。这样做的一个副作用是return
单独在一条线上会返回未定义。这意味着以下代码将不会产生预期的效果,并将返回 undefined:
return
{
x: 12
};
类型
代码执行时的效果不同于它看起来的效果,也可能不同于它的预期效果,因此建议不要这样做。
为了获得所需的效果,左大括号应该与 return 语句在同一行,如下所示:
return {
x: 12
};
然而,JavaScript 确实有一个面向对象的编程,它避免了面向对象编程中的一个经典困难:必须在第一时间获得正确的本体。对象通常最好不是作为类的实例来构造,而是由工厂来构造。道格拉斯·克洛克福特大概被缩写了。原型仍然是语言的一部分,就像许多好的和坏的特性一样,但是除了深奥的用例,对象通常应该由工厂制造,工厂允许“比本体驱动的类更好”的面向对象编程方法。我们将避免伪经典的新function()
,不是因为如果你忘记了新的,它会破坏全局变量,而是因为它类似于传统的面向对象编程,实际上并没有多大帮助。
类型
您应该知道 JavaScript 中有一个广受尊重的约定,即打算使用new
的构造函数以大写字母开头。如果函数名以大写字母开头,打算和new
关键字一起使用,如果调用时没有new
关键字,可能会发生奇怪的事情。
在 JavaScript 中,一些其他语言中的经典面向对象编程所提供的兴趣有时最好由函数式编程来推进。
循环
循环包括for
循环、for in
循环、while-do
循环和do-while
循环。for
循环的工作原理与 C:
var numbers = [1, 2, 3];
var total = 0;
for(var index = 0; index < numbers.length; ++index) {
total += numbers[index];
}
for in
循环将覆盖对象中的所有内容。hasOwnProperty()
方法只能用于检查对象的场。名为obj
的对象的两个变体如下:
var counter = 0;
for(var field in obj) {
counter += 1;
}
这将包括原型链中的任何字段(这里没有解释)。为了检查对象本身的直接属性,而不是来自原型链的潜在噪声数据,我们使用对象的hasOwnProperty()
方法:
var counter = 0;
for(var field in obj) {
if (obj.hasOwnProperty(field)) {
counter += 1;
}
}
订购不保证;如果您正在寻找任何特定的字段,值得考虑的是只迭代您想要的字段,并在对象上检查它们。
看一下 ECMAScript 6
JavaScript 工具一直在蓬勃发展,一种工具被另一种工具所取代,还有一个令人难以置信的丰富的生态系统,很少有开发人员可以夸耀自己对它既了解广泛又了解深刻。然而,核心语言 ECMAScript 或 JavaScript 已经稳定了几年。
ECMAScript 6 有一个从http://tinyurl.com/reactjs-ecmascript-6获得的介绍性路线图,对核心语言引入了深刻的新变化和扩展。通常,这些特性增强、深化或使 JavaScript 的功能方面更加一致。可能有人会建议,不做这种工作的 ECMAScript 6 特性,比如增强的面向类的语法糖,让 Java 程序员假装 JavaScript 意味着用 Java 编程,可能会被忽略。
ECMAScript 6 的特性是一股值得考虑的力量,在撰写本文时,它已经开始进入主流浏览器。如果你想扩展和提高你作为一名 JavaScript 开发人员的价值,不要局限于深入挖掘丰富的 JavaScript 生态系统,不管这有多重要。学习新的 JavaScript。学习一个堆着更多更好零件的电子地图。
总结
在这段关于 JavaScript 更好的部分的旋风之旅中,我们已经介绍了一些基础的构建模块,随着我们在 JavaScript 中的深入,这些模块可能会有所帮助。这些包括变量和赋值、注释、流控制、值、NaN 函数和 ECMAScript 6。
在变量和赋值部分,我们介绍了大多数编程的一些基本构造块,尽管功能反应编程的重点可能在别处。在评论部分,我们理解了评论在哪里都是一样的,但这里主要关注的是防止奇怪的惊喜。
流量控制部分涵盖了一个功能内的基本流量控制(或者可能在任何功能之外,尽管这通常是要避免的)。
在一节关于值和 NaN 的注释中,我们讨论了类似于 Perl,JavaScript 认为真理是不言而喻的;也就是说,如果这些东西是空的、零的、空的、不是数字的等等,那么它们就是假的,对于列表中没有的任何东西都是真的。
在函数部分,我们看到了包含一个稍微复杂的例子的函数。在 ECMAScript 6、部分,我们讨论了核心 JavaScript 语言是如何扩展的。
这是一个简短的亮点之旅,而不是一次全面的旅行。如果你需要一个更全面的 JavaScript 基础,有多个选项可以选择。我们将在下一章继续讨论反应式编程的基本理论。
版权属于:月萌API www.moonapi.com,转载请注明出处