JavaScript速学记录

全文摘抄自:http://www.cnblogs.com/xdp-gacl/

1. JavaScript中的null、Nan、undefined的区别是什么?

js中的数据类型有undefined,boolean,number,string,object等5种,前4种为原始类型,第5种为引用类型。

未定义的值和定义未赋值的为 undefined,null是一种特殊的object, NaN是一种特殊的number。
(1)undefined与null是相等的;(2)NaN与任何值都不相等,与自己也不相等。
Null、NaN和undefined都是变量的默认初始值。变量类型不同,系统给与的初始值就不同:
Boolean - false
Number - NaN
String,Array,Object - null
未指定变量类型 - undefined
var tmp = undefined; 
if (typeof(tmp) == "undefined"){ 
alert("undefined"); 
}
var tmp = null; 
if (!tmp && typeof(tmp)!="undefined" && tmp!=0){ 
alert("null"); 
}
var tmp = 0/0; 
if(isNaN(tmp)){ 
alert("NaN"); 
}
var tmp = undefined; 
if (tmp== undefined) 
{ 
alert("null or undefined"); 
}
if (tmp== null) 
{ 
alert("null or undefined"); 
}
var tmp = null; 
if (!tmp) 
{ 
alert("null or undefined or NaN"); 
}

2. 逻辑运算NOT、AND

逻辑 NOT一定返回Boolen值,NOT 运算符的行为如下:

  • 如果运算数是对象,返回 false
  • 如果运算数是数字 0,返回 true
  • 如果运算数是 0 以外的任何数字,返回 false
  • 如果运算数是 null,返回 true
  • 如果运算数是 NaN,返回 true
  • 如果运算数是 undefined,发生错误
判断JavaScript变量的Boolean 值时,也可以使用逻辑NOT运算符。这样做需要在一行代码中使用两个 NOT 运算符。无论运算数是什么类型,第一个NOT运算符返回 Boolean值,第二个NOT将对该Boolean值取反,从而给出变量真正的Boolean值。使用not运算符判断JavaScript变量的Boolean值是一个非常有用的技巧,只要知道了变量的boolean值,那么当使用变量进行&&或者||运算时,就可以很快知道运算的结果了。

逻辑AND运算并不一定返回Boolean值,AND运算符的运算行为如下:

  1. 如果一个运算数是对象,另一个是 Boolean 值true,返回该对象,false和对象返回false。
  2. 如果两个运算数都是对象,返回第二个对象。
  3. 如果某个运算数是 null,返回 null。
  4. 如果某个运算数是 NaN,返回 NaN。
  5. 如果某个运算数是 undefined,发生错误。
  6. 如果两个运算数都是boolean类型,则返回boolean值
  7. false&&null 返回false
  8. false&&NaN返回false
  9. NaN&&null 返回NaN
  10. null&&NaN 返回null

  与Java中的逻辑AND运算相似,JavaScript 中的逻辑AND运算也是简便运算,即如果第一个运算数决定了结果,就不再计算第二个运算数。对于逻辑AND运算来说,如果第一个运算数是false,那么无论第二个运算数的值是什么,结果都不可能等于true。

3.JavaScript数据类型判断

Number类型判断:

function isNumber(val){
     return typeof val === 'number' && isFinite(val);
}
isFinite() 函数用于检查其参数是否是无穷大,如果 number 是有限数字(或可转换为有限数字),那么返回 true。否则,如果 number 是 NaN(非数字),或者是正、负无穷大的数,则返回 false。

boolen数据类型判断:

function isBooleanType(val) {
    return typeof val ==="boolean";
}
string数据类型判断:

function isStringType(val) {
    return typeof val === "string";
}
undefined数据类型判断:

function isUndefined(val) {
    return typeof val === "undefined";
}
Object数据类型判断:

function isObj(str){
    if(str === null || typeof str === 'undefined'){
        return false;
    }
    return typeof str === 'object';
}
由于当变量是空值Null时,typeof也会返回object,所以Object不能直接用 typeof 判断。

Null数据类型判断:

function isNull(val){
      return  val === null;
}
Array数据类型判断:

数组类型不可用typeof来判断。因为当变量是数组类型是,typeof会返回object。这里有两种方法判断数组类型:

/*判断变量arr是不是数组
方法一
*/
function isArray1(arr) {
    return Object.prototype.toString.apply(arr) === '[object Array]';
}

