首页 > 代码库 > 你应该知道的几个js知识

你应该知道的几个js知识

1.在javascript的块级作用域是以函数来决定的.

     if(true){
         var scopeName = "felayman";
     }
    print(scopeName);
上述虽然scopeName是在{}内声明的,但是它不属于块级作用域,在{}范围内依然能进行访问.因此,如果想在javascript中声明一个块级作用域,需要借助于函数来实现,如

 <span style="font-size:18px;"> function scope(){
        if(true){
            var scopeName = "felayman";
        }
    }</span>
这样,scopeName才属于一个函数级(块级)作用域,在函数外是无法访问scopeName的.

2.javascript中的变量查找链,如

 <span style="font-size:18px;">   var scope = "golbalScope";
    var fun = function(){
       alert(scopr);
        var scope = "funcScope";
    };
    fun();</span>
其结果为undefined,上述的代码可能跟我们所想的有所不同,没有输出golbalScope,也没有输出funcScope,而是输出了undefined?这是声明原因呢?这个是javascript的一个重要特性---作用域搜索顺序,当我们去访问某个变量的时候,它会优先搜索scope所在环境,因此搜索到了var scope = "funcScope";,因此会

抛弃掉上一级环境的var scope = "golbalScope";也就是var scope = "golbalScope";会被变量scope所在的环境给屏蔽掉,然后当程序执行到alert(scope);的时候,但是

scope在当前环境中并没有进行初始化,因而输出了undefined.

3.javascript的函数作用域的嵌套

<span style="font-size:18px;"> var fun = function(){
    var scope = "scope_one";
    (function(){
        alert(scope);
        var scope = "scope_two";
        (function(){
            alert(scope);
        })();
    })();
};
fun();</span>

这个例子的结果为,undefined和scope_two,正如上面一个例子一样,我们可以理解其结果行为,但是我们必须去注意,函数作用域的嵌套关系是在定义的时候决定的,而不是调用的时候决定的,也就是说,javascript的作用域是静态作用域,也叫词法作用域,这个例子主要是想说明闭包,在javscript中,一般情况下闭包就是匿名函数,而关于闭包的定义,有这么一种说法:由函数及其封闭的自由变量组成的集合体.,因此在一般情况下,在javascript我们视函数即为闭包,我们看一个例子

<span style="font-size:18px;">function print(s){document.write(s+"<br/>")};
  var getResult = function(){
    var count = 0;
    var get = function(){
        count++;
        return count;
    };
    return get;
};
var counter = getResult();
print(counter());
print(counter());
print(counter());</span>
结果为1,2,3.如果熟悉其他语言,或许会恨困惑这个结果,因为函数,俗称方法,在其他语言中就是一个保存临时变量和结果的一个容器,当函数执行完毕后,会释放该函数内的所有的局部变量和临时结果,因此当我们每次调用同一个函数的时候,其结果是不变的,但是在jabascript,却因为闭包改变了这个规则.这个也是闭包的特性---当一个函数返回它内部定义的一个函数的时候,就产生了一个闭包,闭包不但包括被返回的函数,还包括这个函数的定义环境,上面的例子,当函数getResult()的内部函数get被一个外部变量counter引用的时候,counter和getResult()的局部变量就形成一个闭包.因此上述的counter()其实就是get(),打印counter()的值,就是打印get()的值,但是于此同时get()函数属于getResult()函数内部的一个闭包(或者可以理解一个布局变量,或者理解get()在getResult()函数的作用域内),在该闭包内依然引用着它的上一级作用域,即getResult()函数作用域,具体的说,在get()函数内部使用了getResult()函数内的变量,因此,虽然我们在getResult()函数外部调用了一个看似与getResult()函数关系不大的counter()函数,但是却是在getResult()函数内部环境去执行的counter()函数.也就是说print(counter());
print(counter());print(counter());)这段代码看似是在getResult()外部执行,其实本质上是在getResult()函数内部执行的,因此上述代码完全等效于下述代码:

