http://blog.wolksoftware.com/becoming-a-javascript-ninja
http://pengisgood.github.io/2016/01/16/becoming-a-javascript-ninja/
Becoming a JavaScript ninja
I’m training really hard to become what some people call a “JavaScript Ninja”. in this post I will share with you some important things that I have learned so far.
1. Use code convections
Coding conventions are a set of guidelines for a specific programming language that recommend programming style, practices and methods for each aspect of a piece program written in this language. These conventions usually cover file organization, indentation, comments, declarations, statements, white space, naming conventions, programming practices, programming principles, programming rules of thumb, architectural best practices, etc. These are guidelines for software structural quality. Software programmers are highly recommended to follow these guidelines to help improve the readability of their source code and make software maintenance easier.
There are tools that will help you to ensure that you and your team follow JavaScript code convections:
Code convections tool | Description |
---|---|
JSLint | JSLint is a JavaScript program that looks for problems in JavaScript programs. It is a code quality tool. It was developed by Douglas Crockford. JSLint takes a JavaScript source and scans it. If it finds a problem, it returns a message describing the problem and an approximate location within the source. The problem is not necessarily a syntax error, although it often is. JSLint looks at some style conventions as well as structural problems. It does not prove that your program is correct. It just provides another set of eyes to help spot problems. You can download it from www.jslint.com. |
JSHint | JSHint is fork of JSLint that was created by Anton Kovalyov because he believes that a code quality tool should be community-driven and because he thinks that sometimes we should be able to decide if we want to follor or not one code convection. As a result of this JS Hint is much more configurable than JS Lint. You can download it from www.jshint.com. |
2. Document your code
I’m sure you are tired of listen to people that tells you that you should document your code.I’m sure you are doing it but sometimes is not that easy to find value on doing it but If after creating the comments you end up with a documentation website, something like MSDN or the Java Documentation it seems to be more valuable, fortunately we also have tools that will generate the documentation for us:
Documentation generation tool | Description |
---|---|
JsDoc Toolkit | Is an application, written in JavaScript, for automatically generating template-formatted, multi-page HTML (or XML, JSON, or any other text-based) documentation from commented JavaScript source code. You can download it from here. |
3. Separate concerns
In computer science, separation of concerns (SoC) is a design principle for separating a computer program into distinct sections, such that each section addresses a separate concern. A concern is a set of information that affects the code of a computer program.
The value of separation of concerns is simplifying development and maintenance of computer programs. When concerns are well separated, individual sections can be developed and updated independently. Of especial value is the ability to later improve or modify one section of code without having to know the details of other sections, and without having to make corresponding changes to those sections.
In a JavaScript application your concerns are HTML, CSS, JavaScript configuration code and JavaScript logic code. To keep them separated you just have to stick to the following rules:
a) Keep your HTML out of JavaScript: Embedding HTML strings inside your JavaScript is a bad practice.
Embedding HTML strings inside your JavaScript is a bad practice.
var div = document.getElementById("myDiv");
div.innerHTML = "<h3>Error</h3><p>Invalid email adress.</p>";
The best way to avoid this problem is to load the HTML from the server via AJAX ore even better load HTML client-side templates from the server. There are tools like handlebars.js that will help you to generate HTML in the client-side without embedding HTML strings inside your JavaScript.
// rendering logic
RenderJson = function(json, template, container) {
var compiledTemplate = Handlebars.compile(template);
var html = compiledTemplate(json);
$(container).html('');
$(container).html(html);
};
// remplate
var template = "{{#each hero}}<tr><td>{{this.name}}" +
"<td></tr>{{/each}}";
// model
var json = {
hero : [
{ name : 'Batman' },
{ name : 'Superman' },
{ name : 'Spiderman' }
]
}
// Invoke rendering logic
RenderJson(json, template, $('#heroes_tbody'));
// DOM where we will insert HTML on rendering
<table>
<thead><tr><th>Hero</th></tr></thead>
<tbody id="heroes_tbody">
<!-- rows added with JS -->
</tbody>
</table>
b) Keep your CSS out of JavaScript
Don’t change CSS rules from JavaScript, try to only work with CSS classes.
// bad
$(this).css( "color", "red" );
// good
$(this).addClass('redFont');
c) Keep your JavaScript out of CSS
Don’t use CSS expressions, if you don’t know what is a CSS expression (An IE8 and earlier feature) then you are in the right way.
d) Keep your CSS out of HTML
Don’t use style tags to apply styles, use always class.
e) Keep your configuration code out of your logic code
Try to encapsulate all the hard coded variables and constants in a configuration object..
var CONFIG = {
MESSAGE : {
SUCCESS :"Success!"
},
DOM : {
HEROE_LIST : "#heroes_tbody"
}
};
// bad
RenderJson(json, template, $('#heroes_tbody'));
// good
RenderJson(json, template, $(CONFIG.DOM.HEROE_LIST));
If you do this finding the cause of issues will be much easier, imagine an incorrect background error, if you know that there is no JavaScript or HTML touching your CSS, then you automatically know that the issue must be in one of the CSS files, you just need to find the CSS class affected and you are done.
4. Avoid global variables
In computer programming, a global variable is a variable that is accessible in every scope. Global variables are a bad practices because it can lead to situations when one method is overriding and existing global variable and because code is harder to understand and mantein when we don’t know where the variables has been declared. The best JavaScript code is the one where no global variable shas been declared. There are a few techniques that you can use to keep things local:
To avoid global one of the first things that you have to ensure is that all your JavaScript code is wrapped by a function. The easiest way of doing this is by using an inmediate function and placing all of your script inside of the function.
(function(win) {
"use strict"; // further avoid creating globals
var doc = window.document;
// declare your variables here
// other code goes here
}(window));
The most common approcach to avoid globals is to create one unique global for your entire application think for example the $ in Jquery. You can then use then a technique known as namespacing. Namespacing is simply a logical grouping of functions under a singleproperty of the global.
Sometimes each JavaScript file is sismply adding to a namespace, in this case, you should ensure that the namespace already exists.
var MyApp = {
namespace: function(ns) {
var parts = ns.split("."),
object = this, i, len;
for(i = 0, len = parts.lenght; i < len; i ++) {
if(!object[parts[i]]) {
object[parts[i]] = {};
}
object = object[parts[i]];
}
return object;
}
};
// declare a namespace
MyApp.namespace("Helpers.Parsing");
// you can now start using the namespace
MyApp.Helpers.Parsing.DateParser = function() {
//do something
};
Another technique used by developers to avoid globals is the encapsulation in modules. A module is a generic pirce of functionality that creates no new globals or namespaces. Instead all the code takes place within a single function that is responsible for executing a task or publishing an interface. The most common type of JavaScript modules is Asynchronous Module Definition (AMD).
//define
define( "parsing", //module name
[ "dependency1", "dependency2" ], // module dependencies
function( dependency1, dependency2) { //factory function
// Instead of creating a namespace AMD modules
// are expected to return their public interface
var Parsing = {};
Parsing.DateParser = function() { //do something };
return Parsing;
}
);
// load a AMD module with Require.js
require(["parsing"], function(Parsing) {
Parsing.DateParser(); // use module
});
5. Avoid Null comparisons
The special value null is often misunderstood and confused with undefined. This value should be used in just a few cases:
a) To initialize a variable that may later be assigned an object value
b) To compare against an initialized variable that may or may not have an object value
c) To pass into a function where an object is expected
d) To return from a function where an object is expected
There are cases in which null should not be used:
a) Do not use null to test whether an argument was supplied.
b) Do not test an uninitialized variable for the value null.
The special value undefined is frequently confused with null. Part of the confusion is that null == undefined is true. However, these two values have two very different uses. Variables that are not initialized have an initial value of undefined.
//bad
var person;
console.log(person == undefined); //true
The general recommendation is to avoid using undefined at all times.
I guess that you must be know wondering how should you do the following without using undefined or null?
//bad
function doSomething(arg){
if (arg != null) {
soSomethingElse();
}
}
Comparing a variable against null doesn’t give you enough information about the value to determine whether is safe to proceed. Furtunately, JavaScript gives youi a few ways to determine the true value of a variable:
a) Primitive values: If your expecting a value to be a primitive type (sting, number, boolean) then the typeof operator is your best option.
// detect a number
if(typeof count === "number") {
//do something
}
b) Reference values: The instanceof operator is the best way to detect objects of a particular reference type.
// detect a date
if(value instanceof Date) {
//do something
}
**c) Functions: Using typeof is the best way to detect functions.
// detect a function
if(MyApp.Helpers.Parsing.DateParser typeof === "function") {
MyApp.Helpers.Parsing.DateParser();
}
d) Arrays: The best way to detect arrays is to use the isArray() function. The only problem is that isArray does not work in old versions of internet explorer. But you can sue the following code for cross browser support.
function isArray(value) {
if (typeof Array.isArray === "function") {
return Array.isArray(value);
} else {
return Object.prototype.toString.call(value) === "[object array]";
}
}
e) Properties: The best way to detect a property is to use the in operator or the hasOwnProperty() function.
var hero = { name : 'superman '};
//chech property
if (name in hero) {
// do something
}
//chech NOT inherited property
if (hero.hasOwnProperty('name')) {
// do something
}
6. Handle errors
Throwing custom errors in JavaScript can help you to decrease your debugging time. It is not easy to know when you should throw a custom error but in general errors should be thrown only in the deepest part of your application stack. Any code that handles application-especific logig should have error-handling capabilities. You can use the following code to create your custom errors:
function MyError(message){
this.message = message;
}
MyError.prototype = new Error(); //extending base error class
Is also a good idea to chek for specifict error types (Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError) to have a more robust error handling:
try {
// Do something
} catch(ex) {
if(ex instanceof TypeError) {
// Handle error
} else if (ex instanceof ReferenceError) {
// Handle error
} else {
//Handle all others
}
}
7. Don’t modify objects that you don’t own
There are things that you don’t own (objects that has not been written by you or your team)
a ) Native objects (e.g. Object, Array, etc.)
b ) DOM objects (e.g. document)
c ) Browser object model (e.g. window)
d ) Library objects (e.g. Jquery, $, _, Handlebars, etc.)
And thing that you should not try on objects that you don’t own:
a ) Don’t override methods
b ) Don’t add new methods
c ) Don’t remove existing methods
If you really need to extend or modify an object that you don’t own, you should create a class that inherits from it and modify your new object. You can use one of the JavaScript inheritance basic forms:
a) Object-based Inheritance: an object inherits from another without calling a constructor function.
var person = {
name : "Bob",
sayName : function() {
alert(this.name);
}
}
var myPerson = Object.create(person);
myPerson.sayName(); // Will display "Bob"
a) Type-based inheritance: tipically requires two steps: prototypal inheritance and then constructor inheritance.
function Person(name) {
this.name = name;
}
function Author(name) {
Person.call(this,name); // constructor inheritance
}
Author.prototype = new Person(); // prototypal inheritance
8. Test everything
As the complexity of JavaScript applications grows we must introduce the same design patterns and preactices that we have been using for years in our server-side and desktop applications code to ensure high quality solutions. So it is time to start writting tests (unit, performance, integration…) for your JavaScript code. The good news is that there are several tools that you can use to help you:
a) Jasmine
b) JsTestDrive
c) PhantonJS
d) QUnit
e) Selenium
f) Yeti
g) YUI Test
9. Automate everything
Continuous Integration is a software development practice where members of a team integrate their work frequently, usually each person integrates at least daily - leading to multiple integrations per day. Each integration is verified by an automated build (including test) to detect integration errors as quickly as possible. Many teams find that this approach leads to significantly reduced integration problems and allows a team to develop cohesive software more rapidly.
Continuous Integrations doesn’t get rid of bugs, but it does make them dramatically easier to find and remove. In this respect it’s rather like self-testing code. If you introduce a bug and detect it quickly it’s far easier to get rid of.
Continuous Integration has been used for a while together with TDD (Test Driven Development). But it was more traditionally linked to server-side code. In the previous point we said that it is time to test our JavasCript code. In this point I want to highlight that it is also time to continuous integrate it.
Build
In a professional development environment you will normaly find the following types of build:
a) Development Build: Run by developers as they are working, it should be as fast as possible to not interrupt productivity.
b) Integration Build: An automated build that run on a regular schedule. These are sometimes run for each commit, but on large projects, they tend to run on intervals for a few minutes.
c) Release Build: an on-deman build that is run prior to production push.
Continuous integration
There are serveral continuous integration servers but the most popular is Jenkings, the most popular build tool is apache ant. The process of building your JavaScript code includes several taks:
a) Test automation As discussed on point 8 you should use test automation tools for testing your JavaScript code.
b) Validation You should add code quality validation to your build process. You can use tools like JSLint or JSHint
c) Concatenation You should join all your JavaScript files in one single file.
d) Baking You can perform tasks like adding a code the license or version code as part of the build.
e) Minification You should integrate as part the build the minification by tools like uglify.js.
f) Compression You should gzip your JavaScript as part of your build.
g) Documentation You should integrate as part of your build the auto-generation id the dosumentation by tools like JS Doc Toolkit.
10. Find a great master
A real ninja never stop learning, so you better find a great master!
Where can you find one? The answer is of course the Internet. I recommend to read the blogs of some of the chome or mozilla developers about javascript as well as other js libraries like jquery.
我努力地训练着想成为大家所说的“JavaScript 忍者”。在这篇文章中我将会分享一些迄今为止我学到的比较重要的东西。
1. 使用代码约定
代码约定是针对一门特定编程语言的编程规范、实践和方法等一系列指导方针的集合。这些约定通常包含文件组织,缩进,注释,定义,声明,空格,命名,编程实践,编程原则,编程经验法则,架构的最佳实践等。这些是软件结构质量的指导方针。为了帮助提高代码的可读性和软件的可维护性,强烈建议软件开发工程师遵循这些指导方针。
有一些工具可以帮助你确保你的团队遵循 JavaScript 的代码约定:
代码约定工具 | 描述 |
---|---|
JSLint | JSLint 是一个用于查找 JavaScript 程序问题的 JavaScript 程序。它是由 Douglas Crockford 开发的一个代码质量工具。JSLint 会扫描 JavaScript 源码。如果发现问题,它会返回描述问题的信息和在源码中的大概位置。该问题不一定是语法错误,尽管经常确实是。JSLint 也会做一些代码风格和结构的检查。它并不证明你的程序是正确的。它只是从另一个角度帮助发现问题。可以从这里下载 JSLint。 |
JSHint | JSHint 是 Anton Kovalyov 从 JSLint 创建的一个分支,因为他相信代码质量工具应该是社区驱动的并且有时候由我们自己决定是否要遵循一些代码约定。因此 JSHint 比 JSLint 更具有可配置性。可以从这里下载 JSHint。 |
2. 为代码编写文档
我确信你会很不赖烦的听到别人说你需要为你的代码编写文档。我确信你正在这样做但是有时候不容易发现它的价值,但是如果你创建的注释最终可以形成类似MSDN 或者 Java 文档这样的文档站点,似乎有更多的价值。幸运的是,我们也有帮助我们生成文档的工具:
文档生成工具 | 描述 |
---|---|
JsDoc Toolkit | 它是用 JavaScript 编写的一个应用程序,用于根据 JavaScript 源码中的注释自动生成通过模板格式化的多页面的 HTML(或者 XML, JSON,或者其他基于文本文件的)文档。你可以从这里下载 JsDoc Toolkit。 |
3. 分离关注点
在计算机科学中,关注点分离(SoC)将计算机程序分开到不同部分中的设计原则,像这样每一个部分表示一个单独的关注点。一个关注点就是一些会影响到计算机程序代码的信息。
分离关注点的价值在于简化计算机程序的开发和维护。当关注点很好的分离之后,每一个部分都能独立开发和更新了。还有一个特别的价值就是它赋予了你在以后的改善或修改一个部分的代码的时候不用关心其他部分的细节,并且不用修改其他部分的能力。
在 JavaScript 应用程序中,你的关注点就是 HTML,CSS,JavaScript 配置代码和 JavaScript 逻辑代码。为了将它们保持分离,你只需要坚持下面几个法则:
a) 从 JavaScript 代码中移除 HTML:在 JavaScript 中嵌入 HTML 字符串是一种坏的实践。
1
|
var div = document.getElementById("myDiv");
|
解决这个问题的最佳方式就是通过 AJAX 从服务端加载 HTML 或者甚至从服务端加载客户端模板。有一些像 handlebars.js
的工具可以帮助你在客户端生成 HTML 的问题,而且不用将 HTML 字符串嵌入到 JavaScript 中。
1
|
// 渲染逻辑
|
b) 将 CSS 从 JavaScript 中移除
请勿通过 JavaScript 修改 CSS 的属性,尽量只通过 CSS 的类。
1
|
// bad
|
c) 从 CSS 中移除 JavaScript
如果你不了解 CSS 表达式,不要使用 CSS 表达式(一个 IE8 早期的特性),以免误入歧途。
d) 从 HTML 中移除 CSS
总是使用class
,而不是通过style
标签添加样式。
e) 从逻辑代码中移除配置代码
尽量把所有的硬编码变量和常量放到一个配置对象中。
1
|
var CONFIG = {
|
如果这样做你会发现找到问题的根源会更加容易些,想象一个场景,背景颜色不对,如果你知道不会有 JavaScript 或者 HTML 涉及到你的 CSS,你自然而然就知道问题肯定处在某一个 CSS 文件中,你只需要找到那个影响到样式的 CSS Class
,然后就完成了。
4. 避免全局变量
在计算机编程中,全局变量指的是在所有作用域中都能访问的变量。全局变量是一种不好的实践,因为它会导致一些问题,比如一个已经存在的方法和全局变量的覆盖,当我们不知道变量在哪里被定义的时候,代码就变得很难理解和维护了。好的 JavaScript 代码就是没有定义全局变量的。有一些技术可以帮助你让所有的事情都保持在本地:
为了避免全局变量,第一件事情就是要确保所有的代码都被包在函数中。最简单的办法就是把所有的代码都直接放到一个函数中去:
1
|
(function(win) {
|
最常用的避免全局变量的方式就是只为应用程序创建唯一的全局变量,像 Jquery中的 $
。然后你可以使用一种技术叫做命名空间namespacing
。命名空间就是在同一全局作用域下对函数从逻辑上进行分组。
有时候每一个 JavaScript 文件都会添加一个自己的命名空间,在这种情况下,需要确保命名空间已经存在了。
1
|
var MyApp = {
|
另一项开发者用来避免全局变量的技术就是封装到模块 Module
中。一个模块就是不需要创建新的全局变量或者命名空间的通用的功能。不要将所有的代码都放一个负责执行任务或者发布接口的函数中。最常见的 JavaScript 模块类型就是异步模块定义 Asynchronous Module Definition (AMD)
。
1
|
//定义
|
5. 避免 Null 比较
特殊值 null
经常被错误理解并且和 undefined
混淆。这个值应该只能出现在一下几个场景:
a) 初始化一个可能后面会被赋值的对象
b) 和已经被初始化的但不确定是否赋过值的变量比较
c) 作为参数传入一个期待参数为对象的函数
d) 作为一个期待返回对象的函数的返回值
有一些场景是不应该使用 null
的:
a) 测试是否有传入参数
b) 测试一个未初始化的变量值是否为 null
特殊值 undefined
经常和 null
混为一谈。部分混淆来自于 null == undefined
的值为 true
。然而,这两个值的用途却不同。未被初始化的变量的默认值为 undefined
。
1
|
//bad
|
一般性的建议就是要避免总是使用 undefined
。
我猜想你一定好奇如何不使用 undefined
和 null
来做下面这件事情?
1
|
//bad
|
比较一个变量和 null
不会给你足够的信息判断是否可以安全的进行。幸运的是,JavaScript 提供了一些方法帮助你决定一个变量的真实的值:
a) 基本值:如果你期待一个值的类型是基本类型(string,number,boolean),那么 typeof
操作符就是最佳选择。
1
|
// detect a number
|
b) 引用值:instanceof
操作符是检测一个对象是否为特定类型的最佳方式。
1
|
// detect a date
|
c) 函数:typeof
操作符是检测函数的最佳方式。
1
|
// detect a function
|
d) 数组:最佳方式是使用 isArray()
函数。唯一的问题是旧版本的 IE 不支持 isArray
,但是你可以用下面的代码来支持多浏览器。
1
|
function isArray(value) {
|
e) 属性:hasOwnProperty()
函数是检测属性的最佳方式。 **
1
|
var hero = { name : 'superman '};
|
6. 处理错误
在 JavaScript 中抛自定义的错误可以帮助你减少调试的时间。不是那么容易得出何时应该抛自定义的错误,但是常规错误一般只有在应用程序最深层才抛。任何处理特定应用逻辑的代码应该有处理错误的能力。你可以用下面的代码创建自定义的错误:
1
|
function MyError(message){
|
检查特定的错误类型也是个好主意(Error,EvalError,RangeError,ReferenceError,SyntaxError,TypeError,URIError)使得错误处理更加健壮:
1
|
try {
|
7. 不要修改不属于你的对象
有一些东西是不属于你的(不是你自己或者团队写创建的):
a) Native 对象 (e.g. Object,Array,etc.)
b) DOM 对象 (e.g. document)
c) 浏览器对象 (e.g. window)
d) 库对象 (e.g. Jquery,$,_,Handlebars,etc.)
有一些事情是你不能在不属于你的对象上做的:
a) 不要复写方法
b) 不要添加新方法
c) 不要删除已经存在的方法
如果你确实需要扩展或者修改不属于你的对象,你应该创建一个类并继承它然后修改你的新类。你可以使用 JavaScript 的一种继承方式:
a) 基于对象的继承: 通过调用构造函数继承。
1
|
var person = {
|
b) 基于类型的继承: 一般需要两步:先原型继承然后构造函数继承。
1
|
function Person(name) {
|
8. 测试一切事情
随着 JavaScript 应用复杂度的增长,我们应该引入和服务端或者桌面端应用使用了多年的一样的设计模式和实践,以帮助我们保证高质量的解决方案。所以是时候开始为你的 JavaScript 代码写测试了(单元测试,性能测试,集成测试……)。好消息是有一些工具可以帮助我们做这些:
a) Jasmine
b) JsTestDriver
c) PhantomJS
d) QUnit
e) Selenium
f) Yeti
g) YUI Test
9. 自动化一切事情
持续集成是一项软件开发实践,多个团队经常集成他们的工作,通常每个人每人至少一次——以至于每天会有多次集成。每一次集成都会通过自动化构建(包括测试)尽早检查发现错误。许多团队发现这种方式可以显著地减少集成问题并且帮助团队快速地开发出内聚的软件。
持续集成不能避免 bug
,但是它会帮助你更容易发现并且干掉 bug
。在这方面它更像是自测试代码。如果你引入了一个 bug
,快速地发现它,更加容易避免它。
持续集成已经和 TDD(测试驱动开发)
一起用了一段时间了。但是它过去总是传统的和服务端代码联系在一起。在前面的建议中我们说到是时候为我们的 JavaScript 代码写测试了。在这个建议中我想强调也是时候去持续集成它了。
构建
在专业的开发环境中你通常会发现以下几种构建:
a) 开发构建: 由开发者在工作的时候运行,为了不中断生产率它应该尽可能快。
b) 集成构建: 会定期运行的自动化构建。可能会在每次提交之后运行一遍,但是一般在大型项目中他们倾向于每个几分钟运行一遍。
c) 发布构建: 在部署到产品环境之前按需运行的构建。
持续集成
有很多做持续集成的服务器但是 Jenkins
是其中最流行的一个,最流行的构建工具是 Apache Ant
(译者注:现在像 Maven,Gradle 远比 Ant 流行)。构建你的 JavaScript 代码包括以下几个任务:
a) Test automation 根据第8点中的讨论,你应该使用自动化工具帮助你测试你的 JavaScript 代码。
b) Validation 你应该在你的构建过程中添加代码质量的验证。可以使用像 JSLint 或者 JSHint 这样的工具。
c) Concatenation 你应该把所有的 JavaScript 文件连接成为一个单独的文件。
d) Baking 你应该让添加 License 或者 Version 的任务作构建的一部分。
e) Minification 你应该使用像 uglify.js
的工具让 Minify 成为集成的一部分。
f) Compression 你应该让 Gzip JavaScript 代码成为够的一部分。
g) Documentation 你应该使用像 JS Doc Toolkit 这样的工具让自动生成文档作为集成的一部分。
10. 找一位大师
一个真正的忍者从来没有停止过学习,所以你最好找一位大师!
从哪里可以找到一位呢?答案是互联网。我推荐阅读一些 Chrome 或者 Mozilla 的开发者关于 JavaScript 的博客和一些其他像 jquery
的 JS 库。