zoukankan      html  css  js  c++  java
  • javascript 学习笔记

    1.0, 概述。JavaScript是ECMAScript的实现之一

    2.0,在HTML中使用JavaScript.

    2.1 

    3.0,基本概念

    3.1,ECMAScript中的一切(变量,函数名,操作符)都是区分大小写的。

    3.2,

    3.3,

    3.4,

     

    3.5,

     

    typeof 用于基本类型的判别,instanceof用于引用类型(Object类型)的判别。

    3.6,

    3.7,

    3.8,Boolean类型有两个取值:true 和 false(区分大小写).使用Boolean()函数如下:注意,除null对象外,其他任何对象的布尔值都是true     n/a:not applicable

     

     Boolean(xxx) == !!xxx

    3.9.0,数值类型

    但16进制0x后面若超过了范围,则会报错。

     

    3.9.1,

    3.9.2,数值范围:

          Number.MIN_VALUE      Number.MAX_VALUE

          Number.NEGTIVE_INFINITY  Number.POSITIVE_INFINITY

          isFinite() 

    3.9.3,NaN (not a number)

        

     

    isNaN(); //

    3.13, 三个数值转换函数。Number() / parseInt() / parseFloat()。转型函数Number()可以用于任何数据类型,而另两个函数则专门用于把字符串转换成数值。

      Number():

      parseInt(str[, radix]):  在ECMAScript 5 JavaScript引擎中,parseInt()函数已经不具备解析八进制的能力。parsetInt('070');//返回70,忽略前导0。

      parseFloat(str): 没有第二个参数。始终忽略前导的0.

     3.14,String类型

     3.14.1,有任何区别。

    3.14.2,转义序列,用于表示非打印字符,

    x表示扩展的ascii码表字符,u表示unicode字符。unicode字符是兼容ascii码表的。如'u00ff'与'xff'都是字符  ÿ。

    3.14.3,转化为字符串方法,String()  /  obj.toString([radix])

      String()方法可以转换任何类型为字符型。

      obj.toString()方法使用于除Undefined和Null之外的其他四种类型(Number, String, Boolean,Object).其中如果是Number类型,此方法还可以指定一个参数: 输出数值的基数。

    3.15,Object类型

    【注】关于Object类型的两种方法toString()和valueOf()。在牵涉到比较或计算时会优先使用valueOf()方法,如果没有此方法才会调用toString()方法。而在字符串连接时则会首先调用toString()方法。

    3.15.1,ECMAScript中的 Object通用方法

    3.16,操作符

    3.16.1,一元操作符(++,--)

      前置的是先变化在计算,后置的是先计算在变化。如,

      var a=3;var b = ++a + 5;//先将a变化为4再计算,结果为b=9, a=4;

      var a=3;var b = a++ + 5;//先计算(用a的原值)再变化,结果为b=8,a=4

     

     

    3.16.2,关系操作符

      两个字符串比较大小时,比较首字母(根据结果推断的,不保证正确)的ascii码值。"Brisk" < 'alpha' ;//true;  "23" < "3";//true

      字符串与数字比较大小时,先将字符串转化为数值在比较大小。“23” < 3;//false

      NaN不等于任何数,包括它自己。NaN == NaN;//返回false;

      任何操作数与NaN进行大小比较时,结果都是false。 'a' < 3;//false;     'a'>=3;//false;    Number('a')=>NaN

      true和false在关系比较时,会先转化为1和0;因此,true==3;//false;  

      undefined和null在关系比较时不会转化为任何数。so, null==0;//false

      null == undefined; // true;  null===undefined;// false,类型不同;

    [新增:]

    除了null对象外,其他对象的所有布尔值都为true,例如空数组,空对象的布尔值都是true逻辑非操作符(!)遵循以下规则:

    相等操作符(==)遵循以下规则:

    例子:[] == true;//false. []是对象,调用valueOf()方法得不到基本类型,调用toString()方法得到基本类型空字符串,true转换为数字0,然后空字符串转换为数字0.最会是比较 0 == 1,返回false

    同理:[] == false;//true.

    [9] == 8;//false. [9]转换为字符串9,在转换为数字9.

    {name:'tong'} == true;//false

    {name:'tong'} == false;//false,解释如下:

    对象{name:'tong'}调用toString()方法得到“[object Object]", 然后这个字符串转换为数字NaN,而false转换为0,NaN == 0;//false

    3.17;

    ,.

    for (var i=1;i<10;i++){...}   alert(i) // 循环结束后,循环体外i的值为10(尽管i是在循环体内定义的).

    3,18;     类似goto语句的label语句:break和continue   与label一起使用时,表示break和continue是对lable指定的地方生效。

    3.19,javascript中独具特色的switch语句:

    PHP中switch语句在比较值时使用的是(==)而不是(===)

    4.0变量,作用域和内存问题

    4.1,javascript中的变量可以参考python,两者可以相同理解。JavaScript中将变量分为基本类型(Undefined,Null,Number,String,Boolean)和引用类型(Object)【引用类型的变量是指向内存对象的一个指针】,类似于python中的不可变类型(数字,字符串,元组等)和可变类型(列表,字典等)。

    参看自己总结的python变量和引用的关系。http://www.cnblogs.com/everest33Tong/p/6537583.html

    再次审视时,发现两者貌似有区别:python中的对象(无论可变类型还是不可变类型)貌似都是存储在堆内存中的[或者理解为,即使不可变类型是存储在栈内存中,但其行为和堆内存没有区别,在栈内存中复制也是复制指针而不像js中是复制副本],变量都可视为一个指向堆内存对象的一个指针,而JavaScript中基本类型是存储在栈内存中的,引用类型是存储在堆内存中的。栈内存复制值时是复制一个值的副本,而堆内存是复制一个指针!不是太确定这些是不是正确

    4.2,传递参数可以看作变量的复制。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制给另一个变量一样。

     

    4.3,延长作用域链:

    下面的一个例子的理解:with语句中定义的变量url是属于buildUrl()函数执行环境的,参见4.5

    4.4,JavaScript / php / python这三种脚本都没有块级作用域,都是函数作用域。

    在其他类C语言中,由花括号封闭的代码块都有自己的作用域(如果用ECMAScript的话来讲,就是它们自己的执行环境),因而支持根据条件来定义变量。例如,下面的代码在JavaScript中并不会得到想要的结果:

    <script>
        var a = 3;
        if (a == 3) {
            var color = 'red';
        }
        console.log(color);//red
    </script>

    这里是在一个if语句中定义了变量color.如果是在C,C++,或Java中,color会在if语句执行完毕后被销毁。但在JavaScript中,if语句中的变量声明会将变量添加到当前的执行环境(在这里是全局环境)中。在使用for语句时尤其要牢记这一差异,例如:

    <script>
        for (var i = 0; i < 10; i++) {
            doSomething(i);
        }
        alert(i);//10
    </script>

    对于有块级作用域的语言来说,for语句初始化变量的表达式所定义的变量,只会存在于循环的环境之中。而对于JavaScrip来说,由for创建的变量i即使在for循环结束之后,也依旧会存在于循环外部的执行环境中。

    4.5,声明变量

     4.6,垃圾收集 和 内存管理

    常用的垃圾收集机制有两种,

    1,标记清除。JavaScript中最常用的垃圾收集方式是标记清除(mark-and-sweep).当变量进入环境(例如,在函数中声明一个变量)时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量作占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当环境离开环境时,则将其标记为“离开环境”。可以使用任何方式来标记变量。比如,可以通过翻转某个特殊的位来记录一个变量何时进入环境或者用一个“进入环境的”变量列表及一个“离开环境的”变量列表来跟踪哪个变量发生了变化。说到底,如何标记变量其实并不重要,关键在于采取什么策略。垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,他会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后在被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些待标记的值并回收他们所占用的内存空间。大部分的浏览器的JavaScript都是使用的此种方式垃圾收集策略。

    2,引用计数。引用计数(reference counting)的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1.如果同一个值被赋给另一个变量,则该值的引用次数加1.相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1.当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。这中垃圾回收机制有一个严重的问题:循环引用。一旦有循环引用存在则永远不会回收这些变量。python脚本语言就是采用引用计数垃圾回收机制,python同时也会监视循环引用的变量,在合适的时机就会回收这些变量。对于循环引用问题,有一个手动解决的方法,就是在不再使用它们的时候手工断开它们之间的连接,即将它们的值设为null。将变量设置为null意味着切断变量与此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存。

    内存管理:优化内存占用的最佳方式,就是为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为null来释放其引用----这个做法叫做解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性。局部变量会在他们离开执行环境时自动被解除引用。如下面例子所示:

    不过解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收。 

    4.7,小结

     

    5.0,引用类型(Object,Array,Date, RegExp等等)

    5.1, 几个概念:var person = new Object();在此行代码中,Object是引用类型,person是引用类型的值,也叫对象。JavaScript中提供了很多原生的引用类型,如Object,Array,Date, RegExp引用类型等等。如果使用typeof Object会发现这是个"function",其实Object也是个构造函数,该函数是出于创建新对象的目的而定义的。

    Object.keys(obj)//取得js对象的键组成的数组,

    Object.values(obj)//取得js对象的值组成的数组.

    5.2,Array引用类型

          数组栈方法:array.push(xx)方法返回修改后数组的长度,array.pop()方法返回弹出的数组项。

          数组队列方法:array.unshift(xx)返回修改后数组的长度,array.shift()返回弹出的数组项。

          转为字符串方法:array.join("separator"): 将数组连接成字符串。array.toString() == array.join(',')

          重排序方法:array.reverse()反序排列,返回经过排序后的数组。    


     array.sort(),返回经过排序后的数组。

                1,如果没有参数,则元素按照ASCII字符顺序排序,即会对数组每个元素调用toString()方法。

                  例如,var arr = [0,1,5,10,15];则arr.sort()返回[0,1,10,15,5].

                2,sort(func)还可以接收一个比较函数,此函数接收两个参数,这两个参数代表数组中的任意两个项。如果省略sortFunction参数,元素将按ASCII字符顺序的升序进行排列。  关于sortFunction的说明:参考文章    http://www.cnblogs.com/longze/archive/2012/11/27/2791230.html

    【新增例子】如果arr.sort(function(a,b){return 1})则等同于arr.reverse(),即直接倒序排列。 
    例一:
      <script type="text/javascript"> var arrSimple2=new Array(1,8,7,6); arrSimple2.sort(function(a,b){ return b-a 或者 return a-b});//前者降序,后者升序 document.writeln(arrSimple2.join()); </script> 解释:a,b表示数组中的任意两个元素,若return > 0 b前a后;reutrn < 0 a前b后;return = 0时存在浏览器兼容. 简化一下:a-b输出从小到大排序,b-a输出从大到小排序。【a-b时,若a-b>0,由于return>0,b前a后,由于a>b,所以是升序】

    例二:
    // 根据元素转换为字符串后的字符长度进行升序排列
    function arraySortByLength(a, b){
        // 为便于用户理解,此处未考虑a或b为undefined或null的情况。
        var aStr = a.toString(), bStr = b.toString();
        // 如果是按照字符长度降序排序,则应该为bStr.length - aStr.length
        return aStr.length - bStr.length;
    }

    操作方法:array.concat()方法不改变原数组。

           array.slice(start[, end])方法不改变原数组。返回截取后的数组片段。[start,end)左闭右开.若start为负,则start=length + start;或者理解为最后一项为-1,往前依次为-2,-3...等等


    array.splice()方法可以用于 删除,插入,替换数组中的项。原始数组会改变,返回的始终是被删除的项组成的数组,如果没有被删除的项,则返回空数组具体用法如下

    ⊙删除 :可以删除任意数量的项,只需指定两个参数:要删除的第一项的位置和要删除的项数。例如,splice(0,2)会删除数组中的前两项。

    ⊙插入:可以向指定位置插入任意数量的项,只需提供3个参数:起始位置、0(要删除的项数)、和要插入的项。如果要插入多个项,可以在传入第四、第五,一直任意多个项。例如,splice(2,0,'red','green')会从当前数组的位置2开始插入字符串"red"和"green"。注意,原来位置2上的值会被挤到后面去。

    ⊙替换:可以向指定位置插入任意数量的项,且同时删除任意数量的项,只需指定3个参数:起始位置、要删除的项数和要插入的任意数量的项。插入的项数不必与删除的项数相等。例如,splice(2,1,'red','green')会删除当前数组位置2的项,然后再从位置2开始插入字符串"red"和"green"。


    位置方法: indexOf(needle[,start])      lastIndexOf(needle[,start]).

    这两个方法都接收两个参数:要查找的项和(可选的)表示查找起点位置的索引。都返回要查找的项在数组中的位置,或者在没有查找到的情况下返回-1.在比较第一个参数与数组中的每一项时,会使用全等操作符===。

    indexOf()当有第二个参数时,查找时从第二个参数指定的位置(包括)开始 从前往后 查询,返回查找到的第一个needle所在整个数组(或字符串)中的位置.

    lastIndexOf()当有第二个参数时,查找时从第二个参数指定的位置(包括)开始 从后往前 查询,返回查找到的第一个needle所在整个数组或字符串中的位置.


    迭代方法:如下 【every();filter();forEach();map();some()】


    说明:

    filter(func): 其中func是 用来测试数组的每个元素的函数。调用时使用参数 (element, index, array)  此函数返回true则表示保留该元素(通过测试),false则不保留。

    对于every()只要有一个值返回false就会停止进一步的动作,同样,对于some()只要有一个值返回true则会停止进一步的动作。forEach()方法本质上和使用for循环迭代数组一样。

    例子: var arr = [1,3,4,5]; var every = arr.every(function(value, index, array){return value > 0});//every返回true.


    缩小方法:array.reduce(callback[, first]); callback是回调函数,此回调函数接收四个参数(prev,cur,index,array),first可选,作为缩小基础的初始值。如果不存在first则数组的第一项作为基础值。index是cur的当前位置。函数的返回值会作为第一个参数传给下一项直至递归完毕。array.reduceRight()用法相同,只不过从后向前遍历。

    例子:var arr = [1,2,3,4,5]; var sum = arr.reduce(function(prev,cur,index,array){return prev+cur}, 10 );// 返回25;10+1+2+3+4+5=25;

    5.3,Date引用类型

    5.3.1, Date类型使用自UTC(Coordinated Universal Time,国际协调时间)1970年一月一日午夜零时开始经过的毫秒数来保存日期。能够精确到1970年一月一日午夜零时 之前或之后的285616年。

    5.3.2,一些用法:

    GMT:Greenwich Mean Time,格林威治标准时间。  本地时区GMT+0800(北京时间)

    var date = new Date();// 不传参数,新创建的对象自动获取当前日期和时间。时间基于本地时区GMT+0800(北京时间)。

    *********************************************************************************************************************************

    *********************************************************************************************************************************

    // 时间戳转换为 日期,这里是转换为的日期对象,如果要转化为日期字符串,则再调用date.toString() 或其他任意可以 转为日期字符串的方法【5.3.4中介绍的方法都可以】

    var date = new Date(3423);//传入毫秒数参数,则根据此特定的日期和时间创建日期对象。基于本地时区。

    // 日期字符串 转换为 时间戳

    Date.parse('日期字符串');//根据日期字符串返回毫秒数。日期字符串格式要求比较宽松。如果不符合日期字符串要求则返回NaN。基于本地时区。

    Date.now();//返回当前时间的毫秒数。

    *********************************************************************************************************************************

    *********************************************************************************************************************************

    var date = new Date('日期字符串');//会自动调用Date.parse(),相当于new Date(Date.parse('日期字符串'));基于本地时区。

    Date.UTC(year, month[,day,hour, minute, second,microsecond]);返回指定日期毫秒数。其中month以0表示一月份开始。日期和日期以后的参数都是可选。如,Date.UTC(2017,4,30,13,55,55);//GMT时间2017年5月30日下午1点55分55秒。注意:Date.UTC()这个时间是基于GMT的。也就是说,这个时间是北京时间30日下午9点55分55秒。

    var date = new Date(2017,4,30,13,55,55);//虽然模仿的Date.UTC()格式,但是这个时间是以本地时区为基准的。这个时间比Date.UTC(2017,4,30,13,55,55)表示的时间要早8个小时。

    5.3.3

    var date = new Date();//显示 Thu Jun 01 2017 20:07:12 GMT+0800 (中国标准时间),虽然date显示的类似字符串,但date是对象。

    date.toString();//   "Thu Jun 01 2017 20:22:12 GMT+0800 (中国标准时间)",date对象的字符串表示。

    date.toLocaleString();//  "2017/6/1 下午8:22:12"。

    date.valueOf();// 1496319732948。此方法返回毫秒数,是个数字。【php的time()返回的数字精确到秒】

    // 获取当前毫秒级时间戳

    date.valueOf() == Date.now() == +date; 

    把+[注:这里的+是一元操作符正号而非连接操作符加号]放在Date对象前时,等同于调用valueOf()方法。

    如,var date = +new Date();//返回当前毫秒数1496317509583。

    5.3.4,日期格式化方法:

    date.toDateString();// "Thu Jun 01 2017"

    date.toTimeString();//"20:22:12 GMT+0800 (中国标准时间)"

      //注:以上这两个方法合起来就是date.toString()的值

    =========================================

    date.toLocaleDateString();// "2017/6/1"

    date.toLocaleTimeString();// "下午8:22:12"

      //注:以上这两个方法合起来就是date.toLocaleString()的值。

    ===========================================

    date.toUTCString();//"Thu, 01 Jun 2017 12:22:12 GMT"。这是GMT时间,比北京时间早了8小时。

    date.toGMTString();//  全等于date.toUTCString();目的在于向后兼容,推荐使用date.toUTCString()方法。

    5.3.5,日期时间组件方法:如下图

     

    5.4,RegExp类型:正则表达式

    5.4.1,JavaScript中正则表达式有两种表达方式:字面量 和 RegExp构造函数[两者创建的都是对象]。

    字面量:

    var expression = /pattern/flags;  注意:这里的expression也是Object类型

    其中pattern部分可以是任何简单或复杂的正则表达式,flags标志有以下三种:

    ※g:表示全局(global)模式,即模式将被应用于所有字符串,而非在发现第一个匹配项时立即停止。

    ※i:表示不区分大小写(ignoreCase)模式

    ※m:表示多行(multiline)模式.即在达到一行文本末尾时还会继续查找下一行中是否存在与模式匹配的项。

    如var pattern = /.at/gi ;//匹配所有以‘at'结尾的3个字符的组合,不区分大小写。

    RegExp构造函数:

    var pattern = new RegExp(".at",'gi');//等同于字面量/.at/gi.

    注意点:RegExp构造函数的两个参数都是以字符串形式传进去的。所以如果涉及到需要转义的字符则需要双重转义。如,

    var pattern = new RegExp("\.at", 'gi');// 等同于字面量/.at/gi。可以利用pattern.source查看转化为字面量时的值。更多例子:

    regexp.toString();//   "/.at/g"   .无论regexp是字面量还是构造函数,返回字面量的字符串形式。

    regexp.toLocaleString();// 同上。

    regexp.valueOf();//返回regexp本身。 regexp.valueOf() === regexp。【对象比较是比较其所在的内存地址是否相同】

    5.4.2 RegExp实例属性

    RegExp的每个实例都具有下列属性:

    ※global: 布尔值,表示是否设置了g标志。

    ※ignoreCase: 布尔值,表示是否设置了i标志。

    ※lastIndex:整数,表示开始搜索下一个匹配项的字符位置,从0算起。

    ※source:正则表达式的字符串表示,按照字面量形式而非传入构造函数中的字符串模式返回。

    5.4.3,RegExp实例方法:

    var matches = regexp.exec(input);

    exec()接收一个参数,即要应用模式的字符串。

    返回值:没有匹配项的情况下返回null,有匹配项的情况下返回匹配项的数组,其中第一项是与整个模式匹配的字符串,其他项是与模式种的捕获组匹配的字符串。此数组有两个额外属性:index和input。index表示匹配项在字符串中的位置。而input表示应用正则表达式的字符串。

    注意点:对于exec()方法而言,即使在模式中设置了全局标志(g),它每次也只会返回一个匹配项。在不设置全局标志的情况下,在同一个字符串上多次调用exec()将始终返回第一个匹配项的信息。而在设置全局标志的情况下,每次调用exec()则都会在字符串中继续查找新匹配项。

    var bool = regexp.test(input);

    test()接收一个参数,即要应用模式的字符串。

    返回值:参数与模式匹配返回true; 否则,返回false。

    (下面论述对于无论是exec()方法还是test()方法都是这样的)在创建正则表达式对象时如果使用了“g”标识符或者设置它了的global属性值为ture时,那么新创建的正则表达式对象将使用模式对要将要匹配的字符串进行全局匹配。在全局匹配模式下可以对指定要查找的字符串执行多次匹配。每次匹配使用当前正则对象的lastIndex属性的值作为在目标字符串中开始查找的起始位置。lastIndex属性的初始值为0,找到匹配的项后lastIndex的值被重置为匹配内容的下一个字符在字符串中的位置索引,用来标识下次执行匹配时开始查找的位置,如果找不到匹配的项lastIndex的值会被设置为0。当没有设置正则对象的全局匹配标志时lastIndex属性的值始终为0,每次执行匹配仅查找字符串中第一个匹配的项。可以通下面的代码来查看在执行匹配相应的lastIndex 属性的值

    5.4.4,RegExp构造函数属性(类似静态属性):

    长属性名 短属性名 说明
    input $_ 最近一次要匹配的字符串
    lastMatch $& 最近一次的匹配项[与整个正则表达式匹配],类似$0,但没有这个表达
    lastParen $+ 最近一次匹配的捕获组
    leftContext $` input字符串中lastMatch之前的文本
    rightContext $' input字符串中lastMatch之后的文本
    multiline $* 布尔值。表示是否所有表达式都使用多行模式
    $1, $2,...$9 分别用于存储第一,第二......第九个匹配的捕获组

     

    注意:段属性名中除了$_, $1...$9是JavaScript中合法的标识符外,其他的都是不合法的标识符。所以必须用方括号访问。RegExp["$&"]

    5.4.5,JavaScript的正则表达式的局限性:

     

    5.5,Function 类型:每个函数都是Function类型的实例

    5.5.1,函数也是对象。函数名(也是个变量)同其他引用类型变量名一样,是指向真正对象的指针。

    5.5.2,函数有三种表达方式:

    ※函数声明:

    function sum(num1, num2){return num1+num2}

    ※函数表达式:

    var sum = function(num1, num2){return num1+num2};

    ※Function构造函数:[不推荐]

    var sum = new Function("num1","num2","return num1+num2");

    -------------------------------------------------------------------------------

    函数声明和函数表达式只有一个区别,除此之外完全等价。区别在于解释器在向执行环境中加载数据时:

    函数声明会在代码执行之前,通过一个名为函数声明提升(Function Declaration hoisting)的过程,读取并将函数声明添加到执行环境中。所以函数声明总是在源代码树的顶部。即使在函数声明之前调用函数也是没问题的。

    但是函数表达式只有在解释器执行到此处时才会加载函数表达式。所以在函数表达式之前调用函数会出错。

    5.5.3,作为值的函数。

    函数名本身就是变量,所以函数也可以用来作为值来使用(就像其他变量一样)。比如,可以像传递参数一样把一个函数传给另一个函数。又比如,可以将一个函数作为另一个函数的结果返回。

    5.5.4,函数内部属性。

    函数内部有两个特殊对象:arguments 和 this。

    ※arguments:用来保存传入函数中的所有参数,是个类数组对象(但它并不是Array的实例)。它有一个属性callee,该属性是个指针,指向拥有这个arguments对象的函数(对象)。

            例子:function factorial(num){

                  if (num <= 1){

                     return 1;

                  } else {

                    return num * arguments.callee(num -1); // 如果用num * facotorial(num-1),则就和函数名factorial高度耦合了。

                  }

                }

    -----------------------------------

    ※this:this对象引用的是函数据以执行的环境对象。当在网页的全局作用域中调用函数时,this对象引用的就是window对象。

    例子一:

    var color = 'red';

    function sayColor(){console.log(this);}

    sayColor();//this指的是window对象。在全局作用域中调用函数sayColor相当于window.sayColor();自然this指的就是window对象。

    例子二:

    var o = {name:'tong'};

    o.sayHi = function(){console.log(this);} 

    o.sayHi();//this指对象o,即Object {name:'tong',sayHi: function}

    -------------------------

    ※函数对象的属性:caller。这个属性保存着调用当前函数 的函数的引用。如果是在全局作用域中调用当前函数,则caller的值为null。例如,

    function outer(){inner();}

    function inner(){console.log(inner.caller);}

    outer();//会打印出function outer(){inner();},即outer函数的源码。因为outer函数调用了inner函数,所以inner.caller属性就指向outer函数。

    5.5.5,函数(对象)属性和方法

    ※length属性:length属性表示函数希望接收的命名参数的个数。   [函数名.length]

    ※prototype属性:后面详解,此属性是不可枚举的,无法使用for-in遍历。

    ----------------------------------------------------

    ※apply()方法:作用是指定函数的作用域,即在特定的作用域中调用函数,实际上等于设置函数体内this对象的值。apply()方法接收两个可选参数:第一个是在其中运行函数的作用域(如需跳过此参数可设为null,下同),第二个是参数数组,可以是array数组,也可以是arguments对象。

    ※call()方法:作用和apply()相同。不同之处在于接收参数的方式(也是两个可选参数)。call()方法第一个参数是this值没有变化,变化的是其余参数都直接传给函数,即逐个列举出来。

    注意一点,在非严格模式下,使用函数的apply()或call()方法时,null或undefined值会被转换为全局对象。而在严格模式下就不会被转换。

    这两个方法可以扩充函数赖以运行的作用域。例如:

    var color = 'red';

    var o = {color:'blue'};

    function sayColor(){console.log(this.color)}

    sayColor(); // red

    sayColor.call(this);//red

    sayColor.call(window);//red

    sayColor.call(o);//blue

    ----------------------------------------------

    ※bind(thisArg[,arg1,arg2,...])方法:这个方法返回一个改变了this值的[即函数的作用域]函数。第一个参数作为this,第二个及以后的参数则作为函数的参数调用。例子如下:

    var color = 'red';

    var o = {color:'blue'};

    function sayColor(){console.log(this.color);}

    var newSayColor = sayColor.bind(o);//在sayColor函数的基础上创建一个新的函数,新函数的this值改变为o,将此函数赋给变量newSayColor。

    newSayColor();//调用新函数,打印blue。

    其他一些应用可以参考:http://www.cnblogs.com/xxxxBW/p/4914567.html

    着重提一下此篇文章中的一点:使用bind()方法使函数拥有预设的初始参数,这些参数会排在最前面,传给绑定函数的参数会跟在它们后面。

    --------------------------------------------------------

    函数的继承方法toString(),toLocaleString(),valueOf()方法都是始终返回函数的源代码。

     

    5.6,基本包装类型(即3个特殊的引用类型:Boolean, Number, String)

    5.6.1,基本类型的值不是对象,因而从逻辑上讲它们不应该有方法,但是JavaScript为基本类型创建了对应的基本包装类型的对象。实际上,每当读取一个基本类型的值的时候,后台就会创建一个对应的基本包装类型的对象,从而让我们能够调用一些方法来操作这些数据。例如:

    var s1 = "some text";

    var s2 = s1.substring(2);

    第二行访问s1时,访问过程处于一种读取模式。读取模式时,后台都会自动完成下列处理:

      ※创建String类型的一个实例;var s1 = new String('some text');

      ※在实例上调用指定的方法;   var s2 = s1.substring(2);

      ※销毁这个实例。      s1 = null;

    5.6.2,引用类型与基本包装类型的主要区别就是对象的生存期。使用new操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。而自动创建的基本包装类型的对象,则只存在于一行代码的执行瞬间,然后立即被销毁。这意味着我们不能在运行时为基本类型值添加属性和方法。例子如下:

    var s1 = 'some text';

    s1.color = 'red';

    console.log(s1.color); // undefined

    第三行代码会打印出undefined。原因在于第二行创建的String对象在执行第三行代码时已经被销毁了。第三行代码又创建自己的String对象,而该对象没有color属性。当然如果用var s1 = new String('some text')就不会出现这种情况。注意,使用new调用基本包装类型的构造函数与直接调用同名的转型函数是不一样的。如:

    var value = '25';

    var number = Number(value);//转型函数

    console.log(typeof number); //"number";    基本类型值

    var obj = new Number(value);//构造函数;

    console.log(typeof obj);// "object";             Number的实例对象。

    5.6.3,Object构造函数会像工厂方法一样,根据传入值的类型返回相应基本包装类型的实例。如:

    var obj = new Object("some text"); 等同于 var obj = new String("some text");

    5.6.4,基本包装类型之 Boolean类型:

      ※除了null对象之外的任何对象在进行逻辑运算时都会转化为true。所以:

        var b = new Boolean(false);//创建了一个值为false的Boolean对象,即b.valueOf() = false;

        b && true; // 返回true.  因为Boolean(b)是true.

      ※基本类型的布尔值不是Boolean类型的实例。即:

        true instanceof Boolean;//false

        b instance of Boolean; // true

    5.6.5,基本包装类型之Number类型:

      ※var num = new Number(10);

      num.toString(base); //将num转化为base进制的字符串形式。

      num.toFixed(小数位位数);//按照指定的小数位返回数值的字符串表示。能够自动舍入,适合处理货币

      num.toExponential(小数位位数);//返回以指数表示法表示的数字的字符串形式。参数指定输出结果中的小数位数。如"1.00e+1"

      num.toPrecision(所有数字的位数,不包括指数部分);//此方法会根据要处理的数值决定是调用toFixed()还是调用toExponential()。如,

        var num = 99;

        num.toPrecision(1); // "1e+2"

        num.toPrecision(2);// "99"

        num.toPrecison(3);// "99.0"

      ※Number对象是Number类型的实例,而基本类型的数值则不是。

        var numObj = new Number(10);

        var numVal = 10;

        numObj instanceof Number;//true

        numVal instanceof Number;//false

    5.6.6,基本包装类型之String类型:

      ※var str = new String('中国');

        str.length;//2

      ※字符方法:

        str.charAt(pos);//返回给定位置的字符

        str.charCodeAt(pos);//返回给定位置的字符编码(unicode码)

        str[pos];//数组索引法

      ※字符串操作方法:

        str.concat(任意多个参数);//用于将一个或多个字符串拼接起来。返回拼接得到的新字符串。原字符串保持不变。【+号操作符连接字符串更常用】

        str.slice(start[, end]);//①返回子字符串[start, end).左闭右开。原字符串不变。②若参数为负数,则规则为:最后一位为-1,往前为-2,-3,....;③如果start > end,结果为空。

        str.substring(start[, end]);//①返回子字符串[start, end).左闭右开。原字符串不变。②若参数为负数,则会自动被转化为0;③如果start > end,则两个参数位置调换一下。

        str.substr(start[, length]); //①返回子字符串,起始位置+长度。原字符串不变。②若参数为负,第一个会按照最后一位为-1的方式处理,第二个参数被转换为0。

      ※字符串位置方法:

        str.indexOf(needle[, 开始搜索位置]);//从前往后查找子字符串第一次出现的位置。如果没找到则返回-1。若传入第二个参数,则从指定位置(包括此位置)开始向后搜索而忽略之前的所有字符。

        str.lastIndexOf(needle[, 开始搜索位置]);//从后往前查找子字符串第一次出现的位置。如果没找到则返回-1。若传入第二个参数,则从指定位置(包括此位置)开始向前搜索而忽略之后的所有字符。

      ※trim()方法:

        str.trim();//无参数。返回删除了 前置和后置所有空格 后的字符串。原字符串保持不变。

        str.trimLeft();

        str.trimRight();

      ※字符串大小写转化方法:

        str.toLowerCase();//无参数。

        str.toUpperCase();//无参数。

        str.toLocaleUpperCase();//针对地区

        str.toLocaleLowerCase();//针对地区

      ※字符串模式匹配方法:

        str.match(正则表达式对象,可以是字面量形式或RegExp对象形式);//此方法和regexp.exec(text)方法本质上是一样的。返回结果也一样,返回一个数组,第一项为与整个模式匹配的字符串,之后的每一项为与正则表达式中的捕获组匹配的字符串。注意:如果正则表达式带有全局标志g,则返回的数组只包含所有的整个模式匹配项,而不包括捕获组。

        str.search(正则表达式对象,可以是字面量形式或RegExp对象形式);//返回字符串中第一个匹配项的索引,如果没有找到,返回-1。可以视为加强版的str.indexOf()方法。

        str.replace(arg1, arg2);//这个方法总的目标是把第一个参数指定的子字符串替换为第二个参数指定的字符串。返回新的字符串,原字符串保持不变。用法比较复杂,详述如下:

          ①,arg1可以是字符串或RegExp对象。当此参数是RegExp对象并且带有全局标志g时,会替换所有的字符串。否则(其他所有情况)只会替换查到的第一个字符串。

          ②,arg2可以是字符串或是一个函数。

              ※当arg2是字符串时,可以用正则表达式中的一些表达,表格如下: 

     

    字符序列 替换文本
    $$ $
    $& 匹配整个模式的子字符串。和RegExp.lastMatch值相同
    $' 匹配的子字符串之后的子字符串。和RegExp.rightContext值相同
    $` 匹配的子字符串之前的子字符串。和RegExp.leftContext值相同
    $n 匹配第n个捕获组的子字符串,n为0-9.如果没有捕获,则使用空字符串
    $nn 匹配第nn个捕获组的子字符串,n为01-99。如果没有捕获,则使用空字符串

                例如:

                var text = “cat, bat, sat, fat”;
                result = text.replace(/(.at)/g, “word ($1)”); //带有全局标志g,内部会使用循环。
                console.log(result); //word (cat), word (bat), word (sat), word (fat)

             ※arg2是函数时,这个函数应该返回一个字符串。此函数接收的参数个数与第一个参数中是否有捕获组有关。如果没有捕获组,此函数接收3个参数:整个模式的匹配项,匹配项在字符串中           的位置,原始字符串。如果有捕获组,则第一个参数是整个模式的匹配组,接下来的分别是各个捕获组,最后两个参数仍然是整个模式的匹配项在字符串中的位置和原始字符串。使用函数可          以实现更加精细的操作。

                例如:

        function htmlEscape(text) {
            return text.replace(/[<>"&]/g, function (match, pos, originalText) {
                switch (match) {
                    case "<":
                        return "&lt;";
                    case ">":
                        return "&gt;";
                    case "&":
                        return "&amp;";
                    case """:
                        return "&quot;";
                }
            });
        }
        console.log(htmlEscape("<p class="greeting">Hello world!</p>"));
        //"&lt;p class=&quot;greeting&quot;&gt;Hello world!&lt;/p&gt";
    View Code

     

        str.split(separator[,limit]);//将字符串分割成数组。第一个参数是分隔符,可以是字符或者RegExp对象。第二个可选参数用于指定返回的数组大小。 

        str.localeCompare(string);//如果str在字母表中排在参数string字符串之前,则返回负数(通常是-1),如果相等返回0,之后则返回1。

        静态方法String.fromCharCode(num1,num2,....);//此方法接收一个或多个字符编码,然后把它们转化为字符串。

     

    5.7,单体内置对象(Global对象和Math对象):在所有代码执行之前,作用域中就已经存在两个内置对象:Global和Math。在大多数的ECMAScript实现中(JavaScript是其实现之一)都不能直接访问Global对象。不过Web浏览器实现了承担该角色的window对象[window对象的功能不止于此]

    5.7.1,Object,Array,String等等都属于内置对象,JavaScript还定义了两个单独的内置对象:Global和Math对象。

    5.7.2,Global对象:

    Global对象比较特殊,从表面上看,它是不存在的。其实,在ECMAScript中,不属于其他任何对象的属性和方法,最终都是属于Global对象的属性和方法。如isNaN(), isFinite(), parseInt(), parseFloat()等等都是Global对象的方法。下面介绍一些Global对象的其他方法。

    ※URI编码方法(Uniform Resource Identifiers通用资源标识符)

    encodeURI(uri);//对uri进行编码。此方法不会编码uri本身的特殊字符,如冒号,正斜杠,问号和井号,具体有:【;/?:@&=+$,#】。

    encodeURIComponent(uri);//对uri进行编码。此方法会编码uri本身的特殊字符。

    注:以下这些字符  【-_.!~*'()】  两个方法都不会编码,它们和 字母与数字一样。

    decodeURI(编码后的uri);//只能对encodeURI()编码的uri进行解码。

    decodeURIComponent(编码后的uri);//可以解码任何字符

    注:这四个URI方法用来敌对代替老版的escape()和unescape()方法。老版的方法只能用于ascii字符,所以被废弃。

    ※eval()方法

    此方法接收一个字符串参数。然后将此字符串作为ECMAScript语句来解析。如:

    var a = 'hello world';

    eval("alert(a)");//相当于alert(a);

    ※Global对象的属性:

     ※ECMAScript虽然没有指出如何直接访问Global对象,但Web浏览器都是将这个全局对象作为window对象的一部分加以实现。因此,在全局作用域中声明的左右变量和函数,就都成了window对象的属性。

    注意,在JavaScritp中,window对象除了扮演ECMAScript规定的Global对象的角色外,还承担了很多别的任务。

    另一种取得Global对象的方法是使用以下代码(ECMAScript方法):

    var global = function(){return this;}();//创建了一个立即调用的函数表达式,返回this的值。在没有给函数明确指定this值(指定方法有:把函数添加为对象方法,调用apply()或call()方法)的情况下,this的值就等于Global对象。

    5.7.3,Math对象

    ※Math对象的属性:

    ※Math.min()和Math.max():

    这两个方法接收任意多个数值参数,求取最小和最大值。如Math.max(1,3,4,5,9);//9

    如果要找到数组中的最大值和最小值,可以像小面这样使用apply方法:Math.max.apply(Math, [1,2,3,4]);//4。把max函数的this值指定为Math[其实设为null也行]

    ※舍入方法:将小数值舍入为整数

    Math.ceil(float);//天花板舍入,总是往上舍入

    Math.floor(float);//地板舍入,总是往下舍入

    Math.round(float);//四舍五入

    ※Math.random();//无参数,返回介于0和1之间一个随机数,不包括0和1。要得到介于[m,n]之间的任意一个整数(包括m和n),有公式如下:

    var value = Math.floor( Math.random() * (n-m+1) + m );如介于[5,11]之间的随机整数:Math.floor(Math.random()*7+5);

    ※Math对象的其他方法

    方法 说明 方法 说明
    Math.abs(num) 返回num的绝对值 Math.asin(x) 返回x的反正弦值
    Math.exp(num) 返回Math.E的num次幂 Math.atan(x) 返回x的反正切值
    Math.log(num) 返回num的自然对数 Math.atan2(y,x) 返回y/x的反正切值
    Math.pow(num,power) 返回num的power次幂 Maht.cos(x) 返回x的余弦值
    Math.sqrt(num) 返回num的平方根 Math.sin(x) 返回x的正弦值
    Math.acos(x) 返回x的反余弦值 Math.tan(x) 返回x的正切值

     

     

    6.0:面向对象的程序设计

    面向对象(Object-Oriented,OO)的语言有一个标志,那就是它们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象。在ECMAScirpt中没有类的概念,因此它的对象也与基于类的语言中的对象有所不同。ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。正因为这样,我们可以把ECMAScript的对象想象成散列表(又叫hash table) :无非就是一组名值对,其中值可以是数据或函数。

    6.1理解对象:

    6.1.1,创建自定义对象的两种方法:

    ①,创建一个Object的实例,然后再为它添加属性和方法。

    ②,直接创建对象字面量。

    6.1.2,对象的特性

    ECMAScript中的对象属性可以分为两种(各种各样的属性分为两类):数据属性和访问器属性。每种属性都有一些特性,这些特性是为了实现JavaScript引擎用的,是"内部的",因而在JavaScript中不能直接访问它们。具体解释如下:

    ※数据属性

    数据属性包含一个数据值的位置。在这个位置可以读取和写入值。数据属性有4个描述其行为的特性:

    ①,configurable:表示1,能否通过delete删除属性从而重新定义属性,2,能否修改属性的特性,3,能否把属性修改为访问器属性。此特性的默认值为true.

    ②,enumerable:表示能否通过for-in循环返回属性。此特性的默认值为true。

    ③,writable:表示能否修改属性的值。此特性的默认值为true。

    ④,value:表示这个属性的数据值。读取属性值的时候,从这个位置读;写入属性值的时候,把新值保存在这个位置。此特性的默认值为undefined。

    例如:var person = {name:"Tong"};//这里创建了一个person对象,它有一个数据属性name,此属性的configuable,enumerable,writable特性都被设置为true,而其value特性被设置为“Tong”。而对这个值的任何修改都将被反映在这个位置。

    --------------------------------------------------------------

    要修改数据属性默认的特性,必须使用ECMAScript5的Object.defineProperty(obj, property, descriptor)方法。此方法接收三个参数:属性所在的对象,属性名称(字符串形式)和一个描述符对象。其中描述符的属性名称必须是configurable,enumerable,writable,value中的一个或多个。例如:

        var person = new Object();
        Object.defineProperty(person, 'name', {
            writable: false, //设置name属性不可改写,即name属性是只读的。
            value: "tong"
        });
        console.log(person.name);//"tong"
        person.name = "Gerrard";//尝试改写,没有效果。如果在严格模式下,会抛出错误。
        console.log(person.name);//"tong"
    View Code

    如果是把configurable设置为false,则①,对属性调用delete[即delete person.name]在非严格模式下什么都不会发生,在严格模式下会导致错误。②一旦把属性定义为不可配置(false)的,就不能再把它变回可配置的了。此时,如果再调用Object.defineProperty()方法修改特性,都会导致错误。

    在调用Object.defineProperty()方法时,如果不指定,configurable,enumerable,writable特性的默认值都是false。

    ※访问器属性

    访问器属性不包含数据值;它们包含一对get和set函数(不过,这两个函数都不是必需的) 。读取访问器属性时,会调用get函数,在写入访问器属性时,会调用set函数。访问器属性有如下4个特性:

    ①,configurable:表示1,能否通过delete删除属性从而重新定义属性,2,能否修改属性的特性,3,能否把属性修改为数据属性。默认值为true.

    ②,enumerable:表示能否通过for-in循环返回属性。

    ③,get:在读取属性时调用的函数。默认值为undefined.

    ④,set:在写入属性时调用的函数。默认值为undefined.

    和数据属性可以在对象中直接定义不同,访问器属性不能直接定义,只能使用Object.defineProperty()来定义。例如:

        var book = {
            _year: 2004,//year前的_是一种常用的记号,用于表示只能通过对象方法访问的属性[技术上可以访问,但约定不在对象方法之外访问]。
            edition: 1
        };
        Object.defineProperty(book, "year", {
            /**
             *访问器属性year包含一个get和一个set函数,get函数返回_year的值,set函数通过计算确定正确的版本。
             *这是使用访问器属性的常见方式,即设置一个属性的值会的导致其他属性发生变化。
             */
            get: function () {
                return this._year; // this即指book对象
            },
            set: function (newValue) {
                if (newValue > 2004) {
                    this._year = newValue;
                    this.edition += newValue - 2004;
                }
            }
        });
        book.year = 2005;
        console.log(book.edition);//2
    View Code

    访问器属性不一定同时指定get和set函数。如果只指定get函数意味着此访问器属只能读不能写(写操作会被忽略),而没有get函数则此属性亦不可读(返回undefined)。

     -----------------------------------------------------------------------------------------------------

    另外,还可以一次定义多个属性的特性,利用Object.defineProperties(obj, descriptor);//接收两个参数,第一个是属性所在对象,第二个是由各个属性组成的对象。例如:

     

        var book = {};
        Object.defineProperties(book, {
            _year: {
                value:2004
            },
            edition: {
                value: 1
            },
            year: {
                get: function () {
                    return this._year;
                },
                set: function (newValue) {
                    if (newValue > 2004) {
                        this._year = newValue;
                        this.edition += newValue - 2004;
                    }
                }
            }
        });
    View Code

    -----------------------------------------------------------------------------------------------------------

    读取某个属性的特性:Object.getOwnPropertyDescriptor(obj, property);//接收两个参数,第一个是属性所在对象,第二个是属性的字符串名称。返回一个对象。例如:

    var book = {name:'xxx'};

    var desc = Object.getOwnPropertyDescriptor(book, 'name');//name是个数据属性,返回对象Object {value: "xxx", writable: true, enumerable: true, configurable: true}

     

    6.2, 创建对象 

    6.2.1,使用Object构造函数或对象字面量都可以创建对象,但其缺点很明显:使用同一个接口创建多个对象,会产生大量重复的代码!

    6.2.2,工厂模式:在ECMAScript中没有类的概念,工厂模式用函数来封装创建对象的细节。例如:

        function createPerson(name, age, job) {
            var o = new Object();
            o.name = name;
            o.age = age;
            o.job = job;
            o.sayName = function () {
                alert(this.name);
            };
            return o
        }
        var person1 = createPerson("Nicholas", 29, "Software Engineer");
        var person2 = createPerson("Greg", 27, "Doctor");
    View Code

    工厂模式的优点:创建多个相似对象时,代码不会有大量的重复。

    工厂模式的缺点:对象识别的问题,即怎么知道一个对象的类型。上面创建的person1 和person2都属于Object对象,但无法进一步知道其是Person对象还是Man对象等等。

     

    6.2.3,构造函数模式:像Object和Array,都属于原生构造函数。我们也可以创建自定义的构造函数,从而自定义对象类型的属性和方法,例如: 

        /**
         * 构造函数模式
         */
        function Person(name, age, job) {
            this.colors = ['red','blue'];
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = function () {
                alert(this.name);
            };
        }
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        var person2 = new Person("Greg", 27, "Doctor");
    View Code

    关于此构造函数,注意:

    ※构造函数名首字母大写,以区别普通函数。这同其他OO语言类似。

    ※没有显式创建对象,而是将属性和方法直接赋给了this对象,没有return语句。

    ※创建Person的新实例必须使用new操作符。以new操作符调用构造函数时,解析器会经历如下步骤:

      ①,创建一个新对象, var tong = {};

        将该对象内置的原型对象设置为构造函数prototype引用的那个原型对象(每个函数在创建时就会自动获得一个prototype属性,指向原型对象)。

      ②,将构造函数的作用域赋给(或者说设为)新对象(因此this就指向了这个新对象),即Person.call(tong);

      ③,执行构造函数中的代码(为这个新对象添加属性和方法)

      ④,返回这个新对象【返回的这个对象即是实例对象】。对象建立之后,对象上的任何访问和操作都只与对象自身及其原型链上的那串对象有关,与构造函数在扯不上关系了。换句话说,构造函数只是在创建对象时起到介绍原型对象和初始化对象两个作用。

    ※构造函数和普通函数并没有实质区别。如果不用new操作符调用Person(),它就和普通函数一样,当在全局作用域中调用一个函数时,this对象总是指向Global对象(在浏览器中就是window对象),所以它的方法和属性都被添加给了window对象了。

    ※Person的实例对象,如例子中的person1和person2,有一个constructor(构造函数)属性,该属性指向Person(即指向实例的构造函数)。即person1.constructor == Person[实际上constructor是原型的属性].

    ※ constructor属性本来是用来检测对象类型的,但是 使用instanof 操作符 检测对象类型更好:

      person1 instanceof Object;//true

      person1 instanceof Person;//true

    ※ 构造函数的优点和缺点:

    优点:解决了工厂模式的缺点,可以识别对象的具体类型

    缺点:它的每个成员都无法得到复用,包括函数(方法)。即每实例化一个实例对象时,这个实例对象中的属性和方法都是独立的,比如上个例子中:对于基本属性,如name,job,age等,两个实例person1和person2实际上各保存一个副本(栈内存)。对于引用类型属性以及方法属性,如colors属性和sayName方法,虽然看似一样,但实际上两个实例保存的也是不同的对象。可以用如下代码检测:

    person1.colors == person2.colors;//返回false;

    person1.sayName == person2.sayName;//返回false;

    对于属性来讲,这不算是构造函数模式的缺点,因为毕竟每个实例都需要有自己独特的属性(实际上这是构造函数模式的优点,结合后面的原型模式就知道了),但是对于方法属性而言,这就是缺点了,因为创建两个完成同样任务的方法对象(Function实例)的确是没有必要的,浪费空间,多个实例间应该共享方法。对于这个问题有一个不好的解决方法:即把方法单独拿出来,即如下所示:

        /**
         *解决构造函数模式的方法属性不能共享的方法
         */
        function Person(name, age, job) {
            this.name = name;
            this.age = age;
            this.job = job;
            this.sayName = sayName;
        }
        function sayName() {
            alert(this.name);
        }
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        var person2 = new Person("Greg", 27, "Doctor");
    View Code

    这样:person1.sayName == person2.sayName;//返回true。

    但是这样同样会有问题:sayName这个方法定义在了全局作用域中,但实际上它只是为Person构造函数服务的,这让全局作用域有点名不副实。更让人无法接受的是,如果构造函数需要很多方法就要定义很多全局函数,于是我们的这个自定义引用类型就毫无封装性可言了。

    6.2.4,原型模式

    ※ECMAScript中创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象。这个对象是(叫做)通过调用构造函数而创建的实例对象的原型对象。原型对象的用处是让所有实例对象共享它所包含的属性和方法。共享原理后面有叙述。

    ※关于构造函数本身Person、构造函数(同时也是实例对象的)原型对象Person Prototype、实例对象person1的关系如下图所示:

        /**
         * 原型模式
         */
        function Person() {
        }
        Person.prototype.colors = ['red', 'blue'];
        Person.prototype.name = "Nicholas";
        Person.prototype.age = 29;
        Person.prototype.job = "Software Engineer";
        Person.prototype.sayName = function () {
            /**
             * 注意这里的 this,如果是person1.sayName();则this指的是person1(实例对象),而不是Person.prototype(原型对象)。
             * 如果Person.prototype.sayName()则this指的是Person.prototype。
             */
            console.log(this);
            console.log(this.name);
        };
    
        var person1 = new Person();
        console.log(Object.getOwnPropertyNames(person1));//[]
        console.log(Object.getOwnPropertyNames(Person.prototype));//["constructor", "colors", "name", "age", "job", "sayName"]
        person1.sayName();//this == person1
        Person.prototype.sayName();//this == Person.prototype
        console.log(Person.prototype.constructor == person1.constructor);//true
        var person2 = new Person();
        person2.sayName();//this == person2
        console.log(person1.sayName == person2.sayName);//true
        console.log(person1 == Person.prototype);//false
    View Code

    图形表示如下:

    创建构造函数的同时,其原型对象会自动获得一个constructor属性。实例中有一个指向原型对象的内部指针[[Prototype]]。

    三个对象Person,Person Prototype, person1的互相联系:

    Person.prototype == (Person Prototype);

    (Person Prototype).constuctor == Person;

    person1.constructor == Person;//这个其实是原型对象的属性,但是原型对象的属性和方法可以共享给实例对象。

    person1.__proto__ == (Person Prototype);//不是所有浏览器都支持

    Object.getPrototypeOf(person1) == (Person Prototype);//Object的方法,ECMAScript5新增。

    (Person Prototype).isPrototypeOf(person1);//true,原型对象的方法。

    共享原理:每当代码读取某个对象的属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从实例对象本身开始,如果找到则返回该属性的值并停止搜索;如果没找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到这个属性,则返回该属性的值。这就是多个对象实例共享原型对象所保存的属性和方法的基本原理[后面继承还会扩展搜索步骤]。

    ※ 实例对象属性会覆盖原型对象属性

        var person1 = new Person();
        console.log(person1.name);//Nicholas
        person1.name = 'tong';//实例对象属性会覆盖原型对象中的属性。
        console.log(person1.name);//tong
        person1.name = null;
        console.log(person1.name);//null
        delete person1.name;//delete可以恢复实例对象与原型对象属性的连接。
        console.log(person1.name);//Nicholas
    View Code

    ※hasOwnProperty()方法:用于判定某个属性属于实例对象还是原型对象,例如:

        var person1 = new Person();
        console.log(Person.prototype.hasOwnProperty("name"));//true
        console.log(person1.hasOwnProperty('name'));//false
        person1.name = 'tong';
        console.log(Person.prototype.hasOwnProperty("name"));//true
        console.log(person1.hasOwnProperty('name'));//true
    View Code

    ※in操作符:

    in操作符有两种用法:单独使用和for-in循环中使用。

    单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例中还是原型中,还包括继承的属性。所以同时使用 in操作符和hasOwnProperty()方法可以确定属性是不是在原型对象中。!object.hasOwnProperty(name) && (name in object);

    for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,无论属性是在实例对象还是在原型对象中。另外有规定:所有开发人员定义的属性都是可枚举的。

    注意:以上两条都是对实例对象用in操作符,如 “name“ in person1, for(var i in person1){}等等,如果对原型对象用in操作符,则只会算在原型对象中的属性,不会算在实例对象中的属性。

    ※Object.keys()方法:接收一个对象作为参数,取得对象上所有可枚举的属性,这个方法区分实例对象和原型对象,即实例对象只返回实例对象中的属性,原型对象值返回原型对象中的属性。

    ※Object.getOwnPropertyNames()方法:接受一个对象作为参数,取得对象上所有的属性,无论是否可枚举(这是和Object.key()方法的唯一区别)。同样区分实例对象和原型对象,注意理解Own的含义。

    ※用对象字面量表示对象原型,如此就不必没添加一个属性和方法就要敲一遍Person.prototype了,代码如下:

        function Person() {
        }
        Person.prototype = {
            name: "Nicholas",
            age: 29,
            job: "Software Engineer",
            sayName: function () {
                console.log(this.name);
            }
        };
        var p1 = new Person();
        console.log(p1 instanceof Object);//true
        console.log(p1 instanceof Person);//true
        console.log(p1.constructor == Person);//false
        console.log(p1.constructor == Object);//true
    View Code

    注意点一:对象字面量本质上是Object对象的实例(相当于new Object() ),所以用 此方法定义原型对象时,此原型对象的constructor属性就是Object实例的constructor属性,也就是构造函数Object原型对象的constructor,所以最终指向的是构造函数Object而不再指向Person函数【也就是说,构造函数创建时自动产生的原型对象被新的对象覆盖掉了】。不过可以显示的覆盖掉,如下:

        function Person() {
        }
        Person.prototype = {
            constructor: Person,
            name: "Nicholas",
            age: 29,
            job: "Software Engineer",
            sayName: function () {
                console.log(this.name);
            }
        };
        var p1 = new Person();
        console.log(p1 instanceof Object);//true
        console.log(p1 instanceof Person);//true
        console.log(p1.constructor == Person);//true
        console.log(p1.constructor == Object);//false
    View Code

    注意点二:【补充:构造函数的原型对象是在构造函数创建时自动生成的(这个是默认的原型对象),实例的原型对象当然是在实例生成的时候即调用构造函数时添加的,看似废话,需要仔细理解下!调用构造函数时会为实例对象添加一个指向最初原型的指针(__proto__或Object.getPrototypeOf()),如果把原型对象修改为另一个对象就等于切断了构造函数与最初原型之间的联系。记住一点:实例中的指针指向的是构造函数的原型。例子如下:

    情形一:

        function Person() {
        }
        /**
         * 最初的原型对象被修改了
         */
        Person.prototype = {
            constructor: Person,
            name: "Nicholas",
            age: 29,
            job: "Software Engineer",
            sayName: function () {
                console.log(this.name);
            }
        };
        var p1 = new Person();//此时实例p1的指针指向修改过后的原型对象
        p1.sayName();//Nicholas
    View Code

    情形二:

        function Person() {
        }
        var p1 = new Person();//此时实例p1的指针指向最初原型对象。
        /**
         * 最初的原型对象被修改了
         */
        Person.prototype = {
            constructor: Person,
            name: "Nicholas",
            age: 29,
            job: "Software Engineer",
            sayName: function () {
                console.log(this.name);
            }
        };
        p1.sayName();//会出错,最初的原型对象中并没有sayName方法。
    View Code

    ※原生对象,如Array,String等的原型也可以自定义属性和方法,但不推荐这么做。

    ※原型模式的优缺点:

    优点:可以让所有实例共享方法

    缺点:1,省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值

       2,原型模式的共享特性也是它的缺点,对于属性来讲,每个实例都应该有自己的独特属性,但原型模式却都是共享的,对于基本类型的属性来讲还可以通过实例属性同名覆盖,但是对于引用类型的属性(一个实例改动此引用类型的属性会影响到另一个实例的此属性)来讲,就没有任何办法了。这个缺点刚好是构造函数模式的优点,所以组合使用构造函数模式和原型模式是创建自定义类型的最常见方式。

     

    6.2.5,组合使用构造函数模式和原型模式

    这是用来定义引用类型的一种默认模式(即创建对象的的默认模式)。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存【即属性是在实例对象中的,而方法是属于原型对象的】。还可以向构造函数传递参数,见如下例子:

        function Person(name, age, job) {
            this.name = name;
            this.age = age;
            this.job = job;
            this.friends = ["Shelby", "Court"];
        }
        Person.prototype = {
            constructor: Person,
            sayName: function () {
                alert(this.name);
            }
        };
        var person1 = new Person("Nicholas", 29, "Software Engineer");
        var person2 = new Person("Greg", 27, "Doctor");
        person1.friends.push("Van");
        alert(person1.friends); //"Shelby,Court,Van"
        alert(person2.friends); //"Shelby,Court"
        alert(person1.friends === person2.friends); //false
        alert(person1.sayName === person2.sayName); //true
    View Code

     

    6.2.6,其他一些模式:了解即可

    ※ 动态原型模式

    ※寄生构造函数模式

    ※稳妥构造函数模式

     

    函数签名是指:函数的参数类型,参数个数,参数顺序。

    6.3 继承

    ECMAScript中的继承是依靠原型链实现的。

    6.3.1,原型链 

    如果让某个引用类型的原型对象等于另一个引用类型的实例,则会构成一个实例与原型的链条,这就是原型链的基本概念。继承实现的本质是:重写原型对象,代之以一个新类型的实例。例子如下:

        //继承
        function SuperType() {
            this.property = true;
        }
        SuperType.prototype.getSuperValue = function () {
            return this.property;
        };
        function SubType() {
            this.subproperty = false;
        }
        //继承SuperType
        SubType.prototype = new SuperType();
        SubType.prototype.getSubValue = function () {
            return this.subproperty;
        };
        var instance = new SubType();
        alert(instance.getSuperValue()); //true
    View Code

     

    instance指向SubType的原型,subType的原型又指向SuperType的原型,层层递进。图解如下:

    注意instance.constructor现在指向SuperType,因为instance的constructor实际是其原型对象的constructor属性,而其原型对象是SuperType的实例,它的constructor属性又是它的原型对象的constructor属性,即是SupertType的原型对象的属性,所以最终指向了SuperType构造函数。

     ※通过实现原型链,本质上扩展了原型搜索机制,搜索过程会沿着原型链一路向上,直到发现了要找的属性。原型链的最上层是Object.prototype,因为所有函数的默认原型都是Object的实例,这也是所有自定义类型都会继承toString()、valueOf()等默认方法的根本原因。

    ※原型链上的原型与实例

        //原型链上的原型与实例
        alert(instance instanceof Object); //true
        alert(instance instanceof SuperType); //true
        alert(instance instanceof SubType); //true
        alert(Object.prototype.isPrototypeOf(instance)); //true
        alert(SuperType.prototype.isPrototypeOf(instance)); //true
        alert(SubType.prototype.isPrototypeOf(instance)); //true
    View Code

    ※ 注意一点:在通过原型链实现继承时,不能使用对象字面量创建原型对象,因为这么做就会重写原型链,切断原来的原型链。

    ※ 原型链的问题:

    通过原型继承时,原型实际上会变成另一个类型的实例,所以原来的实例属性现在就变成了原型属性了,如果包含有引用类型的属性,那么此属性就会被所有的子类型的实例共享,一个实例改变此属性会影响到其他实例的此属性.例子如下:

        function SuperType()
        {
            this.colors = ["red", "blue", "green"];
        }
        function SubType()
        {
        }
        //inherit from SuperType
        SubType.prototype = new SuperType();
        var instance1 = new SubType();
        instance1.colors.push("black");
        alert(instance1.colors); //"red,blue,green,black"
        var instance2 = new SubType();
        alert(instance2.colors); //"red,blue,green,black"
    View Code

     

    6.3.2,借用构造函数(constructor stealing) 【注:这也是ECMAScript中的一种继承方式,和原型链继承是并列关系】

    ※为了解决原型链对于引用类型属性的问题,可以用一种叫做 借用构造函数 的技术。其基本思想很简单,即在子类型构造函数内部调用超类型的构造函数,并把子类型的执行环境赋予其(通过call或者apply方法).如下所示:

        //借用构造函数 技术
        function SuperType(name)
        {
            this.name = name;
            this.colors = ["red", "blue", "green"];
        }
        function SubType()
        {
            SuperType.call(this, 'Gerrard');//继承了SuperType
        }
        var instance1 = new SubType();
        instance1.colors.push('black');
        console.log(instance1.colors);//["red", "blue", "green", "black"]
        var instance2 = new SubType();
        console.log(instance2.colors);//["red", "blue", "green"]
    View Code

    如上代码所示,在调用子类型构造函数时,就会执行SuperType()函数中定义的所有对象初始化代码,结果就是SubType的每个实例就都会具有自己的colors属性的副本了。

    ※借用构造函数的缺点:也就是构造函数的缺点,即方法都是在构造函数中定义的,无法做到函数复用。这里注意一点,借用构造函数技术中,在超类型的原型中定义的方法对子类型而言是不可见的,因为这种继承方法只是在初始化子类型时调用超类型构造函数,没有涉及到超类型的原型对象。

    6.3.3,组合继承:

    结合原型链和借用构造函数,发挥两者各自的长处。这是JavaScript中最常用的继承模式。其思路是,使用原型链继承 实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。这样,既通过在原型上定义方法实现了函数复用,又能保证每个实例都有自己的属性。例子如下:

        /**
         * 组合继承
         */
        function SuperType(name)
        {
            //需要继承的实例属性
            this.name = name;
            this.colors = ["red", "blue", "green"];
        }
    
        //需要继承的共享的方法
        SuperType.prototype.sayName = function ()
        {
            console.log(this.name);
        };
        //需要继承的共享属性
        SuperType.prototype.xxx = 'xxx';
    
        function SubType(name,age)
        {
            SuperType.call(this, name);//继承实例属性
            this.age = age;
        }
        /**
         * 这里注意,SubType.prototype中同样也保存着SuperType实例的属性name和colors;
         * SubType在实例化时通过借用构造函数技术在实例中覆盖了这些属性,
         * 所以说在SubType.prototype中的name和colros属性是多于的,这是组合继承的一个小缺点,可以用寄生组合式继承解决。
         */
        SubType.prototype = new SuperType();//继承需要共享的原型属性和方法
    
        var instance1 = new SubType("tong",11);
        instance1.colors.push('black');
        console.log(instance1.colors);//["red", "blue", "green", "black"]
        console.log(instance1.name);//tong
        console.log(instance1.age);//11
        console.log(instance1.xxx);//xxx
    
        var instance2 = new SubType("Gerrard",22);
        console.log(instance2.colors);//["red", "blue", "green"]
        console.log(instance2.name);//Gerrard
        console.log(instance2.age);//22
        console.log(instance2.xxx);//xxx
    View Code

     

    6.3.4,原型式继承【这是个很基础的模型,好好理解也很容易理解】

    基本思想是:借助原型,基于已有的对象创建新对象,同时也不必因此创建新类型。为了达到这个目的,可以利用如下函数:

        function object(o) {//o是传入的对象

          function F() {} //创建一个临时性的构造函数

          F.prototype = o;//将传入对象作为临时构造函数的原型

          return new F();//返回临时类型的一个实例,此实例有个指针指向传入的对象o,整个函数的作用相当于对传入对象o作了一次浅复制

        }

    浅复制(浅拷贝):只是增加了一个指针指向已经存在的内存;

    深复制(深拷贝):增加一个指针并且申请一个新的内存,并使这个指针指向这个内存

    ※ECMAScript5中通过新增Object.create(proto[, props])方法规范类原型式继承。此方法接收两个参数,第一个是参数是用作新对象原型的对象,第二个可选参数是为新对象定义额外属性的对象,在传入一个参数的情况下,Object.create(obj)与上面object(obj)方法的行为相同,即创建一个以obj为原型的实例对象【新创建的这个实例对象没有任何属性,所以它可以用来解决组合继承中子类的原型中包含多余属性的问题!】。Object.create()方法的第二个参数与Object.defineProperties()方法的第二个参数格式相同,形如:{xxx:{value:'xxx',writable:true}}。同样,调用此方法,如果不指定writable等属性就会默认为false。

     

    6.3.5, 寄生式继承【实质还是原型式继承】

    寄生式继承是在原型式继承模型的基础上扩展来的,就是在原型式继承的基础上进一步封装。即把扩展对象的属性等代码封装到一个函数中,最后再返回此扩展对象,代码如下:

    functon createAnother(original) {

      var clone = object(original);//这个就是原型式继承中的函数,创建一个新对象。这里不一定必须使用object()函数,任何能够返回新对象的函数都适用于此。

      clone.sayHi = function(){alert('hi');};// 扩展新对象的属性和方法

      return clone;

    }

    6.3.6,寄生组合式继承(这是最理想的继承范式)

    上面提到组合继承(即原型式继承和借用构造函数式组合在一起)是JavaScript中最常用的继承模式,但它也有个小缺点(一般情况下可以忽略此缺点),即子类型原型中存着多余的属性,这是因为组合继承无论在什么情况下都会调用两次超类型的构造函数:按顺序第一次是在创建子类型原型的时候,第二次是在子类型构造函数内部。

     

    /**
         *组合继承的缺点介绍
         */
        function SuperType(name) {
            this.name = name;
            this.colors = ["red", "blue", "green"];
        }
        SuperType.prototype.sayName = function () {
            alert(this.name);
        };
        function SubType(name, age) {
            SuperType.call(this, name); //第二次调用SuperType()
            this.age = age;
        }
        /**
         * 第一次调用超类型,超类型的实例属性name 和colors也就存在于子类型的原型中了,
         * 这些属性就是多余的属性,因为第二次调用超类型构造函数时,子类型实例对象也会获得这些属性,
         * 从而覆盖掉了子类型原型中的那些同名多余属性。所以最理想的方式就是让子类型的原型中不保存这些多余的属性。
         * 要做到这点,就要利用到原型式继承模型,即**让子类型的原型等于超类型的一个浅复制**用寄生模式封装起来就可以了
         */
        SubType.prototype = new SuperType(); //第一次调用SuperType()
        SubType.prototype.constructor = SubType;
        SubType.prototype.sayAge = function () {
            alert(this.age);
        };

     

     所以,寄生组合式继承可以按如下方法实现只需改变第一次调用超类型构造函数的那一行代码即可,两种方式的唯一区别在于:原来的子类原型对象被设置为SuperType的一个实例,所以包含SuperType构造函数中定义的属性,而现在借助于一个新的构造函数(假设为F)将子类型的原型对象设置为F的一个实例,同时将F的构造函数的原型设置为SuperType.prototype,F中没有定义任何属性,所以子类型的的原型对象便是一个没有任何属性的对象,且和SuperType的实例对象一样,此对象内部有一个指针指向SuperType.prototype,一句话概括就是:借助新的空构造函数将子类型的原型对象由原来的 SuperType的实例对象变化为 新构造函数的实例对象)

    function inheritPrototype(subType, superType) {

      var prototype = object(supertType.prototype);//创建对象,即超类型原型的一个浅复制

      prototype.constructor = subType;//扩展对象

      subType.prototype = prototype;//把子类型的原型指定为超类型原型的浅复制

    }

    然后把第一次调用超类型构造函数的那一行代码改为调用上面的函数:

    inheritPrototype(SubType, SuperType);即可。

     

    7.0函数表达式

    7.1,函数表达式最常见的表现形式:

    ※匿名函数(也叫拉姆达函数)表达式,可以把其视为一个变量,适用于变量适用的一切地方,如函数表达式可以作为函数的返回值。

    ※命名函数表达式,如var a = function f(arg1,arg2){...};调用时要用a();而f会被认为是个未定义的符号。a.name == f

    一些浏览器会给函数一个非标准的name属性,这个属性值永远是function标识符后面的那个名字,对于匿名函数来说就是空字符串。

    7.2,递归

    ※递归函数内部中用arguments.callee代替函数名可以减少耦合性(函数名可能会变化)。但是在严格模式下,callee属性是不可用的。只是可以利用命名函数表达式实现减少耦合性的递归,如下:

    var factorial  = function f(num){

      if (num<=1){return 1;}else{return num * f(num-1);}

    };

     

    7.3,闭包 

    ※闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数(内部的那个函数就叫闭包). 

     

    ★★★★★执行环境,作用域链,变量对象详述以及它们之间的关系:

    ★执行环境:当某个函数第一次被调用时,会创建一个执行环境及相应的作用域链。可以理解为,执行环境是一个包含 作用域链和变量对象 的容器。

    ★作用域链:当某个函数被创建时(某个函数被返回也被视为被创建),它会有一个初始化的作用域链,这个作用域链包括了除了自己之外的所有其他包括这个函数的作用域,当这个函数被调用时,其自身的作用域被创建并被推入整个作用域链的前端。本质上讲,作用域链是一个指向变量对象的指针列表,它只引用但不实际包含变量对象。当在函数中访问一个变量时,就会从作用域链中从前端依次搜索具有相应名字的变量,直到找到为止。

    ★变量对象:每个执行环境都有一个表示变量的对象,即为变量对象。比如一个在全局作用域中定义的函数,其变量对象包括:this,arguments,命名参数。

    关于三者之间的关系,见如下例子:

     

     

     

    ※闭包的作用域链:

     

        /**
         * 闭包的作用域链
         * 一般函数的变量对象在函数执行完毕后就会被销毁,但闭包不同。
         */
        function outer(name) {
            //被返回的这个匿名函数即是闭包
            return function (obj) {
                return obj[name];
            }
        }
        var obj = {name: 'tong'};
        /**
         * 当调用outer函数后,返回了匿名闭包函数,此时匿名函数的作用域链被初始化为
         * 包含outer函数的变量对象和全局变量对象,所以匿名函数就可以访问outer变量对象
         * 以及全局变量对象。而且,在调用outer函数后,outer函数的作用域链会被销毁,但是
         * 其变量对象并不会被销毁,因为匿名函数的作用域链仍然在引用这个变量对象。直到匿名
         * 函数被销毁后,outer函数的变量对象才会被销毁
         *
         */
        var out = outer('name');//将闭包匿名函数赋值给变量out,此时其作用域链有两个指针:outer和全局
        var val = out(obj);//调用闭包函数,此时其作用域链有三个指针:指向自身的变量对象,指向outer函数的变量对象,指向全局的变量对象
        console.log(val);//tong
        /**
         * 解除对匿名函数的引用,之后垃圾回收例程就会将匿名函数清除以回收内存,
         * 随着匿名函数的作用域链被销毁,outer函数的变量对象也可以安全的销毁了。
         */
        out = null;

     

     

    ※ 闭包与变量(闭包的一个副作用)

    注意到一点:闭包函数的作用域链中指针指向的是外部包含函数的整个变量对象,这导致了一个问题,即闭包只能取得包含函数中任何一个变量的最后一个值。见下面一个例子:

    function createFunction(){

      var result = new Array();

      for (var i = 0; i < 10; i++){

        result[i] = function(){

          return i;

        };

      }

      return result;

    }

    这个函数会返回一个函数数组,函数中每个函数都是一个闭包。表面上看,每个函数都会返回自己的索引值,但实际上每个函数都返回10!!这是因为每个闭包函数的的作用域链中都保存着createFunction函数的变量对象,所以它们引用的都是同一个变量i。当createFunction函数返回后,变量i的值是10,所以每个函数内部i的值都是10(沿着各自的作用域链搜索i的值,结果发现都是10)。

    这个问题可以如下解决:

        function createFunction() {
            var result = new Array();
            for (var i = 0; i < 10; i++){
                result[i] = function (num) {
                    return function () {
                        return num;
                    };
                }(i);
            }
            return result;
        }

     

    ※ 关于this对象

    每个函数在被调用时,其活动对象(即变量对象)都会自动取得两个特殊变量:this和arguments。闭包函数(即函数内部的函数)在搜索这两个变量时,只会搜索到其自己的活动对象为止,因此永远不可能直接访问到外部函数中的这两个变量。不过可以把外部作用域中的this对象保存在一个闭包能够访问到的变量里,就可以让闭包访问该对象了。例子如下:

     

        var o = {
            name: "mY",
            getName: function () {
                var a = this;
                return function () {
                    console.log(a);//对象o
                    console.log(this);//window对象
                }
            }
        };
        o.getName()();//o.getName()返回闭包函数,把o.getName()替换掉,就相当于在全局中调用闭包,因此闭包this指的就是window对象

     

     

     

    7.4,模仿块级作用域

    ※Javascript(以及python,php)都是没有块级作用域的,它们都属于函数作用域。比如:

    function output(count){

      for(var i = 0; i < count; i++){

        console.log(i);

      }

      alert(i);//这里在for循环体外,依然可以访问到i的值

    }

    在Java,C++等语言中,变量i只会在for循环的语句快中有定义,循环一旦结束,变量i就被销毁。但在Javascript中,变量i的值在for循环外依然可以访问到。

    ※既然JS是函数作用域,那么就可以利用函数作用域来模拟块级作用域,即把循环体放在一个匿名函数内,如下:

     

        function output(count) {
            var tmp = function () {
                for (var i = 0; i < count; i++) {
                    console.log(i);
                }
            }();//立即调用此匿名函数
            alert(i);//这里会报错,i未定义。
        }
        output()

     

     上面的代码中tmp变量其实没有任何用处,但是如果直接把(var tmp = )去除的话会报错,因为以function关键字开头会被认为是一个函数声明的开始(函数声明必须要有函数名的),而函数声明后面不能跟圆括号。然而,函数表达式的后面可以跟圆括号。要将函数声明转换成函数表达式只要把这个声明用一对圆括号括起来即可。如下:

     

        function output(count) {
            (function () {
                for (var i = 0; i < count; i++) {
                    console.log(i);
                }
            })();
            alert(i);//这里会报错,i未定义。
        }
        output();  

    因此,块级作用域形式如下:

        (function () {
            /*这里是块级作用域,出了这块区域,这里的所有局部变量就会被销毁
          但注意,如果是一个不带var表示的变量,表示这是一个全局变量,      可以在块级作用域之外访问。
          */
        b = 3;//这是个全局变量,可以在块级作用域之外访问到。 })()

    即:创建并立即调用一个函数,这样既可以执行其中的代码,又不会在内存中留下对该函数的引用。

     

    ※私有变量,静态私有变量

    模块模式(单例模式)

    实质上就是块级作用域的应用。创建并立即调用一个函数,此函数的内部属性和方法都是属于私有的,然后返回一个能够访问这些私有属性和方法的对象。例子如下:

     

        //single是个全局变量,保存着公有属性和方法,这些公有方法可以访问私有属性和方法
        var single = function () {
            var pri = 'pri';//私有属性,外部无法直接访问
    
            //私有函数,外部无法直接访问
            function priFun() {
                return pri;
            }
    
            //返回的对象可以用对象字面量,也可以用new构造函数模式
            return {
                pubPro: 'pubPro',
                pubFun: function () {
                    return priFun();
                }
            }
        }();

     

     

     

    本章总结:

     

     

    8.0,BOM(Browser Object Module)

    8.1,window对象--BOM的核心

    ※全局变量与window属性关系:

    联系:全局变量、函数都是window对象的属性和方法。

    区别:

    1,全局变量不能通过delete操作符删除,而在window对象上定义的属性可以,如下:

    var age = 22;

    window.color = 'red';

    delete age;//false

    delete window.age;//false

    delete window.color;//true

    console.log(window.age);//22

    console.log(window.color);//undefined

    通过Object.getOwnPropertyDescriptors(window,'age')查看可知age属性的[[configrable]]特性为false,所以不可删除。

    2,对于未定义的变量,

    var val = xx;//抛出错误,因为xx未定义

    var val = window.xx;//val为undefined而不会出错,因为这是一次属性查询

     

    ※框架和窗口关系:

    1,每个框架都有自己的window对象。每个window对象都一个name属性,值为框架的名称。最上层window对象的name为空字符串。

    2,所有的框架都保存在frames【即window.frames】集合中(注意是只包含框架,不包含最上层的window)。frames.length得到框架数量。

    3, top对象始终指向最高(最外)层的窗口,也就是浏览器窗口。

      parent对象始终指向当前框架的直接上层框架。

      self对象始终指向window;实际上window和self可以互换使用。

    4,defaultView;//返回活动文档的Window对象

    5,window[index];//返回指定索引位置的框架的window对象。注意,只包含框架,即window[0]指的是第一个框架,而不是最外层的window

    6,window['name'];//返回指定名称的内嵌框架的window对象。同上,也是只包含框架。

    ※窗口位置

    window.screenTop / window.screenLeft;//获取

    window.screenY / window.screenX;//获取

    window.moveTo(11,111) / window.moveBy(33,44);//只对window.open()的窗口有效!且不适用于框架,只能对最外层window对象使用

    ※窗口大小

    window.innerWidth / window.innerHeight ;//获取

    window.outerWidth / window.outerHeight;//获取

    document.documentElement.clientWidth;//获取

    document.body.clientWidth;//获取

    window.resizeTo(100,100) / window.resizeBy(11,11);//只对window.open()窗口有效,且不适用于框架,只能对最外层window对象使用。

    ※导航和打开窗口

    var newWin = window.open(url,name,features,replace);//打开新的标签或窗口,四个参数都是可选的:

      :要打开的新的网址

      name:和<a>标签的target属性一样

      features:新窗口的特性字符串,如:"width=300,height=300,left=50,top=50"

      replace: 新打开的url是在浏览历史中新增一个条目,还是替换当前的条目。true为替换,false为新增。

    此函数返回一个指向新窗口的引用。

    newWin.close();//此方法只对window.open()方法打开的窗口或标签有效。

    newWin.closed;//检查窗口是否关闭了

    newWin.opener;//此属性指向调用window.open()的窗口或框架 的window对象。此属性只在弹出窗口的最外层window对象上有定义。

    在chrome中,如果把opener属性设置为null,则表示在单独的进程中运行新标签页,如果这样,两个窗口将无法通信。

    ※超时调用和间歇调用

    var timeoutId = window.setTimeout(code, ms);

    第一个参数表示要执行的代码,可以是包含js代码的字符串(不推荐,因为会有性能损失),也可以是函数。

    第二个参数表示等待多长时间的毫秒数,但经过该时间后,指定的代码不一定会执行。JavaScript是一个单线程序的解释器,因此一段时间内只能执行一段代码。为了控制要执行的代码,就要有一个JavaScript任务队列。这些任务会按照它们被添加到队列的顺序执行。setTimeout的第二个参数告诉JavaScript解释器再过多长时间把当前任务添加到队列中。如果队列是空的,那么添加的代码会立即执行,如果队列不是空的,就要等前面的代码执行完了以后才会执行

    另外注意:setTimeout()中用到的函数的环境总是window,所以如果需要使用某个特定的this对象,需要将this保存到一个变量中。例如:

        var o = {
            nm: 'tong',
            sayName: function () {
                var that = this;//this指的是对象o,保存在变量that中,以便在setTimeout中使用。
                setTimeout(function () {
                    console.log(this);//尽管在对象o里面,这个this仍指的是window对象
                    console.log(this.nm);//undefined;
                    console.log(that.nm);//tong
                }, 5000)
            }
        };
        o.sayName();

    window.clearTimeout(timeoutId);

    ----

    var intervalId = window.setInterval();//能不用间歇调用就不用,因为间歇的下一次可能在上一次结束之前就启动,一般间歇调用都可以用超时调用来模拟。

    window.clearInterval(intervalId);

    ※系统对话框

    调用alert(str) / confirm(str) / prompt(str1, str2)方法可以显示系统对话框。系统对话框外观由操作系统或浏览器决定,而不是由CSS决定。系统对话框都是同步和模态的,同步即打开对话框代码停止执行,关掉就会继续执行。模态是指要想对其他地方进行操作,必须先响应系统对话框。

    prompt(str1,str2);//str1是用于提示用户的文本,str2是输入域的默认值。返回输入值或者null(没有输入)。

     

    8.2, location对象

    window.location === document.location。两者是同一个对象。

    location对象的属性列表:

     ※浏览器地址位置操作:

    location.assign(URL);

    location.href = URL;//会调用assign()方法

    window.location = URL;//会调用assign()方法

    location.hash/search/host/.... = ...;//修改地址一部分

    上述方法在修改URL之后,浏览器的历史记录中就会生成一条新的记录,因此可以通过后退按钮导航到前一个页面。

    locatioin.replace(URL);//以新的URL替换当前页面,此方法不会在历史记录中生成新的记录,因此无法回到前一个页面。

    location.reload();//重载当前页面,如果页面没变则会从浏览器缓存中加载。

    location.reload(true);//强制从服务器重新加载当前页面。

     

    8.3,navigator对象

    navigator对象主要提供与用户代理(即浏览器)相关的信息。比较有用的信息有:

    navigator.cookieEnabled ;//cookie是否启用

    navigator.platform;//浏览器所在的操作系统平台

    navigator.plugins;//浏览器中安装的插件信息的数组

    navigator.userAgent;//用户代理信息字符串

     

    8.4,screen对象【用处不大】

    8.5,history对象

    history.go(num);//整数表示前进num页,负数表示后退,0表示刷新

    history.forward();//前进一页

    history.back();//后退一页

    history.length;//历史记录的数量

    9.0客户端检测:

    ※为了避免在全局作用域中定义太多变量,可以采用 模块模式 :

    代码如下:

        var client = function () {
            //呈现引擎,私有属性,外部不可直接访问
            var engine = {
                ie: 0,
                gecko: 0,
                webkit: 0,
                khtml: 0,
                opera: 0,
                //完整的版本号
                ver: null
            };
            /**
             * 检测呈现引擎(五大呈现引擎:IE,Gecko[firefox],WebKit[chrome,safari],KHTML,Opera)
             * 检测呈现引擎关键是检测顺序
             */
            var ua = navigator.userAgent;
            if (window.opera) {
                //首先检测opera
                engine.ver = window.opera.version();
                engine.opera = parseFloat(engine.ver);
            } else if (/AppleWebKit/(S+)/.test(ua)) {
                //检测WebKit中独一无二的【AppleKit/版本号】
                engine.ver = RegExp["$1"];
                engine.webkit = parseFloat(engine.ver);
            } else if (/KHTML/(S+)/.test(ua)) {
                engine.ver = RegExp["$1"];
                engine.khtml = parseFloat(engine.ver);
            } else if (/rv:([^)]+)) Gecko/d{8}/.test(ua)) {
                //检测Gecko,Gecko的版本号在“rv:”的后面
                engine.ver = RegExp["$1"];
                engine.gecko = parseFloat(engine.ver);
            } else if (/MSIE ([^;]+)/.test(ua)) {
                //检测IE,IE版本号位于字符串 MSIE 后面。
                engine.ver = RegExp["$1"];
                engine.ie = parseFloat(engine.ver);
            }
    
            //返回对象
            return {
                engine: engine
            }
        }();

     考虑到检测浏览器,操作系统平台,移动设备和游戏系统,更具体的用户代理字符串检测脚本如下:

        var client = function () {
            //rendering engines 呈现引擎
            var engine = {
                ie: 0,
                gecko: 0,
                webkit: 0,
                khtml: 0,
                opera: 0,
                //complete version完整的版本号
                ver: null
            };
            //browsers 检测浏览器类型
            var browser = {
                //browsers
                ie: 0,
                firefox: 0,
                safari: 0,
                konq: 0,
                opera: 0,
                chrome: 0,
                //specific version具体版本号
                ver: null
            };
            //platform/device/OS 平台 移动设备 操作系统
            var system = {
                win: false,
                mac: false,
                x11: false,
                //mobile devices移动设备
                iphone: false,
                ipod: false,
                ipad: false,
                ios: false,
                android: false,
                nokiaN: false,
                winMobile: false,
                //game systems游戏系统
                wii: false,
                ps: false
            };
            //detect rendering engines/browsers检测呈现引擎和浏览器
            var ua = navigator.userAgent;
            if (window.opera) {
                engine.ver = browser.ver = window.opera.version();
                engine.opera = browser.opera = parseFloat(engine.ver);
            } else if (/AppleWebKit/(S+)/.test(ua)) {
                engine.ver = RegExp["$1"];
                engine.webkit = parseFloat(engine.ver);
                //figure out if it’s Chrome or Safari确定是chrome还是safari
                if (/Chrome/(S+)/.test(ua)) {
                    browser.ver = RegExp["$1"];
                    browser.chrome = parseFloat(browser.ver);
                } else if (/Version/(S+)/.test(ua)) {
                    browser.ver = RegExp["$1"];
                    browser.safari = parseFloat(browser.ver);
                } else {
                    //approximate version近似确定版本号
                    var safariVersion = 1;
                    if (engine.webkit < 100) {
                        safariVersion = 1;
                    } else if (engine.webkit < 312) {
                        safariVersion = 1.2;
                    } else if (engine.webkit < 412) {
                        safariVersion = 1.3;
                    } else {
                        safariVersion = 2;
                    }
                    browser.safari = browser.ver = safariVersion;
                }
            } else if (/KHTML/(S+)/.test(ua) || /Konqueror/([^;]+)/.test(ua)) {
                engine.ver = browser.ver = RegExp["$1"];
                engine.khtml = browser.konq = parseFloat(engine.ver);
            } else if (/rv:([^)]+)) Gecko/d{8}/.test(ua)) {
                engine.ver = RegExp["$1"];
                engine.gecko = parseFloat(engine.ver);
                //determine if it’s Firefox 确定是否为firefox
                if (/Firefox/(S+)/.test(ua)) {
                    browser.ver = RegExp["$1"];
                    browser.firefox = parseFloat(browser.ver);
                }
            } else if (/MSIE ([^;]+)/.test(ua)) {
                engine.ver = browser.ver = RegExp["$1"];
                engine.ie = browser.ie = parseFloat(engine.ver);
            }
            //detect browsers 检测IE和opera浏览器
            browser.ie = engine.ie;
            browser.opera = engine.opera;
            //detect platform   检测平台
            var p = navigator.platform;
            system.win = p.indexOf("Win") == 0;
            system.mac = p.indexOf("Mac") == 0;
            system.x11 = (p == "X11") || (p.indexOf("Linux") == 0);
            //detect windows operating systems检测具体windows系统版本
            if (system.win) {
                if (/Win(?:dows )?([^do]{2})s?(d+.d+)?/.test(ua)) {
                    if (RegExp["$1"] == "NT") {
                        switch (RegExp["$2"]) {
                            case "5.0":
                                system.win = "2000";
                                break;
                            case "5.1":
                                system.win = "XP";
                                break;
                            case "6.0":
                                system.win = "Vista";
                                break;
                            case "6.1":
                                system.win = "7";
                                break;
                            default:
                                system.win = "NT";
                                break;
                        }
                    } else if (RegExp["$1"] == "9x") {
                        system.win = "ME";
                    } else {
                        system.win = RegExp["$1"];
                    }
                }
            }
            //mobile devices移动设备
            system.iphone = ua.indexOf("iPhone") > -1;
            system.ipod = ua.indexOf("iPod") > -1;
            system.ipad = ua.indexOf("iPad") > -1;
            system.nokiaN = ua.indexOf("NokiaN") > -1;
            //windows mobile
            if (system.win == "CE") {
                system.winMobile = system.win;
            } else if (system.win == "Ph") {
                if (/Windows Phone OS (d+.d+)/.test(ua)) {
                    system.win = "Phone";
                    system.winMobile = parseFloat(RegExp["$1"]);
                }
            }
            //determine iOS version
            if (system.mac && ua.indexOf("Mobile") > -1) {
                if (/CPU (?:iPhone )?OS (d+_d+)/.test(ua)) {
                    system.ios = parseFloat(RegExp.$1.replace("_", "."));
                } else {
                    system.ios = 2; //can’t really detect - so guess
                }
            }
            //determine Android version
            if (/Android (d+.d+)/.test(ua)) {
                system.android = parseFloat(RegExp.$1);
            }
            //gaming systems
            system.wii = ua.indexOf("Wii") > -1;
            system.ps = /playstation/i.test(ua);
            //return it
            return {
                engine: engine,
                browser: browser,
                system: system
            };
        }();
    View Code

    10,DOM(Document Object Module)

    10.1,Node类型

    每个dom元素都可视为一个node节点,节点属于节点类型,节点类型的属性和方法如下:

    var div = document.getElementById('xxx');//获取一个id为xxx的div节点。

    div.nodeType;//1;  

    nodeType表示节点的类型,必属于下面12中之一:

    Node.ELEMENT_NODE (1)
    Node.ATTRIBUTE_NODE (2)
    Node.TEXT_NODE (3)
    Node.CDATA_SECTION_NODE (4)
    Node.ENTITY_REFERENCE_NODE (5)
    Node.ENTITY_NODE (6)
    Node.PROCESSING_INSTRUCTION_NODE (7)
    Node.COMMENT_NODE (8)
    Node.DOCUMENT_NODE (9)
    Node.DOCUMENT_TYPE_NODE (10)
    Node.DOCUMENT_FRAGMENT_NODE (11)
    Node.NOTATION_NODE (12)

    div.nodeName;//DIV

    div.nodeValue;//null

    div.childNodes;//

      childNodes属性是一个NodeList对象,类数组对象。有length属性。它是基于DOM结构动态执行查询的结果,因此DOM结构的变化能够自动反应在NodeList对象中。可以通过方括号或item()方法访问此对象中的节点。如:

      div.childNodes[0];

      div.childNodes.item(0);

      div.childNodes.length;

      另外注意,换行符和空白符会被有些浏览器认为是一个text节点,可以利用nodeType属性过滤掉。

    div.parentNode;

    div.previousSibling;

    div.nextSibling;

    div.firstChild;

    div.lastChild;

    div.hasChildNodes();//无参数,有子节点返回true,无则返回false

    div.ownerDocument;//该属性指向顶端的表示整个文档的 文档节点#document

    操作节点的方法:

    div.appendChild(newNode);//在div的childNodes列表的末尾添加一个节点。返回值为新增的节点newNode。如果newNode已经是文档的一部分了,那么节点将从原来的位置转移到新的位置。

    div.insertBefore(要插入的节点, 作为参照的节点);//如果参照节点为null,新插入的节点位于div的childNodes的末尾。返回新插入的节点。

    div.replaceChild(要插入的节点,被替换的节点);//返回被替换的节点。

    div.removeChild(要移除的节点);//返回被移除的节点。

    div.cloneNode(bool);//返回一个完全相同的副本。参数为true时执行深复制,为false时执行浅复制。深复制复制节点以及整个子节点树;浅复制只复制节点本身。

    10.2, Document类型

    document对象表示整个HTML页面。

    document.nodeType;//9

    document.nodeName;//'#document'

    document.nodeValue;//null

    document.parentNode;//null

    document.ownerDocument;//null

    document.childNodes;//chrome中返回 [<!DOCTYPE html>, <!--html标签前的一个注释-->, <html>​…​</html>​]

    document.documentElement;//始终指向页面中的<html>元素。

    document.body;//指向<body>元素。

    document.doctype;// 指向 <!DOCTYPE html>

    document.title;//获取或设置title

    document.URL;//获取页面完整的URL,不可设置

    document.domain;//获取页面的域名,可以设置,但有限制,如下:

    document.referrer;//获取连接到当前页面的那个页面的URL,不可设置

     查找元素

    var hc = document.getElementsByTagName('div');//返回值类型为HTMLCollection对象。访问HTMLCollection对象中的元素方法有:

    ①hc[0];   ②hc['name'];  ③hc.item(1);    ④hc.namedItem('name');

    以下属性都返回HTMLCollection对象:

    document.anchors;//包含文档中所有带有name特性的<a>元素。

    document.links;//包含文档中所有带href特性的<a>元素。

    document.forms;//包含文档中所有的<form>元素。

    document.images;//包含文档中所有的<img>元素。

    document.write()/writeln();//在页面呈现过程中调用此方法则直接向网页输出内容。如果在文档加载完毕后再调用此方法,那么输出的内容将会重写整个页面。

    10.3, Element类型

    所有的HMTL元素都是HTMLElement类型或其更具体的子类型来表示的。下表列出了所有的HTML元素以及与之关联的类型,其中斜体表示已经不推荐使用了。

    var div = document.getElementById('div');//获取一个Element类型元素

    Element类型的元素属性:

    div.nodeType;//1

    div.nodeName;//元素标签名

    div.nodeValue;//null

    div.tagName;//和div.nodeName一样返回元素标签名,语义更清晰

    ---------------------------------------

    HTML元素的公认特性都可以作为HTMLElement类型的属性直接访问或设置,如:

    div.id;//获取或设置元素的id

    div.title;//说明

    div.lang;//语言

    div.dir;//方向

    div.className;//类名,因为class是保留字

    div.align;//对齐方式

    -----------------------------------------------------------

    div.getAttribute(属性名);//获取属性,通常用来获取自定义属性。有两类特殊的特性,通过属性名直接访问1和通过getAttribute()方法访问结果不同:

    一是:style特性。div.style返回的是一个对象,div.getAttribute('style')返回的是style特性值中包含的文本。

    二是:类似onclick这样的事件属性。div.onclick返回一个JavaScript函数,div.getAttribute('onclick')返回代码字符串。

    div.setAttribute(属性名,属性值);//设置属性,通常用来设置自定义属性,而固有属性直接用属性名设置。

    div.removeAttribute(属性名);//移除属性

    div.attributes;//返回一个NameNodeMap对象,类似于NodeList,是一个‘动态’集合。包含div的所有属性。

    ※创建元素

    var div = document.createElement(tagName);//只有一个参数,即标签名。返回新建的元素,可以进一步添加属性等,然后在添加到文档树中。

    getElementById()只能在document对象上使用,其他的比如getElementsByTagName();可以用于任意的HTML元素,搜索时只会搜索此元素的后代元素(不包括此元素本身)

    10.4,Text类型

    设变量text为一个Text类型的节点,则:

    text.nodeType;//3

    text.nodeName;//"#text"

    text.nodeValue;//包含文本,可以获取或设置

    text.data;//等于text.nodeValue;

    text.length == text.nodeValue.length == text.data.length;节点中字符的数目

    text.appendData(text);//将text添加到节点末尾

    text.deleteData(offset, count);//从offset指定的位置开始,删除count个字符

    text.insertData(offset, text);//在offset指定的位置插入text

    text.replaceData(offset, count, text);//用text替换从offset开始的count个字符

    text.splitText(offset);//从offset指定的位置将当前文本节点分成两个文本节点。

    text.substringData(offset, count);//提取从offset位置开始的count个字符。

    创建文本节点:

    var textNode = document.createTextNode(text);//接收一个参数:要插入节点的文本

    如果连续插入两个文本节点,它们不会合并依然是两个节点,但是显示时它们会连起来,中间不会有空格。

    连续的两个文本节点只会人为生成,浏览器永远不会解析出两个两个连续的文本节点。在文本节点的父节点上调用normalize()方法可以合并相邻的文本节点,此方法无参数。

    10.5,Comment类型

    注释在DOM中通过Comment类型表示,其属性如下:

    cmt.nodeType;// 8

    cmt.nodeName;// "#comment"

    cmt.nodeValue;//注释内容,不包括注释符号<!---->

    cmt.data;//等于cmt.nodeValue

    cmt除了没有splitText()方法之外,和Text类型属性和方法完全一样。

    var cmtNode = document.createComment("这是一条注释");//创建注释节点。没什么卵用

    10.6,CDATASection类型

    CDATASection类型只针对基于XML的文档,表示的是CDATA区域。类似于Text类型,拥有除splitText()方法之外的所有文本节点类型的属性和方法。

    10.7, DocumentType:并不常用

    var dt = document.doctype;//<!DOCTYPE html>,只能获取不能设置

    dt.nodeType;//10

    dt.nodeName;//值为doctype的名称即:"html"

    dt.nodeValue;//null

    dt.name;//等于dt.nodeName,返回“html”

    10.8,DocumentFragment类型

     

     10.9,Attr类型【没什么用】

    说明一点:Attr类型就是html的属性,也就是存在于元素的attributes属性中的节点,如id,class,align等等。它们也是节点,但不被认为是dom文档树的一部分。

    ----------------------------------------------------------------------------

    10.10,动态添加脚本

    创建动态脚本有两种方法:插入外部文件 和 直接插入JavaScript代码

    ①,插入外部文件,代码如下:

    <script>
      //可以封装起来 var script = document.createElement('script'); script.type = 'text/javascript'; script.src = 'xxx.js';//指定外部js文件 document.body.appendChild(script);//执行此句代码之后,才会下载外部文件 </script>

    ②,直接插入JavaScript代码,代码如下:

    <script>
        /**
         * 以这种方式加载的代码会在全局作用域中执行,脚本执行后立即可用。实际上,
         * 这样执行代码和在全局作用域中把相同的字符串传递给eval()是一样的。
         */
        var script = document.createElement('script');
        script.type = 'text/javascript';
        var code = "alert('hi');";
        try {
            script.appendChild(document.createTextNode(code));//IE无效
        } catch (ex) {
         //针对IE的代码
         //注意这里的text属性貌似是script标签独有的,像style标签就没有这个属性。但是textContent属性貌似有。 script.text
    = code;//Safari3.0之前无效. } document.body.appendChild(script); </script>

    10.11, 动态添加样式:

    和动态添加脚本类似,不同的地方是针对id的代码

    try{}catch(){

    style.styleSheet.cssText = “body{backgrouond-color:red}”;

    }

    10.12,创建表格

    10.13,关于NodeList、NamedNodeMap、HTMLCollection

    这三个集合都是“动态的”;换句话说,每当文档结构发生变化时,它们都会更新。因此,它们始终都会保存着最新、最准确的信息。从本质上说,所有NodeList对象都是在访问DOM文档时 实时运行的查询。例如,下面的代码将导致无限循环:

    var divs = document.getElementsByTagName('div'),

      i,

      div;

    //divs是一个HTMLCollection,假设开始长度为1.

    for (var i = 0; i < divs.length; i++){

      //循环体中的divs.length每次都会更新

      div = document.createElement('div');

      document.body.appendChild(div);

    }

    11,DOM扩展

    三部分扩展:Selectors API、HTML5 DOM扩展、专有DOM扩展

    11.1,Selectors API:

    querySelector(css选择符);//通过document类型调用此方法时,会在文档元素的范围内查找匹配的元素。而通过Element类型调用此方法时,只会在该元素后代元素范围内(并不包括该元素)查找匹配的元素。

    querySelectorAll(css选择符);//返回一个NodeList对象。

    matchesSelector(css选择符);//调用元素与该选择符匹配,返回true,否则返回false。目前没有浏览器支持此方法,但IE9+支持msMatchesSelector(),chrome和Safari5+支持webkitMatchesSelector(),FireFox3.6+支持mozMathcesSelector()。

    11.2,Element Traversal 元素遍历

    为解决元素间的空格也会返回文本节点的问题(<=IE9的不会,其余都会),Element Traversal API为DOM元素新增5个属性:

    firstElementChild;//指向第一个子元素(不包括文本节点和注释),firstChild的元素版。

    lastElementChild;//指向最后一个子元素,lastChild的元素版。

    previousElementSibling;//指向前一个同辈元素,previousSibling的元素版。

    nextElementSibling;//指向后一个同辈元素,nextSibling的元素版。

    childElementCount;//返回子元素的个数,length属性的元素版。

    11.3,HTML5 DOM扩展

    11.3.1,与类(即class属性)相关的扩充

    getElementsByClassName("class1 class2");//参数为一个或多个类名字符串,顺序无所谓,返回同时包含多个类名的元素。可以在document对象上调用,也可以在元素上调用(只在其后代元素中查找).

    classList属性;//返回一个包含所有类名的集合,要取得每个元素可以使用item()方法也可以使用方括号语法。此集合有如下属性和方法:

      length;//类名的个数

      add(value);//将给定的字符串值添加到列表中。如果值已经存在,就不添加了

      remove(value);//从列表中删除给定的字符串。

      toggle(value);//如果列表中存在给定的值,删除它并返回false;如果没有,添加它并返回true。

      contains(value);//给定列表中是否存在给定的值,存在返回true,不存在返回false.

    11.3.2, 焦点管理

    document.activeElement;//此属性始终引用DOM中当前获得了焦点的元素。默认情况下,文档刚刚加载完成时,此属性保存的是document.body元素的引用。

    HTMLElement.focus()方法;//只能对HTML元素类型调用此方法,为此元素获取焦点。

    document.hasFocus();//只能对document对象使用。用于确定文档是否获得了焦点。如此便可知道用户是不是正在与页面交互。

    11,3,3; HTMLDocument :

    ※document.readyState;//可能的值有:

      loading:表示正在加载和解析文档。

      interactive:文档已被解析,但浏览器正在加载其中链接的资源(如图像和媒体文件等)

      complete:文档已被解析,所有资源也被加载完毕。

    配合document.onreadystatechange事件使用。

    ※document.compatMode;//检测页面的渲染模式,有两个值:

      "CSS1Compat":标准模式,即文档最开始有<!DOCTYPE html>

      "BackCompat":混杂模式  / 怪异模式。即最开始没有<!DOCTYPE html>

    ※document.head;//引用文档的<head>元素。等同于document.getElementsByTagName('head')[0];

    11,3,4,字符集属性
    document.charset;//获取或设置文档的字符集编码

    document.characterSet;//返回文档的字符集编码。这是一个只读属性。

    document.defaultCharset;//获取浏览器所使用的默认字符编码

    11.3.5,自定义数据属性

    <div data-name='tong'>myDiv</div>

    div.dataset;//返回一个DOMStringMap对象 {name:"tong"}。可以获取或设置自定义属性。

    11.3.6,插入标记

    div.innerHTML;//获取或设置

    div.outerHTML;//包含div本身,获取或设置

    div.insertAdjacentHTML(插入位置,要插入的HTML文本);//具体见下图:

    ※性能与内存问题

     

     11.3.7,scrollIntoView(bool)方法:

    此方法可以在任何HTML元素上调用,效果是让调用元素出现在窗口中。参数为true则调用元素顶部和视口顶部尽可能齐平;参数为false则调用元素会尽可能出现在视口中。

    当页面发生变化时,一般用此方法来吸引用户的注意力。另外,为某个元素设置焦点也会导致浏览器改动并显示出获得焦点的元素。需要注意的是,对于input button等元素,默认是可以获得焦点的,但一般元素如div等默认并不能获得焦点,需要给这些元素设置tabindex属性之后才能获取焦点

    11.4,专有扩展: 尚未被标准化的一些扩展,但支持的浏览器可能并不少。。

    11.4.1,IE独有的文档模式 X-UA-Compatible,略

    11.4.2,children属性:

      和childNodes属性相比,children属性只包含元素子节点,而不包含文本和注释节点.其他都一样。

    11.4.3,contains(node)方法:

      祖先节点调用此方法,如果此节点包含给定node节点,返回true,否则返回false;

      还有一个方法可以确定节点间的关系:compareDocumentPosition();//此方法是在DOM level3(第三版)中定义的。

      refNode.compareDocumentPosition(otherNode);//参考节点refNode; 给定节点otherNode,返回一个表示两个节点间关系的位掩码(bitmask),位掩码值如下:

    11.4.4,插入文本:innerText 和 outerText 

    innerText属性:将元素子节点的所有文本拼接起来。可以通过innerText属性过滤HTML标签,如下:

      div.innerText = div.innerText;//如此便可过滤掉HTML标签。

      类似属性有textContent属性(DOM Level3定义的)。

    outerText属性:读取时和innerText返回值一样,设置时则不一样。

     12,DOM2 和 DOM3 

    12.1,DOM变化

    ※document.importNode(需要引入的节点[, 是否复制子节点]);//此方法的用途是从一个文档中取得一个节点,然后将其导入到另一个文档,使其成为这个文档结构的一部分。

    ※要确定文档的归属窗口,可以使用一下代码:

    var parentWindow =  document.defaultView || document.parentWindow; IE只支持后者

    ※要访问内嵌框架的文档对象,除了使用之前的frames集合之外,还可以通过元素直接取得这个文档对象,使用以下代码:

    var iframe = document.getElementById('myIframe');

    var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;

    12.2,样式[CSS]

    12.2.1,访问元素样式

    ※设置HTML元素的样式用三种方式:外部样式表、内嵌样式表、行内样式表。

    ※var div = document.getElementById('myDiv');

    div.style;//style属性包含着行内样式表中指定的所有样式信息,但不包括与外部样式表或内嵌样式表经层叠而来的样式。此属性是CSSStyleDeclaration的实例。

    div.style.color;//可以获取或设置div的样式。注意css的float属性,由于float本是javascript中的保留字。DOM2规定其在样式对象上相应的属性名是:cssFloat。IE支持的则是styleFloat.不过经测试现在的浏览器也支持直接的float属性。

    div.style还有一些属性和方法,详述如下:

    ※计算的样式

    style属性只包含行内样式,如果要取得元素从其他样式表(内嵌的或外部的)层叠的样式,可以使用document.defaultView对象的getComputedStyle()方法。

    document.defaultView.getComputedStyle(元素[,伪元素字符串]);//接收两个参数:要取得计算样式的元素和一个可选的伪元素字符串(如“:after”)。如果不需要伪元素信息,可以设置为null。同style属性一样,此方法返回一个CSSStyleDeclartion对象。

    IE不支持getComputedStyle()方法,但它有一个类似的概念。在IE中,每个具有style属性的元素还有一个currentStyle属性。这个属性是CSSStyleDeclaration的实例,包含当前元素全部计算后的样式。var comptedStyle = div.currentStyle;

    注意:所有的计算样式都是只读的,不能做修改

    12.2.2,操作样式表

    CSSStyleSheet类型表示的是样式表,包括通过<link>元素包含的外部样式表和通过<style>元素定义的内嵌样式表。注意,不包含行内样式。每个<link>元素或<style>元素对应一个CSSStyleSheet对象。对于HTMLLinkElement或HTMLStyleElement而言,可以直接通过sheet属性获取此元素对应的CSSStyleSheet对象。IE不支持sheet而是支持styleSheet属性。

      var link = document.getElementsByTagName('link')[0];

      var sheet = link.sheet || link.styleSheet;

    document.styleSheets;//返回文档的所有样式表的集合。集合中包含所有的CSSStyleSheet类型。

    CSSStyleSheet中的属性除了disabled是可读可写的之外(将此属性设置为true可以禁用此样式表),其他的属性都是只读的。

    12.2.3,元素大小

    ※偏移量

    可以通过HTMLElement的以下属性获取元素的偏移量:这些属性都是只读的。

    注: offsetParent属性最外层是<body>元素, 如果对body元素使用offsetParent属性则返回null

    ※,客户区大小

    元素的客户区大小指的是元素内容及其内边距padding所占空间的大小。可以通过HTMLElement元素的以下属性获取客户区大小:这些属性是只读的。

    clientWidth;//内容宽度加上左右内边距

    clientHeight;

    cilentTop;//实际指的是上边框的大小。

    clientLeft;//实际指 的是左边框的大小。

    注释:若通过此属性获取document.body或document.documentElement元素的大小,因为有滚动条的存在,返回值并不是我们想要的结果。请看下面的滚动大小。

    ※,滚动大小

    注意:对于不包含滚动条的页面而言,scrollWidth / scrollHeight 和 clientWidth / clientHeight之间的关系并不清晰。没有什么规律可言。在确定文档的总高度时,必须取得scrollWidth/clientWidth 和 scrollHeight/clientHeight中的最大值。

    ※,确定元素大小:

    HTMLElement.getBoundingClinetRect();//无参数,返回一个矩形对象,包含6个属性:

    元素的宽度,包含边框。

    height:元素的高度,包含边框。

    top: 上边框距离坐标原点的垂直距离。

    bottom:下边框距离坐标原点的垂直距离。

    left:左边框距离坐标原点的水平距离。

    right:有边框距离 坐标原点的水平距离。

    坐标原点一般都是指(0,0)坐标点,但IE8及之前设为(2,2)。另外这些属性会受到滚动的影响。

    12.3 遍历 Traversal

    "DOM2级遍历和范围"模块定义了两个可以遍历DOM结构的类型:NodeIterator 和 TreeWalker。这两个类型都能基于给定起点对DOM结构执行深度优先(depth-first)的遍历操作。

    ※NodeIterator:

    var iterator = document.createNodeIterator();//4个参数

    关于whatToShow参数:

    关于filter参数:filter参数可以是个对象或一个函数,具体如下:

        //filter 为对象时:
        var filer = {
            acceptNode: function (node)
            {
                return node.tagName.toLowerCase() == 'p' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
            }
        };
        //filter 也可以是一个与acceptNode()方法类似的函数:
        var filter = function (node)
        {
            return node.tagName.toLowerCase() == 'p' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
        };

    iterator有两个方法:nextNode() 和 previousNode()。这里这个指针有点不好理解。

    ※TreeWalker

    var walker = document.createTreeWalker();// 同上4个参数。唯一不同的是第三个参数,见下说明:

    walker除了有nextNode()和previousNode()方法之外,还有如下属性和方法:

    currentNode属性:获取或设置当前所在节点位置。

    parentNode();//返回当前节点的父节点

    firstChild();//

    lastChild();//

    nextSibling();//下一个同辈节点

    previousSibling();//

    12.4,范围 Range

    "DOM级遍历和范围“模块定义了”范围“接口。

    由于比较繁琐,估计用到的也少,就不记录了。

    13,事件 Event

    13.1,事件处理程序:

    相应某个事件的函数叫做事件处理程序(又叫事件侦听器)。事件处理程序的名字以"on"开头。事件处理程序有以下几种:

    ①,HTML事件处理程序,如,

    <div id='div' onclick="console.log(event);">Hello World!</div>

    注意:

      这里onclick的值是函数调用,函数也可以是在<script>标签或外部文件中定义的。

      this值等于事件的目标元素。

      事件对象event保存在变量event中,可以直接访问,不用自己定义,也不用从函数的参数列表中读取。

    ②,DOM0级事件处理程序,如

    div.onclick = function(){do something};

    注意:

      以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

      此时,事件处理程序是在元素的作用域中运行的,即this引用的是当前元素

      如果对同一元素添加多个相同类型(如都为click事件)的事件处理程序,则后面会覆盖前面。

      可以通过如下方法删除事件处理程序:div.onclick = null;

    DOM2级事件处理程序

    "DOM2级事件"定义了两个方法,用于指定和删除事件处理程序:addEventListener()和removeEventListener().

    div.addEventListener('click', 匿名函数或函数名,boolean);//boolean为false表示表示在冒泡阶段调用此事件处理程序,为true则表示在捕获阶段调用事件处理程序。

    通过addEventListener()添加的事件处理程序只能使用removeEventListener()来删除。移除时传入的参数与添加时使用的参数必须相同。也就意味着如果addEventListener()中是通过匿名函数处理的,则无法被移除。

    注意:

      DOM2级事件处理程序也是在其依附的元素的作用域中运行。

      此种方法可以为同一元素添加多个相同类型(如都为click事件)的事件处理程序,事件会以它们被添加的顺序依次触发。

    ④,IE的事件处理程序,

    IE实现了DOM中类似的两个方法:attachEvent()和detachEvent()。两个方法接收相同的两个参数:事件处理程序名称,如'onclick'和事件处理程序函数。是在冒泡阶段调用。删除事件时也要求参数必须相同,匿名函数也无法被删除。

    注意:  

      此时事件处理程序是在全局作用域中运行的,因此this的值为window。

      此种方法也可以为同一个元素添加多个事件处理程序,但是触发的顺序和上面相反。

    13.2, 事件对象: event对象 【IE的事件对象有所不同】

    各种不同事件的 事件对象 所共有的属性和方法如下:

    其中:对象this始终等于currentTarget的值,而target则只包含事件的实际目标(也就是同心圆中最中间的那个元素)。

    13.4,事件类型

    13.4.1,UI事件(User Interface 用户界面)

    ※load事件(5种触发条件):

    ①当页面完全加载后(包括所有图像、javascript文件、css文件等外部资源)在window上触发,onload事件处理程序有两种方式:

      window.onload = function(event){}

      <body onload='alert("hello");'>//在window 上发生的任何事件都可以在<body>元素中通过相应的特性来指定(下同)。这只是为了向后兼容的一种权宜之计(因为在HTML中无法访问到window对象)

    ②在图像加载完毕后在图像上触发。

      注意一点:当创建新的<img>元素时,可以为其指定一个事件处理程序,以便图像加载完毕后给出提示。此时,最重要的是要在指定src属性之前先指定事件。新图像元素不是在将元素添加到文档后才开始下载,而是在设置了src属性就会开始下载。如果在图片已经下载好了之后解析器才读到img的onload事件,则这个onload事件不会起任何作用。可以如下验证:在window.onload函数中写img.onload事件是没有作用的! 【如果load事件放在之后,问题也不大,只要不打断点cpu的解析频率肯定大于图片加载速度,不过还是按规范理论走的好。】

    ③<script>元素也会触发load事件,以便确定动态加载的JavaScript文件是否加载完毕。与图像不同,只有在设置了<script>元素的属性并将该元素添加到文档后,才会开始下载JavaScript文件。

    ④,当所有框架都加载完毕后,在框架集上面触发。

    ⑤,当嵌入内容加载完毕后,在<object>元素上面触发。

    ※unload事件:window.onunload

    关闭窗口 或者 刷新窗口 或者 在当前窗口打开超链接(在新标签中打开超链接不会触发unload),就会发生unload事件。可以用如下代码验证:

        window.addEventListener('unload', function (e) {
            localStorage.clear();
        });

    而利用这个事件最多的情况就是清除引用,以避免内存泄露。关于unload事件注意一点:

    既然unload事件是在一切都被卸载之后才触发,那么在页面加载后存在的那些对象,此时就不一定存在了。此时,操作DOM节点或者元素的样式就会导致错误。比如下面的代码:

        window.addEventListener('unload', function (e) {
            alert("Unloaded");//并不会有效果,刷新很快时,可以看到浏览器提示Block alert(...) during unload;
         console.log(333);//刷新很快时,可以看见333 });

    ※resize事件: 在窗口或框架上触发。window.onresize

    ※scroll事件:可以在带有滚动条的元素上使用,也可以在window对象上使用。

    13.4.2,焦点事件

    注意一点:像div等这些元素默认不会得到焦点,设置了tabindex属性之后才可以获取焦点。

    ※focus / blur事件:不会冒泡。

    ※focusin / focusout 事件:会冒泡。

    13.4.3,鼠标事件

    DOM3级事件中定义了9个鼠标事件,如下

    鼠标事件对象中保存着一些有用的属性:

    ※鼠标指针位置属性:

    event.clientX: 鼠标指针在视口中的水平坐标。视口即意味着不考虑滚动。

    event.clinetY:

    event.pageX:鼠标光标在页面中的位置。页面而非视口,即意味着考虑了滚动。

    event.pageY:

    event.screenX:鼠标光标距离左侧屏幕的坐标。

    event.screenY

    ※ 修改键属性,即按下鼠标时是否按了shift,ctrl, alt, meta键。

    event.shiftKey / ctrlKey / altKey/ metaKey;//按了则返回true,否则返回false.

    ※鼠标按钮

    event.button ;// 0:鼠标左键, 1:鼠标中键,2:鼠标右键

    13.4.4,滚轮事件

    mousewheel事件【firefox不支持】:这个事件可以在任何元素上触发,最终会冒泡到document(IE)或window对象。

    滚轮事件的event对象中有一个特殊的wheelDelta属性,当用户向前滚动滚轮时,wheelDelta是120的倍数;当用户向后滚动鼠标滚轮时,wheelDelta是-120的倍数。

    firefox支持一个类似事件:DOMMouseScroll,经测试只能通过addEventListener()添加有效。当向前滚动时,其event.detail是3的倍数,否则是-3的倍数。

    13.4.5,键盘事件

    说明一点:当元素能够获取焦点时,在获取焦点的情况下,键盘事件会生效。

    keydown和keypress的区别是:

    按下一个按键时,首先会触发keydown事件,然后是keypress事件。

    keydown事件可以由键盘上的任意一个键触发,其event.keyCode返回的是键盘的代码,每个按键都对应唯一一个代码,包括esc键。

    keypress事件只能由字符键触发,keyCode返回的是ASCII码。event.charCode是此事件独有的属性,返回值也是ASCII码。

    键盘事件的event对象的属性:

    ※键码

    event.keyCode

    对于keyup和keydown事件,每个按键的对应键码如下:

    ※event.shiftKey / ctrlKey / altKey / metaKey;//如果按下的是这些键,返回true,否则返回fasle.

    13.4.6, 文本事件

    “DOM3级事件"引入了一个新的事件: textInput。[firefox不支持]

    此事件只能在可编辑区域才能触发。

    event.data;// 具体的字符,如"s", "S"

    13.4.7,复合事件  【DOM3级】

    用于处理IME(Input Method Editor输入法编辑器)输入序列。比如当开启中文输入法后输入字符则会触发这些事件。

    13,.4,8,变动事件 mutation 【DOM2级】

    当文档的结构发生变化时会触发这些事件。如增加 移除节点等等

    13.4,9,HTML5事件

    ※contextmenu 事件

    可以设置点击鼠标右键时弹出的菜单。可以在任何元素上设置,最终会冒泡到document。此事件属于鼠标事件,其event对象包含与光标位置有关的所有属性。通常使用onclick事件处理程序来隐藏该菜单(浏览器默认就是如此)。 

    一个例子: 

    <!DOCTYPE html>
    <html>
    <head>
        <title>ContextMenu Event Example</title>
    </head>
    <body>
    <div id="myDiv">Right click or Ctrl+click me to get a custom context menu.
        Click anywhere else to get the default context menu.
    </div>
    <ul id="myMenu" style="position:absolute;visibility:hidden;background-color:
    silver">
        <li><a href="http://www.nczonline.net">Nicholas's site</a></li>
        <li><a href="http://www.wrox.com">Wrox site</a></li>
        <li><a href="http://www.yahoo.com">Yahoo!</a></li>
    </ul>
    </body>
    <script>
        window.addEventListener("load", function (event) {
            var div = document.getElementById("myDiv");
            document.addEventListener("contextmenu", function (event) {
                event.preventDefault();
                var menu = document.getElementById("myMenu");
                menu.style.left = event.clientX + "px";
                menu.style.top = event.clientY + "px";
                menu.style.visibility = "visible";
            }, false);
            document.addEventListener("click", function (event) {
                document.getElementById("myMenu").style.visibility = "hidden";
            }, false);
        }, false);
    </script>
    </html>
    View Code

    ※beforeunload事件

    当刷新或离开页面之前会触发此事件,可以告诉用户此页面将被卸载,询问用户是否真的要关闭页面。为了显示这个弹出对话框,必须将event.returnValue的值设置为要显示给用户的字符串,同时作为函数的值返回。例子如下:

        window.addEventListener('beforeunload', function (event) {
            var msg = 'you wanna leave?';
            event.returnValue = msg;
            return msg;
        }, false);

    ※DOMContentLoaded事件

    window的load事件会在页面中的一切都加载完毕时触发。而DOMContentLoaded事件则在形成完整的DOM树之后就会触发,不理会图像,JavaScript文件,CSS文件或其他资源是否已经下载完毕。此事件可以在window或document上添加。

    ※readystatechange事件

    document.onreadystatechange = function(e){alert(document.readyState);}// loading  interactive complete

    ※pageshow 和 pagehide事件

    ※hashchange事件

    window.onhashchange;

    event.oldURL;//旧的网址

    event.newURL;//新的网址

    13.4.10,设备事件[手机等]

    ※orientationchange事件

    window.onoritationchange = function(){};

    window.orientation;//此属性有三个值:0,90,-90

    ※触摸事件

    在document或window上。

    ※手势事件

    貌似只有ios的safari支持。

    13.5,内存和性能

    13.5.1,事件委托

    事件委托利用了事件冒泡原理。可以减少时间处理程序的数量。对于同一类型的事件,事件委托技术只需要为整个页面指定一个事件处理程序,原则是在DOM树中尽量最高的层次上添加一个事件处理程序,经常是在document对象上添加。通过event.target识别具体元素。

    13.5.2,移除事件处理程序

    在不需要的时候移除事件处理程序,也可以回收内存。有两种情况会造成“空事件处理程序”:

    ①,从文档中移除带有事件处理程序的元素时。比如,removeChild() 和 replaceChild(),更多的是使用innerHTML替换页面某一部分的时候。比如:

    ②卸载页面的时候。

    13.6,模拟事件

    即不通过用户操作交互就可以触发事件。之前的做法是通过 createEvent() 和 dispatchEvent()来模拟[IE有自己的方法],现在是通过事件构造器如 var event = new KeyboardEvent(typeArg, KeyboardEventInit);

    https://developer.mozilla.org/zh-CN/docs/Web/API/KeyboardEvent

    14.0表单脚本

    ※document.forms;//取得页面中所有的表单。可以通过数值索引或name值取得特定的表单。

    ※document.forms[0].elements;//取得页面中某个表单的所有控件。

    ※HTMLFormElement独特的属性和方法如下:

    form.submit();//不会触发onsubmit事件

    form.reset();//会触发onreset事件

    ※表单独有事件

    form.onsubmit

    form.onreset

    form.onchange;//

    14.1,表单字段,如input button select 等等。

    ※提交按钮可以使用图像,如下:

    <input type='image' src='xxx.gif' />

    ※表单字段(如input ,button, select等)共有的属性和方法

    HTML5表单字段中新增了一个autofocus属性。

    ※,默认情况下,只有表单字段可以获得焦点。对于其他元素而言,如果先将其tabIndex属性设置为-1,然后在再调用focus()方法,也可以让这些元素获得焦点。只有Opera不支持这种技术。

    ※表单字段的共有事件:

    14.2,文本框脚本:<input> 和 <textarea>

    ※<input type='text' size='11' maxlength='20' />;//size表示可以显示的字符数,maxlength表示最多接收的字符数。

    ※textbox.select();//表单元素的select方法,用于选中文本框中的所有文本。

    ※textbox.onselect = function(){};//选择事件,在选择了文本框中的文本时触发。

    ※textbox.selectionStart / textbox.selectionEnd;//两个属性,用于取得所选文本 。

    ※textbox.setSelectionRange(start,end);//

    14.3, 过滤输入

    ※屏蔽字符:响应向文本框中插入字符的操作是keypress事件,因此可以通过阻止这个事件的默认行为来屏蔽某些字符。代码如下:

        var textbox = document.getElementById("textbox");
        textbox.onkeypress = function (e) {
    //        console.log(e.charCode);
            //只允许数字输入,其他可输入字符都屏蔽掉
            if (!/d/.test(String.fromCharCode(e.charCode))) {
                e.preventDefault();
            }
        }

    ※操作剪切板[注意:这个不只是 针对文本框,其他元素也能用这些事件]

    6个剪切板事件:

    ※一些用于验证的属性和方法

    textbox.checkValidity();//检测字段是否有效

    textbox.validity;

    form.novalidate;//设置表单不进行验证。可以在HTML元素中添加这个字段,另外在提交按钮上添加formnovalidate属性可以在此按钮提交时不进行验证,但在其他按钮上提交正常验证。

    14.4,选择框脚本:<select>与<option>

    ※HTMLSelectElement类型有如下属性和方法:

    ※HTMLOptionElement有如下属性和方法:

    14.5,表单序列化

    表单字段数据是如何发送给服务器的:

    14.6,富文本编辑

    ※ 所谓富文本编辑就是在网页中编辑内容,就像博客园的随笔功能一样。有两种方法实现富文本编辑:

    ①,使用html元素的contenteditable属性[布尔属性]。这个属性可以给页面中的任何元素,然后就可以编辑此元素。通过javaScript可以设置这个属性的值,如,div.contentEditable = true / false / inherit

    ②,在页面中嵌入一个包含空HTML页面的的iframe。通过设置designMode属性[注:designMode属性是属于document对象的。],这个空白的HTML页面可以被编辑,而编辑对象则是该页面<body>元素的HTML代码。designMode有两个可能的值:"off"[默认值] 和 "on"。在设置为‘on'时,整个文档就可以编辑。可以为空白页面应用CSS样式。但要注意一点,designMode属性只有在页面完全加载之后才能设置,因此需要使用onload事件处理程序来设置designMode。如下:

    window.onload = function(){frames['iframe'].document.designMode = 'on';}

    ※操作富文本

    操作富文本一般是通过按钮执行的(当然,不通过按钮也可以,但是需要先把焦点聚焦到需要执行编辑的地方)。就像博客园上面的编辑按钮一样。主要是通过document.execCommand()方法执行预定义的命令。此方法接收三个参数:命令名称,表示浏览器是否应该为当前命令提供用户界面的一个布尔值,和执行命令必须的一个值(如果不需要可以设为null)。一般第二个参数都设为false。下图列出了被多数浏览器支持的命令(注,cut / copy / paste并没有被支持,一般浏览器都是用快捷键操作的):

    案例代码如下:

       <iframe src="test.html" name="xxx">iframe</iframe>
        <button id="myBtn">按钮纽</button>
    <script>
        var btn = document.getElementById("myBtn");
         window.onload = function (e) {
            var xx = frames['xxx'].document;
            xx.designMode = 'on';
            btn.onclick = function () {
                xx.execCommand('bold', false, null)
            }
        }
    </script>

    另外:此方法同样也适用于页面中contenteditable属性为true的区块,只要在当前窗口的document对象上应用execCommand()方法即可。

    其他一些与富文本操作命令有关的的方法:

    document.queryCommandEnabled(“bold");//此方法接收一个参数,即要检测的命令。此方法用于检测传入的命令是否可用(即浏览器是否支持此命令)。

    document.queryCommandState("bold");//同上,接收一个命令参数,用于确定是否已经将指定命令应用到了选择的文本。可以利用这个方法更新按钮的状态。

    document.queryCommandValue('bold');//同上,接收一个命令参数,用于取得执行命令时传入的值。

    ※富文本选区:略

    ※富文本作为表单提交:

    富文本内容是用iframe实现的,所以富文本编辑器中的HTML不会被自动提交给服务器。但是我们可以变相提交。即提交表单之前,从iframe中提取出HTML,并将其插入到隐藏的字段中。代码如下:textbox.value = frames['xxx'].document.body.innerHTML;

    15,使用canvas画图

    var cvs = document.getElementById('canvas');

    var ctx = document.getContext('2d');

    var imgURI = cvs.toDataURL("image/png");//传入一个MIME类型。用于导出在canvas元素上绘制的图像。

    15.1,2d 省略

    15.2, WebGL:是针对canvas的3D上下文。非W3C指定的标准,而是由Khronos Group指定的。WebGL是基于OpenGL ES 2.0制定的。www.opengl.org  www.learningwebgl.com  http://www.hiwebgl.com/?p=42  {这是中文教程,很好。想学可以看看这个网站}

    ※ArrayBuffer:数组缓冲类型。每个ArrayBuffer对象表示的是内存中指定的字节数,但不会指定这些字节用于保存什么类型的数据。通过ArrayBuffer所能做的,就是为了将来使用而分配一定数量的字节。例如,var buffer = new ArrayBuffer(20);//在内存中分配20B。

    ※var gl = cvs.getContext('webgl');//获取3d上下文。不仅浏览器要支持,显卡也要支持。

     

    16,HTML5脚本编程

    16.1,跨文档消息传递 cross-document messaging

    ※只要 协议,主机,端口三者有一个不同,则就是跨文档。

    ※window.postMessage(msg, origin);//第一个参数是消息字符串,第二个参数表示消息接收方来自哪个域的字符串。关于此方法说明如下:

    ①,此方法只能在dom加载完毕后使用,否则会出现意想不到的错误。即在window.onload 或btn.onclick之类的事件中使用。

    ②调用此方法的是window对象,而且需要向哪个window传递消息就在哪个window上调用此方法

    ③接收消息的文档需要使用window对象的message事件,此事件是异步触发的。事件对象event包含三个重要属性:

      ■data:接收到的消息字符串,即是postMessage的第一个参数

      ■origin:发送消息的文档所在域

      ■source:发送消息的文档的window对象的代理。注意这个只是window对象的代理,并非实际的window对象。也就是说,不能通过这个代理对象访问window对象的其他任何信息。这个代理对象主要用于向发送消息的文档反馈信息,即在此window代理对象上调用postMessage()方法。

    一个例子如下:

    http://localhost/   下的发送消息的文档代码:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8">
    </head>
    <body>
    <iframe src="http://tong/html/sss.html" name="ifr" id="ifr">iframe</iframe>
    <button id="btn">GOO</button>
    <div id="div">这里显示反馈消息</div>
    </body>
    <script type="text/javascript">
        var btn = document.getElementById('btn');
        var div = document.getElementById('div');
        //鼠标点击事件来发送消息
        btn.onclick = function (e) {
            //postMessage()的第二个参数和iframe中的src必须一致。
            frames['ifr'].postMessage('send a message to iframe["ifr"]', 'http://tong');
        };
    //    //或者在DOM加载完毕后就发送消息
    //    window.onload = function () {
    //        frames['ifr'].postMessage('send a message to iframe["ifr"]', 'http://tong');
    //    };
    
        //接收反馈信息
        window.onmessage = function (e) {
            div.innerHTML = e.data;
        }
    </script>
    </html>

    http://tong/  下接收消息的文档sss.html代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    这是框架sss.html
    </body>
    <script>
        //接收消息
        window.onmessage = function (e) {
            document.body.innerHTML = e.data;
            //向发送消息的文档反馈信息
            e.source.postMessage('Received', 'http://localhost')
        }
    
    </script>
    </html>

     16.2,原生拖放

    16.3,媒体元素:<video>和<audio>标签

    16,4,历史状态管理:[HTML权威指南中27.6.2章节]

    主要作用是改变同一个页面的状态(如查询字符串的改变,hash的改变等等不会新增页面的页面内部的变化)时,可以使用后退或前进按钮在不同的状态间切换。具体细节略。

    pushState() / replaceState()

     

    17,错误处理和调试

    ※try{...} catch(error){...} finally{...}

    catch和finally只要有一个即可,无论如何finally子句都会执行,即使在try或catch语句中包含return语句都不会阻止finally子句的执行。

    此函数返回0,如果finally子句中没有return语句则返回2.

    ※错误类型:7种

    ①Error;//基类型,其它错误都继承自该类型。此类错误很少见,主要目的是工开发人员抛出自定义错误。

    ②EvalError;//和eval()函数有关的异常。浏览器并没有按标准执行,遇到这类错误的可能性极小。

    ③RangeError;//数值超出相应范围时触发。如:var a = new Array(-2);

    ④ReferenceError;//在找不到对象的情况下触发。通常在访问不存在的变量时就会发生这类错误。

    ⑤SyntaxError;//语法错误。把语法错误的JavaScript字符串传入eval()函数时,就会导致此类错误。在eval()函数之外的语法错误会导致JavaScript代码立即停止执行。

    ⑥TypeError;//

    ⑦URIError;///使用encodeURI()或decodeURI()函数时,如果URI格式不正确,就会导致URIError。

    利用不同的错误类型:

    ※抛出错误

    throw操作符,用于随时抛出自定义错误。throw的值可以是任何类型。在遇到throw操作符时,代码会立即停止执行。仅当有try-catch语句捕获到被抛出的值时,代码才会继续执行。

    throw 1234;//1234

    throw true;//true

    通过使用某种内置错误类型,可以模拟浏览器错误。每种错误类型接收一个参数,即实际的错误消息。

    throw new RangeError('error occured');//模拟浏览器抛出错误

    还可以通过原型链创建自定义错误类型,此时需要为新创建的错误类型指定name和message属性。例子如下:

        function CustomError(msg) {
            this.name = 'CustomeError';
            this.message = msg;
        }
        CustomError.prototype = new Error();
        throw new CustomError('My Message');//Uncaught CustomeError: My Message 

    ※错误事件

    window.onerror = function(msg,url,line){...};//这种DOM0级事件处理程序处理错误事件时,参数不是event对象,而是三个分别表示错误消息,文档位置和行号的参数。但是如果用DOM2级事件处理程序(即addEventListener()),就只传入一个event对象了。

    错误事件对图像也适用。无论是DOM0还是DOM2级事件处理程序都是只传入一个event对象。

    ※ 记录错误日志(一个技术:利用img对象)

    ※错误调试

    console对象的一些方法:

     

    20,JSON

    JSON:JavaScript Object Notation。JSON是一种数据格式,而不是一种编程语言。很多语言都有针对JSON的解析器和序列化器。

    20.1,语法

    20.1.1,简单值:

    JSON的简单值如:5,“hello world”,true,null等等。注意一点,JSON的字符串必须用双引号(单引号会导致语法错误)

    20.1.3,对象:

    JSON中的对象,如:{"name":"tong","age":11}。注意一点:JSON对象中的属性必须加双引号【JavaScript中的对象字面量中的属性可以加也可以不加】。

    20.1.4,数组:

    JSON中的数组,如:[25, "hi", true]。

    20.2,解析与序列化

    ※,早期JSON解析器基本是使用eval()函数。ECMAScript 5对解析JSON的行为进行了规范,定义了全局对象JSON

    20.2.1,JSON对象:

    ※序列化:JSON.stringify();//接收一个JavaScript对象,返回序列化之后的JSON字符串。此方法还可以接收另外两个参数,具体见下面序列化选项。

    ※解析:JSON.parse();//接收一个JSON字符串,将其解析为原生的JavaScript值。注意,如果不是把JSON字符串保存在变量中传给此函数,而是直接把JSON字符串传进去,则需要注意,如:JSON.parse('"Hello World"');//JSON字符串是”hello world“,但是传进去的时候还需要用单引号把它围起来。又如,JSON.parse("[1,3,4]");//数组需要用单引号或双引号围起来,对象也是。此方法还可以接收另外一个参数,具体见下面解析选项。

    ※序列化选项:

    JSON.stringify();//除了第一个参数之外,还可以接收另外两个可选参数。第二个参数是个过滤器,可以是一个数组,也可以是个函数。第三个参数是个选项,表示是否在JSON字符串中保留缩进。

    ①过滤器

    如果过滤器参数是个数组,那么JSON.stringify()的结果中将值包含数组中列出的属性。例子如下:

        var o = {"a": "aa", b: 'bb', c: 'cc'};
        var jsonText = JSON.stringify(o,["a",'b']);
        console.log(jsonText);//{"a":"aa","b":"bb"}

    如果第二个参数是函数,此函数接收两个参数:属性名和属性值。具体用法见例子:

        var o = {"a": "aa", b: 'bb', c: 'cc'};
        var ss = JSON.stringify(o, function (key, value) {
            /**
             * 这里注意,对象o中的键和值会依次传给此函数,但是第一次传的键为空字符串,
             * 而值为整个对象o。
             */
    //        console.log('GOD',value);
            switch (key) {
                case "a":
                    return value.toUpperCase();
                    break;//可有可无
                case "b":
                    return undefined;//无效的JSON值,返回的JSON字符串中将跳过此属性(b)。
                default:
                    /**第一次传值给函数时直接到这里,然后原始的整个对象(o)就会被替换为这里的返回值,
                     * 然后再用这个返回值依次传值给函数。所以这里的返回值不能乱用,直接用value就行
                     */
                    return value;
            }
        });
        console.log(ss);//{"a":"AA","c":"cc"}

    ②字符串缩进

    第三个参数用于控制结果中的缩进和空白符。此参数可以是数值或字符串。如果是数值,表示结果字符串中每个级别缩进的空格数。如果是字符串,则这个字符串将在JSON字符串中被用作缩进字符(不再使用空格)。另外注意,无论数值还是字符串,最大都是10,超过10都会被视为10。例子如下:

        var o = {"a": "aa", b: {bb:'bbb',bbb:"bbbb"}, c: 'cc'};
        var jsonText1 = JSON.stringify(o,null,4);
        var jsonText2 = JSON.stringify(o,null,'----');
        console.log(jsonText1);
        console.log(jsonText2);

    ③toJSON()方法:

    toJSON()方法也是JSON.stringify()方法的一部分。可以为任何对象添加toJSON()方法,然后调用对象上的toJSON()方法,返回其自身的JSON数据格式。具体见如下描述:

    一个例子:

        var book = {
            "title": "Professional JavaScript",
            "authors": [
                "Nicholas C. Zakas"
            ],
            edition: 3,
            year: 2011,
            toJSON: function () {
                return this.title;
            }
        };
        var jsonText = JSON.stringify(book);
        console.log(jsonText);//"Professional JavaScript"

    ※,解析选项

    JSON.parse()方法也可是接收另一个参数,这个 参数和stringify()的第二个参数为函数时类似,被称为还原函数。

    日期字符串被还原成了日期对象。

    21,Ajax 和 Comet 

    ※,ajax技术就是 无需刷新页面即可从服务器取得数据,但数据不一定是XML数据,ajax通信与数据格式无关。

    22,高级技巧

    22.1,高级函数

    22.1.1, 安全的类型检测

    22.1.2,作用域安全的构造函数

    22.1.3,惰性载入函数(两种方法)

    22.1.4,函数绑定(之前讲到过,就是bind()函数的使用)

    22.1.5,函数柯里化

    22.2,防篡改对象

    对于对象属性,前面介绍过如何手工设置每个属性的[[Configrable]]、[[Writable]]等等特性,以改变属性的行为。同样,ECMAScript5也增加了几个方法用以指定对象的行为。

    注意:一旦把对象定义为防篡改,就无法撤销了。如果不是这样,定义防篡改也就没什么意义了。

    22.2.1,不可扩展对象

    Object.preventExtensions(obj);//obj对象将不能被添加新的属性和方法,但已有属性仍可以修改和删除。

    Object.isExtensible(obj);//确定对象是否可以扩展。

    22.2.2,密封的对象[sealed object]

    密封对象不可扩展,而且已有成员的[[Configurable]]特性会被设置为false。这就意味着不能删除属性和方法, 也不能使用Object.defineProperty()把数据属性修改为访问器属性,或者把访问器属性修改为数据属性。但是密封对象的属性值是可以修改的。

    Object.seal(obj);//将对象密封。

    Object.isSealed(obj);//确定对象是否被密封了。密封对象也是不可扩展的。

    22.2.3,冻结的对象[frozen object]

    最严格的防篡改级别是冻结对象。冻结对象即不可扩展,又是密封的。而且对象数据属性的[[Writable]]特性会被设置为false。如果定义[[Set]]函数,访问器属性仍然是可写的。

    Object.freeze(obj);//冻结对象。

    Object.isFrozen(obj);//检测对象是否被冻结。冻结对象既是密封的又是不可扩展的。所以用Object.isExtensible()和Object.isSealed()检测冻结对象分别返回false和true.

    22.3, 高级定时器

    setInterval()和setTimeout()可以创建定时器。人们对于JavaScript的定时器存在普遍的误解,认为它们是线程,其实JavaScript是运行于单线程的环境中的,而定时器仅仅只是计划代码在未来的某个时间执行。执行的时机是不能保证的,因为在页面的生命周期中,不同时间可能有其他代码在控制JavaScript进程。

    除了主JavaScript执行进程外,还有一个需要在进程下一次空闲执行的代码队列。关于定时器最重要的事情是,指定的时间间隔表示何时将定时器的代码添加到队列,而不是何时实际执行代码。因为添加到队列的代码不意味着它会被立即执行,而只能表示它会尽快执行。比如,设定一个150ms后执行的定时器不代表他会在150s后立刻执行,而是表示代码会在150ms后被加入到队列中。如果在这个时间点上,队列中没有其他东西,那么这段代码就被立即执行,否则代码需要等待更长时间才能执行。

    22.3.1, 重复的定时器

    setInterval()创建的重复定时器有一些缺点,所以通常使用链式setTimeout()调用来模拟setInterval()。代码如下:

        setTimeout(function () {
            //这里做一些处理
            
            setTimeout(arguments.callee, 2000)
        },2000)

    22.3.2,柔和的处理(Yielding Processes)

    桌面应用往往能够随意控制它们要的内存大小和处理器时间,但是javaScript被严格限制了,以防止恶意的Web程序员把用户的计算机搞挂了。其中一个限制就是长时间运行脚本的制约。如果代码运行时间过长,浏览器就会停止运行报错。脚本长时间运行往往是以下两个原因造成的:过长的、过深嵌套的函数调用 或者是 进行大量处理的循环。后者是较为容易解决的问题,可以使用定时器分割这个循环。这是一种叫做数组分块(array chunking)的技术,小块小块地处理数组。在数组分块模式中,array变量本质上就是一个“待办事宜”列表,它包含了要处理的项目。具体代码如下:

        function chunk(array, process, context) {
            setTimeout(function () {
                var item = array.shift();
                process.call(context, item);
                if (array.length > 0) {
                    setTimeout(arguments.callee, 100);
                }
            }, 100);
        }
        var data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25];
        function printValue(item) {
            var div = document.getElementById("myDiv");
            div.innerHTML += item + "<br > ";
        }
        chunk(data, printValue);//调用chunk函数处理数据的同时,data数组中的条目也在改变。如果想保持原数组不变,可以将该数组的克隆传递给chunk(),即chunk(data.concat(),printValue);

    运行后会有一个延迟出现的效果。

    22.3.3,函数节流

    浏览器中的DOM操作比起非DOM操作交互需要更多的内存和CPU时间。连续尝试进行过多的DOM相关操作可能会导致浏览器挂起,甚至崩溃,在使用onresize事件处理程序时容易发生。为类绕开这个问题,可以使用定时器对该函数进行节流。

    函数节流的基本思想是指,某些代码不可以在没有间断的情况下连续重复执行。代码如下:

        //节流函数throttle
        function throttle(method, context) {
            clearTimeout(method.tId);//method是个函数,函数的本质也是对象,可以为其设置属性。此属性初次执行时并不存在。
            method.tId = setTimeout(function () {
                method.call(context);
            }, 100)
        }
        function resizeDiv() {
            var div = document.getElementById('myDiv');
            div.style.height = div.offsetWidth + 'px';
        }
        window.onresize = function () {
            /**无论onresize被触发的有多频繁,通过节流函数throttle都可以保证
             * resizeDiv函数只在最后一次resize被触发后的100ms之后执行一次
             */
            throttle(resizeDiv);
        }

     

    22.4,自定义事件

     事件是与DOM交互最常见的方式,但事件也可以用于非DOM代码中--通过自定义事件。自定义事件背后的概念是 创建一个管理事件的对象,让其他对象监听那些事件。具体代码如下:

    <script type='text/javascript'>
        function EventTarget() {
            this.handlers = {};
        }
        EventTarget.prototype = {
            constructor: EventTarget,
            addHandler: function (type, handler) {
                if (typeof this.handlers[type] == "undefined") {
                    this.handlers[type] = [];
                }
                this.handlers[type].push(handler);
            },
            fire: function (event) {
                if (!event.target) {
                    event.target = this;
                }
                if (this.handlers[event.type] instanceof Array) {
                    var handlers = this.handlers[event.type];
                    for (var i = 0, len = handlers.length; i < len; i++) {
                        handlers[i](event);
                    }
                }
            },
            removeHandler: function (type, handler) {
                if (this.handlers[type] instanceof Array) {
                    var handlers = this.handlers[type];
                    for (var i = 0, len = handlers.length; i < len; i++) {
                        if (handlers[i] === handler) {
                            break;
                        }
                    }
                    handlers.splice(i, 1);
                }
            }
        };
    //使用自定义事件
    function handleMessage(event) { alert("Message received: " + event.message); } //create a new object var target = new EventTarget(); //add an event handler target.addHandler("message", handleMessage); //fire the event target.fire({type: "message", message: "Hello world!"}); //remove the handler target.removeHandler("message", handleMessage); //try again - there should be no handler target.fire({ type: "message", message: "Hello world!"}); </script>

     使用自定义事件有助于将不同部分的代码相互之间解耦,

    22.5,拖放(未看)

    23,离线应用和客户端存储

    23.1,离线检测

    navigator.onLine;//表示设备能否上网。

    window.ononline事件:从离线变为在线时触发

    window.onoffline事件:从在线变为离线时触发

    23.2,应用缓存[application cache或appcache]

    HTML5的应用缓存是专门为开发离线web应用而设计的。简单说来就是在网络可用时,下载一些必要的文件以便离线时仍然可以使用这些资源。具体细节省略。

    23.3,数据存储

    数据存储指的是直接在客户端上存储用户的信息,如登陆信息,偏好设定或其他数据。有如下实现方式:

    23.3.1,cookie

    ※全称为HTTP Cookie。服务器对任意HTTP请求发送Set-Cookie HTTP头作为响应的一部分。cookie的性质是绑定在特定的域名下的。当设定了一个cookie后,再给创建它的域名发送请求时,都会包含这个cookie(而其他域名无法访问到这个cookie中的信息)。

    ※cookie的构成:

    cookie有浏览器保存的一下几块信息构成:

    ①名称:cookie的名称必须是经过URL编码的。关于URL编码,简单说来就是在十六进制ascii码前面加上百分号。对于普通字符如字母数字等可以编码也可以不编码,但对于特殊字符(特殊字符包含部分ascii码中的内容如&=等,也有非ascii字符,如中文字符等等)则必须编码。三个字节的中文字符的URL编码方法是在每个字节的十六进制前面加上百分号。具体内容见如下网址:http://www.cnblogs.com/jerrysion/p/5522673.html 

    ②值:值也必须被URL编码

    ③域:表明cookie对于哪个域是有效的。所有项该域发送的请求中都会包含这个cookie信息。如果没有明确设定,那么这个域 会被认为来自设置cookie的那个域。这个值可以包含子域(如,www.xxx.com),也可以不包含(如,.xxx.com,则对于xxx.com的所有子域都有效)。

    ④路径:路径是对域的补充。对于指定了路径的cookie,这只有此路径才能访问cookie,如http://www.xxx.com/books/下设定了cookie,则cookie只会向此路径发送cookie,而http://www.xxx.com则不会被发送cookie,即使请求都是来自同一个域的。

    ⑤失效日期:表示cookie何时应该被删除的时间戳(也就是,何时应该停止向服务器发送这个cookie)。默认是绘话结束时即将所有cookie删除。如果设置的失效时期是当前时间以前的时间,这cookie会被立即删除。日期格式为GMT格式,如:Mon, 22-Jan-07 07:10:22 GMT。

    ⑥安全标志:指定后,cookie只有在使用SSL连接的时候才发送到服务器。例如,cookie信息只能发送给https://www.xxx.com,而http://www.xxx.com的请求则不能发送cookie。

    服务器端设置一个完整的cookie的例子如下,分号加空格分割每一段:

    Set-Cookie: name=value; expires=Mon, 22-Jan-2017 11:11:11 GMT; domain=.xxx.com; path=/; secure

    ※JavaScript中的cookie

    document.cookie可以获取cookie值,也可以设置cookie值

    ①,获取cookie时,得到一系列由分号隔开的键值对,所有的名字和值都是经过URL编码的所以必须使用decodeURIComponent()来解码。

    ②,设置cookie时,这个cookie字符串会被添加到现有的cookie集合中,设置document.cookie不会覆盖cookie,除非设置的cookie的名称已经存在。设置cookie的格式和Set-Cookie头中使用的格式是一样的,例如:

    document.cookie = "name=tong; domain=.xxx.com; path=/; secure";

    实际操作中,最好把名称和值通过encodeURIComponent()编码下,如下:

    document.cookie=encodeURIComponent('name') + '=' + encodeURIComponent('tong') + "; domain=.xxx.com; path=/; secure"。

    ③由于JavaScript中读写cookie不是很直观,所以可以用一些函数来简化cookie的功能,代码如下:

    <script type="text/javascript">
        var CookieUtil = {
            get: function (name) {
                var cookieName = encodeURIComponent(name) + "=",
                        cookieStart = document.cookie.indexOf(cookieName),
                        cookieValue = null;
                if (cookieStart > -1) {
                    var cookieEnd = document.cookie.indexOf(";", cookieStart);
                    if (cookieEnd == -1) {
                        cookieEnd = document.cookie.length;
                    }
                    cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd));
                }
                return cookieValue;
            },
            set: function (name, value, expires, path, domain, secure) {
                var cookieText = encodeURIComponent(name) + "=" + encodeURIComponent(value);
                if (expires instanceof Date) {
                    cookieText += "; expires=" + expires.toGMTString();
                }
                if (path) {
                    cookieText += "; path=" + path;
                }
                if (domain) {
                    cookieText += "; domain=" + domain;
                }
                if (secure) {
                    cookieText += "; secure";
                }
                document.cookie = cookieText;
            },
            unset: function (name, path, domain, secure) {
                this.set(name, "", new Date(0), path, domain, secure);
            }
        };
    //使用
    CookieUtil.set('name','Tong',new Date('2017-7-19'),null,'.xxx.com');
    </script>

    ※子cookie:

    由于某个域名下的cookie有数量限制(不同浏览器限制不同,一般是30个-50个),如果担心数量不够用,可以使用子cookie,也就是使用单个cookie记录多个cookie的键值对,如:

    documen.cookie="data=name=tong&age=11&address=china";

    ※cookie信息越大,完成对服务器请求的时间也越长,所以最好尽可能在cookie中少存储信息,避免影响性能。另外,cookie数据并非存储在一个安全环境中,所以cookie中的保存的都是不重要的数据。

    23.3.2,IE用户数据(略)

    23.3.3,Web存储机制

    Web Storage的目的是克服由cookie带来的一些限制。

    具体内容参见html学习笔记第25条记录。 

    23.3.4,IndexedDB (Indexed Database API,索引数据库API)

    虽然 Web Storage 对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB提供了一个解决方案。

    参见下面两篇文章:

     http://www.tfan.org/using-indexeddb/  [很详细,下面代码主要就是看这篇文章]

     http://web.jobbole.com/81793/ 

     
        var request;
    //第一个参数为数据库名称,第二个参数可选,为数据库版本
    request = indexedDB.open('database', 40);
    /**
    * onblock事件主要处理并发问题(好几个标签打开了这个数据库)。后面还有一个onversionchange事件处理程序也是处理并发问题的.
    */
    request.onblocked = function (e) {
    alert('请先关闭已经打开的本网站,然后重新加载此网页');
    };
    /**
    * 在打开数据库时常见的可能出现的错误之一是 VER_ERR。这表明存储在磁盘上的数据库的版本高于你试图打开的版本。这是一种必须要被错误处理程序处理的一种出错情况。
    */
    request.onerror = function (e) {
    alert('error: ' + e.target.error.message);
    };
    request.onsuccess = function (e) {
    alert('on success');
    var database = e.target.result;//获取打开的数据库
    console.log(database.version,"&&&&&&&&&&&&");
    /**成功打开数据库后,要对数据表进行操作(增删改查),首先要开启一个事务.
    *transaction()函数接收两个参数,第一个参数是涉及到的数据表(以数组形式传入),如果是空数组表示涉及所有数据表;
    * 第二个参数可以是readonly(只读事务),readwrite,versionchange,默认是只读事务
    *事务可以接收三种不同类型的DOM事件:error,abort以及complete
    */
    var transaction = database.transaction(['table'], 'readwrite');
    transaction.oncomplete = function (e) {
    alert('all done!')
    };
    transaction.onerror = function (e) {
    alert('事务错误')
    };
    transaction.onabort = function (e) {
    alert('transaction abort!')
    };

    /**
    * 有了事务之后,需要从其获取一个数据表引用,此数据表必须是创建事务时已经指定过的数据表。
    */
    var tb = transaction.objectStore('table');
    //tb.indexNames可以查看表的所有索引
    //tb.deleteIndex(索引名称);//删除索引。因为删除索引不会影响数据表中的数据,所以这个操作没有任何回调函数。
    /**
    * 获取了数据表之后就可以利用此数据表引用的方法来增删改查数据表了.
    * 增加数据方法:add()或put()。区别是当指定的键值(本例为id)已经存在时,add()方法会报错,put()方法则会更新
    * 删除方法:delete().传入主键(id).
    * 查询方法: get(). 传入主键.
    * clear()方法: 删除所有对象
    */
    var pput = tb.put({id: "ID10088", name: 'tong', age: 22});
    pput.onsuccess = function (e) {
    alert('pput success')
    };
    var del = tb.delete(3);
    del.onsuccess = function (e) {
    alert('dddelte success')
    };
    var gget = tb.get(2);
    gget.onsuccess = function (e) {
    alert('gget.success');
    console.log(gget.result);
    };

    /**
    * 使用游标:使用get()获取需要知道想要检索哪一个键。如果想遍历数据表中的所有值,就必须使用游标
    * openCursor()方法可以接收两个参数,具体见下面
    */
    //这里是对主键使用游标,下面还有对索引使用游标的例子
    tb.openCursor().onsuccess = function (e) {
    var cursor = e.target.result;
    if (cursor) {
    /**
    * 使用游标可以更新个别的记录,用update()方法,也可以删除某些记录,用delete()方法。
    * 两者都返回一个请求,可以为这个请求指定onsuccess和onerror事件处理程序,这样便可知道更新或删除的结果
    */
    if (cursor.key == 'foo'){
    value = cursor.value;
    value.name = 'magic';
    updateRequest = cursor.update(value);
    updateRequest.onsuccess = function (e) {
    alert('使用游标更新成功');
    };
    updateRequest.onerror = function (e) {
    alert('游标更新失败');
    };
    }
    console.log(cursor.key + "--", cursor.value);
    /**
    * continue([key])方法可以接收一个可选参数。指定这个参数,游标会移动到指定键的位置
    * 调用continue()会触发另一次请求,进而再次调用onsuccess事件处理程序。
    * 还有另外一种方法发起另一次请求:
    * advance(count);//向前移动count指定的项数
    */
    cursor.continue();
    } else {
    alert('no more entries');
    }
    };
    // //getAll()方法不是标准,是mozilla自己的实现。
    // tb.getAll().onsuccess = function (e) {
    // console.log(e.target.result[0]);
    // };

    /**
    * 使用索引:
    */
    var index = tb.index('name');
    //index.getKey('tong');//可以取得索引值tong对应的主键(id)
    index.get('tong').onsuccess = function (e) {
    //由于索引name=tong的记录可能有多条,但是得到的总是键值(id)最小的那个
    console.log(e.target.result);
    };
    //如果想访问所有索引name=tong的记录,可以对索引使用标.索引游标有两种方式,区别在于返回的值不同,可以分别打印出两个cursor查看区别
    index.openCursor().onsuccess = function (e) {
    var cursor = e.target.result;
    if (cursor) {
    //区别:cursor.value就是整条记录的对象
    console.log(cursor.key, cursor.value, '$$$');
    cursor.continue();
    }
    };
    index.openKeyCursor().onsuccess = function (e) {
    var cursor = e.target.result;
    if (cursor) {
    //区别:没有cursor.value,无法得到记录的其他字段信息
    console.log(cursor.key, cursor.primaryKey, "###");
    cursor.continue();
    }
    };
    /**
    * 指定游标的范围和方向:
    * openCursor()和openKeyCursor()还可以接收两个可选参数,第一个参数指定游标范围,第二个参数指定方向
    * 第一个参数有如下用法:
    * // 只匹配 "Donna"
    var singleKeyRange = IDBKeyRange.only("Donna");

    // 指定结果集的下界(即游标开始的位置).例如,游标从键为Bill的对象开始,然后继续向前移动,直至最后一个对象
    var lowerBoundKeyRange = IDBKeyRange.lowerBound("Bill");

    //如果想忽略Bill,从它的下一个对象开始,那么可以传入第二个参数true.
    var lowerBoundOpenKeyRange = IDBKeyRange.lowerBound("Bill", true);

    // 指定结果集的上界。例如,游标从头开始,直到取得键为"Donna"的对象终止,如果传入第二个参数则不包含"Donna"。
    var upperBoundOpenKeyRange = IDBKeyRange.upperBound("Donna", true);

    //同时指定上下界的bound()方法。接收四个参数:下界键值,上界键值,是否跳过下界的布尔值,是否跳过上界的布尔值
    var boundKeyRange = IDBKeyRange.bound("Bill", "Donna", false, true);
    * 第二个参数可以是:'next', 'nextunique', 'prev', or 'prevunique'
    */

    };
    /**
    *在数据库第一次被打开或者当指定的版本号高于当前存储的数据库的版本号时,会触发这个upgradeneeded事件,然后可以在此事件中更新数据库。
    *版本号是unsigned long long型,浮点数会被地板转换。
    * onupgradeneeded 是我们唯一可以修改数据库结构的地方。在这里面,我们可以创建和删除对象存储空间(即数据表)以及构建和删除索引。
    */
    request.onupgradeneeded = function (e) {
    alert('upgradeneeded');
    var db = e.target.result;//将得到的数据库保存到变量中。
    /**
    * 处理并发问题,onversionchange事件处理程序,立即关闭数据库从而保证版本更新顺利完成!
    */
    db.onversionchange = function (e) {
    alert('检测到版本变化!')
    db.close();
    };
    //在数据库中创建数据表,ObjectStore可以理解为数据表, 第二个对象参数(可选)中的keyPath可以理解为主键.往数据表中添加记录时,必须要有主键,否则报错
    var table = db.createObjectStore('table', {keyPath: 'id'});
    //为数据表添加索引,参数一为索引名称,参数二为键名,参数三可选,指定一些选项,如此索引是否唯一等等。
    table.createIndex('name', 'name', {unique: false});
    table.add({id: 11, name: 'tong', age: 33});//可以向表中添加记录
    };

     本章总结:本章介绍了在客户端存储数据的几种方法:cookie, web storage(localStorage/sessionStorage), indexedDB。在客户端存储数据需要注意一点:不要存储敏感数据,因为客户端数据缓存不会加密。

     24,最佳实践

    24.1,可维护性

    一些常见的做法(合理的注释,命名有意义等,合理的命名,如变量用名词,函数名用动词,返回布尔类型的函数一般以is开头。)就不提了。下面是一些保持代码可维护性的方法:

    ①,变量类型透明:由于JavaScript中变量是松散类型的,很容易就忘记变量所应包含的数据类型,有以下三种方式可以表示变量数据类型

    ※初始化。缺点是无法用于函数中的参数。

    ※使用匈牙利标记法来指定变量类型。缺点是使代码变的难以阅读。

    ※使用类型注释。缺点是无法再将这些代码大段注释,只能一行一行注释(很多编辑器都可以完成此工作)

    ②,松散耦合(即低耦合)

    只要代码 的某一部分过于依赖另一部分,就是耦合过紧,难以维护。可以参考原文,很有价值。

    ※解耦 HTML/JavaScript

      1,不要使用内嵌<script>标签在html中书写js代码,不要使用html属性分配事件处理程序。这些情况都属于耦合过于紧密。应该通过外部文件来包含javascript

      2,反过来,js中的代码也尽量不要包含html代码。例如,在js中用innerHTML插入一段html文本到页面。这就属于紧密耦合了。一般可以如下解决:可以先在页面中直接包含并隐藏html标记,然后等整个页面渲染好了之后在用javascript显示它而非生成它。另一种方法是使用ajax获取更多要显示的html,这个方法可以让同样的渲染层(php,ruby,jsp等等)来输出标记,而不是直接嵌在javascript中。

    ※解耦 CSS/JavaScript

      1,最常见的css和javascript耦合的例子是使用javascript来更改某些样式,如element.style.color='red'。由于常常需要使用js更改样式,所以不可能将css和js完全解耦,但还是能够让耦合尽量低的。如,可以通过动态更改样式类而非特定样式。如element.className='edit';如此便可使大部分样式信息严格保留在css中。

    ※解耦 应用逻辑/事件处理程序:事件处理程序值处理事件,由事件得到的信息需要处理则放到另外的应用逻辑层处理。

    ③,编程实践

    ※尊重对象所有权。意思是你不能修改不属于你的对象,包括别人的对象以及原生对象。如果需要扩展某个对象可以创建自己的对象并继承需要扩展的对象。

    ※避免全局量。最多创建一个全局变量,让其他对象和函数存在其中。单一的全局变量的延伸便是命名空间的概念。YAHOO.util.Event / YAHOO.util.Dom / YAHOO.lang

    ※避免与null进行比较。与null比较常常由于不充分而导致错误。应该使用更充分的类型比较。

    ※使用常量

    JavaScript中没有常量的正式概念,但它还是很有用的。将数据从应用逻辑分离出来的思想,可以用如下方式表示:

    var Constants = {

       CONSTANT1: "value1",

          CONSTANT2: "value2"

    }

    24.2, 性能

    注意以下方面,可以改进代码的整体性能。

    ※注意作用域:

      访问全局变量总是要比访问局部变量慢,因为需要遍历作用域链。只要能减少花费在作用域链上的时间,就能增加脚本的总体性能。

      1,避免全局查找:将一个函数中多次用到的全局对象(比如document)存储为局部变量总是没错的。

      2,避免with语句:因为with语句会创建自己的作用域,因此增加了其中的代码的作用域链的长度。同样可以用一个局部变量来代替with语句中的表达式。

    ※选择正确的方法:

      计算机科学中,算法的复杂度是用符号O来表示的,最简单、最快捷的的算法是常数值即O(1)。

      

      以下是javascript中的复杂度:

      O(1):  字面值、存储在变量中的值、访问数组元素

      O(n):访问对象上的属性(必须在原型链中对拥有该名称的属性进行一次搜索)。

      

      1,避免不必要的属性查找

      如:var query = window.location.href.substring(window.location.href.indexOf("?"));  这段代码中有6此属性查找,可以优化。

      一旦多次用到对象属性,应该将其存储在局部变量中。第一次访问是O(n),后续访问都会是O(1)。上述代码可优化为:

      var url = window.location.href;

      var query = url.substring(url.indexOf("?")); 这段代码中只有4次属性查找,相对于原来节省了33%.

      2,优化循环

      例如下面一个循环:

      for (var i = 0; i < values.length; i++) { process(values[i])}

      这里每次循环都要判断的终止条件values.length是O(n)的,因此可以单独拿出来存在一个变量里,或者使用减值迭代,如下

      for (var i = values.length - 1; i >= 0; i--){process(values[i])}. 这两种方法都可以使终止条件简化为O(1).

      3,避免双重解释

      所谓双重解释意思是,出现了需要按照JavaScript解释的字符串。比如,当时用eval()函数,或者使用Function构造函数,或者使用setTimeout() / setInterval()传一个字符串参数时都会发生这种情况。如下例子;

    eval("alert('hello world')");// 避免!!

    var sayHi = new Function("alert('hello world')"); 或者

    var sum = new Function("a","b","c=a+b;return c");

    setTimeout("alert('hello world')", 1000);

    以上这些例子中,都要解析包含了JavaScript代码的字符串。这个操作是不能在初始化的解析过程中完成的,因为代码是包含在字符串中的,也就是说在JavaScript代码运行的同时必须新启动一个解析器来解析新的代码。实例化一个新的解析器有不容忽视的开销,所以这种代码比直接解析要慢的多。

    以上的例子都有另外的办法,见下图。除了极少的情况下eval()是绝对必要的,其他情况都要避免使用双重解释。

      4,性能的其他注意事项:

      以下并非主要的问题,不过如果使用得当也会有相当大的提升。

    ※最小化语句数

    JavaScript代码中的语句数量也影响所执行的操作的速度。完成多个操作的单个语句要比完成单个操作的多个语句速度快,所以就要找出可以组合在一起的语句,以减少脚本整体的执行时间。以下几个模式可以参考:

    1,多个变量声明

    //4个语句---很浪费

    var count  = 5;

    var color = 'red';

    var arr = [1,3,4];

    var now = new Date();

    在强类型语言中,不同的数据类型的变量必须在不同的语句中声明。然而,在JavaScript中所有的变量都可以使用单个var语句来声明。以上代码可以如下优化重写:

    //一个语句,用逗号隔开

    var count = 5,

      color = 'red',

      arr = [1,3,4],

      now = new Date();

    2,插入迭代值(即自增自减),如下例子:

    var name = values[i];

    i++;

    上述两个语句可以合并成一个语句,var name = values[i++];这样效果更优。

    3,使用数组和对象字面量:

    创建数组或对象有两种方法:使用构造函数或者使用字面量。前者用到的语句要比后者多,所以一般尽可能用字面量创建数组或对象。

    //4个语句创建和初始化数组----浪费!

    var values = new Array();

    values[0] = 123;

    values[1] = 345;

    values[2] = 456;

    改为:var values = [123,345,456];//一条语句完成创建

    //4个语句创建和初始化对象----浪费!

    var person = new Object();

    person.name = 'Tong';

    person.age = 11;

    person.sayName = function(){alert(this.name);}

    改为:var person = {

          name : "Tong",

          age: 11,

          sayName : function(){alert(this.name);}

        };//一条语句完成对象的创建

    ※优化DOM交换

    在JavaScript的各个方面中,DOM毫无疑问是最慢的一部分。理解如何优化与DOM的交互可以极大的提高脚本完成的速度。 

    1,最小化现场更新

    如果你正在更改的DOM已经是显示的页面的一部分,那么你就是在进行一个现场更新。每一个更改,不管是插入单个字符还是移除整个片段,都有一个性能惩罚,因为浏览器要重新计算无数尺寸以进行更新。例子如下:

    var list = document.getElementById('myList'),

      item,

      i;

    for (i = 0; i < 10; i++){

      item = document.createElement("li");

      item.appendChild(document.createTextNode("item" + i));

      list.appendChild(item);

    }

    这段代码为列表添加了10个项目,添加每个项目时,都有两个现场更新:一个添加<li>元素,另一个给他添加文本节点。总共进行了20次现场更新。要修正这个性能瓶颈,可以使用文档碎片来构建DOM结构【在本页搜索{10.8,DocumentFragment类型}】,然后一次性添加到list元素中。改进如下:

    var list = document.getElementById("myList"),

      fragment = document.createDocumentFragment(),

      item,

      i;

    for (i = 0; i< 10; i++){

      item = document.createElement("li");

      item.appendChild(document.createTextNode("item" + i));

      fragment.appendChild(item);  

    }

    list.appendChild(fragment); //这样只会进行一次现场更新,大大提高性能。

    2,使用innerHTML

    有两种在页面上创建DOM节点的方法:使用诸如createElement()和appendChild()之类的DOM方法,或者使用innerHTML。当把innerHTML设置为某个值时,后台会创建一个HMML解析器,然后使用内部的DOM调用来创建DOM结构,而不是基于JavaScript的DOM调用。由于内部方法是编译好的而非解释执行的,所以执行得快的多。上面的例子还可以用下面的方法优化:

    var list = document.getElementById("list"),

      html = '',

      i;

    for( i = 0; i < 10; i++){

      html += "<li>item " + i + "</li>";

    }

    list.innerHTML = html;

    另外注意,使用一次innerHTML就是进行一次现场更新,所以也要尽量减少innerHTML的使用次数,先把字符串拼接好然后再一次性更新而不是在for循环体里每次更新。

    3,使用事件代理(又叫事件委托):

    页面上的事件处理程序的数量越多,页面的响应速度就越慢。为减轻这种惩罚,最好使用事件代理。事件代理利用到了事件冒泡,即在尽可能高层的元素上添加一个事件处理程序来处理下层的多个同类型的事件,通过event.target.id来分别不同的下层元素。【参见13.5.1,事件委托】

    4,最小化访问HTMLCollection

    任何时候要访问HTMLCollection,不管它是一个属性还是一个方法,都是在文档上进行一个查询,这个查询开销很昂贵。所以要尽量最小化HTMLCollection访问(尤其在循环中)。

    在JavaScript中,发生一下情况时会返回HTMLCollection对象:

      □ 进行了对 getElementsByTagName()的调用;

      □ 获取了元素的childNodes属性;

      □ 获取了元素的attributes属性;

      □ 访问了特殊的集合,如document.forms、document.images等;

    24.3,部署

    24.3.1,构建过程

    24.3.2,验证

    24.3.3,压缩

    JavaScript中的压缩涉及到两个方面:代码长度和配重(Wire weight)。代码长度指的是浏览器所需解析的字节数,配重是指实际从服务器传送到浏览器的字节数。前者可以通过文件压缩来减少文件字节数,后者可以HTTP压缩来减少传输字节数。

    ※文件压缩:可以通过一些压缩工具(如YUI)进行压缩,压缩器一般进行如下一些步骤:

    □ 删除额外的空白(包括换行);

    □删除所有注释;

    □缩短变量名。

    ※HTTP压缩:文件压缩后,还可以继续通过HTTP压缩减少传输字节数。浏览器接收到压缩文件后再解压缩。

    25,新兴的API

    25.1,requestAnimationFrame() 

    HTML5/CSS3时代,我们要在web里做动画选择其实已经很多了:

    你可以用CSS3的animation+keyframes;

    你也可以用css3的transition;

    你还可以用通过在canvas上作图来实现动画,也可以借助jQuery动画相关的API方便地实现;

    当然最原始的你还可以使用window.setTimout()或者window.setInterval()通过不断更新元素的状态位置等来实现动画,前提是画面的更新频率要达到每秒60次才能让肉眼看到流畅的动画效果。

    现在又多了一种实现动画的方案,那就是还在草案当中的window.requestAnimationFrame()方法。下面介绍一个这个方法:

    mozilla开发手册中的介绍:https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame

    其实这个方法和setTimeout用法非常相似,可以说requestAnimationFrame就是一个性能优化版、专为动画量身打造的setTimeout,不同的是requestAnimationFrame不是自己指定回调函数运行的时间,而是跟着浏览器内建的刷新频率来执行回调,这当然就能达到浏览器所能实现动画的最佳效果了。下面是requestAnimationFrame的一个例子:进度条的实现

    0%

    代码如下:

    <div id="test" style="1px;height:17px;background:#0f0;">0%</div>
    <input type="button" value="Run" id="run"/>
    <script type="text/javascript">
        //requestAnimationFrame的用法和setTimeout及其相似
        window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
        var start = null;
        var ele = document.getElementById("test");
        var progress = 0,
                startTime;
        function step(timestamp) {
    //        console.log(timestamp);
            /**timestamp是自动传给回调函数的,打印的结果并不是普通的时间戳,感觉像是
            /*页面被加载后开始计时的时间戳
             */
            if (startTime) {
                console.log(timestamp - startTime);//打印两次时间间隔
            }
            startTime = timestamp;
            progress += 1;
            ele.style.width = progress + "%";
            ele.innerHTML = progress + "%";
            if (progress < 100) {
                start = requestAnimationFrame(step);
            }
        }
        //    requestAnimationFrame(step);
        document.getElementById("run").addEventListener("click", function () {
            ele.style.width = "1px";
            progress = 0;
            cancelAnimationFrame(start);
            requestAnimationFrame(step);
        }, false);
    </script>

    25.2,Page Visibility API(页面可见性API)

    此API由三部分组成:

    说明一点:经测试,在chromium37中,visibilitychange事件只能通过DOM2级事件处理程序[addEventListener]触发,DOM0级事件处理程序[onclick]无效 !

    具体参见很详细的一篇文章:http://www.zhangxinxu.com/wordpress/2012/11/page-visibility-api-introduction-extend/ 

    下面的代码是整理自上述文章中:跨浏览器兼容的Page Visibility API,

        var pageVisibility = (function () {
            var prefixSupport, keyWithPrefix = function (prefix, key) {
                if (prefix !== '') {
                    return prefix + key.substring(0, 1).toUpperCase() + key.substring(1);
                }
                return key;
            };
            var isPageVisibilitySupport = (function () {
                var support = false;
                ['webkit', 'moz', 'o', ''].forEach(function (prefix) {
                    if (document[keyWithPrefix(prefix, 'hidden')] != undefined) {
                        prefixSupport = prefix;
                        support = true;
                    }
                });
                return support;
            })();
            var visibilityState = function () {
                if (isPageVisibilitySupport) {
                    return document[keyWithPrefix(prefixSupport, 'visibilityState')]
                }
                return undefined;
            };
            var hidden = function () {
                if (isPageVisibilitySupport) {
                    return document[keyWithPrefix(prefixSupport, 'hidden')];
                }
                return undefined;
            };
    
            return {
                hidden: hidden(),
                visibilityState: visibilityState(),
                visibilitychange: function (fn) {
                    if (isPageVisibilitySupport && typeof fn == 'function') {
                        return document.addEventListener(prefixSupport + 'visibilitychange', function (e) {
                            this.hidden = hidden();
                            this.visibilityState = visibilityState();
                            fn.call(this, e);
                        }.bind(this))
                    }
              return undefined; } } })(); (
    function () { pageVisibility.visibilitychange(function (e) { console.log((new Date).toLocaleString() + ": " + this.visibilityState); }) })();
     

    应用一:网页视频播放时,当此网页不可见时暂停视频播放,当再次可见时继续播放,代码如下:

    HTML代码:
    <video id="eleVideo" width="50%" src="res/feather.mp4" controls muted>
    
    JS代码:
        (function () {
            var pauseByVisibility = false,
                    eleVideo = document.querySelector('#eleVideo');
            if (pageVisibility.hidden != undefined) {
                //视频元素的时间更新事件
                eleVideo.addEventListener('timeupdate', function (e) {
                    document.title = "第" + Math.floor(this.currentTime) + "秒";
                }, false);
                //视频元素的暂停事件
                eleVideo.addEventListener('pause', function (e) {
                    if (pageVisibility.hidden) {
                        pauseByVisibility = true;
                    }
                }, false);
                //视频元素的播放事件
                eleVideo.addEventListener('play', function (e) {
                    pauseByVisibility = false;
                });
                pageVisibility.visibilitychange(function (e) {
                    if (this.hidden) {
                        eleVideo.pause();
                    } else if (pauseByVisibility) {
                        eleVideo.play();
                    }
                })
            } else {
                alert('您的浏览器不支持Page Visibility API!!!')
            }
        })();

    应用二:先打开一个未登录的网页,此网页显示未登录。在另一个网页中登录之后在返回原来的那个未登录的网页,实时显示登录状态。代码如下:

    <div id="msg">您尚未登录,请<a href="./d.html" target="_blank">登录</a></div>
    <script type="text/javascript">
        (function () {
            if (pageVisibility.hidden != undefined) {
                var msg = document.querySelector('#msg'),
                        loginFun = function () {
                            /**
                             * sessionStorage特点:1,即使同源的不同网页信息也不共享;2,关闭此窗口,信息即消失;
                             * localStorage特点:1,同源(协议,主机,端口都同)的不同网页信息共享;2,关闭窗口,信息仍然存在。
                             */
                            var name = sessionStorage.name || localStorage.name;
                            var pwd = sessionStorage.pwd || localStorage.pwd;
                            if (name) {
                                sessionStorage.name = name;
                                sessionStorage.pwd = pwd;
                                msg.innerHTML = "<span>欢迎回来," + name + "。您的密码为" + pwd + "</span>";
                            }
                        };
                loginFun();
                pageVisibility.visibilitychange(function (e) {
                    if (!this.hidden) {
                        loginFun();
                    }
                });
    
                /**
                 * 注意:unload事件在 关闭此窗口刷新此窗口在当前窗口打开超链接 都会触发!!!
                 */
                window.addEventListener('unload', function (e) {
                    localStorage.clear();
                })
            }
        })();
    </script>

    25.3,Geolocation API

    参见html学习笔记记录: http://www.cnblogs.com/everest33Tong/p/6706497.html

    25.4,File API

    1,文件处理通过文件表单元素:<input type='file' />。用法如下:

    <input type="file" id="eleFile" multiple/>
    <script type="text/javascript">
        var eleFile = document.getElementById('eleFile');
        /**文件表单元素有一个files集合,files集合中包含一组File对象,每个File对象对应着一个文件。
         * 每个File对象都有下面的只读属性(更新的浏览器支持的属性应该还支持其他一些属性,可自己尝试看看):
         * name:本地文件系统中的文件名,
         * size:文件的字节大小,
         * type:字符串,文件的MIME类型,
         * lastModifiedDate: 文件上一次被修改的日期
         */
        console.log(eleFile.files);
        //文件表单元素有一个change事件,当选择的文件发生变化时触发:
        eleFile.onchange = function (e) {
            alert('选择的文件发生了变化');
        };
    </script>

    2,FileReader 类型

    FileReader类型实现的是一种异步文件读取机制。可以把FileReader想象成XMLHttpRequest,区别只是它读取的是文件系统,而不是远程服务器。为了读取文件中的数据,FileReader提供了如下方法:

    FileReader有个属性readyState标志着当前读取状态:

    EMPTY : 0 : 表示尚未开始读取数据,

    LOADING : 1 : 表示正在加载数据,

    DONE : 2 : 表示数据已读取完毕。

    由于读取过程是异步的,因此FileReader提供了几个事件(按照事件的依次触发顺序):

    loadstart事件:最先被触发。

    progress事件:表示是否又读取了新数据。每过50ms左右就会触发一次progress事件,通过事件对象event可以获取与XML的progress事件相同的属性(信息):lengthComputable, total, loaded。每次progress事件中都可以通过FileReader的result属性获取已经读取到的内容。

    load事件:文件加载成功之后就会触发load事件。

    loadend事件最终总会被触发的事件,loadend事件发生意味着已经读取完整个文件,或者读取时发生了错误,或者读取过程被中断。

    另外几个事件:

    error事件:由于种种原因无法读取文件,就会触发error事件。触发error事件时,相关的信息会被保存在FileReader的error属性中。error属性是个对象,有如下三个属性:message, code, name.

    abort事件:如果调用了FileReader的abort()方法就会触发abort事件。

    测试时发现的一个小点(关键点:异步读取!!),注意下,如下:

    <script type="text/javascript">
        var eleFile = document.getElementById('eleFile');
        eleFile.onchange = function (e) {
            var reader = new FileReader(),
                    file = this.files[0];
            reader.onload = function (e) {
                console.log(e.target.result);//正确的读取位置,在load事件中读取。
            };
            reader.readAsText(file, 'gbk');
            /**
             * 注意如果在下面打印reader对象和其result属性,此时reader.readyState仍为1,result会显示为undefined,
             * 但是如果在浏览器控制台中展开打印的reader对象,会发现readyState是2,result属性也有结果,这是因为展开
          * 看时显示的是已经读取完毕时的状态。
    */ console.log(this.result,reader); }; </script>

    3,读取部分内容:

    只读文件的一部分内容可以节省时间,非常适合只关注数据中某个特定部分(如文件头部)的情况。具体用法如下:

    <body><a id='rtobottom' href='#blog_post_info_block'>底部</a>
    <input type="file" id="eleFile"/>
    <div id="output"></div>
    <!--<video id="videoElement" src="./res/home.mp4" controls></video>-->
    <script type="text/javascript" src="../amomeshoe/js/jquery2.1.4.js"></script>
    <script type="text/javascript">
        (function () {
            var eleFile = document.querySelector('#eleFile'),
                    output = document.querySelector('#output');
            eleFile.onchange = function (e) {
                var file = eleFile.files[0],
                        reader = new FileReader,
                        html = '';
                /**
                 * file属于File类型,blob属于Blob类型,Blob是File类型的父类型。
                 * File对象支持slice(起始字节数,要读取的字节数)方法来实现部分读取,其父类型Blob类型也支持slice()方法以进一步切割数据
                 */
                var blob = file.slice(0,10);
                var blob1 = blob.slice(1,3);
                console.log(file,blob);
                reader.readAsText(blob1);//此处参数为File或Blob类型,即可以从File类型或Blob类型中读取数据
                reader.onload = function (e) {
    //                html = "<img src='" + reader.result + "'/>";
                    html = reader.result;
                    output.innerHTML = html;
                };
            }
        })();
    </script>
    </body>

    4,对象URL(和Data URI不同,Data URI是将内容本身存储在字符串中,放在任何地方都可以使用,而对象URL是将内容存储在内存中)

    对象URL也叫blob URL,是指引用了  存储在File或Blob中的数据   的URL。使用对象URL的好处是可以不必把文件内容读取到JavaScript中而直接使用文件内容,为此只要在需要文件内容的地方提供对象URL即可。要创建对象URL,可以使用window.URL.createObjectURL()方法,并传入File或Blob对象。这个函数的返回值是一个字符串,指向一块内存的地址【理解方式(关键):这个字符串就是个URL,和普通URL一样指向一个地方,这里是指向了一块内存(而普通的URL指向的是文件夹中的文件)】。这个字符串 就是[保存在内存中的]文件内容的URL。只要这块内存还在,就可以使用这个URL来指向文件。比如,可以用如下代码显示一个图像文件:

    <body><a id='rtobottom' href='#blog_post_info_block'>底部</a>
    <input type="file" id="eleFile"/>
    <div id="output"></div>
    <!--<video id="videoElement" src="./res/home.mp4" controls></video>-->
    <script type="text/javascript" src="../amomeshoe/js/jquery2.1.4.js"></script>
    <script type="text/javascript">
        (function () {
            var eleFile = document.querySelector('#eleFile'),
                    output = document.querySelector('#output');
            eleFile.onchange = function (e) {
                var file = eleFile.files[0],
                        reader = new FileReader,
                        html = '',
                        url = window.URL.createObjectURL(file);
    
                if (/image/.test(file.type))
                {
                    /**
                     * 直接把对象URL放在img标签中,就省去了把数据读到JavaScript中的麻烦。img标签会直接从URL中读取数据并显示图像
                     */
                    html = "<img src='" + url +"' />";
                } else
                {
                    html = url;
                }
                output.innerHTML = html;
           window.URL.revokeObjectURL(url); } })();
    </script> </body>

    如果不再需要相应的数据,最好释放它占用的内存。当页面卸载时会自动释放对象URL占用的内存。不过最好在不需要某个对象URL时,就马上释放其占用的内存。要手工释放内存,可以把对象URL传给window.URL.revokeObjectURL()方法。

    5,读取拖放的文件:【可以参见 HTML学习笔记      http://www.cnblogs.com/everest33Tong/p/6706497.html  中的{使用拖放}】

    结合使用HTML5的拖放API和文件API,可以读取拖放的文件内容。下列代码可以将拖放到页面指定位置的文件信息显示出来。

    <body><a id='rtobottom' href='#blog_post_info_block'>底部</a>
    <input type="file" id="eleFile"/>
    <div id="target"></div>
    <div id="output"></div>
    <!--<video id="videoElement" src="./res/home.mp4" controls></video>-->
    <script type="text/javascript" src="../amomeshoe/js/jquery2.1.4.js"></script>
    <script type="text/javascript">
        (function () {
            var eleFile = document.querySelector('#eleFile'),
                    output = document.querySelector('#output'),
                    target = document.querySelector("#target"),
                    files,
                    i, len, info = '<table border="1">';
            target.ondragover = handledrag;
            target.ondragenter = handledrag;
            function handledrag(e) {
                e.preventDefault();//dragenter 和 dragover事件的默认操作为阻止文件拖放,所以要取消默认操作
            }
    
            target.ondrop = function (e) {
                e.preventDefault();//drop事件的默认操作是打开文件的URL,所以也要取消drop的默认操作
                files = e.dataTransfer.files;//这里读取到的文件列表和文件输入字段取得的文件列表对象是一样的,都是由一个个File对象(即对应一个文件)组成。
                len = files.length;
                for (i = 0; i < len; i++) {
                    info += "<tr>" + "<td>" + files[i].name + "</td>" + "<td>" + files[i].type + "</td>" + "<td>" + files[i].size + "Bytes</td><tr/>";
                }
                output.innerHTML = info + "</table>";
            };
        })();
    </script>
    </body>

    6,利用 XHR 和 拖放 上传文件

    <body>
    <form action="test.php" method="post" id="form">
        <table id="data" border="1">
        </table>
        <input type="submit" value="GO" id="sub" />
    </form>
    <div id="target">拖放至此</div>
    <div id="re"></div>
    <script>
        var target = document.getElementById('target');
        var ff = document.getElementById('ff');
        var sub = document.getElementById('sub');
        var form = document.getElementById('form');
        var re = document.getElementById('re');
        var files;
        target.ondragenter = handleDrag; //若要释放区事件drop生效,必须首先阻止释放区的dragenter和dragover事件的默认行为
        target.ondragover = handleDrag;
        function handleDrag(e) {
            e.preventDefault();
        }
        target.ondrop = function (e) {
            files = e.dataTransfer.files;
            e.preventDefault();
            var formData = new FormData(form);
            var hr = new XMLHttpRequest();
            hr.onreadystatechange = function (e) {
                if (e.target.readyState == XMLHttpRequest.DONE && e.target.status == 200) {
                    re.innerHTML = e.target.responseText;
                }
            };
            hr.open('post', form.action);
            if (files) {
                /**
                 * 服务器端只需要像处理表单提交的文件一样处理即可,可以打印$_FIELS看下详情。
                 */
                formData.append('fileee',files[0]);//上传拖放的文件
            }
            hr.send(formData);
        };
    </script>
    </body>

    25.5, Web Timing API

    1,这个API的功能是分析页面性能。Web Timing API机制的核心是window.performance对象。对页面的所有度量信息,都包括在这个对象中。最新的浏览器中performance有三个属性,navigation、timing、memory。其中前两个属性的具体信息如下截图:

    25.6,Web Workers

    1,随着Web应用复杂性的与日俱增,越来越复杂的计算在所难免。长时间运行的JavaScript 进程会导致浏览器冻结用户界面,让人感觉屏幕“冻结”了。Web Workers规范通过让JavaScript在后台运行解决了这个问题。浏览器实现Web Workers规范的方式有很多种,可以使用线程、后台进程、或者运行在其他处理器核心上的进程,等等。具体的实现细节无需细究,重要的是开发现在可以放心地运行JavaScript而不必担心会影响用户体验了。

    2,如何使用Worker

    参考网站,很详细:【 http://www.cnblogs.com/feng_013/archive/2011/09/20/2175007.html 】 

    一个详细的例子,代码如下:

    ---- 主线程页面

    <!DOCTYPE HTML>
    <html>
    <head>
        <title>a.html</title>
        <meta charset="utf-8">
    </head>
    <body>
    <div id="msg"></div>
    <script>
        //这个是主线程
        var work = new Worker('a.js');
        /**
         * 向分支线程传递数据,与XDM(cross-document message)的postMessage()不同,这里可以传递对象参数
         */
            //work.postMessage([1,3,5,2]);//传数组测试
    
        /**
         * 由于javascript是单线程执行的,在求数列的过程中浏览器不能执行其它javascript脚本,
         * UI渲染线程也会被挂起,从而导致浏览器进入僵死状态。使用web worker将数列的计算过程
         * 放入一个新线程里去执行将避免这种情况的出现
         */
        work.postMessage(10);//在自己的电脑上测试,40大概耗时16秒,50大概耗时30分钟。
        console.time('worker');
        //接收分支线程传回的数据,注意是在work上调用message事件
        work.onmessage = function (e) {
            console.timeEnd('worker');//可以打印出大概的计算耗时
            console.log(e.data);
        };
        /**
         * Worker不能完成给定的任务时会触发error事件。即分支线程内部的JavaScript在执行中只要遇到错误,就会触发error事件。
         * error事件对象中包含三个属性:filename[文件名]、lineno[错误代码行号]、message[错误消息].
         * 最好始终使用onerror处理程序,否则,Worker在发生错误时就会悄无声息的失败了
         */
        work.onerror = function (e) {
            console.log("ERROR: " + e.filename + " (" + e.lineno + "): " + e.message)
        };
        console.log('hello WORLD"');//验证JavaScript是否被阻塞,也可是设置个setTimeout函数验证
        /**
         * 任何时候,只要调用terminate()方法就可以停止Worker工作。而且分支线程Worker中的代码会立即停止执行,
         * 后续的所有过程都不会发生(包括error和message事件也不会再触发);
         */
        //work.terminate();
    </script>
    </body>
    </html>

    分支线程

    /**
     * fibonacci.js, 分支线程
     * 关于Web Worker,最重要的是要知道它所执行的JavaScript代码完全在另一个作用域中,与当前网页(即主线程)中的代码不共享作用域。
     * 在Web Worker中,同样有一个全局对象和其他对象以及其他方法。但是Web Worker中的代码不能访问DOM,也无法通过任何方式影响页面
     * 的外观。
     * Web Worker中的全局对象是worker对象本身(注意和主线程中的worker对象不是同一个对象)。在这个特殊的全局作用域中,this 和 self
     * 引用的都是worker对象。为便于处理数据,Web Worker本身也是一个最小化的运行环境。具体说来如下:
     * ■ 最小化的navigator对象
     * ■ 只读的location对象
     * ■ setTimeout(),setInterval(),clearTimeout(),clearInterval()方法
     * ■ XMLHttpRequest构造函数
     *显然Web Worker的运行环境和页面环境相比,功能是相当有限的。(未来可能会扩展)
     */
    self.onmessage = function (e) {
        //接收数组测试
        // var d = e.data;
        // d.sort(function (a, b) {
        //     return a-b;//升序排列
        // });
        // postMessage(d);
    
        //Fibonacci
        var d = parseInt(e.data, 10);
        postMessage(fibonacci(d));//向主线程返回数据
    };
    //计算fibonacci数列的函数
    function fibonacci(n) {
        return n < 2 ? n : arguments.callee(n - 1) + arguments.callee(n - 2);
    }
    
    
    /**
     * 在Worker内部,调用close()方法也可以停止工作。就像在主线程中调用terminate()一样。
     * 但是貌似close()方法只会阻止“下一级别的代码”执行,同一级别的代码即使在close()之后
     * 也会被执行。比如close()放在onmessage内部postMessage()之前,postMessage()方法依然会发回消息!
     */
    //self.close();
    //以下代码非必要,做一些杂乱的验证
    console.log(this, self, this===self);//DedicatedWorkerGlobalScope(专用Worker), true
    console.log("YYYYYYYY");
    setTimeout(function () {
        console.log("Delayyyyyyed");
    }, 2000);

    3,在Worker中包含其他脚本

    Worker 中无法动态创建新的<script>元素,但是Worker的全局作用域提供了包含其他脚本的功能,即调用importScripts()方法。这个方法接收一个或多个指向JavaScript文件的URL。每个加载过程都是异步的,因此所有脚本加载并执行之后,importScripts()才会执行。例如:

    importScripts('file1.js', 'file2.js');

    即使file2.js限于file1.js下载完,执行的时候仍然会按照先后顺序执行。而且需要注意一点:这些脚本是在Worker的全局作用域中执行的。

    |§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

    =========||||||||||||||||||||=====附录======|||||||||||||||||||||||=========

    §§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§

    JavaScript以后可能会增加的功能,好多是借鉴了python

    1,常量 const PI = 3.14;

    2,  块级作用域,关键字let

    3,  函数

    3.1,剩余参数和分布参数

    3.2,默认参数值(才发现JavaScript竟然不支持函数参数的默认值)

    3.3,生成器,python中有类似概念

    4,数组和其他结构

    4.1,迭代器,python中有

    4.2,数组领悟(array comprehensions)。这个也是借鉴了python。如,var s = [ i for each (i in numbers) ];

    4.3, 解构赋值。借鉴了python,如[a, b] = ['hello', 'world'];那么a='hello', b='world';

    5, 新对象类型

    5.1,代理对象

    5.2,代理函数

    5.3,映射和集合

    5.4,WeakMap

    5.5, StructType

    5.6, ArrayType

    6, 类

    6.1,私有成员

    6.2,getter 和 setter

    6.3, 继承

    6.4,模块

    附录B:严格模式下的一些变化(略)

    附录C:JavaScript库,如jQuery等等

      

     
  • 相关阅读:
    TopK问题:什么是TopK问题?用堆和快排这两种方式来实现TopK
    volatile是什么?volatile能保证线程安全性吗?如何正确使用volatile?
    并行的执行效率一定高于串行吗?(多线程的执行效率一定高于单线程吗?)
    位运算和取模运算的运算效率对比
    jdk1.8源码解析:HashMap底层数据结构之链表转红黑树的具体时机
    jdk1.8 HashMap底层数据结构:散列表+链表+红黑树(图解+源码)
    根据jdk1.8源码整理而得,java集合体系(继承、实现关系)图解,超清晰,一看就懂,方便记忆
    java代码实现简易版IOC容器,含IOC容器实现步骤分解
    自己实现SpringAOP,含AOP实现的步骤分解
    自己实现简易版AOP,含AOP实现的步骤分解
  • 原文地址:https://www.cnblogs.com/everest33Tong/p/6896036.html
Copyright © 2011-2022 走看看