JavaScript相关知识

开发小组内培训,自己编写的资料。现在没事发上来。

内容来源于从网上看到的一些文章。

主要介绍一些 javascript 比较混乱的语法和一些不太常用的用法。

 

基本语法

 

Js 是弱类型动态语言,所以任意变量名都可随时改变数据类型,在js 里面一切都是对象,一切都是数据。

函数是js 的一等公民。

Ojbect 是所有对象的父,遍历prototype 对象时最终一定会到达Object.prototype

 

变量的声明

 

• JS 的作用域只有全局和局部

• 所有运行于浏览器的js 解释器的全局作用域都是window ,其它的脱离浏览器的js 解释器都有各自的全局对象名称

• 所有全局变量都是全局对象的属性

• 函数也是一种数据类型,所以全局作用域的函数也是全局对象的属性

• var 关键字是指在当前作用域声明变量

 
Js代码  收藏代码

    //eg. Test.js  
        var a;// 在全局作用域声明一个变量  
        function f(){  
           var a;// 在函数f 作用域声明一个变量  
        }  

 

 

• 使用一个变量时如果没有在当前作用域链中找到就会在全局作用域查找。如果这个全局变量存在就使用,如果不存在就认为是undefined 。如果当前是赋值语句就自动创建一个全局变量

 

 
Js代码  收藏代码

    //eg. Test.js  
        a;// 在全局作用域声明一个全局变量  
        function f(){  
           a=1;// 在函数f 为全局变量a 赋值  
           b;//undefined  
           c= ‘c ’;// 在函数f 内声明一个全局变量  
        }  

 

 

• 使用function  fname (){} ,如果当前作用域是全局的,就声明一个全局函数

如果当前作用域是在函数内容,就声明一个内部函数

与var fname=function(){}区别如下

 
Js代码  收藏代码

    function f1(){  
      f1();//出错  
      f2();//调用成功  
      var f1=function(){};//看过的一本老外的书上推荐这么写  
      function f2(){}//不推荐这么写  
      f1();//可调用成功  
      f2();//可调用成功  
    }  

 

• 由于任意类型都是数据,函数也不例外,可以把一个函数的定义作为变量值赋给一个变量

 
Js代码  收藏代码

    function global(){// 在全局作用域声明一个全局函数  
       function global2(){}// 在函数内声明一个内部函数  
       var fn=function(){};  
       // 在函数内声明一个内部函数,分号不能丢  
       var fn2=function fn3(){  
            fn3();// 成功  
       };  
       fn3();//undefined  
    }  

 

 

• Js 解析器会自动提升变量和函数声明到当前作用域的最前面,但是相应的赋值语句位置不变

• Js 变量只有函数作用域和全局作用域,没有块作用域。也就是在if for switch while 内声明的变量也是在整个函数内可见的

 
Js代码  收藏代码

    alert(a);//undefined  
    var f=function(){alert(a);};  
    var a=1;  
    f();// 可访问 变量a  

 

 

上面的代码相当于

 
Js代码  收藏代码

    var a;  
    alert(a);//undefined  
    var f=function(){alert(a);};  
    a=1;  
    f();// 可访问 变量a  
    // 另一个例子  
    function f(){  
       for(var i=0;i<10;i++){var a=0;}  
       alert(i+ ‘ ’+a);//10 0  
    }  
    function f(){  
       for(var i=0;i<10;i++){  
        setTimeout(function(){alert(i);},500);  
        // 一共alert10 个10  
      }  
    }// 应该是下面的样子  
    function f(){  
       for(var i=0;i<10;i++){  
      setTimeout((function(i){  
            return function(){alert(i);};  
        })(i),500);  
      }  
    }  
    // 或者是  
    function f(){  
       for(var i=0;i<10;i++){  
       (function(i){  
      setTimeout(function(){alert(i);},500);  
        })(i);  
       }  
    }  

 

 

变量的作用域链

• Js 有两个变量作用域链

• 一个是闭包上下文作用链

• 一个是prototype 继承作用域链

• 一个Function 对象如果是另一个对象的成员这个Function 对象就是它的拥有者的方法,如果一个Function 对象是独立的它就是一个函数

