目录
前言:本篇文章是我在查询时偶然间发现的,虽然年代久远但是依旧非常适合入门学习,特此翻译下分享给大家,顺便给大家加了一些备注方便阅读(特意加粗刷存在感)。原文全文和链接在最后
———————-以下是正文———————-
备注:本篇文章是34岁的程序员Ayman Hourieh在2006年发布的。我找到了他网站的链接,但是网站已经不存在了。我用Wayback Machine找到了文章的镜像。为了让更多人看到把他摘录分享出来。我只做了一些链接的修改。
JavaScript是一个功能强大的面向对象语言。表面上看他他和JAVA和C非常相似,但是他却截然不同,其核心在于JavaScript更像一个功能性语言。本篇文章是一些JavaScript的小提示,一部分提供一些类C语言的功能模拟,另一部分是想要提高性能并探讨下脚本语言中一些比较难懂的东西。索引如下:
- 多用途的Array
- 栈
- 队列
- 二叉树
- String Concatenation 对比 Array.join
- 给对象绑定方法
- 使用自定义排序
- Assertion
- 静态局部变量
- null, undefined, and delete
- 深层嵌套
- 使用Firebug
- $ 和 $$
- console.log(format, obj1, …)
- console.trace()
- inspect(obj)
多用途的Array
尽管JavaScript在数据结构方面看起来很奇特,他的Array比其他编程语言(如C ++或Java)用途更加广泛。 它通常用作数组或关联数组,后面将演示如何将其用作堆栈,队列和二叉树。 复用Array而不是编写其他的数据结构有两个好处:首先,不用浪费时间去重写已经存在的功能,其次,内置浏览器对JavaScript的运行将更高效。
栈
大家都知道栈是后进先出:最后插入的会被最先移除。array有两个方法来实现栈的功能。他们是push()
和 pop()
。 push()
用于插入item到array的尾部,而pop()
则用于移除并返回最后一个item。以下是代码的实例:
var stack = [];
stack.push(2); // 当前栈是 [2]
stack.push(5); // 当前栈是 [2, 5]
var i = stack.pop(); // 当前栈是 [2]
alert(i); // 显示 5
队列
队列是先进先出的:现行插入的将会被最先移除。array可以用 push()
和 shift()
来实现队列。 push()
用于插入item到尾部, shift()
用于移除第一个item。案例如下:
var queue = [];
queue.push(2); // 现在的队列 [2]
queue.push(5); // 现在的队列 [2, 5]
var i = queue.shift(); // 现在的队列 [5]
alert(i); // 显示 2
值得注意的是,array还有一个unshift()
的函数。这个函数用于将item放到array的头部。所以栈也可以使用 unshift()
/shift()
,而队列可以用unshift()
/pop()
。
如果这些函数名称让你迷茫了(译者注:因为unshift()
是处理头部,所以相对应的栈需要从头部出去,队列需要换到尾部),你可以给他们取个别名,比如,创建队列的方法名为add
和 remove
:
var queue = [];
queue.add = queue.push;
queue.remove = queue.shift;
queue.add(1);
var i = queue.remove();
alert(i);
二叉树
二叉树是在树的节点表示数据。每个节点有一个数据并且有两个子节点(左叉树和右叉树)。在C语言里,这种数据结构通常使用指针来实现。这种实现方法也可以在JavaScript中使用对象和引用来实现。然而,对于一些小的二叉树,有一种更简单便捷的方法,array的第一个item作为树的头。 如果可以使用以下公式计算节点i,则索引左右节点:
leftChild(i) = 2i + 1
rightChild(i) = 2i + 2
这张图揭示了这个算法: (来自于Wikipedia):
正如你所看到的,这种方法并不是JavaScript的独有之处,但是在处理小的二叉树时非常有用。 例如,您可以编写自己的函数来获取和设置节点的值或子节点,并遍历二叉树,这些方法与做计算或for循环一样简单。但是,这种方法的缺点是随着树的深度增加,浪费的存储空间越来越多。
String Concatenation 对比 Array.join
大家都知道,如果做太多的字符串链接会让性能下降(译者注:不知道的去补课),并且这个非常容易避免。如果你想要用各个字符来组成一个字符串,最差的方法是使用+把所有的字符结合到一起:
str = '';
for (/* each piece */) {
str += piece; // bad for performance!
}
return str;
这种方法将使用太多的字符串链接,会让性能枯竭。
在JavaScript中有个更好的办法,就是 Array.join()
,他可以让所有array内的元素连接成一个字符串:
var tmp = [];
for (/* each piece */) {
tmp.push(piece);
}
str = tmp.join(' '); // 用空格作为分隔符
return str;
该方法不会受到额外的字符串对象的影响,通常执行的非常高效
给对象绑定方法
任何使用JavaScript事件的人都可能遇到了一种情况,他们需要将对象的方法分配给事件处理程序。 这里的问题是事件处理程序会被HTML调用,即使它们最初被绑定到另一个对象。 为了解决这个问题,我用一个函数将对象和方法绑定; 它他会运行对象和方法,并返回一个在该对象调用该方法的函数。 我在Prototype中找到了一个窍门,并且写了以下函数来在不包含Prototype的项目中使用它:
function bind(obj, method) {
return function() { return method.apply(obj, arguments); }
}
如何使用:
var obj = {
msg: 'Name is',
buildMessage: function (name) {
return this.msg + ' ' + name;
}
}
alert(obj.buildMessage('John')); // displays: Name is John
f = obj.buildMessage;
alert(f('Smith')); // displays: undefined Smith
g = bind(obj, obj.buildMessage);
alert(g('Smith')); // displays: Name is Smith
使用自定义排序
排序是一个常见的工作。 JavaScript提供了一种排序数组的方法。 但是,该方法默认按字母顺序排列 —— 非字符串元素在排序之前被强制转换为字符串,这个使得数字排序会有意想不到的结果:
var list = [5, 10, 2, 1];
list.sort()
// list is now: [1, 10, 2, 5]
这个解释很容易: 数字被强制转换成了字符串,所以10编程了’10’而2变成了’2’,那么JavaScript对比两个字符串的时候,先对比第一个字符。如果str1的第一个字符出现在字符集中的第一个字符之前,则str1被认为是“小于”str2。 在我们的情况下,’1’在’2’之前,所以’10’小于’2’。
幸运的是,JavaScript提供一个重写机制,让我们可以自定义如何排序,我们用a和b两个元素最为例子:
- 如果a<b,返回小于0
- 如果a=b,返回0
- 如果a>b,返回大于0
写这样的程序比较容易:
function cmp(a, b) {
return a - b;
}
我们现在可以使用这个方法来做排序:
var list = [5, 10, 2, 1];
list.sort(cmp);
// list is now: [1, 2, 5, 10]
Array.sort()
牛逼的地方是允许更复杂的排序。 假设你有一个论坛帖子,每个帖子看起来像:
var post = {
id: 1,
author: '...',
title: '...',
body: '...'
}
如果你想用id排序,只需要创建以下函数:
function postCmp(a, b) {
return a.id - b.id;
}
可以说,使用浏览器的方法进行排序将比在JavaScript中实现排序函数更有效。 但是,数据应该在服务器端进行排序。所以,除非必要,否则不应该让数据在客户端排序。
Assertion
Assertion是一种常用的调试技术,它用于确保表达式在执行期间计算为真。 如果表达式计算为假,则表示代码中可能出现的错误。 JavaScript缺少一个内置的Assertion功能,但幸运的是,它很容易编写一个。 如果传递的表达式的计算结果为假,以下实现会抛出AssertException类型的异常:
function AssertException(message) { this.message = message; }
AssertException.prototype.toString = function () {
return 'AssertException: ' + this.message;
}
function assert(exp, message) {
if (!exp) {
throw new AssertException(message);
}
}
自己抛出异常并不是非常有用,但是当与有用的错误消息或调试工具结合使用时,您可以检测到有问题的部分。
您还可以使用以下代码段检查异常是否为Assertion异常:
try {
// ...
}
catch (e) {
if (e instanceof AssertException) {
// ...
}
}
该函数的使用类似于C或Java:
assert(obj != null, 'Object is null');
如果obj碰巧为null,Firefox将在JavaScript控制台中打印以下消息:
uncaught exception: AssertException: Object is null
Static Local Variables
大家知道,一些语言像C ++,他们有静态变量的概念,用于函数调用。 JavaScript并不支持此技术。 然而,“功能也是对象”让这个功能成为可能。 方法是:将静态变量变为函数的属性。 假设我们要创建一个计数器函数:
function count() {
if (typeof count.i == 'undefined') {
count.i = 0;
}
return count.i++;
}
当第一次调用count时,count.i是未定义的,所以if条件为true,count.i为0。因为我们将变量存储为一个函数属性,它将在函数调用之间保留它的值, 因此它可以被认为是一个静态变量。
这里有个性能提升小技巧:
function count() {
return count.i++;
}
count.i = 0;
虽然第一个例子将Count的所有逻辑封装在主体中,但第二个例子更为有效。
null, undefined, and delete
因为JavaScript有undefined
和 null
所以他不同于其他语言。null
是一个特别的数值,他表示没有数值。null
会被认为一个特别的对象,因为typeof null
会返回null。
undefined
表示变量没有被定义,或者定义了但没有给数值。以下情况都会显示undefined
:
// i is not declared anywhere in code
alert(typeof i);
var i;
alert(typeof i);
虽然undefined
和 null
是两个不同的类型,但是如果使用 == ,会被判定为相等,但是如果是 === 则不等。
JavaScript还有一个删除操作符,”undefines”一个属性,可以将其应用于对象属性和数组成员,使用var声明的变量不能被删除,而是隐式声明(implicitly declared )的变量可以:
var obj = {
val: 'Some string'
}
alert(obj.val); // displays 'Some string'
delete obj.val;
alert(obj.val); // displays 'undefined'
深层嵌套
如果您需要在深层嵌套对象上执行多个操作,最好将其引用到临时变量中,而不是每次对其进行解引用。 例如,假设您想在文本字段上执行一系列操作:
document.forms[0].elements[0]
建议您存储到变量中,并使用此变量而不是以上构造:
var field = document.forms[0].elements[0];
// Use field in loop
每个点都导致一个操作来检索一个属性,在一个循环中,这些操作是相加的,所以最好做一次,将对象存储在变量中并重新使用它。
Using Firebug
Firefox有一个非常棒的扩展,用于调试名为Firebug的JavaScript代码。 它提供一个具有断点和堆栈视图的调试器,以及一个JavaScript控制台。 它还可以监视Ajax请求。 此外,扩展提供了一组JavaScript函数和对象来简化调试。 您可以在Firebug的文档页面中详细研究它们。 这里有一些我觉得有用的:
$ and $$
熟悉Prototype的人马上就认出他们了,
$() 接受一个字符串参数,并返回其ID是传递的字符串的DOM元素。(译者注:Jquery)
$('nav') // returns the element whose id is #nav.
$$()
返回DOM的数组
$('div li.menu') // returns an array of li elements that are
// located inside a div and has the .menu class
console.log(format, obj1, …)
console对象提供显示log消息的方法,这个将比alert更加好用, console.log()
有点像C里面的printf
,他会将输入转化为字符串在console中展示:
var i = 1;
console.log('i value is %d', i);
// prints:
// i value is 3
console.trace()
此方法打印一个堆栈跟踪调用它。 它不需要输入参数
inspect(obj)
该功能需要一个参数。 它切换到检查选项卡并检查传递的对象。
———————-以下是原文———————-
Source: http://codetunnel.com/9-javascript-tips-you-may-not-know/
NOTE: This is a blog post from 2006 by a now 34 year old programmer named Ayman Hourieh. I found links to his site for an article I wanted to read but his site is down and looks like it’s been down for quite some time. I used The Wayback Machine to find the post and I’m sharing it here for posterity’s sake. The only thing I’ve modified is some of the links so they work.
JavaScript is a fully-featured Object-Oriented programming language. On the surface, it shares syntactical similarities with Java and C, but the mentality is quite different. At its core, JavaScript is more similar to functional languages. Inside is a list of JavaScript tips, some offer techniques to simulate features found in C-like languages (such as assertions or static variables). Others are meant to improve performance and explore some of the more obscure parts of the web scripting language.
- Arrays as Multipurpose Data Structures
- Stack
- Queue
- Binary Tree
- String Concatenation vs. Array.join
- Binding Methods to Objects
- Sorting With a Custom Comparison Function
- Assertion
- Static Local Variables
- null, undefined, and delete
- Deep Nesting of Objects
- Using Firebug
- $ and $$
- console.log(format, obj1, …)
- console.trace()
- inspect(obj)
Arrays as Multipurpose Data Structures
Although that JavaScript may seem limited on the data structure front at first glance, its Array class is much more versatile than the usual array type found in other programming languages (like C++ or Java). It’s commonly used as an array or associative array, and this tip demonstrates how to use it as a stack, queue, and binary tree. Re-using the Array class instead of writing such data structures provides two benefits: First, time isn’t wasted rewriting existing functionality, and second, the built-in browser implementation will be more efficient than its JavaScript counterpart.
Stack
A stack follows the Last-In First-Out (LIFO) paradigm: an item added last will be removed first. The Array class has 2 methods that provide stack functionality. they are push()
and pop()
. push()
appends an item to the end of the array, and pop()
removes and returns the last item in the array. The next code block demonstrates how to utilize each of them:
var stack = [];
stack.push(2); // stack is now [2]
stack.push(5); // stack is now [2, 5]
var i = stack.pop(); // stack is now [2]
alert(i); // displays 5
Queue
A queue follows the First-In First-Out (FIFO) paradigm: the first item added will be the first item removed. An array can be turned into a queue by using the push()
and shift()
methods. push()
inserts the passed argument at the end of the array, and shift()
removes and returns the first item. Let’s see how to use them:
var queue = [];
queue.push(2); // queue is now [2]
queue.push(5); // queue is now [2, 5]
var i = queue.shift(); // queue is now [5]
alert(i); // displays 2
It’s worth noting that Array also has a function named unshift()
. This function adds the passed item to the beginning of an array. So a stack can also be simulated by using unshift()
/shift()
, and a queue can be simulated by using unshift()
/pop()
.
If all these function names are confusing, you may create aliases with your own names. For example, to create a queue with methods named add
and remove
:
var queue = [];
queue.add = queue.push;
queue.remove = queue.shift;
queue.add(1);
var i = queue.remove();
alert(i);
Binary Tree
A binary tree represents data in a tree of nodes. Each node has a value and two children (left and right). In C, this data structure is usually implemented using structures and pointers. This implementation is possible to do in JavaScript using objects and references; however, for smaller trees, there is an easier and quicker way using only one array. The first item of the array will be the head of the tree. Indexes of left and right child nodes if node i
can be calculated using the following formula:
leftChild(i) = 2i + 1
rightChild(i) = 2i + 2
This image illustrates the method (courtesy of Wikipedia):
As you see, this method isn’t exclusive to JavaScript, but it can be very useful when dealing with small trees. You can, for example, write your own helper functions for getting and setting a node’s value or children, and traversing the tree, and those methods will be as simple as doing arithmetic calculations and/or for loops. On the other hand, the disadvantage of this method is that wasted space grows as the depth of the tree increases.
String Concatenation vs. Array.join
This cannot be stressed enough, doing many string concatenation operations can be a major hit on performance, and it’s easy to avoid in many situations. Consider for example that you want to build a string out of many pieces, one bad way to do this is using the +
to concatenate all pieces into a huge string, one piece at a time:
str = '';
for (/* each piece */) {
str += piece; // bad for performance!
}
return str;
This method will result in too many intermediate strings and concatenation operations, and will poorly perform overall.
A better approach to this problem is using Array.join()
, this method joins all array elements into one string:
var tmp = [];
for (/* each piece */) {
tmp.push(piece);
}
str = tmp.join(''); // Specified an empty separator, thanks Jonathan
return str;
This method doesn’t suffer from the extra string objects, and generally executes faster.
Binding Methods to Objects
Anyone who works with JavaScript events may have come across a situation in which they need to assign an object’s method to an event handler. The problem here is that event handlers are called in the context of their HTML element, even if they were originally bound to another object. To overcome this, I use a function that binds a method to an object; it takes an object and method, and returns a function that always calls the method in the context of that object. I found the trick in Prototype, and wrote the following function to use it in projects that don’t include Prototype:
function bind(obj, method) {
return function() { return method.apply(obj, arguments); }
}
And this snippet shows how to use the function:
var obj = {
msg: 'Name is',
buildMessage: function (name) {
return this.msg + ' ' + name;
}
}
alert(obj.buildMessage('John')); // displays: Name is John
f = obj.buildMessage;
alert(f('Smith')); // displays: undefined Smith
g = bind(obj, obj.buildMessage);
alert(g('Smith')); // displays: Name is Smith
Sorting With a Custom Comparison Function
Sorting is a common task. JavaScript provides a method for sorting arrays. However, the method sorts in alphabetical order by default. Non-string elements are converted to strings before sorting, which leads to unexpected results when working with numbers:
var list = [5, 10, 2, 1];
list.sort()
// list is now: [1, 10, 2, 5]
The explanation of this behavior is simple: Numbers are converted to strings before sorting them, so 10 becomes ’10’ and 2 becomes ‘2’. The JavaScript interpreter compares two strings by comparing the first two characters of each: str1 is considered “less than” str2 if str1’s first character comes before str2’s first character in the character set. In our case, ‘1’ comes before ‘2’ so ’10’ is less than ‘2’.
Fortunately, JavaScript provides a way to override this behavior by letting us supply a comparison function. This function defines how elements are sorted, it takes two compared elements a and b as parameters, and should return:
- A value less than zero if a < b.
- zero if a == b.
- A value greater than zero if a > b.
Programming such a function for number comparison is trivial:
function cmp(a, b) {
return a - b;
}
Now we can sort our array using this function:
var list = [5, 10, 2, 1];
list.sort(cmp);
// list is now: [1, 2, 5, 10]
This flexibility in Array.sort()
allows for more sophisticated sorting. Let’s say you have an array of forum posts, each post looks something like:
var post = {
id: 1,
author: '...',
title: '...',
body: '...'
}
If you want to sort the array by post id’s, create the following comparison function:
function postCmp(a, b) {
return a.id - b.id;
}
It’s reasonable to say that sorting using a native browser method is going to be more efficient than implementing a sort function in JavaScript. Of course, data should be sorted server-side if possible, so this shouldn’t be used unless absolutely necessary (for example, when you want to offer more than one sort order on one page, and do the sorting in JavaScript).
Assertion
Assertion is one of the commonly-used debugging techniques. It’s used to ensure that an expression evaluates to true during execution. if the expression evaluates to false, this indicates a possible bug in code. JavaScript lacks a built-in assert
function, but fortunately it’s easy to write one. The following implementation throws an exception of type AssertException
if the passed expression evaluates to false:
function AssertException(message) { this.message = message; }
AssertException.prototype.toString = function () {
return 'AssertException: ' + this.message;
}
function assert(exp, message) {
if (!exp) {
throw new AssertException(message);
}
}
Throwing an exception on its own isn’t very useful, but when combined with a helpful error message or a debugging tool, you can detect the problematic assertion. You may also check whether an exception is an assertion exception by using the following snippet:
try {
// ...
}
catch (e) {
if (e instanceof AssertException) {
// ...
}
}
This function can be used in a way similar to C or Java:
assert(obj != null, 'Object is null');
If obj happens to be null, the following message will be printed in the JavaScript console in Firefox:
uncaught exception: AssertException: Object is null
Static Local Variables
Some languages like C++ support the concept of static variables; they are local variables that retain their values between function calls. JavaScript doesn’t have a static
keyword or direct support for this technique. However, the fact that functions are also objects makes simulating this feature possible. The idea is storing the static variable as a property of the function. Suppose that we want to create a counter function, here is a code snippet that shows this technique in action:
function count() {
if (typeof count.i == 'undefined') {
count.i = 0;
}
return count.i++;
}
When count
is called for the first time, count.i
is undefined, so the if condition is true and count.i
is set to 0. Because we are storing the variable as a property, it’s going to retain its value between function calls, thus it can be considered a static variable.
We can introduce a slight performance improvement to the above function by removing that if check and initialize count.i
after defining the function:
function count() {
return count.i++;
}
count.i = 0;
While the first example encapsulates all of count
‘s logic in its body, the second example is more efficient. The choice is up to you.
null, undefined, and delete
JavaScript is difference from other programming languages by having both undefined
and null
values, which may cause confusion for newcomers. null
is a special value that means “no value”. null
is usually thought of as a special object because typeof null
returns 'object'
.
On the other hand, undefined
means that the variable has not been declared, or has been declared but not given a value yet. Both of the following snippets display 'undefined'
:
// i is not declared anywhere in code
alert(typeof i);
var i;
alert(typeof i);
Although that null
and undefined
are two different types, the == (equality) operator considers them equal, but the === (identity) operator doesn’t.
JavaScript also has a delete
operator that “undefines” an object a property (thanks zproxy), it can be handy in certain situations, you can apply it to object properties and array members, variables declared with var
cannot be deleted, but implicitly declared variables can be:
var obj = {
val: 'Some string'
}
alert(obj.val); // displays 'Some string'
delete obj.val;
alert(obj.val); // displays 'undefined'
Deep Nesting of Objects
If you need to do multiple operations on a deeply nested object, it’s better to store a reference to it in a temporary variable instead of dereferencing it each time. For example, suppose that you want to do a series of operations on a textfield accessible by the following construct:
document.forms[0].elements[0]
It’s recommended that you store a reference to the textfield in a variable, and use this variable instead of the above construct:
var field = document.forms[0].elements[0];
// Use field in loop
Each dot results in an operation to retrieve a property, in a loop, these operations do add up, so it’s better to do it once, store the object in a variable, and re-use it.
Using Firebug
Firefox has a fabulous extension for debugging JavaScript code called Firebug. It offers an object inspector, a debugger with breakpoints and stack views, and a JavaScript console. It can also monitor Ajax requests. Moreover, the extension provides a set of JavaScript functions and objects to simplify debugging. You may explore them in detail at Firebug’s documentation page. Here are some that I find most useful:
$ and $$
Those familiar with Prototype will immediately recognize these two functions.
$()
takes a string parameter and returns the DOM element whose id is the passed string.
$('nav') // returns the element whose id is #nav.
$$()
returns an array of DOM elements that satisfy the passed CSS selector.
$('div li.menu') // returns an array of li elements that are
// located inside a div and has the .menu class
console.log(format, obj1, …)
The console object provides methods for displaying log messages in the console. It’s more flexible than calling alert. The console.log()
method is similar to C’s printf
. It takes a formatting string and optional additional parameters, and outputs the formatted string to the console:
var i = 1;
console.log('i value is %d', i);
// prints:
// i value is 3
console.trace()
This method prints a stack trace where it’s called. It doesn’t have any parameters.
inspect(obj)
This function takes one parameter. It switches to the inspection tab and inspects the passed object.