为什么80%的码农都做不了架构师?>>>
类和模块
类和原型
- JavaScript中, 类的所有实例对象都从同一个原型对象上继承属性的. 我们可以使用Object.create()来实现:
function range(from, to) {
var r = Object.create(range.method);
r.from = from;
r.to = to;
return r;
}
range.method = {
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 + ")"; }
};
var r = range(1, 3);
// true
console.log(r.includes(2));
// 1 2 3
r.foreach(console.log);
// (1...3)
console.log(r.toString());
类和构造函数
- 构造函数是用来初始化新创建的对象的. 调用构造函数的一个重要特征是: 构造函数的prototype属性被用来做新对象的原型. 这意味着通过同一个构造函数创建的所有对象都继承自同一个相同的对象, 因此它们都是同一个类的成员.
- 定义的构造函数名字首字母通常要大写
function Range(from, to) {
this.from = from;
this.to = to;
}
Range.prototype = {
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 + ")"; }
};
var r = new Range(1, 3);
// true
console.log(r.includes(2));
// 1 2 3
r.foreach(console.log);
// (1...3)
console.log(r.toString());
- instanceof用于检查一个实例对象是否继承至某种原型:
r instanceof Range // 如果r继承自Range.prototype, 则返回true
这里, instanceof并不会检查r是否由Range()构造函数初始化而来, 而会检查r是否继承自Range.prototype. 所以更原始检测原型的方法是:
Range.prototype.isPrototypeOf(r) // 检查r的原型是否为Range.prototype
但上例中存在一定的错误, 当我们执行以下代码时候:
r.constructor.prototype == Range.prototype ==> false
这里是false, 是因为我们并没有给Range的constructor增加Range.
- 每个函数都具有constructor属性, 用于指明此函数是如果构建的:
var F = function(){}
F.constructor == F.prototype.constructor ==> false
F.constructor ==>
function Function() { [native code] }
F.prototype.constructor ==>
function (){}
所以, 针对我们之前所编写的Range.prototype, 我们由于给Range.prototype重新赋值了, 所以需要添加上constructor:
Range.prototype = {
constructor: Range,
...
}
或者我们对Range.prototype进行扩充, 则无需添加constructor:
Range.prototype.includes = function(x) { ... }
JavaScript中的Java式继承
- Range的例子中完美的诠释了继承: 构造函数对象(Range), 原型对象(Range.prototype), 实例对象(new Range(1, 3)).
- 其中, 原型对象中的所有方法和字段, 均会被构造函数对象中同名的方法和字段所覆盖.
- 每个实例对象具有自己特有的函数和方法(来自构造函数对象中所定义的), 而共享的函数和方法来自原型对象中.
- 所以, 一般将通用的方法放在原型对象中, 而原型对象中通常不存放字段.
类的扩充
JavaScript中基于原型的继承机制是动态的: 对象从其原型继承属性, 如果创建对象之后原型的属性发生改变, 也会影响到继承这个原型的所有实例对象.
var o = {
show: function() {
console.log("show");
}
};
var sub_o = Object.create(o);
o.play = function() {
console.log("play");
};
// play
sub_o.play();
// show play
for (var k in sub_o) {
console.log(k);
}
这里, 给原型添加属性, 默认情况下是可枚举的; 在ECMA5下, 可以使用Object.defineProperty()设置为不可枚举, 但不能保证所运用的Web浏览器支持其defineProperty().
所以, 一般我们不推荐给Object.prototype添加方法, 或者给具体的类如String.prototype/Array.prototype添加方法, 也是基于这种考虑的.
类和类型
- instanceof: 如果o继承自c.prototype, 则表达式o instanceof c值为true.
构造函数是类的公共标识, 但原型是唯一的标识. 尽管instanceof运算符的右操作数是构造函数, 但计算过程实际上是检测了对象的继承关系, 而不是检测创建对象的构造函数.
- isPrototypeOf: 检测对象的原型链上是否存在某个特定的原型对象:
range.methods.isPrototypeOf(r); // range.method 是原型对象
instanceof/isPrototypeOf的不足之处在于两点: 1是我们无法确切知道(o instanceof c)中o的具体类名; 2是在多窗口多框架的子页面中, Array()并不相等.
- constructor: 用于指明对象是如何构建的.
备注: instanceof和constructor都无法用来检测对象是因为, 它们在多个执行上下文中是不同的.
- 构造函数的名字: 如果一个函数具有名字, 则无论在不同的上下文中, 它们均是相同的:
function type(o) {
var t, c, n;
if (o === null) return "null";
if (o !== o) return "nan";
if ((t = typeof o) !== "object") return t;
if ((c = classof(o)) !== "Object") return c;
if (o.constructor && typeof o.constructor === "function"
&& (n = o.constructor.getName())) return n;
return "Object";
}
function classof(o) {
return Object.prototype.toString.call(o).slice(8, -1);
}
Function.prototype.getName = function() {
if ("name" in this) return this.name;
return this.name = this.toString().match(/function\s*([^(]*)\(/)[1];
}
var o = Object.create(Array);
// Function
console.log(type(o));
JavaScript中的面向对象技术
- 一个集合类
function Set() {
this.values = {};
this.n = 0;
this.add.apply(this, arguments);
}
Set.prototype.add = function() {
for (var i = 0; i < arguments.length; i++) {
var val = arguments[i];
var str = Set._v2s(val);
if (!this.values.hasOwnProperty(str)) {
this.values[str] = val;
this.n++;
}
}
return this;
};
Set.prototype.remove = function() {
for (var i = 0; i < arguments.length; i++) {
var str = Set._v2s(arguments[i]);
if (this.values.hasOwnProperty(str)) {
delete this.values[str];
this.n--;
}
}
return this;
};
Set.prototype.contains = function(value) {
return this.values.hasOwnProperty(Set._v2s(value));
};
Set.prototype.size = function() {
return this.n;
};
Set.prototype.foreach = function(f, context) {
for (var s in this.values) {
if (this.values.hasOwnProperty(s)) {
f.call(context, this.values[s]);
}
}
};
Set.prototype.toString = function() {
var _arr = [];
for (var k in this.values) {
_arr.push(this.values[k]);
}
console.log('' + _arr);
}
Set._v2s = function(val) {
switch (val) {
case undefined: return 'u';
case null: return 'n';
case true: return 't';
case false: return 'f';
default: switch (typeof val) {
case 'number': return '#' + val;
case 'string': return '"' + val;
default: return '@' + objectId(val);
}
}
function objectId(o) {
var prop = "|**objectid**|";
if (!o.hasOwnProperty(prop)) {
o[prop] = Set._v2s.next++;
}
return o[prop];
}
};
Set._v2s.next = 100;
var set = new Set(1, 2, 3, 2, 1);
// 1,2,3
set.toString();
set.add(3, 4, 5);
// 1,2,3,4,5
set.toString();
set.remove(1, 2);
// 3,4,5
set.toString();
// true
console.log(set.contains(4));
- 枚举类型
function enumeration(namesToValues) {
var enumeration = function() { throw "can't Instantiate Enumerations"; };
var proto = enumeration.prototype = {
constructor: enumeration,
toString: function() { return this.name; },
valueOf: function() { return this.value; },
toJSON: function() { return this.name; }
};
enumeration.values = [];
for (var name in namesToValues) {
var e = Object.create(proto);
e.name = name;
e.value = namesToValues[name];
enumeration[name] = e;
enumeration.values.push(e);
}
enumeration.foreach = function(f, c) {
for (var i = 0; i < this.values.length; i++) f.call(c, this.values[i]);
}
return enumeration;
}
var Coin = enumeration({Penny: 1, Nickel: 5, Dime: 10, Quarter: 25});
var c = Coin.Dime;
// true
console.log(c instanceof Coin);
// true
console.log(c.constructor == Coin);
// 40
console.log(Coin.Quarter + 3 * Coin.Nickel);
// true
console.log(Coin.Dime == 10);
// true
console.log(Coin.Dime > Coin.Nickel);
// Dime:10
console.log(String(Coin.Dime) + ":" + Coin.Dime);
1) 之所以要在开头编写:
var enumeration = function() { ... }
是因为防止如下的调用:
// "can't Instantiate Enumerations
Coin();
本身, enumeration为一个类型, 而非一个函数.
2) 在enumeration中的每一个元素均为proto的继承类型, 在proto中还定义了toString()/valueOf()/toJSON的方法. 例如对proto进行计算时候, 如:
Coin.Quarter + 3 * Coin.Nickel
本身调用的是proto的valueOf()方法, 而调用:
String(Coin.Dime)
本身调用的是proto的toString()方法.
- 标准转换方法
toString(): 返回一个可以表示这个对象的字符串. 在希望用到字符串的地方用到对象的话, JavaScript会自动调用这个方法.
valueOf(): 将对象转换为原始值, 例如进行数学运算符/关系运算符作用于数字文本表示的对象时候, 则自动调用这个方法.
toJSON(): 调用JSON.stringify()时候自动调用.
- 私有状态
如果想要变量为私有, 则可以运用闭包特性(在实际的项目中, 很少使用)
function Range(from, to) {
this.from = function() { return from; };
this.to = function() { return to; };
}
子类
一般使用Object.create()来创建子类(ECMA5中定义的方法).
var super_o = {
_x: undefined,
_y: undefined,
add: function() {
/* ... */
}
};
var sub_1 = Object.create(super_o);
var sub_2 = Object.create(super_o);
sub_1.sub = function() {
/* ... */
};
sub_2.mul = function() {
/* ... */
};
这里, super_o本身为一个prototype原型, 它提供了共享的add方法和_x/_y属性. 但一般情况下数据属性不应该被共享, 而应该绑定到具体的实例中, 所以可修改如下:
var super_o = {
add: function() {
return this._x + this._y;
}
};
var sub_1 = Object.create(super_o);
sub_1._x = 1;
sub_1._y = 2;
// 3
console.log(sub_1.add());
或者如教科书般的写法:
function Super(x, y) {
this._x = x;
this._y = y;
}
Super.prototype = {
add: function() {
return this._x + this._y;
}
};
var sub_1 = new Super(1, 2);
console.log(sub_1.add());
ECMAScript5中的类
- 通过defineProperty()来定义对象不可枚举:
var arr = [1, 2, 3];
arr.show = function() {
console.log('' + this);
};
// 0 1 2 show
for (var k in arr) {
console.log(k);
}
Object.defineProperty(arr, "show", {
writable: true, // 可修改
enumerable: false, // 不可枚举
configurable: true, // 可删除
});
arr.show = 11;
// 11
console.log(arr.show);
// 0 1 2
for (var k in arr) {
console.log(k);
}
- 定义get/set来封装对象状态
var o = {
_x: undefined,
get x() {
return this._x;
},
set x(value) {
this._x = value;
}
};
o.x = 123;
// 123
console.log(o.x);
Object.defineProperty(o, "y", {
get: function() { return this._y; },
set: function(value) { this._y = value; }
});
o.y = 321;
// 321
console.log(o.y);
- 使用Object.preventExtensions()将对象设置为不可扩展, 也就是不能给对象添加任何新属性; Object.seal()不仅不能添加新属性, 而且已有的属性不可配置.