JavaScript代码编写中各种各样的坑和填坑方法


Posted in Javascript onJune 06, 2014

坑”这个字,在此的意思是“陷阱”。由于 JavaScript “弱语言”的性质,使得其在使用过程中异常的宽松灵活,但也极为容易“中招”。这些坑往往隐藏着,所以必须擦亮双眼,才能在学习与应用 JS 的道路上走的一帆风顺。

一、全局变量

JavaScript 通过函数管理作用域。在函数内部声明的变量只在这个函数内部,函数外面不可用。另一方面,全局变量就是在任何函数外面声明的或是未声明直接简单使用的。

“未声明直接简单使用”,指的是不用 var 关键字来声明变量。这个我们已经非常清楚,避免造成隐式产生全局变量的方法就是声明变量尽量用 var 关键字。

可你以为用了 var 就 ok 了?来看看这个坑:

function foo() {
    var a = b = 0;
    // body...
}

也许你期望得到的是两个局部变量,但 b 却是货真价实的全局变量。why? Because 赋值运算是自右往左的 ,所以这相当于:

 
function foo() {
    var a = (b = 0);
    // body...
}

所以 b 是全局变量。

填坑:变量声明,最好一个个来,别搞批发~_~;

二、变量声明

先来看坑:

 
myName = "global";function foo() {
    alert(myName);
    var myName = "local";
    alert(myName);
}
foo();

乍看上去,我们预计期望两次 alert 的结果分别为 “global” 与 “local”,但真实的结果是 “undefined” 与 “local”。why? Because 变量在同一作用域(同一函数)中,声明都是被提至作用域顶部先进行解析的。

所以以上代码片段的执行行为可能就像这样:

function foo() {
    var myName;
    alert(myName); // "undefined"
    myName = "local";
    alert(myName); // "local"
}

用另一个坑来测试下你是否真的理解了预解析:
 
if (!("a" in window)) {
    var a = 1;
}alert(a);

a 变量的声明被提前到了代码顶端,此时还未赋值。接下来进入 if 语句,判断条件中 "a" in window 已成立(a 已被声明为全局变量),所以判断语句计算结果为 false,直接就跳出 if 语句了,所以 a 的值为 undefined。
var a; // "undefined"
console.log("a" in window); // trueif (!("a" in window)) {
    var a = 1; // 不执行
}
alert(a); // "undefined"

填坑:变量声明,最好手动置于作用域顶部,对于无法当下赋值的变量,可采取先声明后赋值的手法。

三、函数声明

函数声明也是被提前至作用域顶部,先于任何表达式和语句被解析和求值的

 
alert(typeof foo); // "function"function foo() {
    // body...
}

可以对比一下:
alert(typeof foo); // "undefined"var foo = function () {
    // body...
};

明白了这个道理的你,是否还会踩以下的坑呢?
 
function test() {
    alert("1");
}test();
function test() {
    alert("2");
}
test();

运行以上代码片段,看到的两次弹窗显示的都是 “2”,为什么不是分别为 “1” 和 “2” 呢?很简单,test 的声明先于 test() 被解析,由于后者覆盖前者,所以两次执行的结果都是 “2”。

填坑:多数情况下,我用函数表达式来代替函数声明,特别是在一些语句块中。

四、函数表达式

先看命名函数表达式,理所当然,就是它得有名字,例如:

var bar = function foo() {
    // body...
};

要注意的是:函数名只对其函数内部可见。如以下坑:
 
var bar = function foo() {
    foo(); // 正常运行
};foo(); // 出错:ReferenceError

填坑:尽量少用命名函数表达式(除了一些递归以及 debug 的用途),切勿将函数名使用于外部。

五、函数的自执行

对于函数表达式,可以通过后面加上 () 自执行,而且可在括号中传递参数,而函数声明不可以。坑:

 
// (1) 这只是一个分组操作符,不是函数调用!
// 所以这里函数未被执行,依旧是个声明
function foo(x) {
  alert(x);
}(1);

以下代码片段分别执行都弹窗显示 “1”,因为在 (1) 之前,都为函数表达式,所以这里的 ()非分组操作符,而为运算符,表示调用执行。
// 标准的匿名函数表达式
var bar = function foo(x) {
  alert(x);
}(1);// 前面的 () 将 function 声明转化为了表达式
(function foo(x) {
  alert(x);
})(1);
// 整个 () 内为表达式
(function foo(x) {
  alert(x);
}(1));
// new 表达式
new function foo(x) {
  alert(x);
}(1);
// &&, ||, !, +, -, ~ 等操作符(还有逗号),在函数表达式和函数声明上消除歧义
// 所以一旦解析器知道其中一个已经是表达式了,其它的也都默认为表达式了
true && function foo(x) {
  alert(x);
}(1);

填坑:这个坑的关键在于,弄清楚形形色色函数表达式的实质。

六、循环中的闭包

以下演示的是一个常见的坑:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h3>when clicking links below, show the number of its sequence</h3>
    <ul>
        <li><a href="#">link #0</a></li>
        <li><a href="#">link #1</a></li>
        <li><a href="#">link #2</a></li>
        <li><a href="#">link #3</a></li>
        <li><a href="#">link #4</a></li>
    </ul>
</body>
</html>
var links = document.getElementsByTagName("ul")[0].getElementsByTagName("a");for (var i = 0, l = links.length; i < l; i++) {
    links[i].onclick = function (e) {
        e.preventDefault();
        alert("You click link #" + i);
    }        
}

我们预期当点击第 i 个链接时,得到此序列索引 i 的值,可实际无论点击哪个链接,得到的都是 i 在循环后的最终结果:”5”。

解释一下原因:当 alert 被调用时,for 循环内的匿名函数表达式,保持了对外部变量 i的引用(闭包),此时循环已结束,i 的值被修改为 “5”。

