JavaScript-面向对象编程-七-

74 阅读21分钟

JavaScript 面向对象编程(七)

原文:zh.annas-archive.org/md5/9BD01417886F7CF4434F47DFCFFE13F5

译者:飞龙

协议:CC BY-NC-SA 4.0

附录 D. 正则表达式

当您使用正则表达式(在第四章中讨论,对象)时,您可以匹配文字字符串,例如:

    > "some text".match(/me/); 
    ["me"] 

然而,正则表达式的真正威力来自于匹配模式,而不是文字字符串。以下表格描述了您可以在模式中使用的不同语法,并提供了一些使用示例:

模式描述

| [abc] | 匹配一类字符:

    > "some text".match(/[otx]/g);   
    ["o", "t", "x",   "t"]   

|

| [a-z] | 作为范围定义的一类字符。例如,[a-d]与[abcd]相同,[a-z]匹配所有小写字符,[a-zA-Z0-9_]匹配所有字符,数字和下划线字符:

    > "Some Text".match(/[a-z]/g);   
    ["o", "m", "e",   "e", "x", "t"]   
    > "Some Text".match(/[a-zA-Z]/g);   
    ["S", "o", "m",   "e", "T", "e", "x", "t"]   

|

| [^abc] | 匹配不属于字符类的所有内容:

    > "Some Text".match(/[^a-z]/g);   
    ["S", " ", "T"]   

|

| a|b | 匹配 a 或 b。管道字符表示或,可以使用多次:

    > "Some Text".match(/t|T/g);
    ["T", "t"]
    > "Some Text".match(/t|T|Some/g);
    ["Some", "T",   "t"]

|

| a(?=b) | 只有在后面跟着 b 时才匹配 a:

    > "Some Text".match(/Some(?=Tex)/g);   
    null   
    > "Some Text".match(/Some(?=Tex)/g);   
    ["Some"]   

|

| a(?!b) | 只有在后面不跟着 b 时才匹配 a:

    > "Some Text".match(/Some(?!Tex)/g);   
    null   
    > "Some Text".match(/Some(?!Tex)/g);   
    ["Some"]   

|

| \ | 转义字符,用于帮助您匹配模式中用作文字的特殊字符:

    > "R2-D2".match(/[2-3]/g);   
    ["2", "2"]   
    > "R2-D2".match(/[2\-3]/g);   
    ["2", "-", "2"]   

|

\n``\r``\f``\t``\v换行符回车换行符制表符垂直制表符

| \s | 空格,或前面五个转义序列中的任何一个:

    > "R2\n D2".match(/\s/g);   
    ["\n", " "]   

|

| \S | 与上面相反;匹配除空格之外的所有内容。与[^\s]相同:

    > "R2\n D2".match(/\S/g);   
    ["R", "2", "D",   "2"]   

|

| \w | 任何字母,数字或下划线。与[A-Za-z0-9_]相同:

    > "S0m3 text!".match(/\w/g);   
    ["S", "0", "m",   "3", "t", "e", "x", "t"]   

|

| \W | \w的相反:

    > "S0m3 text!".match(/\W/g);   
    [" ", "!"]   

|

| \d | 匹配数字,与[0-9]相同:

    > "R2-D2 and C-3PO".match(/\d/g);   
    ["2", "2", "3"]   

|

| \D | \d的相反;匹配非数字,与[⁰-9]或[^\d]相同:

    > "R2-D2 and C-3PO".match(/\D/g);   
    ["R", "-", "D",   " ", "a", "n", "d",
      " ", "C",   "-", "P", "O"]   

|

| \b | 匹配词边界,如空格或标点符号。匹配 R 或 D 后面跟着 2:

    > "R2D2 and C-3PO".match(/[RD]2/g);   
    ["R2", "D2"]   

与上面相同,但只在单词的末尾:

    > "R2D2 and C-3PO".match(/[RD]2\b/g);   
    ["D2"]   

相同的模式,但输入中有一个破折号,这也是一个单词的结尾:

    > "R2-D2 and C-3PO".match(/[RD]2\b/g);   
    ["R2", "D2"]   

|

| \B | \b的相反:

    > "R2-D2 and C-3PO".match(/[RD]2\B/g);   
    null   
    > "R2D2 and C-3PO".match(/[RD]2\B/g);   
    ["R2"]   

|

[\b]匹配退格字符。
\0空字符。

| \u0000 | 匹配 Unicode 字符,由四位十六进制数字表示:

    > "стоян".match(/\u0441\u0442\u043E/);   
    ["сто"]   

|

| \x00 | 匹配由两位十六进制数字表示的字符代码:

    > "\x64";   
     "d"   
    > "dude".match(/\x64/g);   
    ["d", "d"]   

|

| ^ | 要匹配的字符串的开头。如果设置了m修饰符(多行),则匹配每行的开头:

   > "regular\nregular\nexpression".match(/r/g);   
    ["r", "r", "r",   "r", "r"]   
    > "regular\nregular\nexpression".match(/^r/g);   
    ["r"]   
   > "regular\nregular\nexpression".match(/^r/mg);   
    ["r", "r"]   

|

| $ | 匹配输入的结尾,或者在使用多行修饰符时,匹配每行的结尾:

    > "regular\nregular\nexpression".match(/r$/g);   
    null   
    > "regular\nregular\nexpression".match(/r$/mg);   
    ["r", "r"]   

|

| . | 匹配除换行符和换行符之外的任何单个字符:

    > "regular".match(/r./g);   
    ["re"]   
    > "regular".match(/r.../g);   
    ["regu"]   

|

| * | 如果出现零次或多次,则匹配前面的模式。例如,/.*/将匹配任何内容,包括空(空输入):

    > "".match(/.*/);   
    [""]   
    > "anything".match(/.*/);   
    ["anything"]   
    > "anything".match(/n.*h/);   
    ["nyth"]   