• 使用this.xxx的方式访问的变量,会首先在当前对象的prototype 查找变量,如果找不到就从它的父的prototype 查找,如果一直找不到会一直追溯到Object.prototype

• Object.prorotype===null;//true

• 不用this.xxx访问的变量,会首先在当前函数作用域查找变量,如果找不到会判断当前函数是不是闭包,如果不是就查找全局作用域。如果是闭包,就在创建闭包的函数内查找,直到全局作用域。

this 的引用

•     this 一定会有一个引用它不可能是 undefined 或 null

•     如果使用 this 的 Function 对象是一个函数,不论这个函数是不是闭包, this 一定会引用全局对象。

•     在一个对象成员方法内通过 this 获取属性会查找 prototype 继承链

•     在构造器内, this 引用新创建的对象

 
Js代码  收藏代码

    function  con(){  
      this.a=1;  
      // 创建一个新属性, this 引用 new con();  
    }  
    con.prototype={f:function(){alert(this.b);},b:2};  
    new con().f();//2  

 

 

call 和 apply

•     它们会临时改变一个函数的 this 引用

•     this 引用 call 和 apply 的第一个参数

 
Js代码  收藏代码

    function foo(arg){  
      alert(this.a);  
      alert(arg);  
    }  
    var o={a:1};  
    foo.call(o,2);//1 2  
    foo.apply(o,[2]);//1 2  

 

 

new 的用法

•     使用 new 调用一个函数时,这个函数默认返回新创建的对象

 
Js代码  收藏代码

    function f(){}  
    var o=new f();//o 的值实际是 new f 的返回值  
    等价于 var o=new f;  
    function f1(){return 1;}  
    var o=new f1();//(o===1)===true  

 

 

•     不使用 new 调用一个函数,函数默认返回 undefined

 
Js代码  收藏代码

    function f(){}  
    var a=f();//undefined  

 

 

prototype

•     Js 采用 prototype 继承方式

•     Object.prototype===null;//true

•     一个构造器创造的所有实例都会引用同样的 prototype 对象,而不会为每一个新对象创建一个新 prototype 对象。

 
Js代码  收藏代码

    function Asian(){};  
    Asian.prototype={haircolor: ’ black ’ };  
    new Asian().constructor.prototype.haircolor= ‘ white ’ ;  
    new Asian().haicolor;// ’ white ’  

•     new Asian().haircolor='white'; 这种写法,实际是直接为新创建的对增加了一个名为haircolor的属性,并没有影响到Asian.prototype。

 

constructor

•     返回一个对象的构造器引用

 
Js代码  收藏代码

    function Asian(){};  
      new Asian().constructor===Asian;//true  

 

 

•     constructor 是 prototype 的一个属性,如果 A 对象的 prototype 是 B 对象的引用,那么 A 、 B 两个对象的 constructor 是一样的。这时我们需要这么做:

 
Js代码  收藏代码

    function A(){}  
    function B(){}  
    A.prototype=new B();  
    A.prototype.constructor=A;  
    ‘’ .constructor===String//true  
    [].constructor===Array;//true  
    1..constructor===Number;//true ,第一个点是小数点  
    (1).constructor===Number;//true  
    true.constructor===Boolean;//true  
    var foo=function(){};  
    new foo().constructor===foo;//true  

 

 

•     我们可以用这种方式精确判断一个对象的类型,比 typeof 和 instanceof 好的多

•     typeofof 只有‘ undefined ’ , ‘ object ’ , ’ string ’ , ’ boolean ’ , ’ number ’ , ’ function ’ 6 个值,而 instanceof 的做法与 java 的 instanceof 一样

arguments

arguments 是 js 函数参数对象,它是一个奇怪的对象。

 

arguments不是全局对象的属性,它是当前函数的一个属性function(){alert(arguments===arguments.callee.arguments);}//显示true

但是只有arguments可以直接取得,当前函数的其它属性无法直接取得,依然只能使用下述方式取得:函数名.属性名、函数名['属性名']、arguments.callee.属性名、arguments.callee['属性名']

而且发生递归时,每次递归都会得到不同的arguments对象。

它的属性如下:

length ——调用时传入函数的参数数目

callee ——函数本身的引用

