重新认识Javascript的一些误区总结

1.在函数内有没有var真的不一样

下面这样一段代码,在函数abc()中,创建了两个变量a, c,并在函数体之外进行alert,想看看有什么事发生:

<script>      
        function abc(){
            var a = "1";
            c = "3";
        }
        alert(a);
        alert(c);
</script>

结果,alert(a)的结果自然不出所料, 报错:未定义这个变量,因为这是一个局部变量, 超出了界限, 自然无法使用.

但alert(c)却能成功显示3, 这有点出乎意料, 一直以为var写不写都可以, 还以为写在function中的变量都是局部变量呢!  

如dolphinX所说的 "变量没有在函数内声明或者声明的时候没有带var就是全局变量,拥有全局作用域,window对象的所有属性拥有全局作用域;在代码任何地方都可以访问,函数内部声明并且以var修饰的变量就是局部变量,只能在函数体内使用,函数的参数虽然没有使用var但仍然是局部变量。"

2.Javascript只有函数级作用域, 没有块级作用域

再看一段:

        if (true) {
            var n = 1;
        }
        alert(n); //1


        function abc() {
            var a = "1";
        }
        alert(a); //报错a未定义

第二个alert报错a未定义 , 这个已经有心理准备了, 因为函数里加了var就是局部变量, 出了函数就不起作用了.

而第一个alert成功显示1, 却是为啥? 看了一些园友的好帖, 终于明白, 这是因为javascript没有块级作用域造成的.

下面这张图是c#代码, 在Form2()中if (4>2)语句块中定义的abc变量, 出了if语句块就无效了, 这就是块级作用域, 更不用提在testBox3_lostfocus()了, 更是无法使用abc变量, 这是函数级别作用域, 这些本来在c#, java中司空见惯的常识, 在javascript中却是行不通了.

因为JavaScript没有块级作用域,这就意味着除了在函数中加var定义的变量, 其他的变量都是全局的了, 即: 函数外的加var或不加var, 函数内不加var, 和在if (4>2){}内定义的变量, 不管加不加var, 都是全局变量了. 也就是if (4>2)后{}, 就当没有这个层次罢了.

这个例子也证明了, javascript有函数级别作用域, 却没有块级作用域.

3.函数可以定义在后,执行在前,是JavaScript预解析的缘故.

接着再看一段代码: 

        var a = "0";
        abc();
        function abc() {
            alert(a);
            var a = "1";           
            c = "3";            
        }
        alert(a);

我定义函数abc()在后,执行abc()却在前,这样没有问题,可以执行,为什么?

原来这叫预解析,借用dolphinX所说, "这是因为JavaScript是解释型语言,但它并不是直接逐步执行的,JavaScript解析过程分为先后两个阶段,一个是预处理阶段,另外一个就是执行阶段。在预处理阶段JavaScript解释器将完成把JavaScript脚本代码转换到字节码,然后第二阶段JavaScript解释器借助执行环境把字节码生成机械码,并顺序执行。也就说JavaScript值执行第一句语句之前就已经将函数/变量声明预处理了,这也是为什么我们可以在方法声明语句之前就调用方法的原因。"

函数abc()内的var a=”1” 相当于两个语句,var a; a=”1”。因为预处理函数abc()时已经将var a给解析执行了,所以到了正式执行alert(a)时,就报undefined的错误了,闹了半天是这么回事.

 
4.对象创建方法之一: Json方式

var person={};

var person={firstname:"John",lastname:"Doe",age:50,eyecolor:"blue"};

这两种创建对象的方式和

var person = new Object();

这种是等价的, {}对应object, 上面两种Json方式的创建对象被称作对象字面量.

5.立即执行函数, 匿名函数

打开jQuery或其他js类库都可以发现如下的代码:

(function(){

//jQuery类库或其他类库

})();

这里的两对括号, 第一对表示里面代码是一个匿名函数, 所谓匿名函数就是没有起名字的函数, 但它也是函数,如下: 

alert((function (x, y) { return x + y; }));

当这个匿名函数被第二对括号括起来,这个匿名函数就能立即运行了!江湖人称, 立即执行函数.  

类似如下: 

 alert((function (x, y) { return x + y; })(2, 3));// "5" 

匿名函数有什么用?最大的用途是创建闭包,并且还可以构建命名空间,以减少全局变量的使用。

例如:自己写一个简单的js类库,代码如下:

(function () {
    var _$ = function () { };

    _$.prototype.ShowMessage = function (inputParam) {
        alert(inputParam);
        return true;
    }

    window.$ = new _$;
})();

使用时代码如下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script src="LzdJsFramework.js" ></script>

</head>
<body>
    <input id="Text1" type="text" value="234"   />
    
    <button type="button" id="btn" >save</button>
     <script>
         $.ShowMessage("hello world!");
    </script>
</body>
</html>

这个简单的例子直接把整个类库的功能塞进了window.$变量里,和jQuery等类库的意图大致是一致的。

6.闭包

通俗的说:子函数可以使用父函数中的局部变量,这种行为就叫做闭包!可以把闭包简单理解成"定义在一个函数内部的函数"。

这事要从在函数外不能得到函数内的私有变量说起, 在一个函数内用var声明的变量在函数外是获取不到的, 但如果就是想获取, 那就得在函数内再写一个子函数, 这个子函数是能得到上父级的变量的, 然后再将这个子函数作为函数的返回值, 就可以了.