请记住,模式是“贪婪”的,这意味着它会尽可能多地匹配:

    > "anything within".match(/n.*h/g);   
    ["nything with"]   

|

| ? | 如果出现零次或一次,则匹配前面的模式:

    > "anything".match(/ny?/g);   
    ["ny", "n"]   

|

| + | 如果至少出现一次(或更多次),则匹配前面的模式:

    > "anything".match(/ny+/g);   
    ["ny"]   
    > "R2-D2 and C-3PO".match(/[a-z]/gi);   
    ["R", "D", "a",   "n", "d", "C", "P", "O"]   
    > "R2-D2 and C-3PO".match(/[a-z]+/gi);   
    ["R", "D", "and",   "C", "PO"]   

|

| {n} | 如果出现 n 次,则匹配前面的模式:

    > "regular expression".match(/s/g);   
    ["s", "s"]   
    > "regular expression".match(/s{2}/g);   
    ["ss"]   
    > "regular expression".match(/\b\w{3}/g);   
    ["reg", "exp"]   

|

| {min,max} | 如果出现在 min 和 max 次之间,则匹配前面的模式。您可以省略 max,这将意味着没有最大值,但只有最小值。您不能省略 min。输入为“doodle”,其中“o”重复了 10 次的示例:

    > "doooooooooodle".match(/o/g);   
    ["o", "o", "o",   "o", "o", 
    "o", "o", "o", "o",   "o"]   
    > "doooooooooodle".match(/o/g).length;   
    10   
    > "doooooooooodle".match(/o{2}/g);   
    ["oo", "oo", "oo",   "oo", "oo"]   
    > "doooooooooodle".match(/o{2,}/g);   
    ["oooooooooo"]   
    > "doooooooooodle".match(/o{2,6}/g);   
    ["oooooo", "oooo"]   

|

| (pattern) | 当模式在括号中时,它会被记住,以便可以用于替换。这些也被称为捕获模式。捕获的匹配可用作11,2,... $9 匹配所有“r”出现并重复它们:

    > "regular expression".replace(/(r)/g, '$1$1');   
    "rregularr exprression"   

匹配“re”并将其变为“er”:

    > "regular expression".replace(/(r)(e)/g, '$2$1');   
    "ergular experssion"   

|

| (?:pattern) | 非捕获模式,不会被记住,也不会在11,2...中可用。以下是一个示例,说明如何匹配“re”,但不会记住“r”,第二个模式变为$1:

   > "regular expression".replace(/(?:r)(e)/g, '$1$1');   
   "eegular expeession"   

|

当特殊字符有两种含义时,请确保您注意,就像^?\b一样。

附录 E. 练习问题的答案

本附录列出了章节末尾练习的可能答案。可能的答案意味着它们不是唯一的答案,所以如果您的解决方案不同,不要担心。

与本书的其余部分一样,您应该在控制台中尝试并玩一下。

第一章和最后一章没有练习部分,所以让我们从第二章开始,原始数据类型、数组、循环和条件

第二章,原始数据类型、数组、循环和条件

让我们尝试解决以下练习:

练习

  1. 结果将如下:
        > var a; typeof a; 
        "undefined" 

当您声明一个变量但不用值初始化它时,它会自动获得未定义的值。您还可以检查:

        > a === undefined; 
        true 

v的值将是:

        > var s = '1s'; s++; 
        NaN 

将数字1添加到字符串'1s'中,返回字符串'1s1',这是不是一个数字,但++运算符应该返回一个数字;所以它返回特殊的NaN数字。

程序如下:

        > !!"false"; 
        true 

问题的棘手部分在于"false"是一个字符串,所有字符串在转换为布尔值时都是true(除了空字符串"")。如果问题不是关于字符串"false"而是布尔值false,则双重否定!!将返回相同的布尔值:

        > !!false; 
        false 

正如您所期望的,单个否定返回相反的值:

        > !false; 
        true 
        > !true; 
        false 

您可以测试任何字符串,它都会转换为布尔值true,除了空字符串:

        > !!"hello"; 
        true 
        > !!"0"; 
        true 
        > !!""; 
        false 

执行undefined后的输出如下:

        > !!undefined; 
        false 

这里undefined是假值之一,它转换为false。您可以尝试任何其他假值,例如前面示例中的空字符串""NaN0

        > typeof -Infinity; 
        "number" 

数字类型包括所有数字、NaN、正数和负数Infinity

执行以下操作后的输出是:

        > 10 % "0"; 
        NaN 

字符串"0"被转换为数字0。除以0得到Infinity,没有余数。

执行以下操作后的输出是:

        > undefined == null; 
        true 

==运算符的比较不检查类型,但转换操作数;在这种情况下,两者都是假值。严格比较也检查类型:

        > undefined === null; 
        false 

以下是代码行及其输出:

        > false === ""; 
        false 

不同类型之间的严格比较(在本例中是布尔值和字符串)注定会失败,无论值是什么。

以下是代码行及其输出:

        > typeof "2E+2"; 
        "string" 

引号中的任何内容都是字符串,尽管:

        > 2E+2; 
        200 
        > typeof 2E+2; 
        "number" 

以下是代码行及其输出:

        > a = 3e+3; a++; 
        3000 

3e+33加上三个零,意思是3000。然后++是后增量,意思是它返回旧值,然后增加它并将其分配给a。这就是为什么您在控制台中得到返回值3000,尽管a现在是3001

        > a; 
        3001 

  1. 执行以下操作后的v的值是:
        > var v = v || 10; 
        > v; 
        10 

如果v从未被声明过,则为undefined,因此这与以下内容相同:

        > var v = undefined || 10; 
        > v; 
        10 

但是,如果v已经被定义并初始化为一个非假值,您将获得先前的值。

        > var v = 100; 
        > var v = v || 10; 
        > v; 
        100 

第二次使用var不会“重置”变量。

