• [No0000103]JavaScript-基础课程3


    在 JavaScript 中,函数的参数是比较有意思的,比如,你可以将任意多的参数传递给一个函数,即使这个函数声明时并未制定形式参数

    function adPrint(str, len, option){
    var s = str || "default";
    var l = len || s.length;
    var o = option || "i";
    
    s = s.substring(0, l);
    switch(o){
    case "u":
    s = s.toUpperCase();
    break;
    case "l":
    s = s.toLowerCase();
    break;
    default:
    break;
    }
    console.log(s);
    }
    adPrint("Hello, world");
    adPrint("Hello, world", 5);
    adPrint("Hello, world", 5, "l");//lower case
    adPrint("Hello, world", 5, "u");//upper case

    数 adPrint 在声明时接受三个形式参数:要打印的串,要打印的长度,是否转换为大小写的标记。但是在调用的时候,我们可以按顺序传递给 adPrint 一个参数,两个参数,或者三个参数(甚至可以传递给它多于 3 个,没有关系),运行结果如下:

    Hello, world
    Hello
    hello
    HELLO

     事实上,JavaScript 在处理函数的参数时,与其他编译型的语言不一样,解释器传递给函 数的是一个类似于数组的内部值,叫arguments,这个在函数对象生成的时候就被初始化了。比如我们传递给 adPrint 一个参数的情况下,其他两个参数分别为 undefined.这样,我们可以才 adPrint 函数内部处理那些 undefined 参数,从而可以向外部公开:我们可以处理任意参数。

    我们通过另一个例子来讨论这个神奇的 arguments:

    function sum(){
    var result = 0;
    for(var i = 0, len = arguments.length; i < len; i++){
    var current = arguments[i];
    if(isNaN(current)){
    throw new Error("not a number exception");
    }else{
    result += current;
    }
    }
    return result;
    }
    print(sum(10, 20, 30, 40, 50));
    print(sum(4, 8, 15, 16, 23, 42));//《迷失》上那串神奇的数字
    print(sum("new"));

     函数 sum 没有显式的形参,而我们又可以动态的传递给其任意多的参数,那么,如何在 sum函数中如何引用这些参数呢?这里就需要用到 arguments 这个伪数组了,运行结果如下:

    150
    108
    Error: not a number exception

    作用域的概念在几乎所有的主流语言中都有体现,在 JavaScript 中,则有其特殊性: JavaScript 中的变量作用域为函数体内有效,而无块作用域,我们在 Java 语言中,可以这样定义 for 循环块中的下标变量:

    public void method(){
    for(int i = 0; i < obj1.length; i++){
    //do something here;
    }
    
    //此时的i为未定义
    for(int i = 0; i < obj2.length; i++){
    //do something else;
    }
    }

    而在 JavaScript 中:

    function func(){
    for(var i = 0; i < array.length; i++){
    
    //do something here.
    }
    
    //此时i仍然有值,及I == array.length
    print(i);//i == array.length;
    }

    JavaScript 的函数是在局部作用域内运行的,在局部作用域内运行的函数体可以访问其外层的(可能是全局作用域)的变量和函数。JavaScript 的作用域为词法作用域,所谓词法作用域是说,其作用域为在定义时(词法分析时)就确定下来的,而并非在执行时确定

    var str = "global";
    function scopeTest(){
    print(str);
    var str = "local";
    print(str);
    }
    scopeTest();

    而正确的结果应该是:

    undefined
    local

    因为在函数 scopeTest 的定义中,预先访问了未声明的变量 str,然后才对 str 变量进行初始化,所以第一个 print(str)会返回 undifined 错误。那为什么函数这个时候不去访问外部的 str 变量呢?这是因为,在词法分析结束后,构造作用域链的时候,会将函数内定义的var 变量放入该链,因此 str 在整个函数 scopeTest 内都是可见的(从函数体的第一行到最后一行),由于 str 变量本身是未定义的,程序顺序执行,到第一行就会返回未定义,第二行为 str 赋值,所以第三行的 print(str)将返回”local”。

    我们再来深入的分析一下作用域,在 JavaScript 中,在所有函数之外声明的变量为全局变量,而在函数内部声明的变量(通过 var 关键字)为局部变量。事实上,全局变量是全局对象的属性而已,比如在客户端的 JavaScript 中,我们声明的变量其实是 window 对象的属性,如此而已。

    那么,局部变量又隶属于什么对象呢?就是我们要讨论的调用对象。在执行一个函数时,函数的参数和其局部变量会作为调用对象的属性进行存储。同时,解释器会为函数创建一个执行器上下文(context),与上下文对应起来的是一个作用域链。顾名思义,作用域链是关于作用域的链,通常实现为一个链表,链表的每个项都是一个对象,在全局作用域中,该链中有且只有一个对象,即全局对象。对应的,在一个函数中,作用域链上会有两个对象,第一个(首先被访问到的)为调用对象,第二个为全局对象。

    当解释器进入 scopeTest 函数的时候,一个调用对象就被创建了,其中包含了 str 变量作 为其中的一个属性并被初始化为 undefined,当执行到第一个 print(str)时,解释器会在作用域链中查找 str,找到之后,打印其值为 undefined,然后执行赋值语句,此时调用对象的属性 str 会被赋值为 local,因此第二个 print(str)语句会打印 local。

    应该注意的是,作用域链随着嵌套函数的层次会变的很长,但是查找变量的过程依旧是遍历作用域链(链表),一直向上查找,直到找出该值,如果遍历完作用域链仍然没有找到对应的属性,则返回 undefined。

    在 Java 或者 C/C++等语言中,方法(函数)只能依附于对象而存在,不是独立的。而在 JavaScript 中,函数也是一种对象,并非其他任何对象的一部分,理解这一点尤为重要,特别是对理解函数式的 JavaScript 非常有用,在函数式编程语言中,函数被认为是一等的。

    函数的上下文是可以变化的,因此,函数内的 this 也是可以变化的,函数可以作为一个对象的方法,也可以同时作为另一个对象的方法,总之,函数本身是独立的。可以通过 Function 对象上的 call 或者 apply 函数来修改函数的上下文:

    call 和 apply 通常用来修改函数的上下文,函数中的 this 指针将被替换为 call 或者 apply的第一个参数

    //定义一个人,名字为jack
    var jack = {
    name : "jack",
    age : 26
    }
    
    //定义另一个人,名字为abruzzi
    var abruzzi = {
    name : "abruzzi",
    age : 26
    }
    
    //定义一个全局的函数对象
    function printName(){
    return this.name;
    }
    
    //设置printName的上下文为jack, 此时的this为jack
    print(printName.call(jack));
    //设置printName的上下文为abruzzi,此时的this为abruzzi
    print(printName.call(abruzzi));
    
    print(printName.apply(jack));
    print(printName.apply(abruzzi));

    只有一个参数的时候 call 和 apply 的使用方式是一样的,如果有多个参数:

    setName.apply(jack, ["Jack Sept."]);
    print(printName.apply(jack));
    
    setName.call(abruzzi, "John Abruzzi");
    print(printName.call(abruzzi));
    Jack Sept.
    John Abruzzi
    apply 的第二个参数为一个函数需要的参数组成的一个数组,而 call 则需要跟若干个参数,参数之间以逗号(,)隔开即可。
    //高级打印函数的第二个版本
    function adPrint2(str, handler){
    print(handler(str));
    }
    
    //将字符串转换为大写形式,并返回
    function up(str){
    return str.toUpperCase();
    }
    
    //将字符串转换为小写形式,并返回
    function low(str){
    return str.toLowerCase();
    }
    
    adPrint2("Hello, world", up);
    adPrint2("Hello, world", low);
    HELLO, WORLD
    hello, world

    应该注意到,函数 adPrint2 的第二个参数,事实上是一个函数,将这个处理函数作为参数传入,在 adPrint2 的内部,仍然可以调用这个函数,这个特点在很多地方都是有用的,特别是,当我们想要处理一些对象,但是又不确定以何种形式来处理,则完全可以将“处理方式”作为一个抽象的粒度来进行包装(即函数)。

    function currying(){
    return function(){
    print("curring");
    }
    }

    函数 currying 返回一个匿名函数,这个匿名函数会打印”curring”,简单的调用 currying()会得到下面的结果:

    function (){
    print("curring");
    }

    如果要调用 currying 返回的这个匿名函数,需要这样:

    currying()();

    第一个括号操作,表示调用 currying 本身,此时返回值为函数,第二个括号操作符调用这个返回值,则会得到这样的结果:

    currying

    JavaScript 的数组也是一个比较有意思的主题,虽然名为数组(Array),但是根据数组对象上的方法来看,更像是将很多东西混在在一起的结果。而传统的程序设计语言如 C/Java 中,数组内的元素需要具有相同的数据类型,而作为弱类型的 JavaScript,则没有这个限制,事实上,JavaScript 的同一个数组中,可以有各种完全不同类型的元素。

    方法描述
    concat() 连接两个或更多的数组,并返回结果。
    join() 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。
    pop() 删除并返回数组的最后一个元素。
    push() 向数组的末尾添加一个或更多元素,并返回新的长度。
    reverse() 颠倒数组中元素的顺序。
    shift() 删除并返回数组的第一个元素。
    slice() 从某个已有的数组返回选定的元素。
    sort() 对数组的元素进行排序。
    splice() 删除元素,并向数组添加新元素。
    unshift() 向数组的开头添加一个或更多元素,并返回新的长度。
    valueOf() 返回数组对象的原始值。

    可以看出,JavaScript 的数组对象比较复杂,包含有 pop,push 等类似与栈的操作, 又有 slice, reverse,sort 这样类似与列表的操作。或许正因为如此,JavaScript 中的 数组的功能非常强大。

    数组包括一些属性和方法,其最常用的属性则为 length,length 表示数组的当前长度与其他语言不同的是,这个变量并非只读属性

    var array = new Array(1, 2, 3, 4, 5); 
    print(array.length);
    array.length = 3; 
    print(array.length);
    print(array); 
    5
    3 
    1,2,3

    注意到最后的 print 语句的结果是”1,2,3”,原因是对 length 属性的修改会使得数组后边的元素变得不可用(如果修改后的 length 比数组实际的长度小的话),所以可以通过设置 length 属性来将数组元素裁减。

    另一个与其他语言的数组不同的是,字符串也可以作为数组的下标,事实上,在 JavaScript 的数组中,字符串型下标和数字型的下标会被作为两个截然不同的方式来处理,一方面,如果是数字作为下标,则与其他程序设计语言中的数组一样,可以通过 index 来进行访问,而使用字符串作为下标,就会采用访问 JavaScript 对象的属性的方式进行,毕竟 JavaScript 内置的 Array 也是从 Object 上继承下来的

    var stack = new Array();
    
    stack['first'] = 3.1415926; 
    stack['second'] = "okay then."; 
    stack['third'] = new Date();
    
    for(var item in stack){ 
    print(typeof stack[item]);
    }
    //
    number
    string
    object

    在这个例子里,还可以看到不同类型的数据是如何存储在同一个数组中的,这么做有一 定的好处,但是在某些场合则可能形成不便,比如我们在函数一章中讨论过的 sum 函数, sum 接受非显式的参数列表,使用这个函数,需要调用者必须为 sum 提供数字型的列表(当然,字符串无法做 sum 操作)。如果是强类型语言,则对 sum 传入字符串数组会被编译程 序认为是非法的,而在 JavaScript 中,程序需要在运行时才能侦测到这一错误。

    var array = new Array();
    var array = new Array(10);//长度
    var array = new Array("apple", "borland", "cisco");
    //不过,运用最多的为字面量方式来创建,如果第三章中的JSON那样,我们完全可以这样创建数组:
    var array = [];
    var array = ["one", "two", "three", "four"];
    var array = [];
    
    array.push(1);
    array.push(2);
    array.push(3);
    
    array.push("four"); 
    array.push("five");
    
    array.push(3.1415926);
    var len = array.length; 
    for(var i = 0; i < len; i++){
    print(typeof array[i]); 
    }
    //
    number
    number
    number
    string
    string
    number
    for(var i = 0; i < len; i++){ 
    print(array.pop());
    }
    print(array.length);
    //
    3.1415926
    five
    four
    3
    2 
    1 
    0

    join,连接数组元素为一个字符串:

    array = ["one", "two", "three", "four", "five"];
    
    var str1 = array.join(","); 
    var str2 = array.join("|");
    
    print(str1);
    print(str2);
    //one,two,three,four,five 
    one|two|three|four|five

    连接多个数组为一个数组:

    var another = ["this", "is", "another", "array"]; 
    var another2 = ["yet", "another", "array"];
    
    var bigArray = array.concat(another, another2);
    //one,two,three,four,five,this,is,another,array,yet,another,array
    从数组中取出一定数量的元素,不影响数组本身:
    print(bigArray.slice(5,9));
    //this,is,another,array

    slice方法的第一个参数为起始位置,第二个参数为终止位置,操作不影响数组本身。下面我们来看splice方法,虽然这两个方法的拼写非常相似,但是功用则完全不同,事实上,splice是一个相当难用的方法:

    bigArray.splice(5, 2);
    bigArray.splice(5, 0, "very", "new", "item", "here");
    //第一行代码表示,从bigArray数组中,从第5个元素起,删除2个元素;而第二行代码表示,
    从第5个元素起,删除0个元素,并把随后的所有参数插入到从第5个开始的位置,则操作结 果为:
    one,two,three,four,five,very,new,item,here,another,array,yet,another, array

    我们再来讨论下数组的排序,JavaScript的数组的排序函数sort将数组按字母顺序排序,排序过程会影响源数组

    var array = ["Cisio", "Borland", "Apple", "Dell"]; 
    print(array);
    array.sort();
    print(array);
    //Cisio,Borland,Apple,Dell 
    Apple,Borland,Cisio,Dell

    这种字母序的排序方式会造成一些非你所预期的小bug

    var array = [10, 23, 44, 58, 106, 235]; 
    array.sort();
    print(array);
    //10,106,23,235,44,58

    可以看到,sort不关注数组中的内容是数字还是字母,它仅仅是按照字母的字典序来进行排序,对于这种情况,JavaScript提供了另一种途径,通过给sort函数传递一个函数对象, 按照这个函数提供的规则对数组进行排序。

    function sorter(a, b){ 
    return a - b;
    }
    
    var array = [10, 23, 44, 58, 106, 235]; 
    array.sort(sorter);
    print(array);
    //函数sorter接受两个参数,返回一个数值,如果这个值大于0,则说明第一个参数大于第二 个参数,
    如果返回值为0,说明两个参数相等,返回值小于0,则第一个参数小于第二个参数,sort根据这个返回值来进行最终的排序:
    10,23,44,58,106,235
    array.sort(function(a, b){return a - b;});//正序
    array.sort(function(a, b){return b - a;});//逆序

    虽然令人费解,但是 JavaScript 的数组对象上确实没有一个叫做 delete 或者 remove 的方法,这就使得我们需要自己扩展其数组对象。一般来说,我们可以扩展 JavaScript 解 释器环境中内置的对象,这种方式的好处在于,扩展之后的对象可以适用于其后的任意场景,而不用每次都显式的声明。而这种做法的坏处在于,修改了内置对象,则可能产生一些难以预料的错误,比如遍历数组实例的时候,可能会产生令人费解的异常。

    var array = ["one", "two","three","four"]; 
    //数组中现在的内容为:
    //one,two,three,four
    //array.length == 4
    delete array[2]; 
    //one, two, undefined, four 
    //array.length == 4

    可以看到,delete 只是将数组 array 的第三个位置上的元素删掉了,可是数组的长度没有改变,显然这个不是我们想要的结果,不过我们可以借助数组对象自身的 slice 方法来做到。 一个比较好的实现,是来自于 jQuery 的设计者 John Resig:

    //Array Remove - By John Resig (MIT Licensed)
    Array.prototype.remove = function(from, to) {
    var rest = this.slice((to || from) + 1 || this.length); 
    this.length = from < 0 ? this.length + from : from; 
    return this.push.apply(this, rest);
    };

    这个函数扩展了 JavaScript 的内置对象 Array,这样,我们以后的所有声明的数组都会自动的拥有 remove 能力,我们来看看这个方法的用法:

    var array = ["one", "two", "three", "four", "five", "six"]; 
    print(array);
    array.remove(0);//删除第一个元素
    print(array);
    array.remove(-1);//删除倒数第一个元素 
    print(array); array.remove(0,2);//删除数组中下标为0-2的元素(3个) 
    print(array);

    会得到这样的结果:

    one,two,three,four,five,six 
    two,three,four,five,six 
    two,three,four,five
    five

    也就是说,remove 接受两个参数,第一个参数为起始下标,第二个参数为结束下标,其中第二个参数可以忽略,这种情况下会删除指定下标的元素。

    //Array Remove - By John Resig (MIT Licensed)
    Array.remove = function(array, from, to) {
    var rest = array.slice((to || from) + 1 || array.length); 
    array.length = from < 0 ? array.length + from : from; 
    return array.push.apply(array, rest);
    };

    其操作方式与前者并无二致,但是不影响全局对象,代价是你需要显式的传递需要操作的数组作为第一个参数:

    var array = ["one", "two", "three", "four", "five", "six"]; 
    Array.remove(array, 0, 2);//删除0, 1, 2三个元素
    print(array);

    这种方式,相当于给 JavaScript 内置的 Array 添加了一个静态方法。

    在对象与 JSON 这一章中,我们讨论了 for...in 这种遍历对象的方式,这种方式同样适 用于数组

    var array = [1, 2, 3, 4]; for(var item in array){
    print(array[item]); }
    //1 
    2 
    3 
    4

    但是这种方式并不总是有效,比如我们扩展了内置对象 Array

    Array.prototype.useless = function(){}
    //然后重复执行上边的代码,会得到这样的输出:
    1
    2
    3
    4
    function(){}

    设想这样一种情况,如果你对数组的遍历做 sum 操作,那么会得到一个莫名其妙的错误,毕竟函数对象不能做求和操作。幸运的是,我们可以用另一种遍历方式来取得正确的结果:

     for(var i = 0, len = array.length; i < len;i++){ 
        print(array[i]);
        }

    这种 for 循环如其他很多语言中的写法一致,重要的是,它不会访问哪些下标不是数字的元素,如上例中的 function,这个 function 的下标为 useless,是一个字符串。从这个例子我们可以看出,除非必要,尽量不要对全局对象进行扩展,因为对全局对象的扩展会造成所有继承链上都带上“烙印”,而有时候这些烙印会成为滋生 bug 的温床。

    正则表达式是对字符串的结构进行的形式化描述,非常简洁优美,而且功能十分强大。很多的语言都不同程度的支持正则表达式,而在很多的文本编辑器如 Emacs,vim,UE 中, 都支持正则表达式来进行字符串的搜索替换工作。UNIX 下的很多命令行程序,如 awk, grep,find 更是对正则表达式有良好的支持。

    JavaScript 同样也对正则表达式有很好的支持,RegExp 是 JavaScript 中的内置“类”, 通过使用 RegExp,用户可以自己定义模式来对字符串进行匹配。而 JavaScript 中的 String 对象的 replace 方法也支持使用正则表达式对串进行匹配,一旦匹配,还可以通过调用预 设的回调函数来进行替换。

    正则表达式的用途十分广泛,比如在客户端的 JavaScript 环境中的用户输入验证,判 断用户输入的身份证号码是否合法,邮件地址是否合法等。另外,正则表达式可用于查找替换工作,首先应该关注的是正则表达式的基本概念。

    正则表达式是来源于数学定义的,而不是程序员。

    元字符,是一些数学符号,在正则表达式中有特定的含义,而不仅仅表示其“字面”上 的含义,比如星号(*),表示一个集合的零到多次重复,而问号(?)表示零次或一次。如果你 需要使用元字符的字面意义,则需要转义。 下面是一张元字符的表:

    元字符含义
    ^ 串的开始
    $ 串的结束
    * 零到多次匹配
    + 一到多次匹配
    ?  零或一次匹配
     单词边界

    特殊字符,主要是指注入空格,制表符,其他进制(十进制之外的编码方式)等,它们的 特点是以转义字符()为前导。如果需要引用这些特殊字符的字面意义,同样需要转义。

    字符    含义
    字符本身    匹配字符本身
    
        匹配回车
    
        匹配换行
    	    制表符
    f    换页
    ##    匹配十六进制数
    cX    匹配控制字符

    我们经常会遇到要描述一个范围的例子,比如,从 0 到 3 的数字,所有的英文字母, 包含数字,英文字母以及下划线等等,正则表达式规定了如何表示范围:

    标志符含义
    [...] 在集合中的任一个字符
    [^...] 不在集合中的任一个字符
    .  出 之外的任一个字符
    w 所有的单字,包括字母,数字及下划线
    W  不包括所有的单字,w 的补集
    s 所有的空白字符,包括空格,制表符
    S     所有的非空白字符
    d 所有的数字
    D   所有的非数字
     退格字符

    在正则表达式中,括号是一个比较特殊的操作符,它可以有三中作用,这三种都是比较常见的: 第一种情况,括号用来将子表达式标记起来,以区别于其他表达式,比如很多的命令行程序都提供帮助命令,键入 h 和键入 help 的意义是一样的,那么就会有这样的表达式:

    h(elp)?//字符h之后的elp可有可无
    这里的括号仅仅为了将 elp 自表达式与整个表达是隔离(因为 h 是必选的)。

    第二种情况,括号用来分组,当正则表达式执行完成之后,与之匹配的文本将会按照规 则填入各个分组,比如,某个数据库的主键是这样的格式:四个字符表示省份,然后是四个数字表示区号,然后是两位字符表示区县,如 yunn0871cg 表示云南省昆明市呈贡县(当 然,看起来的确很怪,只是举个例子),我们关心的是区号和区县的两位字符代码,怎么分离出来呢?

    var pattern = /w{4}(d{4})(w{2})/;
    var result = pattern.exec("yunn0871cg");
    print("city code = "+result[1]+", county code = "+result[2]);
    result = pattern.exec("shax0917cc");
    print("city code = "+result[1]+", county code = "+result[2]);
    //正则表达式的 exec 方法会返回一个数组(如果匹配成功的话),数组的第一个元素(下标为 0)表示整个串,第一个元素为第一个分组,第二个元素为第二个分组,以此类推。因此上例的执行结果即为:
    city code = 0871, county code = cg 
    city code = 0917, county code = cc

    第三种情况,括号用来对引用起辅助作用,即在同一个表达式中,后边的式子可以引用前边匹配的文本,我们来看一个非常常见的例子:我们在设计一个新的语言,这个语言中有 字符串类型的数据,与其他的程序设计语言并无二致,比如:

    var str = "hello, world"; var str = 'fair enough';
    //均为合法字符,我们可能会设计出这样的表达式来匹配该声明:
    var pattern = /['"][^'"]*['"]/;
    //看来没有什么问题,但是如果用户输入:
    var str = 'hello, world"; var str = "hello, world';

    我们的正则表达式还是可以匹配,注意这两个字符串两侧的引号不匹配!我们需要的是,前边是单引号,则后边同样是单引号,反之亦然。因此,我们需要知道前边匹配的到底是“单” 还是“双”。这里就需要用到引用,JavaScript 中的引用使用斜杠加数字来表示,如1 表 示第一个分组(括号中的规则匹配的文本),2 表示第二个分组,以此类推。因此我们就设计出了这样的表达式:

    var pattern = /(['"])[^'"]*1/;
    //在我们新设计的这个语言中,为了某种原因,在单引号中我们不允许出现双引号,同样,在双引号中也不允许出现单引号,我们可以稍作修改即可完成:
    var pattern = /(['"])[^1]*1/;
    //这样,我们的语言中对于字符串的处理就完善了。

    创建一个正则表达式有两种方式,一种是借助 RegExp 对象来创建,另一种方式是使用正则表达式字面量来创建。在 JavaScript 内部的其他对象中,也有对正则表达式的支持,比如 String 对象的 replace,match 等。我们可以分别来看:

    //使用字面量:
        var regex = /pattern/;
    //使用 RegExp 对象:
    var regex = new RegExp("pattern", switchs);
    //而正则表达式的一般形式描述为:
    var regex = /pattern/[switchs]; 
    修饰符描述
    i 忽略大小写开关
    g 全局搜索开关
    m 多行搜索开关(重定义^与$的意义)

    比如,/java/i 就可以匹配 java/Java/JAVA,而/java/则不可。而 g 开关用来匹配整个串 中所有出现的子模式,如/java/g 匹配”javascript&java”中的两个”java”。而 m 开关定义 是否多行搜索,比如:

    var pattern = /^javascript/;
    print(pattern.test("java
    javascript"));//false 
    pattern = /^javascript/m;
    print(pattern.test("java
    javascript"));//true

    RegExp 对象的方法:

    方法名描述
    test() 测试串中是否有合乎模式的匹配
    exec() 对串进行匹配
    compile() 编译正则表达式

    RegExp 对象的 test 方法用于检测字符串中是否具有匹配的模式,而不关心匹配的结果, 通常用于测试

    var variable = /[a-zA-Z_][a-zA-Z0-9_]*/;
    
    print(variable.test("hello"));//true
    print(variable.test("world"));//true
    print(variable.test("_main_"));//true
    print(variable.test("0871"));//false

    而 exec 则通过匹配,返回需要分组的信息,在分组及引用小节中我们已经做过讨论,而 compile 方法用来改变表达式的模式,这个过程与重新声明一个正则表达式对象的作用相同.

    除了正则表达式对象及字面量外,String 对象中也有多个方法支持正则表达式操作,我们 来通过例子讨论这些方法:

    方法作用
    match 匹配正则表达式,返回匹配数组
    replace 替换
    split 分割
    search 查找,返回首次发现的位置
    var str = "life is very much like a mirror."; 
    var result = str.match(/is|a/g); 
    print(result);//返回["is", "a"]

    这个例子通过 String 的 match 来匹配 str 对象,得到返回值为[“is”, “a”]的一个数组。

    var str = "<span>Welcome, John</span>"; 
    var result = str.replace(/span/g, "div"); 
    print(str);
    print(result);
    //<span>Welcome, John</span> 
    
    <div>Welcome, John</div>

    replace 方法不会影响原始字符串,而将新的串作为返回值。

    var result = str.replace(/(w+),s(w+)/g, "$2, $1"); 
    print(result);
    //<span>John, Welcome</span>
    //因此,我们可以通过$n 来对第 n 个分组进行引用。
    var str = "john : tomorrow   :remove:file";
    var result = str.split(/s*:s*/);
    print(str);
    print(result);
    //john : tomorrow     :remove:file 
    john,tomorrow,remove,file
    //注意此处 split 方法的返回值 result 是一个数组。其中包含了 4 个元素。
    var str = "Tomorrow is another day"; 
    var index = str.search(/another/); 
    print(index);//12
    //search 方法会返回查找到的文本在模式中的位置,如果查找不到,返回-1。

    本小节提供一个实例,用以展示在实际应用中正则表达式的用途,当然,,一个例子不可能涵盖所有的内容,只是一个最常见的场景。

    考虑这样一种情况,我们在 UI 上为用户提供一种快速搜索的能力,使得随着用户的键 入,结果集不断的减少,直到用户找到自己需要的关键字对应的栏目。在这个过程中,用户可以选择是否区分大小写,是否全词匹配,以及高亮一个记录中的所有匹配。

    显然,正则表达式可以满足这个需求,我们在这个例子中忽略掉诸如高亮,刷新结果集等部分,来看看正则表达式在实际中的应用:

    在列表中使用 JSFilter(结果集随用户输入而变化)来看一个代码片段:

    this.content.each(function(){
    var text = $(this).text();
    var pattern = new RegExp(keyword, reopts);
    if(pattern.test(text)){
    var item = text.replace(pattern, function(t){ 
    return "<span
    class=""+filterOptions.highlight+"">"+t+"</span>"; 
    });
    $(this).html(item).show(); 
    }else{//clear previous search result
    $(this).find("span."+filterOptions.highlight).each(function(){ $(this).replaceWith($(this).text());
    }); 
    }
    });

    其中,content 是结果集,是一个集合,其中的每一个项目都可能包含用户输入的关键字,keyword 是用户输入的关键字序列,而 reopts 为正则表达式的选项,可能为(i,g,m),each 是 jQuery 中的遍历集合的方式,非常方便。程序的流程是这样的:

    • 进入循环,取得结果集中的一个值作为当前值
    • 使用正则表达式对象的test方法进行测试
    • 如果测试通过,则高亮标注记录中的关键字
    • 否则跳过,进行下一条的检测

    遍历完所有的结果集,生成了一个新的,高亮标注的结果集,然后将其呈现给用户。而且可以很好的适应用户的需求,比如是否忽略大小写检查,是否高亮所有,是否全词匹配,如果自行编写程序进行分析,则需要耗费极大的时间和精力。

    闭包向来给包括 JavaScript 程序员在内的程序员以神秘,高深的感觉,事实上,闭包 的概念在函数式编程语言中算不上是难以理解的知识。如果对作用域,函数为独立的对象这样的基本概念理解较好的话,理解闭包的概念并在实际的编程实践中应用则颇有水到渠成之 感。

    在 DOM 的事件处理方面,大多数程序员甚至自己已经在使用闭包了而不自知,在这种情况下,对于浏览器中内嵌的 JavaScript 引擎的 bug 可能造成内存泄漏这一问题姑且不论, 就是程序员自己调试也常常会一头雾水。

    用简单的语句来描述 JavaScript 中的闭包的概念:由于 JavaScript 中,函数是对象,对象是属性的集合,而属性的值又可以是对象,则在函数内定义函数成为理所当然,如果在函数 func 内部声明函数 inner,然后在函数外部调用 inner,这个过程即产生了一个闭包

    var outter = []; 
    function clouseTest () {
      var array = ["one", "two", "three", "four"]; 
      for(var i = 0; i < array.length;i++){
        var x = {};
        x.no = i;
        x.text = array[i];
        x.invoke = function(){
          print(i); 
        }
        outter.push(x);
      }
    }
    //调用这个函数 
    clouseTest();
    
    print(outter[0].invoke()); 
    print(outter[1].invoke()); 
    print(outter[2].invoke()); 
    print(outter[3].invoke());
        4
        4 
        4 
        4

    其实,在每次迭代的时候,这样的语句 x.invoke = function(){print(i);}并没有被执行, 只是构建了一个函数体为print(i);的函数对象,如此而已。而当 i=4 时,迭代停止,外部函数返回,当再去调用 outter[0].invoke()时,i 的值依旧为 4,因此 outter 数组中的每一个元素的 invoke 都返回 i 的值:4。

    var outter = [];
    function clouseTest2(){
    var array = ["one", "two", "three", "four"]; 
    for(var i = 0; i < array.length;i++){
    var x = {};
    x.no = i;
    x.text = array[i]; 
    x.invoke = function(no){
    return function(){ 
    print(no);
    } 
    }(i);
           outter.push(x);
        }
    }
    clouseTest2();
    //这个例子中,我们为 x.invoke 赋值的时候,先运行一个可以返回一个函数的函数,然后立 即执行之,这样,x.invoke 的每一次迭代器时相当与执行这样的语句:
    //x == 0
    x.invoke = function(){print(0);}
    //x == 1
    x.invoke = function(){print(1);} 
    //x == 2
    x.invoke = function(){print(2);} 
    //x == 3
    x.invoke = function(){print(3);}

    这样就可以得到正确结果了。闭包允许你引用存在于外部函数中的变量。然而,它并不是使用该变量创建时的值,相反,它使用外部函数中该变量最后的值

    现在,闭包的概念已经清晰了,我们来看看闭包的用途。事实上,通过使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面升代码的执行效率。

    常见格式:(function() { /* code */ })(); 解释:包围函数(function(){})的第一对括号向脚本返回未命名的函数,随后一对空括号立即执行返回的未命名函数,括号内为匿名函数的参数。 作用:可以用它创建命名空间,只要把自己所有的代码都写在这个特殊的函数包装内,那么外部就不能访问,除非你允许(变量前加上window,这样该函数或变量就成为全局)。各JavaScript库的代码也基本是这种组织形式。 总结一下,执行函数的作用主要为 匿名 和 自动执行,代码在被解释时就已经在运行了。

    所有的变量,如果不加上 var 关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全 局对象有很多坏处,比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。除了每次使用变量都是用 var 关键字外, 我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,比如 UI 的初始化,那么我们可以使用闭包:

    var datamodel = { 
    table : [],
    tree : {} 
    };
    
    (function(dm){
    for(var i = 0; i < dm.table.rows; i++){
    var row = dm.table.rows[i];
    for(var j = 0; j < row.cells; i++){
               drawCell(i, j);
           }
    }
    
        //build dm.tree
    })(datamodel);

    我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,最主要的是这种机制不会污染全局对象。

    设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如 果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。 闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。

    var CachedSearchBox = (function(){ 
    var cache = {},
    count = []; 
    return {
    attachSearchBox : function(dsid){ 
    if(dsid in cache){//如果结果在缓存中
    return cache[dsid];//直接返回缓存中的对象 
    }
    var fsb = new uikit.webctrl.SearchBox(dsid);//新建 
    cache[dsid] = fsb;//更新缓存
    if(count.length > 100){//保正缓存的大小<=100
    delete cache[count.shift()]; 
    }
    return fsb; 
    },
    
    clearSearchBox : function(dsid){ 
    if(dsid in cache){
    cache[dsid].clearSelection(); 
    }
    } 
    };
    })(); 
    
    CachedSearchBox.attachSearchBox("input1");

    当我们第二次调用 CachedSearchBox.attachSerachBox(“input1”)的时候,我 们就可以从缓存中取道该对象,而不用再去创建一个新的 searchbox 对象。

    可以先来看一个关于封装的例子,在 person 之外的地方无法访问其内部的变量,而通过提供闭包的形式来访问:

    var person = function(){ 
    //变量作用域为函数内部,外部无法访问 
    var name = "default";
    
    return {
    getName : function(){
    return name; 
    },
    setName : function(newName){ 
    name = newName;
    } 
    }
    }();
    
    print(person.name);//直接访问,结果为undefined
    print(person.getName()); 
    person.setName("abruzzi"); 
    print(person.getName());
    undefined
    default
    abruzzi

    闭包的另一个重要用途是实现面向对象中的对象,传统的对象语言都提供类的模板机制,这样不同的对象(类的实例)拥有独立的成员及状态,互不干涉。虽然 JavaScript 中没 有类这样的机制,但是通过使用闭包,我们可以模拟出这样的机制。

    function Person(){
    var name = "default";
    
    return {
    getName : function(){
    return name; },
        setName : function(newName){ 
        name = newName;
    } 
    }
    };
    
    var john = Person(); 
    print(john.getName()); 
    john.setName("john"); 
    print(john.getName());
    
    var jack = Person(); 
    print(jack.getName()); 
    jack.setName("jack"); 
    print(jack.getName());
    default
    john
    default
    jack

    由此代码可知,john 和 jack 都可以称为是 Person 这个类的实例,因为这两个实例对 name 这个成员的访问是独立的,互不影响的。

    事实上,在函数式的程序设计中,会大量的用到闭包

    在不同的 JavaScript 解释器实现中,由于解释器本身的缺陷,使用闭包可能造成内存泄漏,内存泄漏是比较严重的问题,会严重影响浏览器的响应速度,降低用户体验,甚至会造成浏览器无响应等现象。

    JavaScript 的解释器都具备垃圾回收机制,一般采用的是引用计数的形式,如果一个对象的引用计数为零,则垃圾回收机制会将其回收,这个过程是自动的。但是,有了闭包的 概念之后,这个过程就变得复杂起来了,在闭包中,因为局部的变量可能在将来的某些时刻 需要被使用,因此垃圾回收机制不会处理这些被外部引用到的局部变量,而如果出现循环引 用,即对象 A 引用 B,B 引用 C,而 C 又引用到 A,这样的情况使得垃圾回收机制得出其 引用计数不为零的结论,从而造成内存泄漏。

    关于 this 我们之前已经做过讨论,它表示对调用对象的引用,而在闭包中,最容易出现错误的地方是误用了 this。在前端 JavaScript 开发中,一个常见的错误是错将 this 类比为其他的外部局部变量:

    $(function(){
    var con = $("di##anel"); 
    this.id = "content"; 
    con.click(function(){
    alert(this.id);//panel 
    });
    });

    此处的 alert(this.id)到底引用着什么值呢?很多开发者可能会根据闭包的概念,做出错误 的判断:content

    由是,this.id 显示的被赋值为 content,而在 click 回调中,形成的闭包会引用到 this.id, 因此返回值为 content。然而事实上,这个 alert 会弹出”panel”,究其原因,就是此处的 this,虽然闭包可以引用局部变量,但是涉及到 this 的时候,情况就有些微妙了,因为调用 对象的存在,使得当闭包被调用时(当这个 panel 的 click 事件发生时),此处的 this 引用 的是 con 这个 jQuery 对象。而匿名函数中的 this.id = “content”是对匿名函数本身做的 操作。两个 this 引用的并非同一个对象。

    $(function(){
    var con = $("di##anel"); 
    this.id = "content";
    var self = this; 
    con.click(function(){
    alert(self.id);//content 
    });
    });

    这样,我们在事件处理函数中保存的是外部的一个局部变量 self 的引用,而并非 this。这 种技巧在实际应用中多有应用

    面向对象编程思想在提出之后,很快就流行起来了,它将开发人员从冗长,繁复,难以调试的过程式程序中解放了出来,过程式语言如C

    JavaScript 本身是基于对象的,而并非基于类。但是,JavaScript 的函数式语言的特性使得它本身是可编程的,它可以变成你想要的任何形式。

    JavaScript 中的继承可以通过原型链来实现,调用对象上的一个方法,由于方法在 JavaScript 对象中是对另一个函数对象的引用,因此解释器会在对象中查找该属性,如果没有找到,则在其内部对象 prototype 对象上搜索,由于 prototype 对象与对象本身的结 构是一样的,因此这个过程会一直回溯到发现该属性,则调用该属性,否则,报告一个错误。

    function Base(){
    this.baseFunc = function(){
    print("base behavior"); }
    }
    
    function Middle(){ 
    this.middleFunc = function(){
    print("middle behavior"); }
    }
    
    Middle.prototype = new Base();
    function Final(){
    this.finalFunc = function(){
    print("final behavior"); 
    }
    }
    Final.prototype = new Middle();
    
    function test(){
    var obj = new Final(); 
    obj.baseFunc(); 
    obj.middleFunc(); 
    obj.finalFunc();
    }

     

    在 function test 中,我们 new 了一个 Final 对象,然后依次调用 obj.baseFunc,由于 obj 对象上并无此方法,则按照上边提到的规则,进行回溯,在其原型链上搜索,由于 Final 的原型链上包含 Middle,而 Middle 上又包含 Base,因此会执行这个方法,这样就实现了类的继承。

    base behavior
    middle behavior
    final behavior

    引用是一个比较有意思的主题,JavaScript 中的引用始终指向最终的对象,而并非引 用本身

    var obj = {};//空对象 
    var ref = obj;//引用
    
    obj.name = "objectA"; 
    print(ref.name);//ref跟着添加了name属性
    
    obj = ["one", "two", "three"];//obj指向了另一个对象(数组对象)
    print(ref.name);//ref还指向原来的对象
    print(obj.length);//3
    print(ref.length);//undefined
    objectA
    objectA
    3
    undefined

    obj 只是对一个匿名对象的引用,所以,ref 并非指向它,当 obj 指向另一个数组对象时可以看到,引用 ref 并未改变,而始终指向那个后来添加了 name 属性的"空"对象”{}”。 理解这一点对后边的内容有很大的帮助。

    var obj = {};//新建一个对象,并被obj引用
    
    var ref1 = obj;//ref1引用obj,事实上是引用obj引用的空对象
    var ref2 = obj; 
    
    obj.func = "function";
    print(ref1.func);
    print(ref2.func);

    声明一个对象,然后用两个引用来引用这个对象,然后修改原始的对象,注意这两步的顺序

    function
    function

    根据运行结果我们可以看出,在定义了引用之后,修改原始的那个对象会影响到其引用上,这一点也应该注意。

    有面向对象编程的基础有时会成为一种负担,比如看到 new 的时候,Java 程序员可能 会认为这将会调用一个类的构造器构造一个新的对象出来

     1 function Shape(type){
     2 this.type = type || "rect"; 
     3 this.calc = function(){
     4 return "calc, "+this.type; 
     5 }
     6 }
     7 
     8 var triangle = new Shape("triangle");
     9 print(triangle.calc());
    10 
    11 var circle = new Shape("circle");
    12 print(circle.calc()); 
    13 //calc, triangle
    14 calc, circle

    Java 程序员可能会觉得 Shape 就是一个类,然后 triangle,circle 即是 Shape 对应的具体对象,而其实 JavaScript 并非如此工作的,罪魁祸首即为此 new 操作符。在 JavaScript 中,通过 new 操作符来作用与一个函数,实质上会发生这样的动作:

    首先,创建一个空对象,然后用函数的 apply 方法,将这个空对象传入作为 apply 的 第一个参数,及上下文参数。这样函数内部的 this 将会被这个空的对象所替代:

    var triangle = new Shape("triangle"); 
    //上一句相当于下面的代码
    var triangle = {}; 
    Shape.apply(triangle, ["triangle"]);

    我们可以通过 JavaScript 的函数实现封装,封装的好处在于未经授权的客户 代码无法访问到我们不公开的数据

    function Person(name){ 
    //private variable
    var address = "The Earth";
    
        //public method
    this.getAddress = function(){ 
    return address;
    }
        //public variable
    this.name = name; }
    
    //public
    Person.prototype.getName = function(){ 
    return this.name;
    }
    //public
    Person.prototype.setName = function(name){ 
    this.name = name;
    }

    首先声明一个函数,作为模板,用面向对象的术语来讲,就是一个类。用 var 方式声明的 变量仅在类内部可见,所以 address 为一个私有成员,访问 address 的唯一方法是通过我们向外暴露的 getAddress 方法,而 get/setName,均为原型链上的方法,因此为公开的。

    var jack = new Person("jack"); 
    print(jack.name);//jack 
    print(jack.getName());//jack 
    print(jack.address);//undefined 
    print(jack.getAddress());//The Earth

    直接通过 jack.address 来访问 address 变量会得到 undefined。我们只能通过 jack.getAddress 来访问。这样,address 这个成员就被封装起来了。

    另外需要注意的一点是,我们可以为类添加静态成员,这个过程也很简单,只需要为函数对象添加一个属性即可。

    function Person(name){ 
    //private variable
    var address = "The Earth";
    
        //public method
    this.getAddress = function(){ 
    return address;
    }
        //public variable
    this.name = name; }
    Person.TAG = "javascript-core";//静态变量 
    
    print(Person.TAG);

    也就是说,我们在访问 Person.TAG 时,不需要实例化 Person 类。这与传统的面向对象语言如 Java 中的静态变量是一致的。

    Base 是由 Dean Edwards 开发的一个 JavaScript 的面向对象的基础包,Base 本身很小,只有 140 行,但是这个很小的包对面向对象编程风格有很好的支持,支持类的定义, 封装,继承,子类调用父类的方法等,代码的质量也很高,而且很多项目都在使用 Base 作 为底层的支持。尽管如此,JavaScript 的面向对象风格依然非常古怪,并不可以完全和传统的 OO 语言对等起来。

    下面我们来看几个基于 Base 的例子,假设我们现在在开发一个任务系统,我们需要抽象出一个类来表示任务,对应的,每个任务都可能会有一个监听器,当任务执行之后,需要通知监听器。我们首先定义一个事件监听器的类,然后定义一个任务类:

    var EventListener = Base.extend({ 
    constructor : function(sense){
    this.sense = sense; 
    },
    sense : null,
    handle : function(){
    print(this.sense+" occured");
    } 
    });
    var Task = Base.extend({ 
    constructor : function(name){
    this.name = name; 
    },
    name : null,
    listener : null, 
    execute : function(){
    print(this.name);
    this.listener.handle(); 
    },
    setListener : function(listener){ 
    this.listener = listener;
    }
    });

    创建类的方式很简单,需要给 Base.extend 方法传入一个 JSON 对象,其中可以有成员和方法。方法访问自身的成员时需要加 this 关键字。而每一个类都会有一个 constructor 的 方法,即构造方法。比如事件监听器类(EventListener)的构造器需要传入一个字符串,而任务类(Task)也需要传入任务的名字来进行构造。好了,既然我们已经有了任务类和事件监听器类,我们来实例化它们:

    var printing = new Task("printing");
    var printEventListener = new EventListener("printing");
    printing.setListener(printEventListener); 
    printing.execute();

    首先,创建一个新的 Task,做打印工作,然后新建一个事件监听器,并将它注册在新建的 任务上,这样,当打印发生时,会通知监听器,监听器会做出相应的判断:

    printing
    printing occurred

    既然有了基本的框架,我们就来使用这个框架,假设我们要从 HTTP 服务器上下载一个页面,于是我们设计了一个新的任务类型,叫做HttpRequester

    var HttpRequester = Task.extend({
    constructor : function(name, host, port){
    this.base(name); 
    this.host = host;
    this.port = port;
    },
    host : "127.0.0.1", 
    port : 9527,
    execute : function(){
    print("["+this.name+"] request send to "+this.host+" of port "+this.port);
    this.listener.handle(); 
    }
    });

    HttpRequester 类继承了 Task,并且重载了 Task 类的 execute 方法,setListener 方法的内容与父类一致,因此不需要重载。

    var requester = new HttpRequester("requester1", "127.0.0.1", 8752); 
    var listener = new EventListener("http_request"); 
    requester.setListener(listener);
    requester.execute();

    我们新建一个 HttpRequester 任务,然后注册上事件监听器,并执行之:

    [requester1] request send to 127.0.0.1 of port 8752
    http_request occured

    应该注意到 HttpRequester 类的构造器中,有这样一个语句:

    this.base(name);

    表示执行父类的构造器,即将 name 赋值给父类的成员变量 name,这样在 HttpRequester 的实例中,我们就可以通过 this.name 来访问这个成员了。这套机制简直与在其他传统的 OO 语言并无二致。同时,HttpRequester 类的 execute 方法覆盖了父类的 execute 方法,用面向对象的术语来讲,叫做重写。

    在很多应用中,有些对象不会每次都创建新的实例,而是使用一个固有的实例,比如提供数据源的服务,报表渲染引擎,事件分发器等,每次都实例化一个会有很大的开销,因此 人们设计出了单例模式,整个应用的生命周期中,始终只有顶多一个实例存在。Base 同样可以模拟出这样的能力:

    var
    ReportEngine = Base.extend({ 
    constructor : null,
    run : function(){
       //render the report
    } 
    });
    var uikit = uikit || {}; 
    uikit.event = uikit.event || {};
    
    uikit.event.EventTypes = { 
    EVENT_NONE : 0, 
    EVENT_INDEX_CHANGE : 1, 
    EVENT_LIST_DATA_READY : 2, 
    EVENT_GRID_DATA_READY : 3
    };
    uikit.event.JSEvent = Base.extend({ 
    constructor : function(obj){
    this.type = obj.type || uikit.event.EventTypes.EVENT_NONE;
    this.object = obj.data || {}; 
    },
    
    getType : function(){ 
    return this.type;
    },
    
    getObject : function(){ 
    return this.object;
    } 
    });
    uikit.event.JSEventListener = Base.extend({ 
    constructor : function(listener){
    this.sense = listener.sense;
    this.handle = listener.handle || function(event){}; 
    },
    
    getSense : function(){ 
    return this.sense;
    } 
    });
    uikit.event.JSEventDispatcher = function(){
    if(uikit.event.JSEventDispatcher.singlton){
    return uikit.event.JSEventDispatcher.singlton; 
    }
    
    this.listeners = {};
    
    uikit.event.JSEventDispatcher.singlton = this;
    this.post = function(event){
    var handlers = this.listeners[event.getType()]; 
    for(var index in handlers){
    if(handlers[index].handle && typeof 
    handlers[index].handle == "function")
    handlers[index].handle(event); 
    }
    };
    
    this.addEventListener = function(listener){ 
    var item = listener.getSense();
    var listeners = this.listeners[item]; 
    if(listeners){
    this.listeners[item].push(listener); 
    }else{
    var hList = new Array(); 
    hList.push(listener); 
    this.listeners[item] = hList;
    } 
    };
    }
    uikit.event.JSEventDispatcher.getInstance = function(){ 
    return new uikit.event.JSEventDispatcher();
    };

    这里定义了一个单例的事件分发器,同一个系统中的任何组件都可以向此实例注册自己,或者发送事件到此实例。事件分发器事实上需要为何这样一个数据结构:

    var listeners = { eventType.foo : [
    {sense : "eventType.foo", handle : function(){doSomething();}}
    {sense : "eventType.foo", handle : function(){doSomething();}}
    {sense : "eventType.foo", handle : function(){doSomething();}}
        ],
    eventType.bar : [
    {sense : "eventType.bar", handle : function(){doSomething();}}
    {sense : "eventType.bar", handle : function(){doSomething();}}
    {sense : "eventType.bar", handle : function(){doSomething();}}
    
    ],.. 
    };

    当事件发生之后,分发器会找到该事件处理器的数组,然后依次调用监听器的 handle 方法进行相应。好了,到此为止,我们已经有了事件分发器的基本框架了,下来,我们开始实现我们的组件(Component)。

    uikit.component = uikit.component || {}; 
    uikit.component.EventSupport = Base.extend({
    constructor : function(){
    },
    
    raiseEvent : function(eventdef){
    var e = new uikit.event.JSEvent(eventdef); 
    uikit.event.JSEventDispatcher.getInstance().post(e);
    },
    
    addActionListener : function(listenerdef){
    var l = new uikit.event.JSEventListener(listenerdef);
    uikit.event.JSEventDispatcher.getInstance().addEventListener(l);
    } 
    });

    继承了这个类的类具有事件支持的能力,可以 raise 事件,也可以注册监听器,这个 EventSupport 仅仅做了一个代理,将实际的工作代理到事件分发器上。

    uikit.component.ComponentBase = uikit.component.EventSupport.extend({ 
    constructor: function(canvas) {
    this.canvas = canvas; 
    },
    
    render : function(datamodel){} 
    });

    定义所有的组件的基类,一般而言,组件需要有一个画布(canvas)的属性,而且组件需要有展现自己的能力,因此需要实现 render 方法来画出自己来。

    uikit.component.JSList = uikit.component.ComponentBase.extend({ 
    constructor : function(canvas, datamodel){
    this.base(canvas);
    this.render(datamodel); 
    },
    
    render : function(datamodel){
    var jqo = $(this.canvas);
    var text = "";
    for(var p in datamodel.items){
    text += datamodel.items[p] + ";"; 
    }
    var item = $("<div></div>").addClass("component");
    item.text(text);
    item.click(function(){
    jqo.find("div.selected").removeClass("selected");
    $(this).addClass("selected");
    
    var idx = jqo.find("div").index($(".selected")[0]); 
    var c = new uikit.component.ComponentBase(null); 
    c.raiseEvent({
    type : uikit.event.EventTypes.EVENT_INDEX_CHANGE,
                  data : {index : idx}
              });
    });
    jqo.append(item); 
    },
    update : function(event){
    var jqo = $(this.canvas); jqo.empty();
    var dm = event.getObject().items;
    
    for(var i = 0; i < dm.length();i++){
    var entity = dm.get(i).item; 
    jqo.append(this.createItem({items : entity}));
    } 
    },
    
    createItem : function(datamodel){ 
    var jqo = $(this.canvas);
    var text = datamodel.items;
    
    var item = $("<div></div>").addClass("component");
    item.text(text);
    item.click(function(){
    jqo.find("div.selected").removeClass("selected"); 
    $(this).addClass("selected");
    
    var idx = jqo.find("div").index($(".selected")[0]); 
    var c = new uikit.component.ComponentBase(null); 
    c.raiseEvent({
    type : uikit.event.EventTypes.EVENT_INDEX_CHANGE,
                  data : {index : idx}
              });
    });
    
    return item; 
    },
    getSelectedItemIndex : function(){
    var jqo = $(this.canvas);
    var index = jqo.find("div").index($(".selected")[0]); 
    return index;
    } 
    });
    $(document).ready(function(){
    var ldmap = new uikit.component.ArrayLike(dataModel);
    
    ldmap.addActionListener({
    sense : uikit.event.EventTypes.EVENT_INDEX_CHANGE, 
    handle : function(event){
    var idx = event.getObject().index; 
    uikit.component.EventGenerator.raiseEvent({
    type : uikit.event.EventTypes.EVENT_GRID_DATA_READY,
    data : {rows : ldmap.get(idx).grid} 
    });
    } 
    });
    
    var list = new uikit.component.JSList("div#componentList", []);
    var grid = new uikit.component.JSGrid("div#conditionsTable table tbody");
    
    list.addActionListener({
    sense : uikit.event.EventTypes.EVENT_LIST_DATA_READY, 
    handle : function(event){
    list.update(event); 
    }
    });
    grid.addActionListener({
    sense : uikit.event.EventTypes.EVENT_GRID_DATA_READY, 
    handle : function(event){
    grid.update(event); 
    }
    });
    uikit.component.EventGenerator.raiseEvent({
    type : uikit.event.EventTypes.EVENT_LIST_DATA_READY, 
    data : {items : ldmap}
    });
    
    var colorPanel = new uikit.component.Panel("div#colorPanel"); 
    colorPanel.addActionListener({
    sense : uikit.event.EventTypes.EVENT_INDEX_CHANGE, 
    handle : function(event){
    var idx = parseInt(10*Math.random())
    colorPanel.update(idx); 
    }
    }); 
    });

  • 相关阅读:
    B16-高可用OpenStack(t版)集群分布式存储Ceph部署
    B15-openstack高可用(t版)-cinder计算节点集群
    B14-openstack高可用(t版)-cinder控制节点集群
    B13-openstack高可用(t版)-horazion计算节点集群
    B12-openstack高可用(t版)-Neutron计算节点集群
    B11-openstack高可用(t版)-Neutron控制/网络节点集群
    mysql(windows 10 安装)
    docker 容器 centos + tomcat + jdk
    docker 安装
    docker 把镜像打包成文件
  • 原文地址:https://www.cnblogs.com/Chary/p/No0000103.html
Copyright © 2020-2023  润新知