arguments[INTEGER_INDEX] ——获取相应的实参值,实参索引与传入函数时的位置一一对应。 INTEGER_INDEX>=0&&INTEGER_INDEX<arguments.length

arguments.callee 会强迫 js 解释器查找当前函数的自身的引用

function foo(){}

// 如果 foo 是局部函数, caller 是 undefined

// 如果 foo 是全局函数, caller 是全局对象

// 如果 foo 是一个对象的方法, caller 与 foo 内的 this 引用相同

这两个属性会引起时间效率的问题

但在有些时候会很方便。我们看下面的例子

setTimeout(function(){

  setTimeout(arguments.callee,0);

},0);

// 在需要递归调用一个匿名函数的时候使用 arguments.callee 会很方便

•     同时可利用 callee 和 caller 在运行时动态创建或修改函数对象的属性值

 
Js代码  收藏代码

    var foo=function(){  
       var callee=arguments.callee;  
       if(!callee.invokeCount){callee.invokeCount=0;}  
       callee.invokeCount++;  
       var caller=callee.caller;  
       if(caller.invokeFoo){caller.invokeFoo=0;}  
       caller.invokeFoo++;  
    };  

•     Array.apply([],arguments);//可以用这种方式将arguments转换成数组
 

 

判断一个函数是不是 new 调用

 
Js代码  收藏代码

    // 现在利用前面说过的 this callee caller 特性判断一个函数是不是在调用时使用了 new  
    function foo(){  
       if(this.constructor===arguments.callee){  
           // 是用 new 调用的  
       }else{  
           // 不是,此时 (constructor===undefined)===true  
       }  
    }// 我们可以用这种方式使用一个 Function 对象即能做函数又能做构造器  

 

 

Function 的 prototype

•     任何 function 声明的对象都是 Function 对象,我们可以利用这一特性完成一些特殊应用

 
Js代码  收藏代码

    Function.prototype.inherit=function(name,fun){  
      if(arguments.length===2){  
        this.prototype[name]=fun;  
      }else{  
        this.prototype=name;  
      }  
       return this;  
    }  
    var o=(function(){}).inherit({a:1}).inherit( ‘ name ’ ,function(){});  
    new o().name();  
    new o().a;  

 

 

基于闭包的偏函数应用

 
Js代码  收藏代码

    var fn=function(args){  
       // 初始化工作  
       // 这部分代码只执行一次  
       return (fn=function(args){  
           // 需要重复执行的代码,  
           // 会用到前面初始化的一些变量  
       })(args);  
    }// 这种用法可以有以下应用场景  
    // 第一次调用时传入比较多的参数,把一部分参数设为固定值,以后再调用时可传入比较少的参数。  

 

 

防止递归溢出的方法

 
Js代码  收藏代码

    var bounce=function(recursion){  
        for(;recursion.constructor===Function;  
               recursion=recursion());  
        return recursion;  
    };  
    var recursion=function(arg){  
        return function(){return arguments.callee(arg);};  
    };  
    bounce(recursion());  
    // 这是个无穷递归,然后被改造成了无限循环。  
    //chrome 递归层次最少只有 2000 多,所以可以增加一个递归计数器,在计数器达到 2000 时采用这种方式  

 

 

其它

•     hasOwnProperty 与 in 关键字

•     if(VAR_NAME in OBJ){} 会判断 VAR_NAME 是不是存在于 OBJ 的 prototype 链上

•     for(var n in obj){} 会遍历 obj 整个 prototype 继承链

•     为了判断属性名是 obj 自身的属性还是 prototype 的属性应该使用 obj.hasOwnProperty(n)

•     propertyIsEnumerable 表示属性名是不是可枚举的

•     如果它返回 true ,在 for in 循环中就会被遍历到

•     如果要为一个对象声明的属性名是 js 保留字,应该这么做: foo[ ‘ class ’ ]= ‘ val ’ ;

 
Js代码  收藏代码

    function foo(a){// 可用这种方式指定默认值  
      a=a|| ’ defaultVal ’ ;  
    }  
    function foo(a){  
       var b=(a && a.constructor===Number && a*10)||0;  
       // 可用这种方式初始化变量,省去复杂的 if 判断  
    }  

 

 

事件模型

•     Js 是基于事件驱动的。包括 setTimeout 和 setInterval 也是