/*判断变量arr是不是数组
方法二
*/
function isArray2(arr) {
    if(arr === null || typeof arr === 'undefined'){
        return false;
    }
    return arr.constructor === Array;
}

4. JavaScript函数可变参数的实现

JavaScript的函数天然支持可变参数,JavaScript有一个arguments变量可以访问所有传到函数内部的参数。

<script type="text/javascript">
    /*add函数是一个参数可变的函数*/
    function add(){
        var result=0;
        for(var i=0;i<arguments.length;i++){
            //alert(arguments[i]);
            result+=arguments[i];
        }

        return result;
    }
    alert("add(1,2,3)="+add(1,2,3));//调用add函数时传入3个参数
    alert("add(1,2,3,4,5,6)="+add(1,2,3,4,5,6));//调用add函数时传入6个参数
    alert("add()="+add());//调用add函数时不传入参数
    alert("add(1,\"HelloWorld\")="+add(1,"HelloWorld"));//调用add函数时传入不同类型的参数
  </script>

5. javascript创建动态函数

JavaScript支持创建动态函数,动态函数必须用Function对象来定义(Function是javascript中的一个对象,是固定不变的,规定Function对象的"F"必须大写,当是function的时候,我们知道是定义函数的时候所使用的一个关键字:function funName(x,y),当是Function的时候(F大写的时候),我们知道是javascript中的对象)

创建动态函数的基本格式:var 变量名 = new Function("参数1","参数2","参数n","执行语句");

使用new关键字(new是javascript中一个关键字,也是固定的,我们在定义动态函数的时候必须要使用new来创建这个Function对象)我们先定义一个变量: var 变量名,在这里,变量名是随便的,然后我们再使用new关键字创建一个Function对象,然后再把这个对象赋值给这个任意的变量,也就是:var 变量名 = new Function("参数1","参数2","参数n","执行语句");Function后面的括号里先是传递给函数的参数,然后用一个逗号(,)隔开然后是这个函数要执行的功能的代码

<script type="text/javascript">
   var square = new Function ("x","y","var sum ; sum = x+y;return sum;");
   alert("square(2,3)的结果是:"+square(2,3));
</script>
square是动态创建的函数,在Function对象后面的括号里的每一部分内容都必须是字符串形式的,也就是说都必须用引号(""或者是'')括起来,第一部分是传递给这个动态函数的第一个参数“x”,第二部分是传递给这个动态函数的第二个参数“y“,第三部分是这个函数要完成的功能的代码,这个函数要完成的功能是定义一个变量sum,让sum等于传递给这个函数的两个参数x和y的和,然后返回他们相加以后的值(return sum)。

匿名函数:
直接声明一个匿名函数,立即使用。用匿名函数的好处就是省得定义一个用一次就不用的函数,而且免了命名冲突的问题,js中没有命名空间的概念,因此很容易函数名字冲突,一旦命名冲突以最后声明的为准。

alert(function(i1, i2) { return i1 + i2; }(10,10));
6. JavaScript中的数组
数组的声明
常规方式声明:

1、var arrName = new Array();//创建一个数组

2、var arrName = new Array([size]); //创建一个数组并指定长度,注意不是上限,是长度

3、var arrName =new Array("孤傲苍狼","白虎神皇","灭世魔尊");//创建一个数组,并初始化数组的内容

  注意:虽然var arrName = new Array([size]);指定了长度,但实际上所有情况下数组都是变长的,也就是说即使指定了长度为2,仍然可以将元素存储在规定长度以外的,注意:这时长度会随之改变。
<script type="text/javascript">
        var names = new Array();//普通方式声明数组,不需要指明数组的长度
        names[0] = "孤傲苍狼";
        names[1] = "白虎神皇";
        names[2] = "灭世魔尊";
        for (var i = 0; i < names.length; i++) {
            document.write("names["+i+"] = "+names[i]);
            document.write("<br/>");
        }

        var pinyins = new Array();
        pinyins["人"] = "ren";
        pinyins["口"] = "kou";
        pinyins["手"] = "shou";
        document.write("pinyins[\"人\"] = "+pinyins["人"]);
        document.write("<br/>");
        document.write("pinyins.手 = "+pinyins.手);//像Hashtable、Dictionary那样用,而且像它们一样效率高。
        document.write("<br/>");
        //Array的简化声明
        var arr1 = [3, 5];//普通数组初始化
         for (var i = 0; i < arr1.length; i++) {
            document.write("arr1["+i+"] = "+arr1[i]);
            document.write("<br/>");
        }
