读Javascript最佳实践(6)-使用现代JS语法
一句话总结:let,const,class,箭头函数是新JavaScript中引入的最基本的新内容,理解它们的基本用法,特别是它们和传统用法的不同,对于更好地使用JavaScript有现实意义。
现代JavaScript正在快速发展,以满足新框架和环境不断变化的需求。了解如何利用这些变化可以节省你的时间,提高你的技能,并标记出好代码和优秀代码之间的差异。
了解现代JavaScript正在尝试做什么可以帮助你决定何时使用新语法以获得最佳效果,以及何时使用传统技术仍然有意义。
紧紧抓住的东西
我觉得现在没有人不对JavaScript的发展状态感到困惑,不管你是JavaScript的新手,还是你已经用了一段时间。这么多新的框架,语言的这么多变化,以及许多需要考虑的上下文。这是一个奇迹,我们每个月都要学习新东西,同时在完成各种各样的任务。
我相信,无论应用程序有多复杂,任何编程语言成功的秘诀都在于回归基础。如果你想理解Rails,首先要研究Ruby技能,如果你想用webpack在同构的React中使用immutables和unidirectional,首先要了解核心JavaScript。
理解语言本身的工作方式比熟悉最新的框架和环境要实用得多。这些变化比天气变化更快。对于JavaScript,我们有在线的关于JavaScript的创建和使用方面的深思熟虑的信息。
问题是,最新版本的JavaScript带来的一些新技术使一些旧规则过时了。但不是全部!有时,新语法可能会取代笨拙的语法来完成相同的任务。其他时候,新方法像是比我们过去方法的更简单的直接替代,但有细微差别,重要的是要了解这些差别是什么。
一匙句法糖
近年来,JavaScript中的许多变化被描述为现有语法的语法糖。在许多情况下,语法糖可以帮助Java程序员学习如何使用JavaScript,或者对于我们其他人来说,我们只想要一种更简洁的方法来完成我们已经知道如何操作的东西。其他变化似乎引入了神奇的新功能。
但是,如果您尝试使用现代语法来重新创建熟悉的旧技术,或者在不了解其实际行为的情况下坚持使用它,则存在以下风险:
- 必须调试之前完美运行的代码
- 引入可能在运行时捕获的细微错误
- 创建在你最不期望的时候以静默方式失败的代码。
事实上,一些直接替代原有方法的新技术存在着差别。在许多情况下,使用原始的旧式风格来完成你想要做的事情会更有意义。认识到何时发生这种情况并知道如何做出选择对于编写有效的现代JavaScript至关重要。
当你const
不一致时
现代的JavaScript推出了两个新的关键字,let
和const
,在大多数情况下有效地取代了var
。但它们的行为方式与var
并不完全相同。
在传统的JavaScript中,使用变量之前用关键字var
声明它们始终是一种干净的编码实践。如果不这样做,则意味着你声明的变量可以在同一上下文中运行的任何脚本在全局范围内访问。而且由于传统的JavaScript经常运行在可能同时加载多个脚本的网页上,这意味着在一个脚本中声明的变量可能会泄漏到另一个脚本中。
现代JavaScript中var
最干净的替代品是let
。但是let
有一些区别于var
的特质。默认情况下,变量声明var
始终被提升到其包含范围的顶部,无论它们放在该范围内的什么位置。这意味着即使是一个深度嵌套的变量也可以被认为是从其包含范围的开头就被声明和可用。同样let
和const
也不相同。
console.log(usingVar); // undefined
var usingVar = "defined";
console.log(usingVar); // "defined"
console.log(usingLet); // error
let usingLet = "defined"; // never gets executed
console.log(usingLet); // never gets executed
使用let
或const
声明变量时,该变量的范围仅限于声明它的本地块。JavaScript中的块由一组花括号区分{},例如函数体或循环中的可执行代码。
这对于诸如在迭代器和循环之类的块范围中使用变量非常方便。以前,在循环中声明的变量可用于包含范围,当多个计数器使用相同的变量名时,可能会导致混淆。但是let,如果你希望在脚本的一个块内部声明的变量可以在其他地方使用,那么可能会让您大吃一惊。
for (var count = 0; count < 5; count++) {
console.log(count);
} // outputs the numbers 0 - 4 to the console
console.log(count); // 5
for (let otherCount = 0; otherCount < 5; otherCount++) {
console.log(otherCount);
} // outputs the numbers 0 - 4 to the console
console.log(otherCount); // error, otherCount is undefined
另一个替代声明是const,它应该代表一个常量。但它并非完全不变。
const变量与var或let变量不同,不能在没有值的情况下声明。
var x; // valid
let y; //valid
const z; // error
声明一个const后,如果你尝试将其设置为一个新值,也将引发错误:
const z = 3; // valid
z = 4; // error
但是如果你希望你的const所有情况下都是不可变的,那么当一个对象或数组声明为一个const,而允许你改变它的内容时,你可能会感到惊讶。
const z = []; // valid
z.push(1); // valid, and z is now [1]
z = [2] // error
出于这个原因,当人们建议const代替var声明所有变量时,我仍然持怀疑态度,即使你有意在他们宣布之后永远不改变它们。
虽然将变量视为不可变是一种很好的做法,但JavaScript不会强制引用变量中的内容,例如:数组和对象。因此,该const关键字可能使偶然的读者和JavaScript的新手期望获得无法获得的保护。
我倾向于使用const声明简单的数字或字符串变量,想初始化并永远不会改变,或者我希望声明定义一次就不再修改的命名函数和类。否则,我用let声明大多数变量 - 尤其是那些我想要限制范围的变量。我最近没有发现需要使用var的情况,但如果我想要一个声明来打破范围并被提升到我的脚本的顶部,那就用var。
限制函数(function)的范围
调用使用关键字function
定义的传统函数,以在传入的任何参数上执,可以传入任意参数,执行块内定义的一系列语句,并可选择返回值:
function doSomething(param) {
return(`Did it: ${param}`);
}
console.log(doSomething("Hello")); // "Did it: Hello"
它们也可以与new关键字一起使用来构造具有原型继承的对象,并且该定义可以放置在可被调用范围内的任何位置:
function Animal(name) {
this.name = name;
}
let cat = new Animal("Fluffy");
console.log(`My cat's name is ${cat.name}.`); // "My cat's name is Fluffy."
可以在调用之前或之后定义函数,这对于JavaScript不是问题。
console.log(doSomething("Hello")); // "Did it: Hello"
let cat = new Animal("Fluffy");
console.log(`My cat's name is ${cat.name}.`); // "My cat's name is Fluffy."
function doSomething(param) {
return(`Did it: ${param}`);
}
function Animal(name) {
this.name = name;
}
传统函数还创建自己的上下文,在其上定义了this
,它仅存在于语句主体的范围内。其中定义的任何语句或子函数都会被执行,并且允许我们在执行时给this
绑定值。
这对于关键字来说要做的事情太多了,而且通常超出了程序员在任何地方的需要。因此,现代JavaScript将传统函数的行为分解为箭头函数和类。
使用Class
传统function的一部分用法已被class关键字接管。这允许程序员选择按照函数式编程范例使用可调用箭头函数,或者使用更加面向对象的方法来替换传统函数的原型继承。
JavaScript中的类看起来和其他面向对象语言中的简单类很相似,对于Java和C ++开发人员来说将JavaScript扩展到服务器时,这可能是一个简单的垫脚石。
在JavaScript中进行面向对象编程时,函数和类之间的一个区别是JavaScript中的类需要前向声明,就像在C ++中那样(尽管不是在Java中)。也就是说,class需要在用new关键字实例化之前在脚本中声明。和class不通,使用function关键字的原型继承,可以稍后在脚本中定义,因为function声明自动提升到顶部。
// Using a function to declare and instantiate an object (hoisted)
let aProto = new Proto("Myra");
aProto.greet(); // "Hi Myra"
function Proto(name) {
this.name = name;
this.greet = function() {
console.log(`Hi ${this.name}`);
};
};
// Using a class to declare and instantiate an object (not hoisted)
class Classy {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hi ${this.name}`);
}
};
let aClassy = new Classy("Sonja");
aClassy.greet(); // "Hi Sonja"
箭头函数的指向差异
现在可以使用箭头函数替代传统函数的一些用法,箭头函数是一种新语法,允许你更简洁地编写可调用函数,以便在回调中更整齐地拟合。实际上,箭头函数最简单的语法是单行完全抛弃花括号,并自动返回执行语句的结果:
const traditional = function(data) {
return (`${data} from a traditional function`);
}
const arrow = data => `${data} from an arrow function`;
console.log(traditional("Hi")); // "Hi from a traditional function"
console.log(arrow("Hi")); // "Hi from an arrow function"
Arrow函数封装了几个可以使它们更方便调用的特性,并省去了在调用函数时没有用的其他行为。它们不是传统function关键字的直接替代品。
例如,一个箭头函数从调用它的上下文继承this
和arguments
。这对于事件处理或setTimeout程序员经常希望被调用的行为应用于请求的上下文这样的情况非常有用。传统函数迫使程序员编写功能费解的代码(.bind(this))将函数绑定到this。箭头函数不需要这些。
class GreeterTraditional {
constructor() {
this.name = "Joe";
}
greet() {
setTimeout(function () {
console.log(`Hello ${this.name}`);
}, 1000); // inner function has its own this with no name
}
}
let greeterTraditional = new GreeterTraditional();
greeterTraditional.greet(); // "Hello "
class GreeterBound {
constructor() {
this.name = "Steven";
}
greet() {
setTimeout(function () {
console.log(`Hello ${this.name}`);
}.bind(this), 1000); // passing this from the outside context
}
}
let greeterBound = new GreeterBound();
greeterBound.greet(); // "Hello Steven"
class GreeterArrow {
constructor() {
this.name = "Ravi";
}
greet() {
setTimeout(() => {
console.log(`Hello ${this.name}`);
}, 1000); // arrow function inherits this by default
}
}
let greeterArrow = new GreeterArrow();
greeterArrow.greet(); // "Hello Ravi"
了解你得到了什么
这不仅仅是语法糖。由于需要新功能,因此引入了许多JavaScript中的新变化。但这并不意味着JavaScript传统语法的旧理由已经消失。通常继续使用传统的JavaScript语法是有意义的,有时使用新语法可以使您的代码编写更快,更容易理解。
查看你关注的在线教程。如果用var初始化所有变量,使用原型继承而忽略class,或者回调中使用function,则可以判断其余语法都是基于较旧的传统JavaScript。那没关系。我们今天仍然可以通过JavaScript一直被教授和使用的传统方式学习和应用。但是,如果你看到let和const用于初始化变量,在回调中使用箭头函数,用类作为面向对象模式的基础,你可能也会看到其他现代JavaScript代码的例子。
现代JavaScript中的最佳实践是关注语言实际在做什么。根据你习惯的情况,可能并不总是很明显。但想想你正在编写的代码正在尝试完成什么,你将需要部署它,以及接下来要修改它的人。然后自己决定最好的方法是什么。