•     setInterval 会每隔指定时间触发事件,把 interval 函数置于事件队列尾,不论之前的 interval 函数有没有执行完或有没有执行

•     setTimeout 会在到达指定时间时触发且仅触发一次 timeout 函数

•     Ajax 会在响应结束后触发事件,并把响应函数置于事件响应队列尾

•     所有事件响应函数都在同一个事件队列里面依次执行

<!--[if !supportLists]-->•     <!--[endif]-->而事件队列属于界面线程

原生对象构造器

Function

•     Function 是所有函数对象的构造器。

•     实际上我们使用 function 关键字定义一个函数时 js 解释器调用的就是 new Function 创建一个新的函数对象

•     当然并不推荐在 js 编程实践中出现 new Function 这样的代码,一方面会降低代码可读性,另一方面会在加载时增加传输字节数,第三会降低 js 解析效率

•     至于第三条的原因,个人猜想应该是 new Function 会强迫 js 解释器在执行上下文中再次调用 js 语法分析器,这样当然不如直接使用 function(){} 声明,在 js 加载完成时一次分析完成所有代码构造出所有对象来的快。

Function.apply(argsArray.concat(codestring))。由于Function的最后一个参数才是函数体,前面的参数都是函数参数,有时如果不得不通过Function动态构造函数,而参数名又都保存在数组里,就可以用这种方式创建一个新函数。Function前面不需要用new。

Array

•     由于 Array 构造器的参数比较混乱,个人猜想应该是 js 解释器内建多个重载的 Array 构造器

•     new Array(1,2,3) <==>[1,2,3]

•     new Array(len) <==>[]//len 可以任意整数

•     new Array( ‘ 1 ’ ) <==>[ ‘ 1 ’ ]

•     建议任何时候都用数组字面量表示法定义数组

RegExp

•     alert('a12b5678'.replace(/\d+/g,'3$&4'));//$&引用被匹配的子串

•     /;$/.test('var a=1;');//浏览器总是从字符串开头逐字符匹配正则表达式直到字符串尾,在所有正则表达式分支都尝试之后匹配才会结束,所以这个正则表达式很慢

•    $1到$9会在replace的第二个参数中引用正则表达式匹配到的子串,$1匹配表达式中第一对括号匹配到的子串,依次类推

•    var s=''.replace(/^\s+/,'');

for(var i=s.length-1;i>=0&&/\s/.test(s.charAt(i));i--);

s=s.substring(0,i+1);

//与''.replace(/^\s+/,'').replace(/\s+$/,'')相比,前者在处理长字符串结尾短空白时更快,但是尾空白太长时并不比后者理想

//这是最快的两种去首尾空白方案,jquery使用后者,有些书上推荐前者

Boolean String Number 等

     value
	

     class
	

     typeof

      "foo"
	

     String    
	

       string

     new String("foo")
	

     String    
	

        object

     1.2
	

     Number    
	

       number

    new Number(1.2)
	

     Number    
	

       object

     true
	

     Boolean    
	

       boolean

    new Boolean(true)
	

     Boolean    
	

       object

    /abc/g
	

     RegExp    
	

       object (function in Nitro/V8)

   new RegExp("meow")
	

     RegExp    
	

       object (function in Nitro/V8)

•     每次用 new 创建 Number String Boolean 对象实际上又为实际值包装了一层引用,类似 java 的 new String(“”)

•     建议只把它们用作类型转换函数,比如:

Number(‘1’)

Boolean(‘true’)

由于 js 是弱引用类型,在进行计算时, js 解释器总是试图把同一表达式内不同类型的值进行自动类型转换, 而不会抛出错误

下面的列表足以说明类型转换的混乱,如果应用了这些特性,会增加调试与维护代码的难度,和出错的可能。

 
Js代码  收藏代码

    ""           ==   "0"           // false  
    0            ==   ""            // true  
    0            ==   "0"           // true  
    false        ==   "false"       // false  
    false        ==   "0"           // true  
    false        ==   undefined     // false  
    false        ==   null          // false  
    null         ==   undefined     // true  
    " \t\r\n"    ==   0             // true  
    ""           ===   "0"           // false  
    0            ===   ""            // false  
    0            ===   "0"           // false  
    false        ===   "false"       // false  
    false        ===   "0"           // false  
    false        ===   undefined     // false  
    false        ===   null          // false  
    null         ===   undefined     // false  
    " \t\r\n"    ===   0             // false  

 

 

