javascript编程容易出现的11个错误
javascript是比较容易学的。但是,对于这门语言需要有一些值得注意的地方。本文将指出javascript编程中可能犯过的10个错误
错误1-使用全局变量
如果你刚开始javascript编程,可能会觉得全局变量很好用。事实上,刚开始javascript编程,你可能不知道使用全局变量会带来什么麻烦。在同一个页面中,全局变量可以在任何内嵌的javascript代码段中或是该页面加载的不同的js文件中,都能访问到。这听起来很强大,是吗?这就使得全局变量可以随时随地的被修改赋值。
事实上这样很糟!
这样做会导致变量在意料之外被修改重写。假设有一个网店,需要用javascript计算并显示购物车所有商品的价格总和(当然,服务器端还会进行重新计算,这里只是为了增强用户的体验)。可能会编写代码如下:
- var total = 0, // total price
- tax = 0.05; // 5%
现在,还需要用javascript代码在网站上展示一些信息,或则是做一个商品仓库。代码如下:
- var total = 15; // number of tweets pulled from twitter
或则是如下代码:
- var tax = function () { /* ... */ }; // Trigger Animation eXperience function
现在,出现问题了:两个重要的变量被重写,但可能还没被意识到。这样代码运行会出错,会花费很多时间来跟踪和修复该错误。
那该如何解决呢?简言之—“封装”:当然封装有很多方法可以实现。第一种做法是将代码放入一个匿名的自调函数中。代码如下:
- (function () {
- var total = 0, tax = 0.05;
- // other code
- }());
这样做,在函数外部是绝对不能访问到函数内部定义的变量。这就形成了个人的代码空间,但是这样就不能公开部分方法或属性。例如,想要创建一个购物车,定义一个总价的变量,作为公共属性,这种情形下可以采用模块式编程。
- var cartTotaler = (function () {
- var total = 0; tax = 0.05;
- // other code
- return {
- addItem : function (item) { },
- removeItem : function (item) { },
- calculateTitle : function () { }
- };
- }());
关于全局变量有一点值得注意,如果不用关键词var来声明创建变量,那么javascript引擎会默认将该变量定义为全局变量。
- (function () {
- tax = 0.05;
- }());
- var totalPrice = 100 + (100 * tax); // 105
这里的变量tax在函数外部也是可以被访问的,因为在定义tax的时候没有使用var关键词。
错误2-不加分号
每句javascript语句必须以分号结尾。在编程时如果忘了加分号,这时javascript编解析器会自动加上。那我们在编程的时候是不是就可以完全不用浪费时间去加分号呢?
但是在一些语句中,分号是必不可少的。如for循环语句中的分号是必不可少的,否则会报语法错误。那么语句末尾的分号呢?
Javascript社区已经讨论过该问题。下面是本文的看法:你的代码,只要是被javascript解析器修改过(即便是很小的修改,如添加分号),就可能会出现一些你意料之外的结果。看看下面这段javascript代码:
- function returnPerson (name) {
- return
- {
- name : name
- };
- }
该方法看起来会返回一个对象,但事实上,javascript解析器会在return后面紧接着添加一个分号,这就导致该函数返回undefined。Return后的对象会被忽略。解决方法很简单,代码如下:
- return {
- name : name
- };
在javascript编程中应严格要求自己添加分号,这个习惯并不难。当然,作为一名web开发人员,你可能也会用到其他的语言(如php),这些语言都是严格要求以分号结尾的。你可以把这些编程语言的习惯带到javascript编程中来。
作者注解:除了你完全肯定可以被忽略的地方,你都不可以忽略添加分号。
错误3-使用==
如果你问一个javascript编程者,在javascript编程中通常会犯什么错误。他/她很可能会说,使用= = =来代替= =。这是什么意思呢?
试试下面的代码:
- if (1 == 1) {
- console.log("it's true!");
- }
代码如你所愿的输出了“it’s true!”那再试试下面的代码:
- if (1 == '1') {
- console.log("it's true!");
- }
这段代码在控制台中尽然也输出了“it’s true!”,但其实这并不是你所期望的输出。这里的==运算符转换了运算数的类型,从而使得两个运算数相等了。这里if语句中的==运算符使得右边string类型的“1”变成了number型的1。
想要得到你想要的输出,这里应该用= = =运算符来代替= =。===不会强制转换运算数的类型,这样才能如你所期望的。同样地,用!= =运算符来替换!=。下面是用==来做比较,得出的结果令人意外。
- '' == '0' // false
- '0' == '' // true
- false == '0' // true
- ' \t\r\n ' == 0 // true
错误4-使用数据类型的包装对象
Javascript提供了各个数据类型的包装对象。
- new Number(10);
- new String("hello");
- new Boolean(true);
- new Object();
- new Array("one", "two", "three");
首先,它们并不好用。上面的代码可以用更少的代码来实现,如下:
- 10;
- "hello";
- true;
- {};
- ["one", "two", "three"];
但是这两种方式还是有所不同的。下面是douglas crockford的观点:
例如用new Boolean(false)创建一个对象,该对象有一个方法valueOf,调用该方法会返回构造器的值。
这意味着,如果运行typeof new Number(10)或者是typeof new String(‘hello’),将返回‘object’,而不是’number’或’string’.另外,用数据类型的包装还会引发一些意料之外的结果。
那么为什么javascript要提供数据类型的包装对象呢?这是因为javascript解析器内部会调用。简单的数据类型是没有方法的(因为它们不是对象),所以当调用简单类型的数据的方法时(如’hello’.replace(‘ello’, ‘i’)),javascript会调用String包装对象来创建一个临时的string对象,然后调用该对象的方法,完成调用后会删除这个临时对象。
所以不要用数据类型的包装对象来创建简单类型的数据。
注意:本来不用这么说明的,但本文还是想明确的告诉初学者:不是说不使用它们和new(尽管有些是推荐使用的),这里需要特别指出的是,这个建议特别针对这些数据类型,如:number、string、Boolean、array和空对象。
错误5-在使用for-in时不对属性检查
我们都很熟悉遍历数组,但是你可能还希望能遍历对象的属性。(题外话:array事实上是属性名为数字的对象)。这是可以用for-in循环语句,代码如下:
- var prop, obj = { name: "Joe", job: "Coder", age: 25 };
- for (var prop in obj) {
- console.log(prop + ": " + obj[prop]);
- }
运行上面的代码,输出如下:
- name: Joe
- job: Coder
- age: 25
但是,浏览器中for-in遍历对象属性和方法时会包括对象原型链上的所有属性和方法。但绝大多数属性是不希望被枚举出来的。可以用hasOwnProperties方法来检测属性是否属于对象。代码如下:
- Function Dog (name) {
- this.name = name;
- }
- Dog.prototype.legs = 4;
- Dog.prototype.speak = function () {
- return "woof!";
- };
- var d = new Dog("Bowser");
- for (var prop in d) {
- console.log( prop + ": " + d[prop] );
- }
- console.log("=====");
- for (var prop in d) {
- if (d.hasOwnProperty(prop)) {
- console.log( prop + ": " + d[prop] );
- }
- }
- // Output
- // name: Bowser
- // legs: 4
- // speak: function () {
- return "woof!";
- // }
- // =====
- // name: Bowser
有时,只希望枚举列出对象的的属性,不包括方法。可以用typeof方法,代码如下:
- for (var prop in d) {
- if (typeof d[prop] !== 'function') {
- console.log( prop + ": " + d[prop] );
- }
- }
不管怎么样,在用for-in循环时要确保对属性进行检测,以避免得到你意料之外的结果。
错误6-使用with或eval
幸运的是,现在大部分javascript教程都不会教你使用with或eval。但是一些老教程或名气不大的资料时(因为有时候好的资料在网上很难找到),可能会发现有使用with或eval。
下面是两个不用with的主要原因:
1、 它会降低代码性能
2、 不易于代码阅读
第一点是它与生俱来的。第二点,看看下面的代码,这里用with访问person对象的name、age属性。
- var person = { name: "Joe", age : 10 };
- with (person) {
- console.log(name); // Joe
- console.log(age); // 10
- }
但是,若果有一个变量和对象其中一个属性同名,那用with会发生什么呢?事实上,这种情况下,访问变量会引用那个变量而不是对象的属性。另一个值得注意的是,在with语句中,如果访问的属性不存在或对象不存在,就不能给对象添加属性,同时会使得作用域链上with作用域后的那个作用域中创建一个变量。
- var person = { name: "Joe", age : 10 },
- name = "Billy";
- with (person) {
- console.log(name); // Billy
- job = "Designer";
- }
- console.log(person.job); // undefined;
- console.log(job); // Designer
那eval呢?它可以接受一个字符串参数,并且解析执行改字符串。
这听起来没有什么不好,甚至觉得很棒,对吗?但问题就是这太棒了!与其将一连串字符串将给它来解析执行,为什么不直接编写在程序中呢?不该这么做的原因如下:
- 完全可以直接编写在代码中。
- eval解析很慢的,性能跟with差不多。
eval的用法是在非运行时运行环境。可以从服务器端或客户端获取代码。难道真的想你的网站用户来底控制你的代码?这样不就意味着你的网站向无数的黑客敞开了大门。用eval就好比,离开了家,并告诉大家钥匙就是门口垫子下面。如果你爱自己或你的用户,就不要用eval。
错误7-在用parseInt时不用基数
Javascript提供了一个非常有用的方法parseInt,它可以将字符串转换为数值。
- parseInt("200"); // 200
- parseInt("043"); // 35
结果是不是令人觉得意外?第二句为什么不是43?事实上,parseInt方法不仅仅是只能把字符串当做十进制数来转换。当parseInt的第一个参数是以0开头,它就会把字符串当作是八进制数来转换。这就是不使用基数出现的意料之外结果。第二个参数–基数,会指定parseInt方法把字符串当做什么进制的数来转换。(当然,它的返回值永远是十进制数)
- parseInt("020", 10); // 20
- parseInt("100", 2); // 4
错误8 if和while语句不使用{}
Javascript最明显的特点是语法要求不那么严格。但正是这样的特点,有时会带来麻烦。If和while语句的{}就会引起一些麻烦。{}是根据if条件成立时执行代码语句的条数来用的。
- if (true)
- console.log("inside the if statement");
这里看起来没什么问题,因为这里的执行语句只有一句
- var arr = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"],
- i = arr.length - i;
- while (i) console.log( arr[i--] );
但是这样做不易于阅读:首先,不用{}代码结构看起来不是那么清晰。
- if (true)
- console.log("inside the if-statement.");
- console.log("outside the if-statement.");
看看上面的代码,第二行console语句是不属于if执行语句的,但是这里它看起来像是if的执行语句。使用{}会使结构更清晰。同时,如果你想在if的执行代码中添加一句,也需要使用{}。习惯使用{}并不是一件难事。
错误9-单个单个地插入dom元素
这并不是javascript自身的问题。99%100的javascript编程都会涉及DOM操作,在对DOM操作上会犯很多错误,但这是最明显的一个。
DOM操作会使浏览器重绘页面,所以如果有一连串的元素一个接一个的插入页面中,这会急剧增加浏览器渲染页面的负担。
- var list = document.getElementById("list"),
- items = ["one", "two", "three", "four"],
- el;
- for (var i = 0; items[i]; i++) {
- el = document.createElement("li");
- el.appendChild( document.createTextNode(items[i]) );
- list.appendChild(el); // slow, bad idea
- }
Document fragments 是一个DOM元素容器,可以使用它同时添加这些元素到页面中。Document fragment自身不是一个DOM节点,它不会在页面DOM树中显示,并且在把它插入DOM之前它是不可见的。下面是它的用法:
- var list = document.getElementById("list"),
- frag = document.createDocumentFragment(),
- items = ["one", "two", "three", "four"],
- el;
- for (var i = 0; items[i]; i++) {
- el = document.createElement("li");
- el.appendChild( document.createTextNode(items[i]) );
- frag.appendChild(el); // better!
- }
- list.appendChild(frag);
非常快速、简洁!
错误10-不懂javascript
许多人不花时间来认真地学习javascript。
Javascript并不等于jquery。这是否吓到你了?如果你会犯以上列出的错误,那么你需要认真地学习javascript。Javascript是一门语言,一门基本上不用学习就可以使用的语言,这就导致许多人不花时间来认真学习。千万不要这么做,已经有太多太多的教程指出这样做的弊端,你没有借口不认真学习javascript。如果你只是了解jquery(或mootools,或别的),那么你学习了解javascript的出发点就已经错了。
错误11-严格遵循以上的规则
“Rules are made to be broken.”(规则是用来被打破的。)
虽然本文列举了以上规则,但像任何事一样,规则是用来被打破的。如果是刚开始学习javascript,你会严于律己,严格遵循以上规则。但是到了真正理解了为什么要遵循以上规则的原因后,你才会知道灵活运用以上规则。例如,eval被反复的说到不能用,但是它却是唯一能解析服务器端返回json字符串的方法。当然这里在运用它时会做很多安全的检测(你可能会用到一个javascript库)。这里想要指明的是,在需要的地方,不应该害怕犯错,大胆的运用它。当然,永远不要犯错误10所指出的问题。
结论:
如果你是javascript新手,希望以上的内容对你javascript编程有所帮助。如果你是一个资深javascript工程师,如过这里有遗漏的,请在留言板中留言告知大家。