MXOXW

Life always finds a way.

JavaScript 权威指南-类和模块

| Comments

9.1 类和原型
9.2 类和构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* A Range class using a constructor
*/

// range2.js: Another class representing a range of values.
// This is a constructor function that initializes new Range objects.
// Note that it does not create or return the object. It just initializes this.
function Range(from, to) {
// Store the start and end points (state) of this new range object.
// These are noninherited properties that are unique to this object.
this.from = from;
this.to = to;
}
// All Range objects inherit from this object.
// Note that the property name must be "prototype" for this to work.
Range.prototype = {
// Return true if x is in the range, false otherwise
// This method works for textual and Date ranges as well as numeric.
includes: function(x) {
return this.from <= x && x <= this.to;
},
// Invoke f once for each integer in the range.
// This method works only for numeric ranges.
foreach: function(f) {
for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
},
// Return a string representation of the range
toString: function() {
return "(" + this.from + "..." + this.to + ")";
}
};
// Here are example uses of a range object
var r = new Range(1, 3); // Create a range object
r.includes(2); // => true: 2 is in the range
r.foreach(console.log); // Prints 1 2 3
console.log(r); // Prints (1...3)

9.2.2 constructor属性

9.4 类的扩充
JavaScript中基于原型的继承机制是动态的:对象从其原型继承属性,如果创建对象之后原型的属性发生改变,也会影响到继承这个原型的所有实例对象。这意味着我们可以通过给原型对象添加新方法来扩充JavaScript类。

