Javascript是一种日益增长的语言,特别是现在ECMAScript规范按照每年的发布时间表发布。伴随着这门语言的规模化和快速发展,掌握JS(不仅仅是jQuery)的重要性,变得更加重要。
这不是一篇自称是 JS 开发者知识圣杯的权威指南。不过里面绝对有一些我曾经错过的,有一些我可能是错用的,还有一些你可能不同意每个JS开发者应该知道的东西。
如何FizzBuzz
译者注:FizzBuzz是英国学校里常玩的游戏,从1数到100,如果遇见了3的倍数要说Fizz,5的倍数到说Buzz,如果即是3的倍数又是5的倍数要说FizzBuzz。
(FizzBuzz测试)通常被认为是一个不错的编码练习,用来在面试初期过程中淘汰没有经验的开发人员,你会惊讶地发现许多Javascript开发人员不知道如何编写一个基本的FizzBuzz测试。
FizzBuzz测试没啥实际用途。它纯粹是一个简单的练习,用来测试候选人潜在的编码能力。
记住在你将面试前端或Javascript开发岗位的大多数公司中,极有可能会问你一个FizzBuzz问题:你准备好了吗?
这是经典的FizzBuzz:
记住,你可能会被要求解决FizzBuzz的不同变种。我去面试过一次,被要求解决基本变种,然后另外两个变种。
这个特别的示例在3的倍数输出“Fizz”。在5的倍数输出“Buzz”,在即是3的倍数又是5的倍数输出“FizzBuzz”。
注意:在早些时候这个实现就有稍微改动。使用条件 if 语句,如果它不是一个条件的倍数,数字被打印出来。现有的解决方案可以工作,但没有打印不满足条件的数字或使用条件 if。
1
2
3
4
5
6
7
8
9
10
11
12
|
for (var i = 1; i <= 100; i++) {
if (i % 15 === 0) {
console.log('FizzBuzz');
} else if (i % 3 === 0) {
console.log('Fizz');
} else if (i % 5 === 0) {
console.log('Buzz');
} else {
console.log(i);
}
}
|
= = 和 = = = 的区别
两个比较运算符你可能熟悉。然而,你知道双重和三重等于比较运算符之间的区别吗?Javascript linter告诉你使用三重等于,但你知道为什么吗?
= = 双重等于(又名松等于)不会比较类型,它将在语句中将值进行类型转换。这就是所谓的强制类型转换,它被认为是一个有害的操作。
1
|
console.log(24 == '24'); // true
|
正如你所看到的,带单引号的24是一个字符串,将被转换成数字。虽然这在某种情况下可能是我们期望的行为,但允许比较运算符改变类型可能不是你想要做的。
不推荐使用双重等于,最理智的Javascript linter在默认情况下将抛出一个错误来告诉你使用严格的相等比较运算符来替换。如果你想强制转换值,在条件外做而不是在里面。
= = =三重等于(又名严格相等)将比较类型,但不会做类型转换,意味着他们在不被转换的情况下比较。因为没有强制类型转换,三重等于速度更快,作为推荐的方法用来比较各种各样的值。这意味着如果条件等于true,两种类型需要相同。
和上面的例子一样,但是三重等于:
1
|
console.log(24 === '24'); // false
|
返回false的原因是因为我们对一个字符串和一个数字进行比较。显然它们是两个不同的类型,因此它将返回false。
我强烈建议你在Mozilla开发者网站阅读更多关于等于比较符。它有一些很棒的解释(比我解释得更好),还有一些例子。
如何在不使用第三方库的情况下查询DOM
你可能知道如何使用jQuery查询DOM,但你能使用Javascript原生方法而不是通过使用第三方库来做吗?
我不只是在谈论能够通过ID或具有特定的class元素查询页面元素,我的意思是jQuery倾向使用表达式在DOM查找元素。
这里有相当多我们可以使用的原生方法,他们和jQuery一样强大,用来在页面查找一个或多个元素。我们可以使用选择器比如first-child,last-child等等。你会惊讶有这么好的原生DOM查询方法。
学习这些原生方法,并在比如jQuery的地方使用它们:
我在必要时链接了Mozilla开发者文档做进一步的阅读。任何地方你看到一个链接,如果是你不太熟悉的东西,我强烈建议你点击它。
- document.getElementById——通过ID查找元素的经典查询。
- document.getElementsByClassName——通过className在DOM中查找元素。
- document.querySelector——这是一个很好的方法。它拥有jQuery $()的所有力量,它是原生的。它将只返回它发现的第一个元素。
- document.querySelectorAll——几乎和上面的方法一样,除了它返回多个元素,而不仅仅是第一个。
- document.getElementsByTagName——这允许查询特定标记名的元素。想在页面或span标签中找到所有DIV元素吗?这是你想要的方法。
值得一提的是querySelector和querySelectorAll方法也可以使用在一个元素上,意味着你可以使用这些方法查询一个元素的内容。
详读Element.querySelector 和 Element.querySelectorAll的使用例子。
变量提升
Javascript是一种有趣的语言,变量的声明,会将声明提升到作用域的顶部。这意味着你可以在当前作用域(比如一个函数被认为是自己的作用域)定义它之前就引用一个变量。
作为一个经验法则:永远在你需要的作用域顶部定义你的变量。如果你在脚本文件(或者在函数中)的顶部使用 ‘use strict’;而你在一个变量被定义之前使用它将抛出一个错误。
大多数Javascript linter比如Jshint,当你没有使用 ‘use strict’的定义会提示,所以提示你应用最佳实践,这样在变量定义前你不可能使用它。
一如既往,Mozilla开发人员文档有一些很好的关于语法和类型的JavaScript文章,这里有一篇关于变量提升的内容。
如何使用浏览器的开发者工具
更具体地是如何调试Javascript,但也注意到你在开发者工具中用到的其他工具。如何设置断点和单步进入应用程序的特定子集的代码。
这些工具很重要,因为它们可跟踪潜在的复杂应用程序的运行步骤,找到导致一个问题或特定瓶颈的原因是什么。了解如何设置一个断点,如何在分子层面(细层面)真正深入到你的代码,看看发生了什么。
理解捆绑在Chrome、Firefox 和后期版本的 Internet Explorer 上的工具是你必不可少的技能,特别是如果你发现自己大量地使用Javascript。同时注意到各种开发工具插件,比如Google Chrome上的Batarang 扩展,它允许你调试AngularJS应用程序。
使用恰当的工具找到实际问题,不要在此之前盲目地优化你的代码。在没有搞清楚问题前解决问题,被称为过早优化,这会浪费时间。
控制台命令
除了知道如何在Chrome、Firefox 或 Internet Explorer开发工具中使用分析和调试工具,你还应该注意到各种控制台命令。
你可能已经知道console.log,还有console.error。但实际上有不少控制台命令可以使用。
请记住,有些命令在不同浏览器下可能无法正常工作。我一直留意应该被现代浏览器良好支持的命令列表,但在你往代码中加入可能不被支持的命令之前,你得试一试。
- console.log——基本的 logging ,用来记录在我的代码中发生的动作的基本消息。格式化标识符在console调用时也被支持。
console.error——在代码中记录错误。我在AJAX请求的错误回调和其他会抛出错误的地方使用console.error。和console.log类似,这个方法还包括一个堆栈,用于跟踪错误在哪里。
console.dir(对象)——这个方便的方法可以在你的控制台窗口打印一个Javascript对象的内容。很方便。
console.group(标题)——这允许你通过一个可选的标题创建一组的控制台日志记录命令。意思你可以将类似的日志信息分组,比如当一段代码负责一个任务时。
console.groupCollapsed——和上述方法完全相同,除了最开始是折叠的,没有打开。
console.groupEnd——这允许你结束上面定义的组。
console.time(标签)——允许你测量一段特定的Javascript代码运行需要多长时间,以毫秒为单位。对测量可能的瓶颈方法尤其有效。
console.timeEnd(标签)——类似于groupEnd方法,这允许你停止计时器记录功能,同时运行时间将在控制台打印出来。
copy(字符串)——在Chrome和Firefox控制台有这个方法,它允许你将一个字符串的内容复制到剪贴板。打开开发工具,试试它,它有时可以派上用场。
理解 this
this关键字,这是不理解其工作原理的Javascript开发人员产生挫败感的一个最大来源之一。真的很容易落入大量陷阱中的某个:“this”最大的问题是什么,取决于你的Javascript结构。
在更传统的编程语言中this是由类实例化的当前对象的一个引用。但Javascript并不是一个传统的编程语言,所以this实际上属于拥有方法的对象。
在Javascript中记住this的最简单方法就是记住它的拥有者,即父亲。this的值将总是等于拥有者,除非通过call,apply或者bind改变。
在下面这个函数中,this实际上是window:
1
2
3
4
|
function myFunction() {
console.log(this === window); // true
}
myFunction();
|
你可能会想:在一个方法中引用this时为什么this会等于window?如果你知道答案,那么很棒,但如果不知道,继续阅读,我们将解释为什么。
当你像上面定义一个函数,它绑定到全局window这个全局对象上。记得我们上面提到的,Javascript将this作为拥有方法的对象对待,而不是当前的对象?
将this的值改变成完全属于它自己的新对象(而不是窗口):
1
2
3
4
|
function myFunction() {
console.log(this === window); // false
}
new myFunction();
|
代码控可能会谴责我刚才做的事情。记住我们只是稍微触及了基本概念。但如你所见,this的值不再是window。为什么?
最简单的解释是什么时候我们使用new关键字,我们创建一个全新的上下文(因为new关键字),new操作符将创建一个全新的对象。
在以下示例中,我们将创建一个虚构的API库,它有一个方法从服务器获取数据。如你所见,我们正在创建一个叫API的对象并添加了方法。
因为我们已经创建了一个新对象,有些神奇的事发生了。上下文从全局对象切换到我们创建API对象上。
1
2
3
4
5
6
7
|
var API = {
getData: function() {
console.log(this === window); // false
console.log(this === API); // true
}
};
API.getData();
|
如你所见,函数内部的this值取决于它如何被调用。因为函数作为API的对象的一部分被调用,API对象是this的拥有着,因此this的值是它。
永远不要认为this的值总是相同的。它基于函数如何被调用而改变,但是如果你使用bind,this的值总是等于bing方法上指定的值。
关于”this”的深入阅读,可以看看 Quirksmode 上的这篇文章。它运行了一些例子并且比我解释得更好。Mozilla开发人员文档也有一些关于“this”的很棒解释,同时有例子(在这里)。
‘use strict’; ‘use strict’;
之前简要讨论了,使用严格模式(这在ECMAScript 5增加)允许你使用更严格的Javascript变量。你在编写任何Javascript时应该使用它,并且有充分的理由。默认情况下Javascript在让你做什么时有点宽容。当你引用一个未声明的变量同时尝试做一些你没有意识到很坏的其它事情时,他会默默地失败,但Javascript不会告诉你。
在Mozilla Developer这里有一整篇关于这个主题的文章,我恳求你阅读,因为它比我深入了更多的细节。
如何写各种类型的循环
你会惊讶地发现我遇到过许多开发人员不知道如何编写合适的 for. .循环,也不知道在Javascript中编写其他类型的循环。能够遍历一个数组或对象是所有Javascript开发人员应该具有的一种必不可少的技能。
没有一刀切的循环,但是你应该知道什么时候需要,同时应该使用哪一种。你可能熟悉for和while,但也许列表中的其他用的不多。
Javascript中不同类型的循环:
- for
- for..in
- for..of (added in ES6)
- forEach
- while
- do..while
For..循环
必要的基本循环是每一个Javascript开发人员需要知道的。for循环是核心,它在语句一直保持condition 2等于true时循环。
1
2
3
|
for (condition 1; condition 2; condition 3) {
// Your code
}
|
Condition 1 – 在循环开始之前执行。通常你将定义用于迭代的一个计数器值和数组的总长度。如果你事先做了设置,可以用一个分号来省略。
Condition 2 – 这是决定循环继续或停止的条件。你一般会将当前的计数器值和数组的总长度进行比较。这是一个true或false值,如果值等于true,循环将继续运行。这个可以用一个分号来省略,强制你在内部结束循环或者你最终也可以得到一个无限循环。
Condition 3 – 这在循环的每次迭代后运行。最常见的是在这里自增计数器值(可能在99%的用例)。这个值也可以用一个分号来省略(比如在内部自增你的循环)。
For..in 循环
每一个Javascript开发人员都应该知道的第二个最重要的循环。它允许您遍历一个对象的属性即其键。让我们看一个例子,好吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
|
var person = {first_name: 'Dwayne', last_name: 'Charrington', age: 27, star_sign: 'Aquarius'};
// The below loop will output:
// "Dwayne"
// "Charrington"
// 27
// "Aquarius"
for (var p in person) {
if (person.hasOwnProperty(p)) {
console.log(person[p]);
}
}
|
你可能注意到,我使用一个名为hasOwnProperty的方法。这个方法检查对象是否直接在它的prototype上具有某个指定的属性,不是一个继承的值。如果你在for..in循环中不使用hasOwnProperty,大多数Javascript linter将抛出一个错误。
For..of 循环
这是ES6上一个新特性,因此现在浏览器对它支持的不是很好。然而,通过使用transpiler,你现在可以直接使用它。
for. .of循环类似for..in,但它只迭代可以迭代的对象。
1
2
3
4
5
|
var fruits = ['orange', 'apple', 'squash', 'pear'];
for (var fruit of fruits) {
console.log(fruit);
}
|
for..of 循环的好处是,我们现在可以最终得到一个数组的所有值,例如无需编写复杂的for循环,创建一个索引,并使用一个变量自增某个计数器。它可以说是从一个数组得到值的最简单的方法。
forEach循环
这是一个有趣的事情,因为即使它是一个循环,它不能被认为是一个传统的循环,因为使用时有一些限制。
forEach循环也仅适用于数组,而不是对象。它的优势是不需要额外的变量定义,因此不会污染你的作用域,它只是一个方法。
一个循环的意义,可能你不需要真正知道是一个循环。一直我觉得它很好用,重要的是你在迭代一个数组的条目时知道你的选择。
1
2
3
4
5
6
7
8
9
10
|
var fruits = ['apple', 'banana', 'orange', 'grapes', 'pear', 'passionfruit'];
// The three values on the callback function are:
// element - The element being traversed
// index - The current index of the item in the array starting at 0
// array - The array being traversed (probably mostly irrelevant)
fruits.forEach(function(element, index, array) {
console.log(index, element);
});
|
有时候你只是想简单地遍历一个数组并得到它的值,修改它们,比如jQuery给我们提供了jQuery.each的形式。
forEach的唯一的缺点是你不能打破这个循环。如果你想使用ES5的语法创建一个循环,可以用Array.every,你可以在这里阅读使用方法。
While循环
while循环类似于for循环,它只接受一个参数,它是一个声明,等于true或false。所以如果你记得早些时候我们在for循环有三个条件,它就是条件2。
While循环通常被认为是最快类型的循环,这是有争议的。你不能不承认他们看起来比其他类型的循环更干净。在某些情况下,他们可以是最快类型的循环,因为他们可以具有更少的复杂性。
在我的经验中,速度最快的while循环是自减的while循环,在这个循环有一个值,自减它直到遇到零(结果为false)。
1
2
3
4
5
|
var i = 20;
while (i--) {
console.log(i);
}
|
Do. .While循环
你不会看到do. .while循环像for或while循环那种受欢迎地大量使用。但你应该知道do while循环如何使用,即使你永远不会使用它。
while循环不会保证一定运行。意思是如果你为while循环提供了一个等于false的表达式,它将不会运行。而do…while被保证至少运行一次。
但这并没有结束。while循环在循环开始之前执行其条件,do. .while执行在循环运行后执行条件。因此为什么do. .while被保证至少运行一次。循环运行,表达式被检查,然后继续。
再次Mozilla开发人员文档有一篇关于大部分的循环的优秀文章,在这里。
基本方法和任务
这里有一些你在Javascript中真的应该知道的基本方法。从使用数组到字符串,Javascript包含一个有用的方法宝库。我们在这篇文章中只涉及处理字符串和数组的方法,没有对象或任何其他类型的东西。
如果你想阅读更多关于关于Javascript处理各种数据类型,Mozilla开发人员文档是一个学习更多关于各种方法的很不错且简洁的网站。
显然有些人会对你是否应该知道所有这些东西有意见,也许有一些我已经忽略。我的记忆只能延伸到当前,但这里有一些我认为大多数开发人员应该知道的。知道如何高效地操纵字符串,在我看来是一种必不可少的技能。
处理字符串
在Javascript除了使用数组和对象,你会发现自己处理字符串可能不少。即使你真的不会使用字符串(或你认为你不会)了解这些处理的字符串的基本方法是值得做的事情。
- String.replace(regexp | replaceThis,replaceWith |callback)-允许你用另一个值替换一个值,甚至使用一个正则表达式。
- String.concat(‘string1’,‘string2’,etc…)-这个方法允许你将一个或多个字符串值连接起来。
- String.indexOf(value)-这个方法允许你找到指定值第一次出现的位置,如果没有找到返回-1。
- String.slice(startIndex,endIndex)-这个方法做了它表达的做法。它需要一个开始索引(从零)和一个结束索引,并返回一个新的字符串块。
- String.split(separator,limit)——这个方法将一个字符串分割成由一个或多个条目组成的数组。
- String.substr(startIndex,length)-该方法将返回字符串中从startIndex开始到指定长度的字符。
- String.toLowerCase-这个方法将返回调用字符串的小写。
- String.toUpperCase-这个方法将返回调用字符串的大写。
- String.trim-调用字符串开头和结尾的空格将被删除。
使用数组
在日常开发过程中,我发现自己大量处理数组。他们通常作为存储数据,跟踪状态和当做映射使用的不错方式。
我认为使用数组来做一些基本的任务是一个Javascript开发人员的基本技能。这些事情你真的不应该再去问谷歌。
- Array.pop-删除数组中的最后一个元素并返回它
- Array.shift-删除数组中的第一个元素并返回它
- Array.push(val1,val2…)-在一个数组的尾部添加一个或多个条目。该方法运行后将始终返回新数组长度。你可以指定多个逗号分隔值。
- Array.reverse-反转数组的顺序(第一个元素成为最后一个同时最后一个成为第一个,等等)。
- Array.sort([compareFunction])—允许你通过指定一个比较函数进行数组排序,比较函数能访问数组中需要排序的每一个值。
- Array.join(separator)-这个方法在数组中取一个或多个条目,并返回一个由分隔符连接的字符串值。如果你不指定一个分隔符,缺省值是一个逗号。
- Array.indexOf(value)-这个方法能得到指定值第一次出现的位置,如果没有找到返回-1。
还有其他处理数组的方法没有列出来,你应该在这里进一步阅读。有一些添加到ES6的令人兴奋的新方法没有列在这里,还有其他应用于具体用例的数组方法。
Call和Apply之间的区别
这两个方法容易误解,同时吓到了很多开发者。虽然可以不使用call或apply,但他们特别方便,因为它们允许你在运行期间调用方法时改变上下文中的this值。
两者的区别仅仅是微妙的,但有一个区别。使用call方法将允许你在调用一个函数时提供无限逗号分隔的参数。
使用apply方法将允许你调用一个方法时使用一个数组作为提供的参数。这个方法在你想使用一个数组作为方法调用参数以及改变上下文的this时非常棒。
如果你只是想要在函数上使用一个数组的值作为参数,ES6提供了spread operator(传播算子)。它不允许你更改上下文的this,但是它允许你使用一个数组值作为参数。
An example using .call:
1
2
3
4
|
function myFunc() {
console.log(arguments);
}
myFunc.call(this, 1, 2, 3, 4, 5);
|
An example using .apply:
1
2
3
4
|
function myFunc() {
console.log(arguments);
}
myFunc.apply(this, [1, 2, 3, 4, 5]);
|
ES6的新特性意味着未来只在非常有限的情况下,我们需要使用call或apply。感谢 spread operators,arrow functions 和从ES5就能用的bind功能。
熟悉框架/库
这段时间Javascript框架族最大的竞争者似乎是AngularJS,React.js和Ember。当然还有更多,但这些是最大的,目前最受欢迎(在我看来)。
随着web应用程序变得复杂起来,这些框架和库使我们的开发工作变得更舒适。在2015年,一个Javascript开发人员应该知道至少一个jQuery以外的框架或库,这并不是不合理的期望。
几乎任何你在搜索前端或Javascript开发人员看到的工作列表很可能会提到一个或两个框架的要求或作为加分项的技能。请不要落伍。
Node.js
毫无疑问Node.js已经证明了它的价值,并没有半路失败的迹象(除非IO.js干掉它)。几乎所有前端工具是在其基础上建立的,并使用Node包管理器,如果你一直还没有学习Node.js,你应该重新考虑。
因为Node.js的内核是Javascript,学习曲线是不存在的,如果你已经有一点Javascript。你会发现你在配置app中需要使用的包上花了更多的时间,而不是在学习Node.js。
我个人认为Node.js是每个开发人员在2015年需要掌握一种技巧。我不是说过于复杂的深入了解它,但足以用它来开发服务器,原型,测试和其他用例,这些地方Node.js将有利于你的工作流。
当然还有一个叫做IO.js的Node.js分支,目前是完全相同的。最后,你只是在编写Javascript,尽管有一些小的差异。
测试
曾经有一段时间我们没有测试Javascript代码,它不是许多人认为有必要的东西,因为我们面对的是:事情从未这么漂亮或复杂地使用。这门语言成长为内在的复杂性,部分归功于各种前端框架比如AngularJS和服务器端Javascript Node.js。
随着Javascript发展,我们用它来做更多的东西,代码膨胀,测试就变得很重要,即使不是至关重要的。如果在2015年你不测试您的代码,你就错了。
我最喜欢的测试工具毫无疑问是Karma。还有很多其它工具可以考虑,但Karma尤其适合测试AngularJS应用程序。如果它对AngularJS足够好,对我来说就已经足够好了。
工具
2015年,作为一个Javascript开发人员意味着知道如何使用task runners,transpilers,分析器和其他工具,我们借助它们编写最好的Javascript代码。
有时浏览器中捆绑的工具并不总是准确地描述你的应用程序内部发生了什么。有时你需要使用专业的工具来获得应用程序的内部工作原理的更详细信息。
有用的工具(如果你还没有使用它们);Gulp,Webpack和BabelJS。这里还有很多工具,task runners比如Gulp和Grunt,他们对现代Javascript的繁重工作流特别有帮助(如果你还没有使用它们)。
下载一个单独的Javascript文件,包含在我们的页面中的日子一去不复返了。这个时期包管理器比如NPM和Bower被用来替代手动下载脚本。
我们使用task runners来合并和压缩脚本,测试使用单独的工具,工作通常更有组织性。
伴随这样的Javascript工具,编写同构的Javascript(服务器和前端之间共享代码库)。
ES6,又名ECMAScript 6,又名ESNext
尽管浏览器在ECMAScript 6大部分好的特性得到支持之前,还有一段时间要走,我们现在可以使用transpilers开始编写ES6代码。
熟悉所有新的API和方法;字符串,数组和其他很酷的功能,比如WeakMaps,Symbols和Classes。在2015年,作为一名开发人员意味着与Javascript语言的变化保持同步。
尤其熟悉Classes的概念。他们只是原型继承上的语法糖(目前),但也有打算在ES7中提升classes,使它们更有用,语法糖更少。
进一步的阅读
在这之外还有很多学习Javascript深入部分的资源,很多比这篇文章更好且更深刻。
- You Might Not Need jQuery
- Mozilla Developer: Javascript Reference & Guide
- JS: The Right Way
- AirBnb’s Javascript Styleguide
结论
还有很多事情我可以继续谈。明显这篇文章很大,现代Javascript开发人员的期望很多。这篇文章真的只是冰山一角。
请不要把这篇文章做为Javascript开发人员应该知道信条或最终列表。但我已经说过,有些东西,所有Javascript开发人员应该知道,这篇文章是我个人的想法。
如果你有任何改进或发现任何可以扩展或补充的,请在下面评论并让我知道。