=== !== 与 ==  != 就像孪生的兄弟姐妹,一半天使一半恶魔

有时我们需要显式类型转换,前面已经提到可用 Number Boolean 等进行显式转换下面还有一些类型转换方式

转化成数字 —— 不推荐使用

 
Js代码  收藏代码

    +'010' === 10  
    parseInt('010', 10) === 10  // 用来转换为整数   
    +'010.2' === 10.2  
    parseInt('010.2', 10) === 10  
    parseInt(‘1a’,10)===1;// 这是糟糕的特性  

 

 

转化字符串

 
Js代码  收藏代码

    ‘’ + 10 === ‘10’; // true  
    [‘aaa’,1234,true].join(‘’);// 连接字符串 ,ie 比较快  
    var repeatString=function(strToRepeat,repeat){return new Array(repeat+1).join(strToRepeat)};  
    // 可用这种方式构造重复 repeat 次的字符串,这是 new Array 的惟一用处   

 

 

  转化成 Boolean ,这种方式更快

 
Js代码  收藏代码

    !!‘foo’;   // true  
    !!‘’;      // false  
    !!‘0’;     // true  
    !!‘1’;     // true  
    !!‘-1’     // true  
    !!{};      // true  
    !!true;    // true   !!undefined ===false   !!null===false !!0===false !!1===true !!-1===true  
    !!function(){} //true  

 

关于类型转换的其它内容

这部分不好分类,就放在这里

 

对两个对象进行比较或四则运算时, 

如果是需要优先转化为数值的会调用对象的valueOf方法,如果是优先转换字符串的会调用对象的toString

其中之一是Date、RegExp、String会优先转换字符串 

Boolean与Number一起会转换成Number,Boolean与其它类型会优先转换字符串 

自定义对象可实现valueOf和toString完成自定义对象的默认类型转换方式 

 

分号

Js 解析器只能解释以分号结束的语句,如果没有加上分号解析器会尝试自己加上分号再解析。所以对于没有分号的 js 可能会出现不符合预期的结果。

为了降低调试难度,应该每条语句都以分号结束。

return 所在的行必须有分号,返回值不能在 returnr 的下一行,比如:

 
Js代码  收藏代码

    return  
    1;//unreachable  

 
Js代码  收藏代码

    return {A:1  
    };//valid  

 

 

eval setTimeout setInterval

eval 调用 js 解析器将字符串参数解析为 js 语句并执行,造成的性能影响与前面说过的 new Function 问题一样

setTimeout setInterval 的第一个参数既可以是 function 也可以是字符串,当传递字符串时也会发生同样的事情。

所以强烈不建议使用 eval ,也不要向 setTimeout 和 setInterval 传递字符串

良好编程规范

1. 不用 for in 遍历数组,因为 for in 会遍历原型对象

2. 不定义全局变量。

3. 每条语句以分号结束

4. 尽量不用保留字

5. 不用 typeof 和 instanceof

6. 不用 parseInt

7. 不使用 == 和 !=

8. 不使用 with 表达式

9. 不用 eval new Function

10. 不向 setTimeout setInterval 传递字符串

11. 少用 continue 会提升性能

12.for if else while 一律使用 {} 包含语句块

13. 使用 var fn=function(){}, 代替 function fn(){}

      因为后者会定义全局函数

14. 不使用 new String

     new Boolean

     new Number

     new Array

15. 尽量少用 callee caller

16. 尽量减少闭包层次和继承层次

17. 对于使用两次以上的对象属性和闭包变量,一定先使用局部变量取得属性值,减少对象属性访问次数

18. 如果必须使用全局对象,一定先用局部变量引用全局对象

19. 不要修改原生对象

生僻浏览器 dom api

1.Window.navigator// 浏览器与系统信息只读

2.document.getElementsByTagName//NodeList 对象,结果集会随着 dom 树的改变而改变

3.UserAgent ,浏览器内核与版本信息