如果v已经是一个假值(不是100),则检查v || 10将返回10

        > var v = 0; 
        > var v = v || 10; 
        > v; 
        10 

  1. 要打印乘法表,请执行以下操作:
        for (var i = 1; i <= 12; i++) { 
          for (var j = 1; j <= 12; j++) { 
            console.log(i + ' * ' + j + ' = ' + i * j); 
          } 
        } 

或:

        var i = 1, j = 1; 
        while (i <= 12) { 
          while (j <= 12) { 
            console.log(i + ' * ' + j + ' = ' + i * j); 
            j++; 
          } 
          i++; 
          j = 1; 
        } 

第三章,函数

让我们做以下练习:

练习

  1. 要将十六进制颜色转换为 RGB,请执行以下操作:
        function getRGB(hex) { 
          return "rgb(" + 
            parseInt(hex[1] + hex[2], 16) + ", " + 
            parseInt(hex[3] + hex[4], 16) + ", " + 
            parseInt(hex[5] + hex[6], 16) + ")"; 
        } 
        Testing: 
        > getRGB("#00ff00"); 
               "rgb(0, 255, 0)" 
        > getRGB("#badfad"); 
               "rgb(186, 223, 173)" 

这种解决方案的一个问题是,像hex[0]这样的字符串数组访问不在 ECMAScript 3 中,尽管许多浏览器长期支持它,现在在 ES5 中也有描述。

但是,在本书的这一部分,尚未讨论对象和方法。否则,符合 ES3 的解决方案将是使用字符串方法之一,例如charAt()substring()slice()。您还可以使用数组来避免太多的字符串连接:

    function getRGB2(hex) { 
      var result = []; 
      result.push(parseInt(hex.slice(1, 3), 16)); 
      result.push(parseInt(hex.slice(3, 5), 16)); 
      result.push(parseInt(hex.slice(5), 16)); 
      return "rgb(" + result.join(", ") + ")"; 
    } 

奖励练习:重写前面的函数,使用循环,这样您就不必三次输入parseInt(),而只需一次。

  1. 结果如下:
        > parseInt(1e1); 
        10 
        Here, you're parsing something that is already an integer: 
        > parseInt(10); 
        10 
        > 1e1; 
        10 

在这里,字符串的解析放弃了第一个非整数值。parseInt()不理解指数文字,它期望整数表示法:

        > parseInt('1e1'); 
        1 

这是解析字符串'1e1',同时期望它是十进制表示法,包括指数:

        > parseFloat('1e1'); 
        10 

以下是代码行及其输出:

        > isFinite(0 / 10); 
        true 

因为0/100,而0是有限的。

以下是代码行及其输出:

        > isFinite(20 / 0); 
        false 

因为除以0Infinity

        > 20 / 0; 
        Infinity 

以下是代码行及其输出:

        > isNaN(parseInt(NaN)); 
        true 

解析特殊的NaN值是NaN

  1. 以下是结果:
        var a = 1; 
        function f() { 
          function n() { 
            alert(a); 
          } 
          var a = 2; 
          n(); 
        } 
        f(); 

这段代码警报2,即使n()在赋值a = 2之前被定义。在函数n()内部,你看到的是在相同作用域中的变量a,并且在调用f()(因此n())时访问它的最新值。由于变量提升,f()的行为就像是:

        function f() { 
          var a; 
          function n() { 
            alert(a); 
          } 
          a = 2; 
          n(); 
        } 

更有趣的是,考虑这段代码:

        var a = 1; 
        function f() { 
          function n() { 
            alert(a); 
          } 
          n(); 
          var a = 2; 
          n(); 
        } 
        f(); 

它警报undefined,然后是2。你可能期望第一个警报显示1,但由于变量提升,a的声明(而不是初始化)被移动到函数的顶部。就像f()是:

        var a = 1; 
        function f() { 
          var a; // a is now undefined 
          function n() { 
            alert(a); 
          } 
          n(); // alert undefined 
          a = 2; 
          n(); // alert 2 
        } 
        f(); 

本地的a“遮蔽”了全局的a,即使它在底部。

  1. 为什么所有这些警报都是“Boo!”

以下是示例 1 的结果:

        var f = alert; 
        eval('f("Boo!")'); 

以下是示例 2 的结果。你可以将一个函数分配给另一个变量。所以f()指向alert()。评估这个字符串就像这样:

        > f("Boo"); 

在我们执行eval()之后,以下是输出:

        var e; 
        var f = alert; 
        eval('e=f')('Boo!'); 

以下是示例 3 的输出。eval()返回评估的结果。在这种情况下,它是一个赋值e = f,也返回e的新值。就像下面这样:

        > var a = 1; 
        > var b; 
        > var c = (b = a); 
        > c; 
        1 

所以eval('e=f')给你一个指向alert()的指针,它立即执行带有"Boo!"alert()

立即(自调用)匿名函数返回对函数alert()的指针,然后立即用参数"Boo!"调用它:

        (function(){ 
          return alert; 
        })()('Boo!'); 

第四章,对象

让我们解决以下练习:

练习

  1. 这里发生了什么?this是什么,o是什么?
        function F() { 
          function C() { 
            return this; 
          } 
          return C(); 
        } 
        var o = new F(); 

在这里,this === window,因为C()是在没有new的情况下调用的。

还有o === window,因为new F()返回C()返回的对象,即this,而thiswindow

你可以将对C()的调用变成构造函数调用:

        function F() { 
          function C() { 
            return this; 
          } 
          return new C(); 
        } 
        var o = new F(); 

在这里,this是由C()构造函数创建的对象。o也是:

        > o.constructor.name; 
        "C" 

在 ES5 的严格模式下会更有趣。在严格模式下,非构造函数调用会导致thisundefined,而不是全局对象。在F()C()构造函数的主体内部使用"use strict"thisC()中将是undefined。因此,return C()不能返回非对象的undefined(因为所有构造函数调用都返回某种对象),并返回F实例的this(在闭包范围内)。试试看:

        function F() { 
          "use strict"; 
          this.name = "I am F()"; 
          function C() { 
            console.log(this); // undefined 
            return this; 
          } 
          return C(); 
        } 