</script>

7. Javascript面向(基于)对象编程

JavaScript使用“原型对象”指Class。封装的范例如下:
<script type="text/javascript">
    /*定义一个Person类*/
    function Person(_name,_age,_salary){
        //Person类的公开属性,类的公开属性的定义方式是:”this.属性名“
        this.Name=_name;
        //Person类的私有属性,类的私有属性的定义方式是:”var 属性名“
        var Age=_age;
        var Salary=_salary;

        //定义Person类的公开方法(特权方法),类的公开方法的定义方式是:”this.functionName=function(){.....}“
        this.Show=function(){
            alert("Age="+Age+"\t"+"Salary="+Salary);//在公开方法里面访问类的私有属性是允许的
        }
        /*
        定义Person类的私有方法(内部方法),
        类的私有方法的定义方式是:”function functionName(){.....}“,
        或者 var functionName=function(){....}
        */
        function privateFn(){
            alert("我是Person类的私有函数privateFn");
        }

        var privateFn2=function(){
            alert("我是Person类的私有函数privateFn2");
        }
    }
    /*通过prototype给可以类的所有对象添加公共(public)方法,
    但是这种方式定义的方法不能去访问类的私有属性和私有方法*/
    Person.prototype.Fn=function(){
        alert("访问公共属性this.Name="+this.Name);//访问公共属性,OK的
        //alert("访问私有属性Aag="+Age);//访问私有属性,这里会报错“Age未定义”
        //privateFn();//调用私有方法,这里会报错“缺少对象”

    }

    var p1 = new Person("孤傲苍狼",24,2300);
    alert("p1.Name="+p1.Name);//访问公有属性,这是可以正常访问的
    alert("p1.Age="+p1.Age+"\t"+"p1.Salary="+p1.Salary);//不能使用类的对象去直接访问类私有属性,这是访问不了的,结果都是undefined
    p1.Show();//调用类的公共函数,这次允许的
    p1.Fn();//调用类的公共函数,这次允许的
    //alert("p1.privateFn():"+p1.privateFn()+" p1.privateFn2():"+p1.privateFn2());//不能使用类的对象去调用类的私有方法,这里会报错”对象不支持此属性或者方法“
  </script>
继承的范例如下:

<script type="text/javascript">
    /*定义Stu类*/
    function Stu(name,age){
        this.Name=name;
        this.Age=age;
        this.Show=function(){
            window.alert("我的名字是:"+this.Name+",今年:"+this.Age);
        }
        this.SayHello = function(){
            window.alert("Hello,"+this.Name);
        }
    }

    /*定义MidStu类*/
    function MidStu(name,age){
        this.stu=Stu;//MidStu类继承Stu类
        this.stu(name,age);//JS中实际上是通过对象冒充来实现继承的,这句话不能少,因为JS是动态语言,如果不执行,则不能实现继承效果
        /*
        从父类继承下来的公共方法,可以根据实际情况选择重写
        */
        //在子类MidStu中重写父类Stu的Show方法
        /*this.Show=function(){
            alert("MidStu.Show()");
        }*/
        //在子类MidStu中重写父类Stu的SayHello方法
        this.SayHello=function(){
            alert("你好,"+this.Name);
        }

    }

    var midStu = new MidStu("孤傲苍狼",24);//创建一个MidStu类实例对象
    alert("访问继承下来的属性Name和Age,midStu.Name="+midStu.Name+",midStu.Name="+midStu.Age);//访问继承下来的属性
    midStu.Show();//调用从父类Stu继承下来的Show方法
    midStu.SayHello();//调用从父类Stu继承下来的SayHello方法,SayHello()在子类中进行了重写,这里调用的是重写过后的SayHello()方法
  </script>
所谓多态,就是指一个引用在不同情况下的多种状态,在Java中多态是指通过指向父类的引用,来调用不同子类中实现的方法。

  JS实际上是无态的,是一种动态语言,一个变量的类型是在运行过程中由JS引擎决定的,所以说,JS天然支持多态。

8. JavaScript的Object类

Object类是所有JavaScript类的基类(父类),提供了一种创建自定义对象的简单方式,不再需要程序员定义构造函数。