<span style="font-size:18px;">/**
 * Created by felayman on 2014/5/27.
 */
  function print(s){document.write(s+"<br/>")};
  var getResult = function(){
    var count = 0;
    var get = function(){
        count++;
        return count;
    };
    print(get());
    print(get());
    print(get());
};
getResult()</span>;
但是如果我们在使用的时候这样使用的话,就没有闭包的优势了----与外界失去了联系,也就是与其他语言中的函数(方法)一样的,而javascript中的函数,也正是由于闭包的特性,才被称为最实用于函数式编程的语言.
4.this所指向的内容

 function print(s){document.write(s+"<br/>")}
    var func1 = {
        name:"func1scope",
        func:function(){
            print(this.name);
        }
    };
    var func2 = {
        name:"func2scope"
    };
    func1.func();  //输出func1
    func2.func = func1.func;
    func2.func();  //输出func2
    var name = "windowscopr";
    window.func = func1.func;
    func();

上述代码会输出:func1scope   func2scope    windowscopr,因此,可以看出this的指向是随着其环境而发生变化,但是我们只需要记着一句话即可---this始终指向其执行期间的环境对象,而非定义时的环境对象.

5.call和apply

如果说在javascript中最让人困惑和难以理解的,

其中call和apply的功能是以不同的对象作为上下文来调用某个对象的函数,或者--就是允许在一个对象中去调用另一个对象中的同名函数(没有会被复制)并执行(摘自--NodeJS开发指南).如下面的例子:

<span style="font-size:18px;">var oneUser = {
    name:"oneUser",
    getName:function(){
        alert(this.name);
    }
};
var otherUser = {
    name:"otherUser",
};
oneUser.getName.call(otherUser);</span>
可以这样理解,我们以每一个对象内部都有一个call对象(函数就是对象),该对象没有具体指向,但是允许指向任何一个对象内部的另一个对象.而applay与call的用法没有上面区别,只是接受的参数不同而已.
6.原型

如果要给原型一个定义,百度百科上说的很详细,但是很抽象,很难理解,而在NodeJs开发指南中,作者用了一个很好的例子----原型就好像是一件艺术品的原件,我们通过一台100%精确的机器把这个原件复制出很多件,如果我们了解类似java的面向对象语言的话,就会知道,在java中所有的对象都来自于类,而C++中所有的对象来自于构造器,但是javascript是一种基于面向对象语言,它既有类似C++的构造器,也有类似类的原型.其实我更倾向于一篇博文中的理解,在javascript中

,原型就是java中的一个类的一个对象,该对象存储该类的所有静态属性或方法.我更建议使用这种方法来理解原型.因为的确是这样的,javascript中使用原型的目的就是让某一类型的所有对象共享一些属性和函数,提供代码的复用性,同时也隐藏一些属性和函数的细节,即提高其内聚性和封装性,这也是javascript基于面向对象的特性.

<span style="font-size:18px;">function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.getInfo = function(){
        return "name ="+this.name+",age = "+ this.age+",job = " +this.job;
    }
}
</span>

上述代码没有使用,原型,因此我们在使用new关键字来创建对象的时候,可以根据不同的name,age,job来构件许许多多的Person的对象,这些对象的属性都不相同,但是作为常识,我们都知道Person都有一个共同的行为--说话(不去钻牛角尖),当然我们可以改写上面的代码来满足我们的需求:

<span style="font-size:18px;">function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.getInfo = function(){
        return "name ="+this.name+",age = "+ this.age+",job = " +this.job;
    }
    this.say = function(){
        alert("人都会说话");
    }
}
</span>

这样的代码是允许的,但是不是被建议的,因为我们在创建一个Person对象的时候,就会在内存中生成say()函数的内容空间,或许在少量的这样的行为的时候我们允许这么做,但是我们再思考一下,如果一个函数中有大量的行为或者属性都像say()函数一样是每一个Person对象所共同拥有的,必然Person会说话,会跑,会思考,会苦,会笑.........如果有了大量类似的属性或行为的话,我们还是写在函数内部的话,就不好了,因为我们在创建基量很大的对象数量的时候,我们就额外增加了非必须且可避免的内存开销了,因为每个对象在内存中都会开辟其所拥有的方法空间,这是不建议的,因此,我们使用了原型的话,这个问题就可以避免了,我们只需要在内存的其他地方创建一个原型对象,改对象保存着Person所有的共同属性或者方法,只要在每个Person实例对象中保存一个指向改内存空间的引用或句柄即可.这样当我们即使创建再多Person实例对象,也没有多少内存开销,这个理解就是javascript原型的特性之一.因此上述代码改写成下面就好了:

<span style="font-size:18px;">function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
}
Person.prototype.getInfo = function(){
    return "name = "+this.name+",age = "+this.age+",job ="+this.job;
};
Person.prototype.say  =function(){
    console.log("人都会说话");
};</span>