填坑:为了得到想要的结果,需要在每次循环中创建变量 i 的拷贝。以下演示正确的做法:

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <h3>when clicking links below, show the number of its sequence</h3>
    <ul>
        <li><a href="#">link #0</a></li>
        <li><a href="#">link #1</a></li>
        <li><a href="#">link #2</a></li>
        <li><a href="#">link #3</a></li>
        <li><a href="#">link #4</a></li>
    </ul>
</body>
</html>
var links = document.getElementsByTagName(“ul”)[0].getElementsByTagName(“a”);
for (var i = 0, l = links.length; i < l; i++) {
    links[i].onclick = (function (index) {
        return function (e) {
            e.preventDefault();
            alert("You click link #" + index);
        }
    })(i);
}

可以看到,(function () { ... })() 的形式,就是上文提到的 函数的自执行 ,i 作为参数传给了 index,alert 再次执行时,它就拥有了对 index 的引用,此时这个值是不会被循环改变的。当然,明白了其原理后,你也可以这样写:

for (var i = 0, l = links.length; i < l; i++) {
    (function (index) {
        links[i].onclick = function (e) {
            e.preventDefault();
            alert("You click link #" + index);
        }
    })(i);
}

It works too.

Javascript 相关文章推荐
$.ajax返回的JSON无法执行success的解决方法
Sep 09 Javascript
JavaScript初学者建议:不要去管浏览器兼容
Feb 04 Javascript
jquery实现背景墙聚光灯效果示例分享
Mar 02 Javascript
jQuery插件slick实现响应式移动端幻灯片图片切换特效
Apr 12 Javascript
一览画面点击复选框后获取多个id值的方法
May 30 Javascript
JavaScript暂停和继续定时器的实现方法
Jul 18 Javascript
JavaScript数组和对象的复制
Mar 21 Javascript
Vue中计算属性computed的示例解读
Jul 26 Javascript
Layui给数据表格动态添加一行并跳转到添加行所在页的方法
Aug 20 Javascript
es6函数之尾调用优化实例分析
Apr 25 Javascript
vue element table中自定义一些input的验证操作
Jul 18 Javascript
vuex中遇到的坑,vuex数据改变,组件中页面不渲染操作
Nov 16 Javascript
按钮接受回车事件的三种实现方法
Jun 06 #Javascript
jquery统计用户选中的复选框的个数
Jun 06 #Javascript
javascript中的throttle和debounce浅析
Jun 06 #Javascript
单击某一段文字改写文本颜色
Jun 06 #Javascript
Javascript中的异步编程规范Promises/A详细介绍
Jun 06 #Javascript
AMD异步模块定义介绍和Require.js中使用jQuery及jQuery插件的方法
Jun 06 #Javascript
jQuery插件开发详细教程
Jun 06 #Javascript
You might like
收音机史话 - 1960年代前后的DIY
2021/03/02 无线电
ThinkPHP提交表单时默认自动转义的解决方法
2014/11/25 PHP
深入了解PHP中的Array数组和foreach
2016/11/06 PHP
解决extjs在firefox中关闭窗口再打开后iframe中js函数访问不到的问题
2008/11/06 Javascript
超棒的响应式布局jQuery插件Freetile.js
2014/11/17 Javascript
jquery获取checkbox的值并post提交
2015/01/14 Javascript
jQuery实现垂直半透明手风琴特效代码分享
2015/08/21 Javascript
浅析Nodejs npm常用命令
2016/06/14 NodeJs
原生js实现键盘控制div移动且解决停顿问题
2016/12/05 Javascript
浅谈JavaScript的函数及作用域
2016/12/30 Javascript
js轮播图无缝滚动效果
2017/06/17 Javascript
详解Vue路由钩子及应用场景(小结)
2017/11/07 Javascript
vue中引用阿里字体图标的方法
2018/02/10 Javascript
vue+element表格导出为Excel文件
2019/09/26 Javascript
jquery获取input输入框中的值
2019/11/13 jQuery
js+for循环实现字符串自动转义的代码(把后面的字符替换前面的字符)
2020/12/24 Javascript
antdesign-vue结合sortablejs实现两个table相互拖拽排序功能
2021/01/08 Vue.js
[03:21]辉夜杯主赛事 12月25日TOP5
2015/12/26 DOTA
[00:33]2016完美“圣”典风云人物:BurNIng宣传片
2016/12/10 DOTA
使用Python脚本来获取Cisco设备信息的示例
2015/05/04 Python
python实现任意位置文件分割的实例
2018/12/14 Python
python 如何将数据写入本地txt文本文件的实现方法
2019/09/11 Python
详解pandas中利用DataFrame对象的.loc[]、.iloc[]方法抽取数据
2020/12/13 Python
python脚本定时发送邮件
2020/12/22 Python
英国婴儿产品专家:Samuel Johnston
2020/04/20 全球购物
C#中类(class)与结构(struct)的异同
2013/11/03 面试题
WebSphere面试题:在WebSphere里面如何部署一个应用
2015/08/02 面试题
出国留学自荐信
2013/10/25 职场文书
银行求职推荐信范文
2013/11/30 职场文书
十岁生日家长答谢词
2014/01/17 职场文书
2014基层党员批评与自我批评范文
2014/09/24 职场文书
《折线统计图》教学反思
2016/02/22 职场文书
如何使用PyCharm及常用配置详解
2021/06/03 Python
详解JAVA的控制语句
2021/11/11 Java/Android
《宝可梦》动画制作25周年到来 官方发布特别纪念视频
2022/04/01 日漫
win10系统计算机图标怎么调出来?win10调出计算机图标的方法
2022/08/14 数码科技