1
2
3
4
5
6
7
8
9
10
11
12
// Define the ES5 String.trim() method if one does not already exist.
// This method returns a string with whitespace removed from the start and end.
String.prototype.trim = String.prototype.trim || function() {
if (!this) return this; // Don't alter the empty string
return this.replace(/^\s+|\s+$/g, ""); // Regular expression magic
};
// Return a function's name. If it has a (nonstandard) name property, use it.
// Otherwise, convert the function to a string and extract the name from that.
// Returns an empty string for unnamed functions like itself.
Function.prototype.getName = function() {
return this.name || this.toString().match(/function\s*([^(]*)\(/)[1];
};

9.5 类和类型
9.5.1 instanceof
instanceof运算符和isPrototypeOf()方法的缺点是,我们无法通过对象来获得类名,只能检测对象是否属于指定的类名。在客户端JavaScript中还有一个比较严重的不足,就是在多窗口和多框架子页面的Web应用中兼容性不佳。每个窗口和框架子页面都具有单独的执行上下文,每个上下文都包含独有的全局变量和一组构造函数。在两个不同框架页面中创建的两个数组继承自两个相同但相互独立的原型对象,其中一个框架页面中的数组不是另一个框架页面的Array()构造函数的实例,instanceof运算结果是false。

9.5.2 constructor属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function typeAndValue(x) {
if (x == null) return ""; // Null and undefined don't have constructors
switch (x.constructor) {
case Number:
return "Number: " + x; // Works for primitive types
case String:
return "String: '" + x + "'";
case Date:
return "Date: " + x; // And for built-in types
case RegExp:
return "Regexp: " + x;
case Complex:
return "Complex: " + x; // And for user-defined types
}
}

9.5.3 构造函数的名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

/**
* A type() function to determine the type of a value
* Return the type of o as a string:
* -If o is null, return "null", if o is NaN, return "nan".
* -If typeof returns a value other than "object" return that value.
* (Note that some implementations identify regexps as functions.)
* -If the class of o is anything other than "Object", return that.
* -If o has a constructor and that constructor has a name, return it.
* -Otherwise, just return "Object".
**/

function type(o) {
var t, c, n; // type, class, name
// Special case for the null value:
if (o === null) return "null";
// Another special case: NaN is the only value not equal to itself:
if (o !== o) return "nan";
// Use typeof for any value other than "object".
// This identifies any primitive value and also functions.
if ((t = typeof o) !== "object") return t;
// Return the class of the object unless it is "Object".
// This will identify most native objects.
if ((c = classof(o)) !== "Object") return c;
// Return the object's constructor name, if it has one
if (o.constructor && typeof o.constructor === "function" &&
(n = o.constructor.getName())) return n;
// We can't determine a more specific type, so return "Object"
return "Object";
}
// Return the class of an object.
function classof(o) {
return Object.prototype.toString.call(o).slice(8, -1);
};
// Return the name of a function (may be "") or null for nonfunctions
Function.prototype.getName = function() {
if ("name" in this) return this.name;
return this.name = this.toString().match(/function\s*([^(]*)\(/)[1];
};

9.5.4 鸭式辩型

When I see a bird that walks like a duck and swims like a duck and quacks like a duck,
I call that bird a duck.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* A function for duck-type checking
*/

// Return true if o implements the methods specified by the remaining args.
function quacks(o /*, ... */ ) {
for (var i = 1; i < arguments.length; i++) { // for each argument after o
var arg = arguments[i];
switch (typeof arg) { // If arg is a:
case 'string': // string: check for a method with that name
if (typeof o[arg] !== "function") return false;
continue;
case 'function': // function: use the prototype object instead
// If the argument is a function, we use its prototype object
arg = arg.prototype;
// fall through to the next case
case 'object': // object: check for matching methods
for (var m in arg) { // For each property of the object
if (typeof arg[m] !== "function") continue; // skip non-methods
if (typeof o[m] !== "function") return false;
}
}
}
// If we're still here, then o implements everything
return true;
}

9.6 JavaScript中的面向对象技术
9.6.1 一个例子:集合类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
* An arbitrary set of values
*/

function Set() { // This is the constructor
this.values = {}; // The properties of this object hold the set
this.n = 0; // How many values are in the set
this.add.apply(this, arguments); // All arguments are values to add
}
// Add each of the arguments to the set.
Set.prototype.add = function() {
for (var i = 0; i < arguments.length; i++) { // For each argument
var val = arguments[i]; // The value to add to the set
var str = Set._v2s(val); // Transform it to a string
if (!this.values.hasOwnProperty(str)) { // If not already in the set
this.values[str] = val; // Map string to value
this.n++; // Increase set size
}
}
return this; // Support chained method calls
};
// Remove each of the arguments from the set.
Set.prototype.remove = function() {
for (var i = 0; i < arguments.length; i++) { // For each argument
var str = Set._v2s(arguments[i]); // Map to a string
if (this.values.hasOwnProperty(str)) { // If it is in the set
delete this.values[str]; // Delete it
this.n--; // Decrease set size
}
}
return this; // For method chaining
};
// Return true if the set contains value; false otherwise.
Set.prototype.contains = function(value) {
return this.values.hasOwnProperty(Set._v2s(value));
};
// Return the size of the set.
Set.prototype.size = function() {
return this.n;
};
// Call function f on the specified context for each element of the set.
Set.prototype.foreach = function(f, context) {
for (var s in this.values) // For each string in the set
if (this.values.hasOwnProperty(s)) // Ignore inherited properties
f.call(context, this.values[s]); // Call f on the value
};
// This internal function maps any JavaScript value to a unique string.
Set._v2s = function(val) {
switch (val) {
case undefined:
return 'u'; // Special primitive
case null:
return 'n'; // values get single-letter
case true:
return 't'; // codes.
case false:
return 'f';
default:
switch (typeof val) {
case 'number':
return '#' + val; // Numbers get # prefix.
case 'string':
return '"' + val; // Strings get " prefix.
default:
return '@' + objectId(val); // Objs and funcs get @
}
}
// For any object, return a string. This function will return a different
// string for different objects, and will always return the same string
// if called multiple times for the same object. To do this it creates a
// property on o. In ES5 the property would be nonenumerable and read-only.
// function objectId(o) {
var prop = "|**objectid**|"; // Private property name for storing ids
if (!o.hasOwnProperty(prop)) // If the object has no id
o[prop] = Set._v2s.next++; // Assign it the next available
return o[prop]; // Return the id
}
};
Set._v2s.next = 100; // Start assigning object ids at this value.

9.8 ECMAScript 5中的类
9.8.1 让属性不可枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Wrap our code in a function so we can define variables in the function scope
(function() {
// Define objectId as a nonenumerable property inherited by all objects.
// When this property is read, the getter function is invoked.
// It has no setter, so it is read-only.
// It is nonconfigurable, so it can't be deleted.
Object.defineProperty(Object.prototype, "objectId", {
get: idGetter, // Method to get value
enumerable: false, // Nonenumerable
configurable: false // Can't delete it
});
// This is the getter function called when objectId is read
function idGetter() { // A getter function to return the id
if (!(idprop in this)) { // If object doesn't already have an id
if (!Object.isExtensible(this)) // And if we can add a property
throw Error("Can't define id for nonextensible objects");
Object.defineProperty(this, idprop, { // Give it one now.
value: nextid++, // This is the value
writable: false, // Read-only
enumerable: false, // Nonenumerable
configurable: false // Nondeletable
});
}
return this[idprop]; // Now return the existing or new value
};
// These variables are used by idGetter() and are private to this function
var idprop = "|**objectId**|"; // Assume this property isn't in use
var nextid = 1; // Start assigning ids at this #
}()); // Invoke the wrapper function to run the code right away

9.8.2 定义不可变的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* Property descriptor utilities
*/

// Make the named (or all) properties of o nonwritable and nonconfigurable.
function freezeProps(o) {
var props = (arguments.length == 1) // If 1 arg
? Object.getOwnPropertyNames(o) // use all props
: Array.prototype.splice.call(arguments, 1); // else named props
props.forEach(function(n) { // Make each one read-only and permanent
// Ignore nonconfigurable properties
if (!Object.getOwnPropertyDescriptor(o, n).configurable) return;
Object.defineProperty(o, n, {
writable: false,
configurable: false
});
});
return o; // So we can keep using it
}
// Make the named (or all) properties of o nonenumerable, if configurable.
function hideProps(o) {
var props = (arguments.length == 1) // If 1 arg
? Object.getOwnPropertyNames(o) // use all props
: Array.prototype.splice.call(arguments, 1); // else named props
props.forEach(function(n) { // Hide each one from the for/in loop
// Ignore nonconfigurable properties
if (!Object.getOwnPropertyDescriptor(o, n).configurable) return;
Object.defineProperty(o, n, {
enumerable: false
});
});
return o;
}

9.8.3 封装对象状态
私有状态存取器方法是可以替换的,而getter,setter方法是无法删除的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
* A Range class with strongly encapsulated endpoints
*/

// This version of the Range class is mutable but encapsulates its endpoint
// variables to maintain the invariant that from <= to.
function Range(from, to) {
// Verify that the invariant holds when we're created
if (from > to) throw new Error("Range: from must be <= to");
// Define the accessor methods that maintain the invariant
function getFrom() {
return from;
}

function getTo() {
return to;
}

function setFrom(f) { // Don't allow from to be set > to
if (f <= to) from = f;
else throw new Error("Range: from must be <= to");
}

function setTo(t) { // Don't allow to to be set < from
if (t >= from) to = t;
else throw new Error("Range: to must be >= from");
}
// Create enumerable, nonconfigurable properties that use the accessors
Object.defineProperties(this, {
from: {
get: getFrom,
set: setFrom,
enumerable: true,
configurable: false
},
to: {
get: getTo,
set: setTo,
enumerable: true,
configurable: false
}
});
}
// The prototype object is unchanged from previous examples.
// The instance methods read from and to as if they were ordinary properties.
Range.prototype = hideProps({
constructor: Range,
includes: function(x) {
return this.from <= x && x <= this.to;
},
foreach: function(f) {
for (var x = Math.ceil(this.from); x <= this.to; x++) f(x);
},
toString: function() {
return "(" + this.from + "..." + this.to + ")";
}
});

9.8.4 防止类的扩展
Object.preventExtensions()可以将类设为不可扩展的;
Object.seal()阻止添加新属性,并将已有属性设为不可配置的;

1
Object.seal(Object.prototype);

Object.freeze()类似于Object.seal(),将所有属性设为只读且不可配置;

9.8.5 子类和ECMAScript 5

9.9 模块
9.9.1 用作命名空间的对象

1
2
3
4
5
6
7
var sets = {};
sets.SingletonSet = sets.AbstractEnumerableSet.extend(...);
var s = new sets.SingletonSet(1);
...

var Set = sets.Set; // Import Set to the global namespace
var s = new Set(1,2,3); // Now we can use it without the sets prefix.

9.9.2 作为私有命名空间的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 声明全局变量Set,使用一个函数的返回值给它赋值
* 函数结束时紧跟的一对圆括号说明这个函数定义后立即执行
* 它的返回值将赋值给Set,而不是将这个函数赋值给Set
* 注意它是一个函数表达式,不是一条语句,因此函数"Invocation"并没有创建全局变量
*/

var Set = (function invocation() {
function Set() { // This constructor function is a local variable.
this.values = {}; // The properties of this object hold the set
this.n = 0; // How many values are in the set
this.add.apply(this, arguments); // All arguments are values to add
}
// Now define instance methods on Set.prototype.
// For brevity, code has been omitted here
Set.prototype.contains = function(value) {
// Note that we call v2s(), not the heavily prefixed Set._v2s()
return this.values.hasOwnProperty(v2s(value));
};
Set.prototype.size = function() {
return this.n;
};
Set.prototype.add = function() { /* ... */ };
Set.prototype.remove = function() { /* ... */ };
Set.prototype.foreach = function(f, context) { /* ... */ };
// These are helper functions and variables used by the methods above
// They're not part of the public API of the module, but they're hidden
// within this function scope so we don't have to define them as a
// property of Set or prefix them with underscores.
function v2s(val) { /* ... */ }

function objectId(o) { /* ... */ }
var nextId = 1;
// The public API for this module is the Set() constructor function.
// We need to export that function from this private namespace so that
// it can be used on the outside. In this case, we export the constructor
// by returning it. It becomes the value of the assignment expression
// on the first line above.
return Set;
}()); // Invoke the function immediately after defining it.

评论