1.hasOwnProperty(propertyName)

判断对象是否有某个特定的属性。必须用字符串指定该属性,例如,obj.hasOwnProperty("name"),返回布尔值。此方法无法检查该对象的原型链中是否具有该属性;该属性必须是对象本身的一个成员。
function extend(target,source){//target 旧的 source新的
  for (var i in source){
        if(target.hasOwnProperty(i)){
        target[i]=source[i];
        }
    }
    return target;
}
var a1={"first":1,"second":"lyl","third":"bob"};
var b1={"third":"leo"};
extend(a1,b1);
for(var i in a1){
    alert(a1[i]);//原本是bob,现在变成leo了
}
hasOwnProperty的用法不仅仅在此,在Jquery中在编写插件中,少不了的一步,就是初始化参数,其中一个很重要的方法就是$.extend();他的原理就是应用了hasOwnProperty()方法;利用for in 循环遍历对象成员中,有没有相同名称的对象成员,有的话就用这个新的对象成员替换掉旧的,通过这种方式,我们就可以通过修改方法中的参数变化,从而控制程序的流程,而对于那些没有改变的部分,仍使用默认值进行控制。
2.isPrototypeOf(object)
判断该对象是否为另一个对象的原型。

  obj1.isPrototypeOf(obj2);

obj1是 一个对象的实例;obj2是另一个将要检查其原型链的对象。原型链可以用来在同一个对象类型的不同实例之间共享功能。如果obj2的原型链中包含 obj1,那么isPrototypeOf 方法返回 true。如果obj2不是一个对象或者obj1没有出现在obj2中的原型链中,isPrototypeOf 方法将返回 false。
<script type="text/javascript">
    function foo(){
        this.name = 'foo';
    }
    function bar(){

    }
    bar.prototype = new foo();
    var goo = new bar();
    alert(goo.name); //foo
    alert(bar.prototype.isPrototypeOf(goo));//true,在bar的原型链中有当前对象goo,则isPrototypeOf方法返回true
  </script>
var Car = function(){};
    Car.prototype.hello = function(){
        alert("hello car");
    };
    var car = new Car();
    car.f = function() {
        alert("自定义方法");
    }
    document.write("<pre>");
    document.writeln("car.hasOwnProperty(\"f\")的结果是:"+car.hasOwnProperty("f"));//ture,car对象有f方法
    document.writeln("car.propertyIsEnumerable(\"f\")的结果是:"+car.propertyIsEnumerable("f"));//ture,car对象有f方法,f方法是可以被枚举的
    document.writeln("car.hasOwnProperty(\"hello\")"+car.hasOwnProperty("hello")); // false,因为car本身没有hello方法
    document.writeln("car.propertyIsEnumerable(\"hello\")的结果是:"+car.propertyIsEnumerable("hello"));   // false,没有这个方法当然不能枚举
    document.writeln("car.constructor.prototype.hasOwnProperty(\"hello\")的结果是:"+car.constructor.prototype.hasOwnProperty("hello"));// true,car的类Car的原型有hello方法
    document.writeln("car.constructor.prototype.propertyIsEnumerable(\"hello\")的结果是:"+car.constructor.prototype.propertyIsEnumerable("hello"));// true, car的类的Car的原型hello方法是可以被枚举的
    document.writeln("Car.prototype.hasOwnProperty(\"hello\")的结果是:"+Car.prototype.hasOwnProperty("hello"));// true,car的类Car的原型有hello方法
    document.writeln("Car.prototype.propertyIsEnumerable(\"hello\")的结果是:"+Car.prototype.propertyIsEnumerable("hello"));
    document.write("</pre>");

9.JavaScript Object的三种创建方式