测试:

        > var o = new F(); 
        > o.name; 
        "I am F()" 

  1. 使用new调用这个构造函数会发生什么?
        function C() { 
          this.a = 1; 
          return false; 
        } 
        And testing: 
        > typeof new C(); 
        "object" 
        > new C().a; 
         1 

new C()是一个对象,不是布尔值,因为构造函数调用总是产生一个对象。这是你得到的this对象,除非你在构造函数中返回其他对象。返回非对象是行不通的,你仍然得到this

  1. 这是做什么?
        > var c = [1, 2, [1, 2]]; 
        > c.sort(); 
        > c; 
         [1, Array[2], 2] 

这是因为sort()比较字符串。[1, 2].toString()"1,2",所以它在"1"之后和"2"之前。

使用join()也是一样的:

        > c.join('--'); 
        > c; 
        "1--1,2--2" 

  1. 假设String()不存在,并创建模仿String()MyString()。将输入的原始字符串视为数组(ES5 中正式支持数组访问)。

这是一个只有练习要求的方法的示例实现。随意继续使用其他方法。参考附录 C,内置对象,获取完整列表。

        function MyString(input) { 
          var index = 0; 

          // cast to string 
          this._value = '' + input; 

          // set all numeric properties for array access 
          while (input[index] !== undefined) { 
            this[index] = input[index]; 
            index++; 
          } 

          // remember the length 
          this.length = index; 
        } 

        MyString.prototype = { 
          constructor: MyString, 
          valueOf: function valueOf() { 
            return this._value; 
          }, 
          toString: function toString() { 
            return this.valueOf(); 
          }, 
          charAt: function charAt(index) { 
            return this[parseInt(index, 10) || 0]; 
          }, 
          concat: function concat() { 
            var prim = this.valueOf(); 
            for (var i = 0, len = arguments.length; i < len; i++) { 
              prim += arguments[i]; 
            } 
            return prim; 
          }, 
          slice: function slice(from, to) { 
            var result = '', 
                original = this.valueOf(); 
            if (from === undefined) { 
              return original; 
            } 
            if (from > this.length) { 
              return result; 
            } 
            if (from < 0) { 
              from = this.length - from; 
            } 
            if (to === undefined || to > this.length) { 
              to = this.length; 
            } 
            if (to < 0) { 
              to = this.length + to; 
            } 
            // end of validation, actual slicing loop now 
            for (var i = from; i < to; i++) { 
              result += original[i]; 
            } 
            return result; 
          }, 
          split: function split(re) { 
            var index = 0, 
               result = [], 
                original = this.valueOf(), 
                match, 
                pattern = '', 
                modifiers = 'g'; 

            if (re instanceof RegExp) { 
              // split with regexp but always set "g" 
              pattern = re.source; 
              modifiers += re.multiline  ? 'm' : ''; 
              modifiers += re.ignoreCase ? 'i' : ''; 
            } else { 
              // not a regexp, probably a string, we'll convert it 
              pattern = re; 
            } 
            re = RegExp(pattern, modifiers); 

            while (match = re.exec(original)) { 
              result.push(this.slice(index, match.index)); 
              index = match.index + new MyString(match[0]).length; 
            } 
            result.push(this.slice(index)); 
            return result; 
           } 
        }; 

测试:

         > var s = new MyString('hello'); 
        > s.length; 
         5 
        > s[0]; 
        "h" 
         > s.toString(); 
         "hello" 
        > s.valueOf(); 
         "hello" 
        > s.charAt(1); 
         "e" 
        > s.charAt('2'); 
        "l" 
        > s.charAt('e'); 
        "h" 
        > s.concat(' world!'); 
        "hello world!" 
        > s.slice(1, 3); 
        "el" 
        > s.slice(0, -1); 
        "hell" 
        > s.split('e'); 
         ["h", "llo"] 
        > s.split('l'); 
         ["he", "", "o"] 

随意使用正则表达式进行拆分。

  1. reverse()方法更新MyString()
        > MyString.prototype.reverse = function reverse() { 
            return this.valueOf().split("").reverse().join(""); 
          }; 
        > new MyString("pudding").reverse(); 
         "gniddup" 

  1. 想象Array()消失了,世界需要你实现MyArray()。以下是一些方法,让你开始:
        function MyArray(length) { 
          // single numeric argument means length 
          if (typeof length === 'number' && 
              arguments[1] === undefined) { 
            this.length = length; 
            return this; 
          } 

          // usual case 
           this.length = arguments.length; 
          for (var i = 0, len = arguments.length; i < len; i++) { 
            this[i] = arguments[i]; 
          } 
          return this; 

          // later in the book you'll learn how to support 
          // a non-constructor invocation too 
        } 

        MyArray.prototype = { 
          constructor: MyArray, 
          join: function join(glue) { 
            var result = ''; 
            if (glue === undefined) { 
              glue = ','; 
            } 
            for (var i = 0; i < this.length - 1; i++) { 
              result += this[i] === undefined ? '' : this[i]; 
              result += glue; 
            } 
            result += this[i] === undefined ? '' : this[i]; 
            return result; 
          }, 
          toString: function toString() { 
            return this.join(); 
          }, 
          push: function push() { 
            for (var i = 0, len = arguments.length; i < len; i++) { 
              this[this.length + i] = arguments[i]; 
            } 
            this.length += arguments.length; 
            return this.length; 
          }, 
          pop: function pop() { 
            var poppd = this[this.length - 1]; 
            delete this[this.length - 1]; 
            this.length--; 
            return poppd; 
          } 
        }; 