4.attachEvent||addEventListener

   // 向 dom 对象添加事件响应函数

   detachEvent||removeEventListener

   // 从 dom 对象移除事件响应函数

5. 
Js代码  收藏代码

    var css=dom.currentStyle||window.getComputedStyle(dom,null);// 获取 css 文件定义的样式  
    var bind=function(dom,e,f){dom.attachEvent?dom.attachEvent(‘on’+e,f):dom.addEventListener(e,f,null);};  
    var unbind=function(dom,e,f){dom.detachEvent?dom.detachEvent(‘on’+e,f):dom.removeEventListener(e,f,null);};  
    var load=(function(fn){  
        return function(e){  
             e=e||window.event;  
            var tar=e.target||e.srcElement;  
            if(e.readyState || e.readyState===‘loaded’|| e.readyState===‘completed’){  
              unbind(tar,’load’,load);  
              unbind(tar,’readystatechange’,load);  
              fn(e);  
            }  
        };  
    })(function(){});  
    bind(dom,’load’,load);  
    bind(dom,’readystatechange’,load);  

6.  dom.cloneNode();//只复制dom

     dom.cloneNode(true);//dom的子节点一起复制  

 

Ie 内存泄漏

1. 循环引用
Js代码  收藏代码

    var myGlobalObject;  
    function SetupLeak() { // First set up the script scope to element reference  
          myGlobalObject = document.getElementById("LeakedDiv");  
        // Next set up the element to script scope reference  
        document.getElementById("LeakedDiv").expandoProperty = myGlobalObject;  
    }  
    //myGlobalObject 引用了 LeakedDiv,LeakedDiv 又引用了 myGlobalObject  
    function Encapsulator(element) { // Set up our element  
        this.elementReference = element; // Make our circular reference     
        element.expandoProperty = this;  
    }  
    function SetupLeak() { // The leak happens all at once  
        new Encapsulator(document.getElementById("LeakedDiv"));  
    }  
      function BreakLeak() {  
        document.getElementById("LeakedDiv").expandoProperty = null;  
    }  
    //Encapsulator 的对象引用了 element , element 又引用了 Encapsulator  

 

 

造成泄漏

2. 闭包

 
Js代码  收藏代码

    function AttachEvents(element) {  
    // This structure causes element to ref ClickEventHandler  
        element.attachEvent("onclick", ClickEventHandler);  
        function ClickEventHandler () { // This closure refs element }  
    }  
    function SetupLeak() { // The leak happens all at once    
        AttachEvents(document.getElementById("LeakedDiv"));  
    }  
      // ClickEventHandler 里面引用了 element , element onclick 事件又引用了 ClickEventHandler  
    // 这也是循环引用的一种  
    // 在事件响应函数内部应该使用 e.srcElement 取得事件触发对象  
    // 同时应该使用 attachEvent 绑定事件响应函数  

 

3. 添加子节点

 
Js代码  收藏代码

    function LeakMemory() {  
        var hostElement = document.getElementById("hostElement");  
        // Do it a lot, look at Task Manager for memory response  
        for(i = 0; i < 5000; i++) {  
           var parentDiv = document.createElement("<div onClick='foo()'>");  
           var childDiv = document.createElement("<div onClick='foo()'>");  
            // This will leak a temporary object  
            parentDiv.appendChild(childDiv);  
            hostElement.appendChild(parentDiv);  
            hostElement.removeChild(parentDiv);  
            parentDiv.removeChild(childDiv);  
            parentDiv = null;  
            childDiv = null;  
         }  
         hostElement = null;  
    }  
      // 先向父节点内添加子节点,最后把父节点添加到根会造成泄漏,把顺序反过来添加顺序就不会  

 

4.Dom 的 text 属性

 
Html代码  收藏代码

    <head>  
    function LeakMemory() { // Do it a lot, look at Task Manager for memory response  
         for(i = 0; i < 5000; i++) {  
           hostElement.text = "function foo() { }";  
        }  
    }  
    </head>  
    <body> <button onclick="LeakMemory()">Memory Leaking Insert</button>  
    <script id="hostElement">function foo() { }</script>  
      </body>  
    <!-- 不断修改一个 dom 的 text 属性会造成内存泄漏-->  

 

你可能感兴趣的:(JavaScript)