1. 构造函数方式
构造函数方式可以创建具备私有成员变量和私有方法的Object。
/*定义一个Person类*/
    function Person(_name,_age,_salary){
        //Person类的公开属性,类的公开属性的定义方式是:”this.属性名“
        this.name=_name;
        //Person类的私有属性,类的私有属性的定义方式是:”var 属性名“
        var age=_age;//私有属性
        var salary=_salary;//私有属性

        /*定义私有属性Age的对外公开访问方法*/
        this.setAge = function(intAge) {
            age = intAge;
        }
        /*定义私有属性Age的对外公开访问方法*/
        this.getAge = function() {
            return age;
        }

        //定义Person类的公开方法(特权方法),类的公开方法的定义方式是:”this.functionName=function(){.....}“
        this.Show=function(){
            document.writeln("在公开方法里面访问类的私有属性是允许的,age="+age+"\t"+"salary="+salary);//在公开方法里面访问类的私有属性是允许的
        }
        //公共方法
        this.publicMethod = function(){
            document.writeln("在公开方法里面访问类的私有方法是允许的");
            privateFn();//在公开方法里面调用类的私有方法
            privateFn2();//在公开方法里面调用类的私有方法
        }
        /*
        定义Person类的私有方法(内部方法),
        类的私有方法的定义方式是:”function functionName(){.....}“,
        或者 var functionName=function(){....}
        */
        function privateFn(){
            document.writeln("我是Person类的私有函数privateFn");
        }

        var privateFn2=function(){
            document.writeln("我是Person类的私有函数privateFn2");
        }
    }

这种方式的优点是:可以根据参数来构造不同的对象实例 ,每个对象的属性一般是不相同的,缺点是构造每个实例对象时, 方法不能共享,Person类里面定义的那些方法,p1对象有一份,p2也有一份,那么在内存中就得开辟两块内存空间来分别存储p1的方法和p2的方法,这样就造成了内存的浪费。对于一个类的不同实例对象,这些对象的属性一般是不相同的,但是方法是相同的,所以节约内存的做法就是把方法放到内存的一块区域中存放,然后每个实例对象都从这块内存中取出方法。
2. 原型方式
使用原型方式编写JavaScript类是无法给类添加私有属性和私有方法的,使用原型方式添加的属性和方法都是public的。
/*定义类Person2*/
    function Person2(){
        
    }

    /*使用原型方式给类定义public属性和public方法更加优雅的写法*/
    Person2.prototype = {
        name:"",//public属性
        age:0,//public属性
        weight:0,//public属性
        height:0,//public属性
        /*public方法*/
        init:function(_name,_age,_weight,_height) {
            this.name = _name;
            this.age = _age;
            this.weight=_weight;
            this.height=_height;
            document.writeln("this.name="+this.name+",this.age="+this.age+",this.weight="+this.weight+",this.height="+this.height);
        },
        /*public方法*/
        show:function(){
            document.writeln("show method");
        }
    };
测试代码如下:
    document.write("<pre>");
    var p2_1 = new Person2();
    var p2_2 = new Person2();
    p2_1.init("孤傲苍狼",24,115,160);
    p2_2.init("白虎神皇",25,120,170);
    document.writeln("p2_1.name="+p2_1.name+",p2_1.age="+p2_1.age+",p2_1.weight="+p2_1.weight+",p2_1.height="+p2_1.height);//访问公有属性,这是可以正常访问的
    document.writeln("p2_2.name="+p2_2.name+",p2_2.age="+p2_2.age+",p2_2.weight="+p2_2.weight+",p2_2.height="+p2_2.height);//访问公有属性,这是可以正常访问的
    document.writeln("p2_1 instanceof Person2的结果是:"+(p2_1 instanceof Person2));//p2_1是Person2类的实例,结果是true
    document.writeln("p2_2 instanceof Person2的结果是:"+(p2_2 instanceof Person2));//p2_2是Person2类的实例,结果是true
    //当==两边的内容是对象或者是对象的函数属性时,则比较内存地址是否相等
    document.writeln("当==两边的内容是对象或者是对象的函数属性时,则比较内存地址是否相等");
    document.writeln("比较p2_1和p2_2这两个对象的init方法的内存地址是否一样:p2_1.init == p2_2.init的结果是:"+(p2_1.init == p2_2.init));//true
    p2_1.name="灭世魔尊";//为公共属性重新赋值
    document.writeln("p2_1.name="+p2_1.name);//访问公有属性,这是可以正常访问的
    p2_1.show();//调用类的公共函数,这次允许的
    document.write("</pre>");
原型方式的优点:所有对象实例都共享类中定义的方法,这样就没有造成内存浪费。缺点,第一,不能定义类的私有属性和私有方法,第二,给在创建对象,给对象的属性初始化时,需要 额外写一个初始化对象的方法

3. 构造函数+原型方式

