JavaScript 面向对象编程(七)
原文:
zh.annas-archive.org/md5/9BD01417886F7CF4434F47DFCFFE13F5译者:飞龙
附录 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) | 当模式在括号中时,它会被记住,以便可以用于替换。这些也被称为捕获模式。捕获的匹配可用作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) | 非捕获模式,不会被记住,也不会在2...中可用。以下是一个示例,说明如何匹配“re”,但不会记住“r”,第二个模式变为$1:
> "regular expression".replace(/(?:r)(e)/g, '$1$1');
"eegular expeession"
|
当特殊字符有两种含义时,请确保您注意,就像^,?和\b一样。
附录 E. 练习问题的答案
本附录列出了章节末尾练习的可能答案。可能的答案意味着它们不是唯一的答案,所以如果您的解决方案不同,不要担心。
与本书的其余部分一样,您应该在控制台中尝试并玩一下。
第一章和最后一章没有练习部分,所以让我们从第二章开始,原始数据类型、数组、循环和条件。
第二章,原始数据类型、数组、循环和条件
让我们尝试解决以下练习:
练习
- 结果将如下:
> 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。您可以尝试任何其他假值,例如前面示例中的空字符串"",NaN或0。
> 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+3是3加上三个零,意思是3000。然后++是后增量,意思是它返回旧值,然后增加它并将其分配给a。这就是为什么您在控制台中得到返回值3000,尽管a现在是3001:
> a;
3001
- 执行以下操作后的
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
- 要打印乘法表,请执行以下操作:
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;
}
第三章,函数
让我们做以下练习:
练习
- 要将十六进制颜色转换为 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(),而只需一次。
- 结果如下:
> 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/10是0,而0是有限的。
以下是代码行及其输出:
> isFinite(20 / 0);
false
因为除以0是Infinity:
> 20 / 0;
Infinity
以下是代码行及其输出:
> isNaN(parseInt(NaN));
true
解析特殊的NaN值是NaN。
- 以下是结果:
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,即使它在底部。
- 为什么所有这些警报都是“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!');
第四章,对象
让我们解决以下练习:
练习
- 这里发生了什么?
this是什么,o是什么?
function F() {
function C() {
return this;
}
return C();
}
var o = new F();
在这里,this === window,因为C()是在没有new的情况下调用的。
还有o === window,因为new F()返回C()返回的对象,即this,而this是window。
你可以将对C()的调用变成构造函数调用:
function F() {
function C() {
return this;
}
return new C();
}
var o = new F();
在这里,this是由C()构造函数创建的对象。o也是:
> o.constructor.name;
"C"
在 ES5 的严格模式下会更有趣。在严格模式下,非构造函数调用会导致this为undefined,而不是全局对象。在F()或C()构造函数的主体内部使用"use strict",this在C()中将是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()"
- 使用
new调用这个构造函数会发生什么?
function C() {
this.a = 1;
return false;
}
And testing:
> typeof new C();
"object"
> new C().a;
1
new C()是一个对象,不是布尔值,因为构造函数调用总是产生一个对象。这是你得到的this对象,除非你在构造函数中返回其他对象。返回非对象是行不通的,你仍然得到this。
- 这是做什么?
> 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"
- 假设
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"]
随意使用正则表达式进行拆分。
- 用
reverse()方法更新MyString():
> MyString.prototype.reverse = function reverse() {
return this.valueOf().split("").reverse().join("");
};
> new MyString("pudding").reverse();
"gniddup"
- 想象
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()上;尽可能多地使用方法。
- 创建一个
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()来更严格地控制和更接近内置对象的复制。
第五章,原型
让我们尝试解决以下练习:
练习
- 创建一个名为
shape的对象,该对象具有type属性和getType()方法:
var shape = {
type: 'shape',
getType: function () {
return this.type;
}
};
- 以下是
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';
- 要添加
getPerimeter()方法,请使用以下代码:
Triangle.prototype.getPerimeter = function () {
return this.a + this.b + this.c;
};
- 测试以下代码:
> var t = new Triangle(1, 2, 3);
> t.constructor === Triangle;
true
> shape.isPrototypeOf(t);
true
> t.getPerimeter();
6
> t.getType();
"triangle"
- 循环遍历
t,只显示自有属性和方法:
for (var i in t) {
if (t.hasOwnProperty(i)) {
console.log(i, '=', t[i]);
}
}
- 使用以下代码片段随机化数组元素:
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]
第六章,继承
让我们解决以下练习:
练习
- 通过混合到原型中进行多重继承,例如:
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
属性a是2,因为obj_b覆盖了与obj_a相同名称的属性(最后一个获胜):
> my.b;
2
> my.c;
3
> my.hello;
"world"
> my.hasOwnProperty('a');
false
> my.hasOwnProperty('hello');
true
- 在
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();
- 要添加菱形、风筝、五边形、梯形和圆(重新实现
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();
测试新形状的结果
- 想出另一种继承的方法。使用
uber让子类可以访问其父类。还要让父类意识到它们的子类。
请记住,并非所有子类都继承Shape;例如,Rhombus继承Kite,Square继承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()等。
第七章,浏览器环境
让我们练习以下练习:
练习
- 标题时钟程序如下:
setInterval(function () {
document.title = new Date().toTimeString();
}, 1000);
- 要动画调整弹出窗口的大小从 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跟踪弹出窗口的大小(为什么不在闭包内保持它私有)。
- 地震程序如下:
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()。
- 带有回调的不同的
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
...
- 要删除内容并清理函数,请使用以下代码:
// 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);
- 要动态包含脚本,请使用以下代码:
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");
- 事件:事件实用程序如下:
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);
- 使用以下代码使用键盘移动
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';
});
- 你自己的 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请求。