测试:

        > var a = new MyArray(1, 2, 3, "test"); 
        > a.toString(); 
        "1,2,3,test" 
        > a.length; 
         4 
        > a[a.length - 1]; 
        "test" 
        > a.push('boo'); 
         5 
        > a.toString(); 
        "1,2,3,test,boo" 
        > a.pop(); 
        "boo" 
        > a.toString(); 
        "1,2,3,test" 
        > a.join(','); 
        "1,2,3,test" 
        > a.join(' isn't '); 
        "1 isn't 2 isn't 3 isn't test" 

如果你觉得这个练习有趣,不要停在join()上;尽可能多地使用方法。

  1. 创建一个MyMath对象,它还具有rand()min([])max([])

这里的重点是Math不是一个构造函数,而是一个具有一些“静态”属性和方法的对象。以下是一些方法,供您开始使用。

我们还可以使用立即函数来保留一些私有实用函数。您也可以采用上面的MyString方法,在那里this._value可以真正是私有的。

        var MyMath = (function () { 

         function isArray(ar) { 
            return 
              Object.prototype.toString.call(ar) === 
                '[object Array]'; 
         } 

          function sort(numbers) { 
            // not using numbers.sort() directly because 
            // `arguments` is not an array and doesn't have sort() 
            return Array.prototype.sort.call(numbers, function (a, b) { 
              if (a === b) { 
                return 0; 
              } 
              return  1 * (a > b) - 0.5; // returns 0.5 or -0.5 
           }); 
          } 

          return { 
            PI:   3.141592653589793, 
            E:    2.718281828459045, 
            LN10: 2.302585092994046, 
            LN2:  0.6931471805599453, 
            // ... more constants 
            max: function max() { 
              // allow unlimited number of arguments 
              // or an array of numbers as first argument 
              var numbers = arguments; 
              if (isArray(numbers[0])) { 
                numbers = numbers[0]; 
              } 
              // we can be lazy:  
              // let Array sort the numbers and pick the last 
              return sort(numbers)[numbers.length - 1]; 
            }, 
            min: function min() { 
              // different approach to handling arguments: 
              // call the same function again 
              if (isArray(numbers)) { 
                return this.min.apply(this, numbers[0]); 
              } 

              // Different approach to picking the min: 
              // sorting the array is an overkill, it's too much  
              // work since we don't worry about sorting but only  
              // about the smallest number. 
              // So let's loop: 
              var min = numbers[0]; 
              for (var i = 1; i < numbers.length; i++) { 
                if (min > numbers[i]) { 
                  min = numbers[i]; 
                } 
             } 
              return min; 
            }, 
            rand: function rand(min, max, inclusive) { 
              if (inclusive) { 
                return Math.round(Math.random() * (max - min) + min); 
                // test boundaries for random number 
                // between 10 and 100 *inclusive*: 
                // Math.round(0.000000 * 90 + 10); // 10 
                // Math.round(0.000001 * 90 + 10); // 10 
                // Math.round(0.999999 * 90 + 10); // 100 

              } 
              return Math.floor(Math.random() * (max - min - 1) + min + 1); 
              // test boundaries for random number 
              // between 10 and 100 *non-inclusive*: 
              // Math.floor(0.000000 * (89) + (11)); // 11 
              // Math.floor(0.000001 * (89) + (11)); // 11 
              // Math.floor(0.999999 * (89) + (11)); // 99 
            } 
          }; 
        })(); 

在您完成本书并了解 ES5 之后,您可以尝试使用defineProperty()来更严格地控制和更接近内置对象的复制。

第五章,原型

让我们尝试解决以下练习:

练习

  1. 创建一个名为shape的对象,该对象具有type属性和getType()方法:
        var shape = { 
          type: 'shape', 
          getType: function () { 
            return this.type; 
          } 
        }; 

  1. 以下是Triangle()构造函数的程序:
        function Triangle(a, b, c) { 
          this.a = a; 
          this.b = b; 
          this.c = c; 
        } 

        Triangle.prototype = shape; 
        Triangle.prototype.constructor = Triangle; 
        Triangle.prototype.type = 'triangle'; 

  1. 要添加getPerimeter()方法,请使用以下代码:
        Triangle.prototype.getPerimeter = function () { 
          return this.a + this.b + this.c; 
        }; 

  1. 测试以下代码:
        > var t = new Triangle(1, 2, 3); 
        > t.constructor === Triangle; 
        true 
        > shape.isPrototypeOf(t); 
        true 
        > t.getPerimeter(); 
        6 
        > t.getType(); 
        "triangle" 

  1. 循环遍历t,只显示自有属性和方法:
        for (var i in t) { 
          if (t.hasOwnProperty(i)) { 
            console.log(i, '=', t[i]); 
         } 
        } 

  1. 使用以下代码片段随机化数组元素:
        Array.prototype.shuffle = function () { 
          return this.sort(function () { 
            return Math.random() - 0.5; 
          }); 
        }; 

测试:

        > [1, 2, 3, 4, 5, 6, 7, 8, 9].shuffle(); 
         [4, 2, 3, 1, 5, 6, 8, 9, 7] 
        > [1, 2, 3, 4, 5, 6, 7, 8, 9].shuffle(); 
         [2, 7, 1, 3, 4, 5, 8, 9, 6] 
        > [1, 2, 3, 4, 5, 6, 7, 8, 9].shuffle(); 
         [4, 2, 1, 3, 5, 6, 8, 9, 7] 

第六章,继承

让我们解决以下练习:

练习

  1. 通过混合到原型中进行多重继承,例如:
        var my = objectMulti(obj, another_obj, a_third, { 
          additional: "properties" 
        }); 
        A possible solution: 
        function objectMulti() { 
          var Constr, i, prop, mixme; 

        // constructor that sets own properties 
        var Constr = function (props) { 
          for (var prop in props) { 
            this[prop] = props[prop]; 
          } 
        }; 

       // mix into the prototype 
       for (var i = 0; i < arguments.length - 1; i++) { 
         var mixme = arguments[i]; 
         for (var prop in mixme) { 
           Constr.prototype[prop] = mixme[prop]; 
         } 
       } 

      return new Constr(arguments[arguments.length - 1]);
   } 

测试:

        > var obj_a = {a: 1}; 
        > var obj_b = {a: 2, b: 2}; 
        > var obj_c = {c: 3}; 
        > var my = objectMulti(obj_a, obj_b, obj_c, {hello: "world"}); 
        > my.a; 
         2 

属性a2,因为obj_b覆盖了与obj_a相同名称的属性(最后一个获胜):

        > my.b; 
        2 
        > my.c; 
        3 
        > my.hello; 
        "world" 
        > my.hasOwnProperty('a'); 
        false 
        > my.hasOwnProperty('hello'); 
        true 

  1. www.phpied.com/files/canvas/上使用画布示例进行练习。

使用以下代码片段绘制几个三角形:

        new Triangle( 
          new Point(100, 155), 
          new Point(30, 50), 
          new Point(220, 00)).draw(); 

        new Triangle( 
          new Point(10, 15),   
          new Point(300, 50), 
          new Point(20, 400)).draw(); 

使用以下代码片段绘制几个正方形:

        new Square(new Point(150, 150), 300).draw(); 
        new Square(new Point(222, 222), 222).draw(); 

使用以下代码片段绘制几个矩形:

        new Rectangle(new Point(100, 10), 200, 400).draw(); 
        new Rectangle(new Point(400, 200), 200, 100).draw(); 

  1. 要添加菱形、风筝、五边形、梯形和圆(重新实现draw()),请使用以下代码:
        function Kite(center, diag_a, diag_b, height) { 
          this.points = [ 
            new Point(center.x - diag_a / 2, center.y), 
            new Point(center.x, center.y + (diag_b - height)), 
            new Point(center.x + diag_a / 2, center.y), 
            new Point(center.x, center.y - height) 
          ]; 
          this.getArea = function () { 
            return diag_a * diag_b / 2; 
          }; 
        } 

        function Rhombus(center, diag_a, diag_b) { 
          Kite.call(this, center, diag_a, diag_b, diag_b / 2); 
        } 

        function Trapezoid(p1, side_a, p2, side_b) { 
          this.points = [p1, p2, new Point(p2.x + side_b, p2.y),
          new Point(p1.x + side_a, p1.y) 
          ]; 

          this.getArea = function () { 
            var height = p2.y - p1.y; 
            return height * (side_a + side_b) / 2; 
          }; 
        } 

        // regular pentagon, all edges have the same length 
        function Pentagon(center, edge) { 
          var r = edge / (2 * Math.sin(Math.PI / 5)), 
              x = center.x, 
              y = center.y; 

          this.points = [new Point(x + r, y),
        new Point(x + r * Math.cos(2 * Math.PI / 5), y - r * 
         Math.sin(2 * Math.PI / 5)), 
        new Point(x - r * Math.cos(    Math.PI / 5), y - r * 
         Math.sin(    Math.PI / 5)), 
        new Point(x - r * Math.cos(    Math.PI / 5), y + r * 
         Math.sin(    Math.PI / 5)), 
        new Point(x + r * Math.cos(2 * Math.PI / 5), y + r * 
         Math.sin(2 * Math.PI / 5)) 
          ]; 

          this.getArea = function () { 
            return 1.72 * edge * edge; 
          }; 
        } 

        function Circle(center, radius) { 
          this.getArea = function () { 
            return Math.pow(radius, 2) * Math.PI; 
          }; 

          this.getPerimeter = function () { 
            return 2 * radius * Math.PI; 
          };   

          this.draw = function () { 
            var ctx = this.context; 
            ctx.beginPath(); 
            ctx.arc(center.x, center.y, radius, 0, 2 * Math.PI); 
            ctx.stroke(); 
          }; 
        } 

        (function () { 
          var s = new Shape(); 
          Kite.prototype = s; 
          Rhombus.prototype = s; 
          Trapezoid.prototype = s; 
          Pentagon.prototype = s; 
          Circle.prototype = s; 
        }()); 

测试:

        new Kite(new Point(300, 300), 200, 300, 100).draw(); 
        new Rhombus(new Point(200, 200), 350, 200).draw(); 
        new Trapezoid( 
          new Point(100, 100), 100,  
          new Point(50, 250), 400).draw(); 
        new Pentagon(new Point(400, 400), 100).draw(); 
        new Circle(new Point(500, 300), 270).draw(); 

练习

测试新形状的结果

  1. 想出另一种继承的方法。使用uber让子类可以访问其父类。还要让父类意识到它们的子类。

请记住,并非所有子类都继承Shape;例如,Rhombus继承KiteSquare继承Rectangle。您最终会得到类似这样的东西:

        // inherit(Child, Parent) 
        inherit(Rectangle, Shape); 
        inherit(Square, Rectangle); 

在本章和上一个练习中的继承模式中,所有子类都共享相同的原型,例如:

        var s = new Shape(); 
        Kite.prototype = s; 
        Rhombus.prototype = s; 

虽然这很方便,但这也意味着没有人可以触及原型,因为这会影响其他人的原型。缺点是所有自定义方法都需要自有属性,例如this.getArea

最好将方法共享在实例之间,并在原型中定义,而不是为每个对象重新创建它们。以下示例将自定义的getArea()方法移动到原型中。

在继承函数中,您会看到子类只继承父类的原型。因此,诸如this.lines之类的自有属性将不会被设置。因此,您需要让每个子类构造函数调用其uber以获取自有属性,例如:

        Child.prototype.uber.call(this, args...) 

另一个很好的功能是将已添加到子类的原型属性传递给子类。这允许子类首先继承,然后添加更多自定义或者反过来,这也更方便一些。

        function inherit(Child, Parent) { 
          // remember prototype 
          var extensions = Child.prototype; 

          // inheritance with an intermediate F() 
          var F = function () {}; 
           F.prototype = Parent.prototype; 
          Child.prototype = new F(); 
          // reset constructor 
          Child.prototype.constructor = Child; 
          // remember parent 
          Child.prototype.uber = Parent; 

          // keep track of who inherits the Parent 
          if (!Parent.children) { 
            Parent.children = []; 
          } 
          Parent.children.push(Child); 

          // carry over stuff previsouly added to the prototype 
          // because the prototype is now overwritten completely 
          for (var i in extensions) { 
            if (extensions.hasOwnProperty(i)) { 
              Child.prototype[i] = extensions[i]; 
            } 
          } 
        } 

Shape()Line()Point()的一切都保持不变。变化只发生在子类中:

        function Triangle(a, b, c) { 
          Triangle.prototype.uber.call(this); 
          this.points = [a, b, c]; 
        } 

        Triangle.prototype.getArea = function () { 
          var p = this.getPerimeter(), s = p / 2; 
          return Math.sqrt(s * (s - this.lines[0].length) * 
        (s - this.lines[1].length) * (s - this.lines[2].length)); 
        }; 

        function Rectangle(p, side_a, side_b) { 
          // calling parent Shape() 
          Rectangle.prototype.uber.call(this); 

          this.points = [ p, 
            new Point(p.x + side_a, p.y), 
            new Point(p.x + side_a, p.y + side_b), 
            new Point(p.x, p.y + side_b) 
          ]; 
        } 

       Rectangle.prototype.getArea = function () { 
           // Previsouly we had access to side_a and side_b  
           // inside the constructor closure. No more. 
          // option 1: add own properties this.side_a and this.side_b 
          // option 2: use what we already have: 
          var lines = this.getLines(); 
          return lines[0].length * lines[1].length; 
        }; 

        function Square(p, side) { 
          this.uber.call(this, p, side, side); 
          // this call is shorter than Square.prototype.uber.call() 
          // but may backfire in case you inherit  
          // from Square and call uber 
          // try it :-) 
        } 

继承:

        inherit(Triangle, Shape); 
        inherit(Rectangle, Shape); 
        inherit(Square, Rectangle); 

测试:

        > var sq = new Square(new Point(0, 0), 100); 
        > sq.draw(); 
        > sq.getArea(); 
        10000 

测试instanceof是否正确:

        > sq.constructor === Square; 
        true 
        > sq instanceof Square; 
        true 
        > sq instanceof Rectangle; 
        true 
        > sq instanceof Shape; 
        true 

children数组:

        > Shape.children[1] === Rectangle; 
        true 
        > Rectangle.children[0] === Triangle; 
        false 
        > Rectangle.children[0] === Square; 
        true 
        > Square.children; 
        undefined 

并且uber看起来也不错:

        > sq.uber === Rectangle; 
        true 

调用isPrototypeOf()也会返回预期的结果:

        Shape.prototype.isPrototypeOf(sq); 
        true 
        Rectangle.prototype.isPrototypeOf(sq); 
        true 
        Triangle.prototype.isPrototypeOf(sq); 
        false 

完整的代码可在www.phpied.com/files/canvas/index2.html上找到,还有来自上一个练习的额外的Kite()Circle()等。

第七章,浏览器环境

让我们练习以下练习:

练习

  1. 标题时钟程序如下:
        setInterval(function () { 
          document.title = new Date().toTimeString(); 
        }, 1000); 

  1. 要动画调整弹出窗口的大小从 200 x 200 到 400 x 400,请使用以下代码:
        var w = window.open( 
            'http://phpied.com', 'my', 
             'width = 200, height = 200'); 

        var i = setInterval((function () { 
          var size = 200; 
          return function () { 
            size += 5; 
            w.resizeTo(size, size); 
            if (size === 400) { 
              clearInterval(i); 
            } 
          }; 
        }()), 100); 

每 100 毫秒(1/10 秒),弹出窗口的大小增加五个像素。您保留对间隔i的引用,以便在完成后清除它。变量size跟踪弹出窗口的大小(为什么不在闭包内保持它私有)。

  1. 地震程序如下:
       var i = setInterval((function () { 
          var start = +new Date(); // Date.now() in ES5 
          return function () { 
            w.moveTo( 
              Math.round(Math.random() * 100), 
              Math.round(Math.random() * 100)); 
            if (new Date() - start > 5000) { 
              clearInterval(i); 
            } 
          }; 
         }()), 20); 

尝试所有这些,但使用requestAnimationFrame()而不是setInterval()

  1. 带有回调的不同的walkDOM()如下:
        function walkDOM(n, cb) { 
          cb(n); 
          var i, 
              children = n.childNodes, 
              len = children.length, 
              child; 
          for (i = 0; i < len; i++) { 
          child = n.childNodes[i]; 
            if (child.hasChildNodes()) { 
              walkDOM(child, cb); 
            } 
          } 
        } 

测试:

        > walkDOM(document.documentElement,
        console.dir.bind(console)); 
       html 
       head 
       title 
       body 
       h1 
       ... 

  1. 要删除内容并清理函数,请使用以下代码:
        // helper 
        function isFunction(f) { 
          return Object.prototype.toString.call(f) === 
            "[object Function]"; 
        } 

        function removeDom(node) { 
          var i, len, attr; 

          // first drill down inspecting the children 
          // and only after that remove the current node 
          while (node.firstChild) { 
            removeDom(node.firstChild); 
          } 

          // not all nodes have attributes, e.g. text nodes don't 
          len = node.attributes ? node.attributes.length : 0; 

          // cleanup loop 
          // e.g. node === <body>,  
          // node.attributes[0].name === "onload" 
          // node.onload === function()... 
          // node.onload is not enumerable so we can't use  
          // a for-in loop and have to go the attributes route 
          for (i = 0; i < len; i++) { 
            attr = node[node.attributes[i].name]; 
            if (isFunction(attr)) { 
              // console.log(node, attr); 
              attr = null; 
            } 
          } 

          node.parentNode.removeChild(node); 
        } 

测试:

        > removeDom(document.body); 

  1. 要动态包含脚本,请使用以下代码:
        function include(url) { 
          var s = document.createElement('script'); 
          s.src = url; 
          document.getElementsByTagName('head')[0].
          appendChild(s); 
        } 

测试:

        > include("http://www.phpied.com/files/jinc/1.js"); 
        > include("http://www.phpied.com/files/jinc/2.js"); 

  1. 事件:事件实用程序如下:
        var myevent = (function () { 

          // wrap some private stuff in a closure 
          var add, remove, toStr = Object.prototype.toString; 

          // helper 
          function toArray(a) { 
            // already an array 
            if (toStr.call(a) === '[object Array]') { 
              return a; 
           } 

            // duck-typing HTML collections, arguments etc 
            var result, i, len; 
            if ('length' in a) { 
              for (result = [], i = 0, len = a.length; i < len; i++)
              { 
                result[i] = a[i]; 
              } 
              return result; 
           } 

            // primitives and non-array-like objects 
            // become the first and single array element 
            return [a]; 
          } 

          // define add() and remove() depending 
          // on the browser's capabilities 
          if (document.addEventListener) { 
            add = function (node, ev, cb) { 
              node.addEventListener(ev, cb, false); 
            }; 
            remove = function (node, ev, cb) { 
              node.removeEventListener(ev, cb, false); 
            }; 
          } else if (document.attachEvent) { 
            add = function (node, ev, cb) { 
              node.attachEvent('on' + ev, cb); 
            }; 
            remove = function (node, ev, cb) { 
              node.detachEvent('on' + ev, cb); 
            }; 
          } else { 
            add = function (node, ev, cb) { 
              node['on' + ev] = cb; 
            }; 
            remove = function (node, ev) { 
              node['on' + ev] = null; 
            }; 
          } 

          // public API 
          return { 

            addListener: function (element, event_name, callback) { 
              // element could also be an array of elements 
              element = toArray(element); 
              for (var i = 0; i < element.length; i++) { 
                add(element[i], event_name, callback); 
              } 
            }, 

           removeListener: function (element, event_name, callback) { 
              // same as add(), only practicing a different loop 
              var i = 0, els = toArray(element), len = els.length; 
             for (; i < len; i++) { 
                remove(els[i], event_name, callback); 
              } 
           }, 

            getEvent: function (event) { 
              return event || window.event; 
            }, 

            getTarget: function (event) { 
              var e = this.getEvent(event); 
              return e.target || e.srcElement; 
            }, 

            stopPropagation: function (event) { 
              var e = this.getEvent(event); 
              if (e.stopPropagation) { 
                e.stopPropagation(); 
              } else { 
                e.cancelBubble = true; 
              } 
            }, 

            preventDefault: function (event) { 
              var e = this.getEvent(event); 
              if (e.preventDefault) { 
                e.preventDefault(); 
              } else { 
                e.returnValue = false; 
              } 
            } 

          }; 
        }()); 

测试:转到任何带有链接的页面,执行以下操作,然后单击任何链接:

        function myCallback(e) { 
          e = myevent.getEvent(e); 
          alert(myevent.getTarget(e).href); 
          myevent.stopPropagation(e); 
          myevent.preventDefault(e); 
        } 
        myevent.addListener(document.links, 'click', myCallback); 

  1. 使用以下代码使用键盘移动div
        // add a div to the bottom of the page 
        var div = document.createElement('div'); 
        div.style.cssText = 'width: 100px; height:
         100px; background: red; position: absolute;'; 
        document.body.appendChild(div); 

        // remember coordinates 
        var x = div.offsetLeft; 
        var y = div.offsetTop; 

        myevent.addListener(document.body, 
         'keydown', function (e) { 
         // prevent scrolling 
          myevent.preventDefault(e); 

          switch (e.keyCode) { 
            case 37: // left 
              x--; 
              break; 
            case 38: // up 
              y--; 
              break; 
            case 39: // right 
              x++; 
              break; 
            case 40: // down 
              y++; 
              break; 
            default: 
              // not interested 
          } 

          // move 
          div.style.left = x + 'px'; 
          div.style.top  = y + 'px'; 

        }); 

  1. 你自己的 Ajax 实用程序:
        var ajax = { 
          getXHR: function () { 
            var ids = ['MSXML2.XMLHTTP.3.0', 
             'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP']; 
            var xhr; 
            if (typeof XMLHttpRequest === 'function') { 
              xhr = new XMLHttpRequest(); 
            } else { 
              // IE: try to find an ActiveX object to use 
              for (var i = 0; i < ids.length; i++) { 
                try { 
                  xhr = new ActiveXObject(ids[i]); 
                  break; 
                } catch (e) {} 
              } 
            } 
            return xhr; 

          }, 
          request: function (url, method, cb, post_body) { 
            var xhr = this.getXHR(); 
            xhr.onreadystatechange = (function (myxhr) { 
              return function () { 
                if (myxhr.readyState === 4 && myxhr.status === 200) { 
                  cb(myxhr); 
                } 
              }; 
            }(xhr)); 
            xhr.open(method.toUpperCase(), url, true); 
            xhr.send(post_body || ''); 
          } 
        }; 

在测试时,请记住相同的来源限制适用,因此您必须在相同的域上。您可以转到www.phpied.com/files/jinc/,这是一个目录列表,然后在控制台中进行测试:

        function myCallback(xhr) { 
          alert(xhr.responseText); 
        } 
        ajax.request('1.css', 'get', myCallback); 
        ajax.request('1.css', 'post', myCallback,
         'first=John&last=Smith'); 

两者的结果是相同的,但是如果您查看 Web 检查器的网络选项卡,您会发现第二个确实是带有主体的POST请求。