构造函数方式和原型方式都有各自的优缺点,因此可以把这两种方式合并起来,用构造函数方式来定义类的属性(public属性,private属性),用原型方式来定义类的方法(public方法)。互补不足,这就有了第三种写法。
/*定义一个Person类*/
    function Person(_name,_age,_salary){
        //在Person类内部定义类的public属性和private属性以及private方法
        //Person类的公开属性,类的公开属性的定义方式是:”this.属性名“
        this.name=_name;
        //Person类的私有属性,类的私有属性的定义方式是:”var 属性名“
        var age=_age;//私有属性,只能在类内部使用
        var salary=_salary;//私有属性,只能在类内部使用
        /*
        定义Person类的私有方法(内部方法),只能在类内部使用
        类的私有方法的定义方式是:”function functionName(){.....}“,
        或者 var functionName=function(){....}
        */
        function privateFn(){
            document.write("<pre>");
            document.writeln("我是Person类的私有属性age,只能在Person类内部使用,初始化后age="+age);
            document.writeln("我是Person类的私有函数privateFn,只能在Person类内部使用");
            document.write("</pre>");
        }

        var privateFn2=function(){
            document.write("<pre>");
            document.writeln("我是Person类的私有属性salary,只能在Person类内部使用,初始化后salary="+salary);
            document.writeln("我是Person类的私有函数privateFn2,只能在Person类内部使用");
            document.write("</pre>");
        }

        privateFn();//在Person类内部调用私有方法
        privateFn2();//在Person类内部调用私有方法
    }

    //使用prototype原型方式定义的方法(public方法)是无法访问类的私有属性和私有方法的
    //使用prototype原型方式定义Person类的方public方法
    Person.prototype={
        setName:function(_name){
            this.name = _name;
            //privateFn();//不能调用Person类定义的私有方法privateFn(),会报错:缺少对象
        },
        getName:function(){
            return this.name;
        },
        show:function(){
            document.writeln("公开方法show");
        },
        //公共方法
        publicMethod:function(){
            document.writeln("公开方法publicMethod");
        }
    };
第三种方式通过前两种方式的结合,算是达到了一个比较理想的写法了,可以通过传参构造对象实例,对象实例都共享同一份方法不造成内存浪费。第三种方式在开发中用得最多。

10. JavaScript的闭包(Cloure)概念

1. Javascript特殊的变量作用域

在JavaScript中,变量的作用域分两种:全局变量和局部变量。

在Javascript中,在函数内部可以直接读取全局变量。
var n=999;//定义全局变量n
function f1(){
  alert("在函数内部访问全局变量n,n="+n);//在函数内部访问全局变量n
}
f1(); // 999
但是反过来则不行,在函数外部无法读取函数内的局部变量。
function f1(){
   var n=999;//在f1函数内部定义局部变量n
}
alert("在函数外部访问局部变量n,n="+n); //在函数外部访问局部变量n,错误:n未定义
这里有一个地方需要注意,函数内部声明变量的时候,一定要使用 var命令。如果不用的话,实际上是声明了一个全局变量!
function f1(){
  n=999;
}
f1();
alert("n在f1函数内部没有使用var来声明,此时n就是一个全局变量,\r\n证明:n="+n+",window.n==n的结果是:"+(window.n==n));
2. 如何从外部读取局部变量
出于种种原因,我们有时候需要得到函数内的局部变量。但是,前面已经说过了,正常情况下,这是办不到的,只有通过变通方法才能实现。那就是在函数的内部,再定义一个函数。
function f1(){
    var n=999;//局部变量n
    //在f1函数内部声明的f2函数
    function f2(){
      alert(n);
    }

    return f2;//将f2函数作为f1函数的返回值
  }
  var result=f1();//f1调用完后的返回值是一个f2函数,此时result就是f2函数
  result(); // 999,调用f2函数
3. 闭包的概念
上一节代码中的f2函数,就是闭包。各种专业文献上的"闭包"(closure)定义非常抽象,比如有这样的一个闭包定义:" JavaScript闭包就是在另一个作用域中保存了一份它从上一级函数或者作用域得到的变量,而这些变量是不会随上一级函数的执行完成而销毁 ",对于这样的闭包定义,我是很难看懂。 我的理解是, 闭包就是能够读取其他函数内部变量的函数 由于在Javascript语言中,只有函数内部的子函数才能读取局部变量,因此可以把闭包简单理解成"定义在一个函数内部的函数" 。所以, 在本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁
4. 闭包的作用
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中
function f1(){
    var n=999;
        //nAdd是一个没有使用var声明的全局变量,这个变量现在指向了在f1函数内部声明的一个匿名函数
    nAdd=function(){n+=1}
    function f2(){
      alert(n);
    }
    return f2;
  }

  var result=f1();//result就是f2函数
  result();//第一次调用result函数 999
  nAdd();//nAdd代表的就是在f1函数内部声明的一个匿名函数,nAdd()就是在调用匿名函数
  result();//第二次调用result函数 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。

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