例如下面取自阮一峰帖子的例子: 函数f2就被包括在函数f1内部,这时f1内部的所有局部变量,对f2都是可见的。但是反过来就不行,f2内部的局部变量,对f1就是不可见的。这就是Javascript语言特有的"链式作用域"结构(chain scope),子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。

既然f2可以读取f1中的局部变量,那么只要把f2作为返回值,我们不就可以在f1外部读取它的内部变量了吗!

  function f1(){
    var n=1;
    function f2(){
       alert(n); 
    }
    return f2;
  }
  var result=f1();
  result(); // 1

这个f2函数,就叫做闭包.在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁. 

闭包可以用在许多地方。它的最大用处有两个,一个是上面例子中提到的可以读取函数内部的变量,另一个就是下面这个例子中提到到让这些变量的值始终保持在内存中。

下面取自baidu知道的例子也是个标准的闭包:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script  >
        function a() {
            var i = 0;
            function b() {
                alert(++i);
            }
            return b;
        }
        var c = a();
        c();
        c();
    </script>

</head>
<body>
</body>
</html>

在函数a中定义了函数b,a又return了b的值, 在var c = a() 这行里,执行了a函数, 现在c的值就是a的返回值函数b.

第一次c()的执行实际执行的就是b函数, 弹出一个值为0的窗口, 到此为止,所有的生命周期按理论来说就算全部结束了.

可是,我们执行第二次c()时, 却弹出了1, 也就是说,第一次c()后, a中的i依然保留, 自然a在内存的栈区依然保留, a是return过了,但是,a及内部值i却依然存在,这就是闭包.

为什么会这样呢?原因就在于a是b的父函数,而b被赋给了一个全局变量c,这导致b始终在内存中,而b的存在依赖于a,因此a也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。


闭包的应用场景:

1、保护函数内的变量安全,模拟实现C#等高级语言的封装特性。

  以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性,实现了封装特性。

2、在内存中维持一个变量,实现对数据的缓存。

  依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。但这样一来,内存消耗就会很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。

3、可以构建命名空间,以减少全局变量的使用。

  所有的变量,如果不加上var关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度。除了每次使用变量都是用var关键字外,还有这样一种

  情况,即有的函数只需要执行一次,其内部变量无需维护,比如UI的初始化,那么我们可以使用闭包,也就是"5.立即执行函数, 匿名函数"中的例子。 

7.undefined与null

参考如下代码: 

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script>
        alert(f);
    </script>
</head>
<body>
</body>
</html>

alert一个未声明过的不存在的变量,会直接报错:未定义。

但是,未定义的变量与undefie本质上有区别的,请看代码段2:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script>
        var b;
        alert(b);

        function abc()
        {
            var s = 1;
        }
        alert(abc());
    </script>
</head>
<body>

</body>
</html>

声明了变量b却未给它赋值,alert的结果就是undefined,但是不报错。

函数abc()没有返回值,alert它的结果和上面一致, undefined。

可见,声明变量不赋值时,Javascript自动给变量的默认值就是undefined.

如果对它们做一下typeof操作和相等的判断,发现还有差异,请看代码段3:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>
    <script>
        alert(typeof (abc)); //undefined

        var b;
        alert(typeof (b)); //undefined

        var b1=undefined;
        alert(typeof (b1)); //undefined
var c = null; alert(typeof (c)); //object alert(b == c); //true alert(b === c); //false alert(abc==b) </script> </head> <body> </body> </html>

未定义的变量abc,运行typeof操作并不报错,直接返回undefined; 但如果试图用它和别的变量比较相等,则会立刻报错未定义,可见,对未定义的变量进行typeof操作是安全不报错的。

已定义未赋值的变量b,typeof的结果是undefined,可见这就是Javascript的默认值,也可以直接赋值为undefined,比如b1,结果是一样的。也就是说,未定义的变量和已定义未赋值的变量typeof操作的结果都是undefined.

已定义却赋值为null的变量c,typeof的结果为object,可见null隶属于object类型,据说其实这是JavaScript最初实现的一个错误,后来被ECMAScript沿用下来。今天解释为,null即是一个不存在的对象的占位符。

如果对变量b与变量c用==判等返回true,用===判断全等返回false,这证明二者并不完全一致。

综合上面的代码,并参照别人的成果,总结如下:

一.Undefined是一种类型,这种类型只有一种值 undefined。undefined 并不等同于未定义的值,typeof 并不真正区分是否是未定义。以下三种情况typeof 返回类型为undefined
1. 当变量未初始化时。
2. 变量未定义时。
3. 函数无明确返回值时(函数没有返回值时返回的都是undefined)。

二.Null也是一种类型,这种类型也只有一个值null,表示一个空指针对象。

三.ECMAScript认为undefined是从null派生出来的,所以把它们定义为相等的, 因此undefined == null。

但是,如果在一些情况下,我们一定要区分这两个值, 可以使用===绝对等于的判断方法。

undefined是Javascript的默认值,即声明了但是没有初始化的变量的默认值。

null表示尚未存在的对象, 例如当页面上不存在id为"UserName"的DOM节点时,null == document.getElementById('UserName')) 这句代码显示为"true",表示我们尝试获取一个不存在的对象。

参考帖子:

学习Javascript闭包(Closure)

http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html

JavaScript内核系列 第7章 闭包

http://hzjavaeyer.group.iteye.com/group/wiki/2279-JavaScript-Core

原文地址:https://www.cnblogs.com/liuzhendong/p/3600191.html