这段代码中另一个值得注意的地方,就是"nAdd=function(){n+=1}"这一行,首先在nAdd前面没有使用var关键字,因此 nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个 setter,可以在函数外部对函数内部的局部变量进行操作。
5. 闭包的注意事项
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。

2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。

11.JavaScript添加动态和静态方法

动态方法

类名.prototype.方法名 = function([param1],[param2],....[paramn]) {

    .................

  }

  [param1],[param2],....[paramn]这些参数都是可选的

  使用这种方式给类添加的扩展方法都是动态的,动态方法是针对类的实例对象的,所以调用必须要用"对象.方法名"的形式去调用,不能用"类名.方法名"的形式去调用!

/*扩展为String类,为String类增加quote(两边加字符)方法*/
        String.prototype.quote = function(quotestr) {
            if (!quotestr) {
                quotestr = "\"";
            }
            return quotestr + this + quotestr;
        };

alert("abc".quote());      
alert("abc".quote("|"));

静态方法

类名.方法名 = function([param1],[param2],....[paramn]) {

    .................

  }

  [param1],[param2],....[paramn]这些参数都是可选的

  使用这种方式给类添加的扩展方法都是静态的,动态方法是针对类的实例对象的,所以调用必须要用"对象.方法名"的形式去调用,而静态方法是针对类的,用"类名.方法名"的形式去调用!

// ----------------------------------------------------------------------
// <summary>
// 扩展String类,添加Format静态方法,模仿C#中的String.Format方法
// </summary>
// <returns>str</returns>
// ----------------------------------------------------------------------
if (!String.Format) {
    String.Format = function () {
        if (arguments.length == 0) {
            return null;
        }
        var str = arguments[0];
        if (arguments[1] instanceof Array) {
            var arr = arguments[1];
            for (var i = 0; i < arr.length; i++) {
                var re = new RegExp('\\{' + i + '\\}', 'gm');
                str = str.replace(re, arr[i]);
            }
        } else {
            for (var i = 1; i < arguments.length; i++) {
                var re = new RegExp('\\{' + (i - 1) + '\\}', 'gm');
                str = str.replace(re, arguments[i]);

            }
        }
        return str;
    }
}
var str="我是{0},我在总结{1}和{2}的学习,我很喜欢{3}这2门语言!";
//使用"类名.方法名"的形式去调用类的静态方法
str = String.Format(str,"孤傲苍狼","java","JavaScript","'java'和'JavaScript'");//把str中的占位符{0},{1},{2},{3}用具体的内容替换掉
alert(str);

12. 变量提升和函数提升


变量提升:
JavaScript引擎的工作方式是,先解析代码,获取所有被声明的变量,然后再一行一行地运行。这造成的结果,就是所有的变量的声明语句,都会被提升到代码的头部,这就叫做变量提升(hoisting)。
console.log(a);
var a = 1;
上面代码首先使用console.log方法,在控制台(console)显示变量a的值。这时变量a还没有声明和赋值,所以这是一种错误的做法,但是实际上不会报错。因为存在变量提升,真正运行的是下面的代码。
var a;
console.log(a);
a = 1;
最后的结果是显示undefined,表示变量a已声明,但还未赋值。

请注意,变量提升只对var命令声明的变量有效,如果一个变量不是用var命令声明的,就不会发生变量提升。
console.log(b);
b = 1;

上面的语句将会报错,提示“ReferenceError: b is not defined”,即变量b未声明,这是因为b不是用var命令声明的,JavaScript引擎不会将其提升,而只是视为对顶层对象的b属性的赋值。
JavaScript引擎将函数名视同变量名,所以采用function命令声明函数时,整个函数会像变量声明一样,被提升到代码头部。所以,下面的代码不会报错。
f();

function f() {}
表面上,上面代码好像在声明之前就调用了函数f。但是实际上,由于“变量提升”,函数f被提升到了代码头部,也就是在调用之前已经声明了。但是,如果采用赋值语句定义函数,JavaScript就会报错。
f();
var f = function (){};
// TypeError: undefined is not a function


13 区块和作用域

JavaScript使用大括号,将多个相关的语句组合在一起,称为“区块”(block)。

与大多数编程语言不一样,JavaScript的区块不构成单独的作用域(scope)。也就是说,区块中的变量与区块外的变量,属于同一个作用域。
{
  var a = 1;
}

a // 1
上面代码在区块内部,声明并赋值了变量a,然后在区块外部,变量a依然有效,这说明区块不构成单独的作用域,与不使用区块的情况没有任何区别。所以,单独使用的区块在JavaScript中意义不大,很少出现。区块往往用来构成其他更复杂的语法结构,比如for、if、while、function等。
作用域(scope)指的是变量存在的范围。Javascript只有两种作用域:一种是全局作用域,变量在整个程序中一直存在,所有地方都可以读取;另一种是函数作用域,变量只在函数内部存在。


14. eval命令

eval命令的作用是,将字符串当作语句执行。
eval('var a = 1;');
a // 1
eval没有自己的作用域,都在当前作用域内执行,因此可能会修改当前作用域的变量的值,造成安全问题。
var a = 1;
eval('a = 2');

a // 2
上面代码中,eval命令修改了外部变量a的值。由于这个原因,eval有安全风险。

为了防止这种风险,JavaScript规定,如果使用严格模式,eval内部声明的变量,不会影响到外部作用域。
(function f() {
  'use strict';
  eval('var foo = 123');
  console.log(foo);  // ReferenceError: foo is not defined
})()
上面代码中,函数f内部是严格模式,这时eval内部声明的foo变量,就不会影响到外部。

不过,即使在严格模式下,eval依然可以读写当前作用域的变量。
(function f() {
  'use strict';
  var foo = 1;
  eval('foo = 2');
  console.log(foo);  // 2
})()

此外,eval的命令字符串不会得到JavaScript引擎的优化,运行速度较慢。这也是一个不应该使用它的理由。

通常情况下,eval最常见的场合是解析JSON数据字符串,不过正确的做法应该是使用浏览器提供的JSON.parse方法。


15. JavaScript原型链

ECMAScript中描述了原型链的概念,并将原型链作为实现继承的主要方法。其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。在JavaScript中,用 __proto__ 属性来表示一个对象的原型链。当查找一个对象的属性时,JavaScript 会向上遍历原型链,直到找到给定名称的属性为止!

/*扩展Object类,添加Clone,JS实现克隆的方法*/
Object.prototype.Clone = function(){
    var objClone;
    if (this.constructor == Object){
        objClone = new this.constructor(); 
    }else{
        objClone = new this.constructor(this.valueOf()); 
    }
    for(var key in this){
        if ( objClone[key] != this[key] ){ 
            if ( typeof(this[key]) == 'object' ){ 
                objClone[key] = this[key].Clone();
            }else{
                objClone[key] = this[key];
            }
        }
    }
    objClone.toString = this.toString;
    objClone.valueOf = this.valueOf;
    return objClone; 
}

/*扩展Object类,添加Extend方法来实现JS继承, 目标对象将拥有源对象的所有属性和方法*/
Object.prototype.Extend = function (objDestination, objSource) {
    for (var key in objSource) {
        if (objSource.hasOwnProperty(key) && objDestination[key] === undefined) {
            objDestination[key] = objSource[key];
        }
    }
    return objDestination;
}
16. new运算符是如何工作的

很简单的一段代码,我们来看看这个new究竟做了什么?我们可以把new的过程拆分成以下三步:

  1.var p={}; 初始化一个对象p

  2. p. __proto__=Person.prototype;,将对象p的 __proto__ 属性设置为 Person. prototype

  3.Person.call(p,"孤傲苍狼",24);调用构造函数Person来初始化p。

那么__proto__是什么?在这里简单地说下。每个对象都会在其内部初始化一个属性,就是 __proto__,当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去__proto__里找这个属性,这个 __proto__又会有自己的__proto__,于是就这样一直找下去,也就是我们平时所说的原型链的概念。
按照标准,__proto__是不对外公开的,也就是说是个私有属性,在IE下是无法访问__proto__属性的,但是Firefox的引擎将他暴露了出来成为了一个公有的属性,我们可以对外访问和设置。
















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值