Object.defineProperty(Object.prototype,"description",{
writable:true,
enumerable:false,
configurable:true,
value:function(){
console.log(this);
}
});
function inherit(p){
if(p == null) throw TypeError(); // p是一个对象,不能是null
if(Object.create) return Object.create(p);
var type = typeof p;
if(type !== "object" && type !== "function") throw TypeError();
function fun(){};
fun.prototype = p;
return new fun();
}
/**
* 用来枚举属性的对象工具
*
* 把p中的可枚举属性复制到o中,并返回o
* 如果o和p中含有同名属性,则覆盖o中的属性
* 这个函数并不处理getter和 setter以及复制属性
*/
function extend(o,p)
{
for(prop in p){
o[prop] = p[prop];
}
return o;
}
"=============类和原型".description()
/**
* 在JavaScript中,类的所有实例对象都从同一个原型对象上继承属性。
*/
// 实现一个能表示值范围的类
function range(from,to){
// 使用inherit()函数来创建对象,该对象继承自在下面定义的原型对象
// 原型对象座位函数的一个属性储存,并定义所有“范围对象”所共享的方法
var r = inherit(range.methods);
// 储存新的“范围对象”的起始位置和结束位置(状态)
// 这两个属性是不可继承的,每个对象都拥有唯一属性
r.from = from;
r.to = to;
// 返回这个新创建的对象
return r;
}
// 原型对象定义方法,这个方法为每个范围对象所继承
range.methods = {
// 包含函数,这个方法可以比较数字 也可以比较字符串和日期范围
includes:function(x){
return this.from <= x && this.to >= x;
},
// 对于范围内的每个整数都调用一次fun //此方法只使用与数字
foreach:function(fun){
for(var x = Math.ceil(this.from); x <= this.to; x++) fun(x);
},
// 返回表示这个范围的字符串
toString:function(){return "(" + this.from + "..." + this.to + ")";}
}
//测试用例
var r = range(1,3);
r.description(); // { from: 1, to: 3 }
r.toString().description(); // [String: '(1...3)']
r.includes(2).description(); // [Boolean: true]
r.foreach(console.log); // 输出 1,2,3
/**
* 注意:这段代码定义了一个工厂方法range(),用来创建新的范围对象。
* 虽然这个定义类的其中一种方法。但这种方法并不常用,毕竟没有定义构造函数。
*/
"=============类和构造函数".description()
/**
* 构造函数的一个重要特性就是,构造函数的prototype属性被用做新对象的原型。
* 这意味着通过同一个构造函数创建的所有对象都继承自一个相同的对象,因此它们都是同一个类的成员。
*/
// 对范围类进行修改,使用构造函数来定义类
// 这里是构造函数,用以初始化新创建的“范围对象”
// 注意,这里并没有创建并返回一个对象,仅仅是初始化
function Range(from,to){
this.from = from;
this.to = to;
};
// 所有 实例对象 都继承自这个对象
Range.prototype = {
// 包含函数,这个方法可以比较数字 也可以比较字符串和日期范围
includes:function(x){
return this.from <= x && this.to >= x;
},
// 对于范围内的每个整数都调用一次fun //此方法只使用与数字
foreach:function(fun){
for(var x = Math.ceil(this.from); x <= this.to; x++) fun(x);
},
// 返回表示这个范围的字符串
toString:function(){return "(" + this.from + "..." + this.to + ")";}
};
//测试用例
var r = new Range(1,3)
r.description(); // { from: 1, to: 3 }
r.toString().description(); // [String: '(1...3)']
r.includes(2).description(); // [Boolean: true]
r.foreach(console.log); // 输出 1,2,3
/**
* 注意:由于Range()构造函数式通过new关键字调用的,因此不必调用inherit()或其他逻辑来创建对象。
* 在调用构造函数之前就已经创建了新对象,通过this关键字可以获得这个新对象。
* Range()构造函数只不过是初始化this.
*/
"--------构造函数和类的标识".description();
/**
* 原型对象是类的唯一标识:当且仅当两个对象继承自同一个原型对象时,它们才是属于同一个类的实例。
*
*/
//实际上instanceof并不会检测r是否是由Range()构造函数初始化而来,而会检查r是否继承自Range.prototype
(r instanceof Range).description(); // [Boolean: true] 如果r继承自Range.prototype
"--------constructor属性".description();
/**
* 每个JS函数(除Function.bind())都自动拥有一个prototype属性。
* 这个属性的值是一个对象,这个对象包含唯一一个不可枚举属性constructor。
* constructor属性的值是一个函数对象
*/
var F = function(){};
var p = F.prototype; // f相关联的原型对象
var c = p.constructor; // 与原型对象相关联的函数
(c === F).description(); // [Boolean: true]
(F.prototype.constructor === F).description(); //[Boolean: true]
/**
* 从上面可以看出 构造函数的原型中存在预先定义好的constructor属性,
* 这意味着对象通常继承的constructor均指它们的构造函数。
*/
// 因此
var o = new F();
(o.constructor === F).description(); // [Boolean: true]
(r.constructor === Range).description(); // [Boolean: false]
/**
* 因为Range类使用它自身的一个新对象重写预定义的Range.prototype对象。
* 这个新定义的原型对象不含有constructor属性。因此Range的实例也不含constructor属性
*/
// 第一种解决方案: 显示的给原型添加一个构造函数
Range.prototype = {
constructor:Range, // 显示设置构造函数反向引用
// 包含函数,这个方法可以比较数字 也可以比较字符串和日期范围
includes:function(x){
return this.from <= x && this.to >= x;
},
// 对于范围内的每个整数都调用一次fun //此方法只使用与数字
foreach:function(fun){
for(var x = Math.ceil(this.from); x <= this.to; x++) fun(x);
},
// 返回表示这个范围的字符串
toString:function(){return "(" + this.from + "..." + this.to + ")";}
};
var r1 = new Range(2,4);
(r1.constructor === Range).description(); // [Boolean: true]
// 第二种解决方案: 给预定义的原型对象添加方法(预定义的原型对象包含constructor属性)
function Range2(from,to){
this.from = from;
this.to = to;
}
Range2.prototype.includes = function(x){return this.from <= x && this.to >= x;};
// ... 省略其他方法
var r2 = new Range2();
(r2.constructor === Range2).description(); // [Boolean: true]
"=============JavaScript中Java式的类继承".description()
/**
* 类似Java或者C++等强类型面向对象语言,类成员一般会分为
* 实例变量
* 实例方法
* 类变量
* 类方法
*/
// 一个用以定义简单类的函数
function defineClass(constructor,methods,statics)
{
if(methods) extend(constructor.prototype,methods);
if(statics) extend(constructor,statics);
}
//这里模拟强类型面向对象语言的类
//用来表示复数的类
/**
* 复数分为 实数部分 和 虚数部分
*/
// 构造函数部分
// 实例变量部分
function Complex(real,imaginary)
{
if(isNaN(real) || isNaN(imaginary))
throw new TypeError();
this.real = real;
this.imaginary = imaginary;
}
// 类的实例方法
Complex.prototype.add = function(that){
return new Complex(this.real + that.real,this.imaginary + that.imaginary);
}
Complex.prototype.toString = function(){
return "{" + this.real + "," + this.imaginary + "}";
}
// 类变量
Complex.ZERO = new Complex(0,0);
// 类方法
// 这个类方法将由实例对象的toString方法返回的字符串格式解析为一个Complex对象
// 或抛出异常
Complex.parse = function(s){
try{
var m = Complex._format.exec(s);// 利用正则表达式进行匹配
return new Complex(parseFloat(m[1]),parseFloat(m[2]));
}catch(x){
// 如果解析失败则抛出异常
throw new TypeError("Can't parse '" + s + "' as a complex number .");
}
};
//下划线前缀表明它是类内部使用的,而不属于类的公有API的部分
Complex._format = /^\{([^,]+),([^}]+)\}$/;
var a = new Complex(2,3);
var b = new Complex(4,5);
a.add(b).toString().description(); // [String: '{6,8}']
Complex.parse("{9,9}").description(); // Complex { real: 9, imaginary: 9 }
Complex.ZERO.description(); // Complex { real: 0, imaginary: 0 }
"=============类的扩充".description();
/**
* JS中就与原型的继承机制是动态的:对象从其原型继承属性,如果创建对象之后原型的属性发生变化,
* 也会影响到继承这个原型的所有实例对象。
* 这意味着我们可以通过给原型对象添加新方法来扩充JS类。
*/
// 这里我们给Complex类添加一个计算复数的共轭复数
// 两个实部相等,虚部互为相反数的复数互为共轭复数
Complex.prototype.conj = function(){return new Complex(this.real,-this.imaginary);};
Complex.parse("{5,9}").conj().description(); // Complex { real: 5, imaginary: -9 }
"=============类和类型".description();
/**
* 主要介绍三种用以检测任意对象的类的技术
* (1)instanceof运算符
* (2)constructor属性
* (3)构造函数的名字
*/
"------------- instanceof运算符".description();
var cmpx = new Complex(1,4);
(cmpx instanceof Complex).description(); // [Boolean: true]
(cmpx instanceof Object).description(); // [Boolean: true]
// 可以发现 只要是原型连上的构造函数,返回结果都是true
// isPrototypeOf() 用来检测是不是对象的原型
range.methods.isPrototypeOf(range(1,3)).description(); // [Boolean: true]
"------------- constructor运算符".description();
function typeAndValue(x){
if(x == null) return ""; // null 和 undefined 没有构造函数
switch(x.constructor){
case Number: return "Number :" + x;
case String: return "String : '" + x + "'";
case Date: return "Date :" + x;
case RegExp: return "Regexp :" + x;
case Complex: return "Complex " + x;
}
}
"------------- 构造函数的名称".description();
/**
* 定义type()函数以字符串的形式返回对象的类型。它用typeof云算符来处理原始值和函数。
* 对于对象来说 要么返回class属性的值 要么返回构造函数的名字
*/
/**
* 类属性
* 对象的类属性是一个字符串,用以表示对象的类型信息。
* 间接查询方法就是 toString()(继承自Object.prototype) 返回类型 [object class]
*/
// 写一个classof()函数
// 这个函数对自定义构造函数创建的对象是没办法区分的
function classof(o){
if (o === null) return "Null";
if (0 === undefined) return "Undefined";
return Object.prototype.toString.call(o).slice(8,-1);
}
Object.defineProperty(Function.prototype,"getName",{
writable:true,
enumerable:false,
configurable:true,
value:function(){
if("name" in this) return this.name;
return this.name = this.toString().match(/function\s*([^(]*)\(/)[1];
}
});
function type(o){
var t,c,n; // type,class,name
// 处理null值的特殊情形
if(o === null) return "null";
// 如果一个特殊情形:NaN和它自身不相等
if(o !== o) return "nan";
// 如果typeof的值不是"object",则使用这个值
// 这可以识别出原始值的类型和函数
if((t = typeof o) !== "object") return t;
// 返回对象的类名,除非值为 "Object"
// 这种方式可以识别出大多数的内置对象
if((c = classof(o)) !== "Object") return c;
// 如果对象构造函数的名字存在的话,则返回它
if(o.constructor && typeof o.constructor === "function" && (n = o.constructor.getName()))
return n;
// 其他的类型都无法判别,一律返回"Object"
return "Object";
}
type(cmpx).description(); // [String: 'Complex']
"------------- 鸭式辨型".description();
/**
* 这个例子使用了鸭式辨型
*/
function quacks(o/*,...*/)
{
for(var i = 1 ; i < arguments.length; i++)
{
var arg = arguments[i];
switch(typeof arg){
case "string": // string:直接用名字做检查
if(typeof o[arg] !== "function") return false;
continue;
case "function": // funtion:检查函数的原型对象上方法
// 如果实参是函数,则使用它的原型
arg = arg.prototype; // 进入下一个 case
case "object": // object: 检查匹配的方法
for(var m in arg){ // 遍历对象的每个属性
if(typeof arg[m] !== "function") continue; // 跳过不是方法的属性
if(typeof o[m] !== "function") return false;
}
}
}
// 如果程序能执行到这里,说明o实现了所有的方法
return true;
}
quacks(cmpx,Object).description();
"=============JavaScript的面向对象技术".description();
"------------- 一个例子:集合类".description();
/**
* Set类,它实现从JS值到唯一字符串的映射,然后将字符串用做属性名。
* 对象和函数都具备如此简明可靠的唯一字符串表示。
* 因此集合类必须给集合中的每一个对象或函数定义一个唯一的属性标识。
*/
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; // 支持链式方法调用
}
// 如果集合包含这个值,则返回true,否则返回false
Set.prototype.contains = function(value){
return this.values.hasOwnProperty(Set._v2s(value));
}
// 返回集合的大小
Set.prototype.size = function()
{
return this.n;
}
// 遍历集合中的所有元素,在指定的上下文中调用f
Set.prototype.foreach = function(fun,context)
{
for(var val in this.values)
{
if (this.values.hasOwnProperty(val)) {
fun.call(context,this.values[val]);
}
}
}
// 这是一个内部函数,用以将JS值和唯一的字符串对应起来
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);
}
}
// 对任意对象来说,都会返回一个字符串
// 针对不同的对象,这个函数会返回不同的字符串
// 对于同一对象的多次调用,总是返回相同的字符串
// 为了做到这一点,它给o创建了一个属性,在ES5中,这个属性是不可枚举且是只读的。
function objectId(o){
var prop = "|**objectid**|";
if(!o.hasOwnProperty(prop))
o[prop] = Set._v2s.next++;
return o[prop];
}
};
Set._v2s.next = 100; // 设置初始id的值。
var set = new Set();
set.add("奥特曼");
set.add(12344);
set.add({x:1,y:2});
set.description();
set.remove(12344);
set.description();
"------------- 一个例子:枚举类型".description();
function enumeration(namesToValues)
{
// 这个虚构造函数是返回值
var enumeration = function()
{
throw "Can't Instantiate Enumeration";
}
// 枚举值的方法
enumeration.prototype.toString = function(){ return this.name; };
enumeration.prototype.valueOf = function(){return this.value;};
enumeration.prototype.toJSON = function(){return this.name;};
var prop = enumeration.prototype;
// 用于储存枚举对象,便于遍历
enumeration.values = [];
// 创建新类型的实例
for(name in namesToValues)
{
var e = inherit(prop);
e.name = name;
e.value = namesToValues[name];
enumeration[name] = e;
enumeration.values.push(e);
}
enumeration.foreach = function(fun,context){
for(var i = 0; i < this.values.length;i++){
fun.call(context,this.values[i]);
}
}
return enumeration;
}
var Coin = enumeration({Penny:1,Nickel:5,Dime:10,Quarter:25});
var c = Coin.Dime;
(c instanceof Coin).description();
(c.constructor === Coin).description();
(Coin.Quarter + 3 * Coin.Nickel).description();
(Coin.Dime == 10).description();
(String(Coin.Dime) + ":" + Coin.Dime).description();
/**
* 使用枚举类型来表一一副扑克
*/
// 定义一个表示 “玩牌”的类
function Card(suit,rank){
this.suit = suit;
this.rank = rank;
}
Card.Suit = enumeration({Club:1,Diamonds:2,Hearts:3,Spades:4});
Card.Rank = enumeration({Two:2,Three:3,Four:4,Five:5,Six:6,Seven:7,Eight:8,
Nine:9,Ten:10,Jack:11,Queen:12,King:13,Ace:14});
Card.prototype.toString = function(){
return this.rank.toString() + " of "+ this.suit.toString();
};
// 比较扑克牌中两张牌的大小
Card.prototype.CompareTo = function(that){
if (this.rank > that.rank) return 1;
if (this.rank < that.rank) return -1;
return 0;
}
// 按照数字进行排序的函数
Card.orderByRank = function(a,b){
return a.compareTo(b);
}
// 按照花色数字的排序函数
Card.orderBySuit = function(a,b){
if (this.suit > that.suit) return 1;
if (this.suit < that.suit) return -1;
return a.compareTo(b);
}
// 定义一副标准扑克类
function Deck()
{
var cards = this.cards = [];
Card.Suit.foreach(function(s){
Card.Rank.foreach(function(r){
cards.push(new Card(s,r));
});
});
}
// 洗牌的 方法:重新洗牌 返回洗好的牌
Deck.prototype.shuffle = function(){
var deck = this.cards;
var len = deck.length;
for(var i = 0; i < len -1; ++i){
var r = Math.floor(Math.random()* (i + 1));
var temp = deck[i];
deck[i] = deck[r];
deck[r] = temp;
}
return this;
}
// 发牌的方法:返回牌的数组
Deck.prototype.deal = function(n){
if(this.cards.length < n) throw "Out of cards";
return this.cards.splice(-n,n);
}
// 创建一副新扑克牌,洗牌并发牌
"创建一副新扑克牌,洗牌并发牌".description();
var deck = (new Deck()).shuffle();
var hand = deck.deal(6);
hand.description();
"------------- 标准转换方法".description();
/**
* 有一些方法是在需要做类型转化的时候由JS解释器自动调用的。
* 不需要为定义的每个类都实现这些方法,但这些方法非常重要。
*/
/**
* 最终要的当属toString()。
* 在希望使用字符串的地方用到对象的话(列入字符串连接),JS会自动调用这个方法
* 如果没有实现这个方法,会默认从Object.prototype中继承toString()方法,结果"[object Object]"。
* 字符串应该返回一个可读的字符串。
*/
"---toString()".description();
"---toLocaleString()".description();
/**
* valueOf(),它用来将对象转换为原始值。
* 大多数对象都没有合适的原始值来表示它们,也没有定义这个方法。
*/
"---valueOf()".description();
/**
* toJSON() 这个方法是由JSON.stringify()自动调用的。
* JSON格式用于序列化良好的数据结构,而且可以处理JavaScript原始值、数组和纯对象。
* 它和类无关,当一个对象执行序列化操作时,它会忽略对象的原型和构造函数。
* 例如Range对象,如果将字符串传入JSON.parse(),则会得到一个和Range对象具有相同属性的纯对象,
* 但这个对象不会包含Range继承来的方法。
*/
"---toJSON()".description();
/**
* 如果将对象用于JS的关系比较运算符,不如"<" 和 "<=",JS会首先调用对象的valueOf()方法。
* 但是大多数类并没有valueOf()方法,为了按照显示定义的规则来比较这些类型的对象,
* 可以定义一个名叫compareTo()的方法。
*/
"------------- 比较方法".description();
"------------- 方法借用".description();
var generic = {
toString:function(){
var s = "[";
if(this.constructor && this.constructor.name)
s += this.constructor.name + ": ";
var n = 0;
for(var name in this){
if(!this.hasOwnProperty(name)) continue;
var value = this[name];
if(typeof value === "function") continue;
if(n++) s += ", ";
s += name + "=" + value;
}
return s + "]";
}
}
//测试
Deck.prototype.toString = generic.toString;
deck.toString().description();
"------------- 私有状态".description();
/**
* 我们可以通过将变量(或参数)闭包在一个构造函数内来模拟实现私有实例字段。
* 为了做到这一点,需要在构造函数内部定义一个函数(因此该函数可以访问构造函数内部的参数和变量),
* 并将这个函数赋值给新创建对象的属性。
*
* 注意::这种封装技术造成更多系统开销,运行会更慢,占用内存更多。
*/
function Point(x,y)
{
// 不要将x,y储存成属性,相反定义寄存器来返回断点的值
// 这些值保存在闭包中
this.getPointX = function(){return x;};
this.getPointY = function(){return y;};
}
Point.prototype.toString = function(){
return "(" + this.getPointX()+ "," + this.getPointY() + ")";
}
var point = new Point(66,88);
point.toString().description();
"------------- 构造函数的重载和工厂方法".description();
/**
* 我们可以通过构造函数重载的方式来实现多样化的初始化方式
*/
function Set(){
this.values = {};
this.n = 0;
// 如果传入一个类数组的对象,将数组元素添加至集合中
// 否则,将所有的参数都添加至集合中
if(arguments.length == 1 && (type(arguments[0]) == "Array"))
this.add.apply(this,arguments[0]);
else if(arguments.length > 0)
this.add.apply(this,arguments);
}
var mset = new Set([1,2,3,4]);
mset.description();
/**
* 使用不同名称的工厂方法可以执行不同的初始化
* 由于构造函数式类的公有标识,因此每个类智能有一个构造函数。
*/
function SetFromArray(a){
// 通过以函数的形式调用Set()来初始化这个新对象
// 将a的元素座位参数传入
Set.apply(this,a);
}
// 设置原型,以便SetFromArray能创建Set的实例
SetFromArray.prototype = Set.prototype;
var s = new SetFromArray([1,2,3]);
s.description(); //Set { values: { '#1': 1, '#2': 2, '#3': 3 }, n: 3 }
(s instanceof Set).description(); //[Boolean: true]
"=============子类".description();
"------------- 定义子类".description();
//首先O要是B的实例,B类继承自A类,B的原型对象继承自A的原型对象
// B.prototype = inherit(A.prototype); // 子类派生自父类
// B.prototype.constructor = B; // 重载继承来的constructor属性
// 例子
// 用一个简单的函数来创建简单的子类
function defineSubclass(superclass,constructor,methods,statics)
{
// 建立子类的原型对象
constructor.prototype = inherit(superclass.prototype);
constructor.prototype.constructor = constructor;
// 像对常规类一样赋值方法和类属性
if (methods) extend(constructor.prototype,methods);
if (statics) extend(constructor,statics);
return constructor;
}
// 也可以通过父类构造函数的方法来做到这一点
Function.prototype.extend = function(constructor,methods,statics)
{
return defineSubclass(this,constructor,methods,statics);
}
function A(){
"A constructor".description();
}
A.prototype.printConstructor = function(){
type(this).description();
}
function B(){
"B constructor".description();
}
// defineSubclass(A,B);
A.extend(B);
(new B()).printConstructor();// [String: 'B constructor'] // [String: 'B']
// 例子
// 如果不使用函数,手动实现子类。
// 这里定义Set的子类SingletonSet,它是只读的,而且含有单独的常量成员
function SingletonSet(member){
this.member = member; // 集合中唯一的成员
}
// 创建一个原型对象,这个原型对象继承自Set的原型
SingletonSet.prototype = inherit(Set.prototype);
// 给原型添加属性
// 如果有同名的属性就覆盖Set.prototype中的同名属性
extend(SingletonSet.prototype,{
// 设置适合的constructor属性
constructor:SingletonSet,
// 这个集合是只读的:调用add()和remove()都会报错
add:function(){throw "read-only set";},
// SingleotonSet的实例中永远只有一个元素
size:function(){return 1;},
// 这个方法只调用一次,传入这个集合的唯一成员
foreach:function(f,context){f.call(context,this.member);},
// contains()方法非常简单:只需检查传入的值是否匹配这个集合唯一的成员即可
contains:function(x){return x === this.member}
});
"------------- 构造函数和方法链".description();
// 例子
// NonNullSet 是Set的子类,它的成员不能是null 和 undefined
function NonNullSet(){
// 仅链接到父类
// 作为普通函数调用父类的构造函数来初始化通过该构造杉树调用创建的对象
Set.apply(this,arguments);
}
NonNullSet.prototype = inherit(Set.prototype);
NonNullSet.prototype.constructor = NonNullSet;
// 为了将null和undefined排除在外,只需重写add()方法
NonNullSet.prototype.add = function()
{
// 检查参数是不是null 或者 undefined
for(var i = 0; i < arguments.length; i++)
{
if(arguments[i] == null)
throw new Error("Can't add null or undefined to a NonNullSet");
// 调用父类的add方法以执行实际插入操作
return Set.prototype.add.apply(this,arguments);
}
}
// 例子
// 我们可以定义一个可以接收两个参数的类工厂,
// 两个参数为子类和addf方法过滤函数
//
// 这个函数返回具体Set类子类
// 并重写该类的add()方法用以对添加的元素做特殊的过滤
function filteredSetSubclass(superclass,filter)
{
var constructor = function(){
superclass.apply(this,arguments);
}
constructor.prototype = inherit(superclass.prototype);
constructor.prototype.constructor = constructor;
constructor.prototype.add = function(){
for(var i = 0; i < arguments.length; i++)
{
var v = arguments[i];
if(!filter(v)) throw("value" + v + " rejected by filter");
}
// 调用父类的add()方法
superclass.prototype.add.apply(this,arguments);
};
return constructor;
}
var StringSet = filteredSetSubclass(Set,function(x){return typeof x === "string"});
var strSet = new StringSet("1");
strSet.description();
/**
* 注意:类似这种类工厂的能力是JS语言动态特性的一个体现,类工厂是一种
* 非常强大和有用的特性,这在Java和C++等语言中是没有的。
*/
"------------- 组合 VS 子类".description();
/**
* "组合优于继承"
* 我们可以利用组合的原理定义一个新的集合
*/
// 例子
// 利用组合代替继承的集合的实现
// 实现一个FileteredSet,它包装某个指定的“集合”对象
// 并对传入add()方法的值应用了某种指定的过滤器
// “范围”类中其他所有的核心方法延续到包装后的实例中
var FilteredSet = Set.extend(
function FilteredSet(set,filter){ //构造函数
this.set = set;
this.filter = filter;
},
{// 实例方法
add:function(){
if(this.filter){
for(var i = 0; i < arguments.length; ++i){
var v = arguments[i];
if(!this.filter(v))
throw new Error("FilteredSet :value " + v + " rejected by filter");
}
}
// 调用set中的add()方法
this.set.add.apply(this.set,arguments);
return this;
},
// 剩下的方法保持不变
remove:function(){
this.set.remove.apply(this.set,arguments);
return this;
},
contains:function(v){return this.set.contains(v);},
size:function(){return this.set.size();},
foreach:function(f,c){this.set.foreach(f,c)}
});
var nullSet = new FilteredSet(new Set(),function(x){return x !== null;});
nullSet.add("1234","null");
nullSet.add(undefined);
// nullSet.add(null);
nullSet.remove("1234");
nullSet.description();
var s = new FilteredSet(new Set(),function(x){return x !== null;});
var t = new FilteredSet(s,function(x){return !(x instanceof Set);});
t.add(1,2,3,4);
t.remove(3);
// t.add((new Set()).add("adsf","adsf"));
t.description();
"------------- 类的层次结构和抽象类".description();
// 这个函数可以用作任何抽象方法
function abstractmethod(){throw new Error("abstract method");}
/**
* AbstractSet类定义了一个抽象方法:contains();
*/
function AbstractSet(){throw new Error("Can't instantiate abstract classes");}
AbstractSet.prototype.contains = abstractmethod;
/**
* NotSet是AbstractSet的一个非抽象子类
* NotSet表示一个由其他集合元素之外的所有元素组成的集合
*/
var NotSet = AbstractSet.extend(
function(set){this.set = set;},
{
contains:function(x){return !this.set.contains(x);},
toString:function(){return "~" + this.set.toString();},
equals:function(that){
return that instanceof NotSet && this.set.equals(that.set);
}
});
/**
* AbstractEnumerableSet 是 AbstractSet的一个抽象子类
* 实现了非抽象方法 isEmpty()、toArray()、toString() 和 equals()方法
* 也定义了一些抽象方法:contains(),size()和foreach()
*/
var AbstractEnumerableSet = AbstractSet.extend(
function(){throw new Error("Can't instantiate abstract classes");},
{
isEmpty:function(){return this.size == 0;},
toArray:function(){
var a = [];
this.foreach(function(v){a.push(v);});
return a;
},
toString:function(){
var s = "{";
var i = 0;
this.foreach(function(v){
if(i++ > 0) s += ",";
s += v;
});
},
equals:function(that){
if(! (that instanceof AbstractEnumerableSet)) return false;
if(this.size() != that.siz()) return false;
// 检查每一个元素是否在that中
try{
this.foreach(function(v){
if (!that.contains(v)) throw false;});
return true;
}catch(x){
if(x === false) return false;//集合不相等
throw x; // 发生了其他的异常:重新抛出异常
}
}
});
/**
* SingletonSet是AbstractEnumenrableSet非抽象类
* SingletonSet 是只读的,它只包含一个成员
*/
var SingletonSet = AbstractEnumerableSet.extend(
function (member){this.member = member;},
{
contains:function(x){return x === this.member;},
size:function(){return 1;},
foreach:function(fun,context){fun.call(context,this.member);}
});
/**
* AbstractWritableSet 是AbstractEnumerableSet的抽象子类
* 它定义了抽象方法 add() 和 remove()
* 实现了非抽象函数 union()、intersection() 和 difference()
*/
var AbstractWritableSet = AbstractEnumerableSet.extend(
function(){throw new Error("Can't instantiate abstract classes");},
{
add:abstractmethod,
remove:abstractmethod,
union:function(that){
var self = this;
that.foreach(function(v){self.add(v);});
return this;
},
intersection:function(that){
var self = this;
var removedSet = [];
this.foreach(function(v){
// 注意:
// 这里千万不能直接调用 self.remove(v);
// 因为集合的值证处于foreach循环中,如果在循环中对集合进行增删操作,
// 可能会打乱循环
if (!that.contains(v)) removedSet.push(v);
});
removedSet.forEach(function(v){
self.remove(v);
});
return this;
},
difference:function(that){
var self = this;
that.foreach(function(v){self.remove(v);});
return this;
}
});
/**
* ArraySet 是 AbstractWritableSet 的非抽象子类
* 它以数组的形式表示集合中的元素
* 对于它的 contains() 方法使用了数组的线性查找
* 因为 contains() 方法的算法复杂度是0(n)而不是0(1)
* 它非常适用于相对小型的集合,注意,这里的实现用到了ES5的数组方法indexOf() forEach()
*/
var ArraySet = AbstractWritableSet.extend(
function(){
this.values = [];
this.add.apply(this,arguments);
},
{
contains:function(v){
return (this.values.indexOf(v) != -1);
},
size:function(){return this.values.length;},
foreach:function(fun,context){
this.values.forEach(fun,context);
},
add:function(){
for(var i = 0; i < arguments.length;++i){
var arg = arguments[i];
if (!this.contains(arg)) this.values.push(arg);
}
return this;
},
remove:function(){
for(var i = 0; i < arguments.length;++i){
var index = this.values.indexOf(arguments[i]);
if(index == -1) continue;
this.values.splice(index,1);
}
return this;
}
});
var arSet = new ArraySet(10,9,8,7,6,5,4);
arSet.add(100,999).description();
// 打印 AbstractSet { values: [ 10, 9, 8, 7, 6, 5, 4, 100, 999 ] }
arSet.contains(5).description(); // [Boolean: true]
arSet.remove(8).description();
// 打印 AbstractSet { values: [ 10, 9, 7, 6, 5, 4, 100, 999 ] }
arSet.union(new ArraySet(666,888)).description();
// 打印 AbstractSet { values: [ 10, 9, 7, 6, 5, 4, 100, 999, 666, 888 ] }
arSet.difference(new ArraySet(9,5,114)).description();
// 打印 AbstractSet { values: [ 10, 7, 6, 4, 100, 999, 666, 888 ] }
arSet.intersection(new ArraySet(10)).description();
// 打印 AbstractSet { values: [ 10 ] }
"=============ES5中的类".description();
"-------------让属性不可枚举".description();
// defineProperty
(function(){
// 定义一个不可枚举的属性objectId,它可以被所有对象继承
// 当读取这个属性时调用getter函数,这个属性是只读
// 它不可配置的
Object.defineProperty(Object.prototype,"objectId",{
get:idGetter, // 读取器
enumeration:false,
configurable:false
});
// 当读取objectId的时候直接调用这个getter函数
function idGetter(){
if(!(idprop in this)){
if(!Object.isExtensible(this)){
throw Error("Can't define for nonextensible objects");
}
Object.defineProperty(this,idprop,{
value:nextid++,
writable:false,
enumerable:false,
configurable:false,
});
}
return this[idprop];//返回已有的或新有的值
};
// idGetter()用到了的变量,这些都属于私有变量
var idprop = "|**objectId**|";
var nextid = 1;
}());
"-------------定义不可变的类".description();
// 创建一个不可变的类,它的属性和方法都是只读的
// 这个方法可以使用new调用,也可以省略new
// 它可以作为构造函数也可以作为工厂函数
function Range2(from,to){
// 这里是对from 和 to 只读属性的描述符
var props ={
from:{value:from,enumerable:false,writable:false,configurable:false},
to:{value:to,enumerable:false,writable:false,configurable:false}
};
if(this instanceof Range2){ // 如果作为构造函数来调用
"Range2 constructor".description();
Object.defineProperties(this,props);} // 定义属性
else{ // 否则,作为工厂方法来调用
"Range2 factory".description();
return Object.create(Range2.prototype,props);}
}
// 注意:属性描述对象 configurable,enumerable,writable 默认值是false
// get set 默认是 undefined
// getOwnPropertyDescriptor 只能获取自有属性的描述
Object.defineProperties(Range2.prototype,{
includes:{
value:function(x){return this.from <= x && x <= this.to;}
},
foreach:{
value:function(f){
for (var x = Math.ceil(this.form);x <= this.to;x++) f(x);
}
},
toString:{
value:function(){return "(" + this.from + "..." + this.to + ")";}
}
});
(Range2(66,88)).from.description();
(new Range2(11,22)).from.description();
var range1 = new Range2(1,3);
range1.includes(2).description();
/**
* 另一种改进的做法是修改这个已定义属性的特性的操作定义为工具函数
*/
// 将o的指定名字(或所有)的属性设置为不可写的和不可配置的
function freezeProps(o){
var props = (arguments.length == 1) ? Object.getOwnPropertyNames(o)
: Array.prototype.splice.call(arguments,1);
props.forEach(function(value){
if(!Object.getOwnPropertyDescriptor(o,value).configurable) return;
Object.defineProperties(o,value,{writable:false,configurable:false});
});
return o;
}
/**
* 注意 Object.defineProperty()和 Object.defineProperties()可以用来创建新属性,也可以修改已有属性
* 当创建新属性的时候,默认值都为false,当修改低吼,默认值保持不变
*/
"-------------封装对象状态".description();
/**
* 通过getter setter方法将状态变量更健壮地封装起来,这两个方法是无法删除的
*/
function Range3(from,to){
if (from > to) throw new Error("Range: from must be <= to");
function getFrom(){return from;};
function getTo(){return to;};
function setFrom(f){if(f <= to) from = f;}
function setTo(f){if(t >= from) to = t;}
// 将使用取值器的属性设置为可枚举的、不可配置的
Object.defineProperties(this,{
from:{get:getFrom,set:setFrom,enumerable:true,configurable:false},
to:{get:getTo,set:setTo,enumerable:true,configurable:false}
});
};
var range3 = new Range3(9,18);
range3.from.description();
"-------------防止类的扩展".description();
/**
* 我们可以通过给原型对象添加方法可以动态地对类进行扩展,这个JS本身的特性
* ES5可以根据需要对此特性加以限制。
* Object.preventExtensions() 可将对象设置为不可扩展
* Object.seal()则更强大,除了不可扩展外,还将当前已有的属性设置为不可配置的
*/
"-------------子类和ES5".description();
/**
* 利用ES5的特性定义的子类
*/
function StringSet1(){
this.set = Object.create(null);//创建一个不包含原型的对象
this.n = 0;
this.add.apply(this,arguments);
}
StringSet1.prototype = Object.create(AbstractWritableSet.prototype,{
constructor:{value:StringSet1},
contains:{value:function(x){return x in this.set;}},
size:{value:function(){return this.n;}},
foreach:{value:function(f,c){Object.keys(this.set).forEach(f,c);}},
add:{
value:function(){
for(var i = 0;i < arguments.length;++i){
if(!(arguments[i] in this.set)){
this.set[arguments[i]] = true;
this.n++;
}
}
return this;
}
},
remove:{
value:function(){
for(var i = 0;i < arguments.length;++i){
if(arguments[i] in this.set){
delete this.set[arguments[i]];
this.n--;
}
}
return this;
}
}
});
var strSet = new StringSet1("123123","55555");
strSet.foreach(function(value){
value.description();
});
"-------------属性描述符".description();
/**
* 给Object.prototype定义properties()方法
* 这个方法返回一个表示调用它的对象上的属性名列表的对象
* (如果不带参数调用它,就表示该对象的所有属性)
* 返回的对象定义了4个有用的方法:toString()、descriptions()、hide()、show()
*/
// 将所有逻辑闭包在一个私有函数作用域中
(function namespace(){
// 这个函数成为所有对象的方法
function properties(){
var names;
if (arguments.length == 0)
names = Object.getOwnPropertyNames(this);
else if (arguments.length == 1 && Array.isArray(arguments[0]))
names = arguments[0];
else
names = Array.prototype.splice.call(arguments,0);
return new Properties(this,names);
}
// 给Object原型添加属性
Object.defineProperty(Object.prototype,"properties",{
value:properties,
enumerable:false,writable:true,configurable:true,
});
// Properties类的定义
function Properties(o,names){
this.o = o;
this.names = names;
};
// 将代表这些属性的对象设置为不可枚举的
Properties.prototype.hide = function(){
var o = this.o;
var hidden = {enumerable:false};
this.names.forEach(function(v){
if(o.hasOwnProperty(v))
Object.defineProperty(o,v,hidden);
});
return this;
};
// 将这些属性设置为只读和不可配置的
Properties.prototype.freeze = function(){
var o = this.o;
var frozen = {writable:false,configurable:factory};
this.names.forEach(function(v){
if(o.hasOwnProperty(v))
Object.defineProperty(o,v,frozen);
});
return this;
};
//返回一个对象,这个对象是名字到属性描述符的映射表
//使用它来复制属性,连同属性特性一起复制
Properties.prototype.descriptors = function(){
var o = this.o;
var desc = {};
this.names.forEach(function(v){
if(o.hasOwnProperty(v)){
desc[v] = Object.getOwnPropertyDescriptor(o,v);
}
});
return desc;
};
// 返回一个格式化良好的属性列表
Properties.prototype.toString = function(){
var o = this.o;
var lines = this.names.map(nameToString);
return "{ {"+lines.join("}, {") + "}";
function nameToString(n){
var s = "";
var desc = Object.getOwnPropertyDescriptor(o,n);
if(!desc) return "nonexistent " + n + ":undefined";
if(!desc.configurable) s += "permanent ";
if((desc.get && !desc.set) || !desc.writable) s += "readonly ";
if(!desc.enumerable) s += "hidden ";
if(desc.get || desc.set) s += "accessor " + n;
else s += n + ": " + ((typeof desc.value === "function") ? "function" : desc.value);
return s;
}
};
// 最后,将原型对象中实例方法设置为不可枚举的
Properties.prototype.properties().hide();
}());
var asd = Object.create(Object.prototype,{
A:{value:11111},
B:{value:22222},
C:{value:"CCC"},
D:{value:[9,8,7,6]}
});
asd.properties().descriptors().description();
asd.properties().toString().description();
"=============模块".description();
/**
* 模块是一个独立的JS文件,模块文件可以包含一个类定义,一组相关的类、一个使用函数库
* 或者一些待执行的代码
*
* JS中并没有定义用以支持模块的语言结构(imports和exports的确是JS保留的关键字)
* 这意味着JS中编写模块化的代码更多的是遵循某一种编码约定
*/
"-------------用做命名空间的对象".description();
/**
* 就拿"集合"类举例子
*/
var sets = {}; //sets对象是模块的命名空间
// 将 集合类 都定义为这个对象的属性
// set.SingletonSet = sets.AbstractEnumerableSet.extend(...)
// var s = new sets.SingletonSet(1);
// 我们可以将命名空间的Set导入全局命名空间中
// var Set = sets.Set;
// var s = new Set(1,2,3);
/**
* 如果命名空间嵌套一个命名空间
* 定义如下
*/
// var collections;
// if(!collections) controllers = {};
// collections.sets = {};
// 最顶层的命名空间往往用来标识创建模块的作者或组织,并避免命名空间的命名冲突。
"-------------作为私有命名空间的函数".description();
/**
* 模块对外导出一些公用API,这些API是提供给其他程序员使用的,
* 它包括函数、类、属性和防范。
* 但模块的实现往往需要一些额外的辅助函数和方法,这些函数和方法不需要在模块外可见。
*/
var Set = (function invocation(){
function Set(){ // 这个构造函数式局部变量
this.values = {};
this.n = o;
thi.add.apply(this,arguments);
}
Set.prototype.contains = function(value){
return this.values.hasOwnProperty(v2s(value));
};
Set.prototype.size = function(){};
Set.prototype.add = function(){};
Set.prototype.remove = function(){};
Set.prototype.foreach = function(){};
// 这里是上面的方法用到了一些辅助函数和变量
// 它们不属于模块的公有API,但它们都隐藏在这个函数作用域内
// 因此我们不必将它们定义为Set的属性或是信用下划线为其前缀
function v2s(value){};
function objectId(o){};
var nextId = 1;
return Set;
}());
// 例子
var PointC = (function(){
function constructor(xx,yy){
"constructor".description();
Object.defineProperties(this,{
x:{
get:function(){
return xx;
},
set:function(v){
xx = v;
}},
y:{
get:function(){
return yy;
},
set:function(v){
yy = v;
}}
});
// this.x = xx ? xx : 0;
// this.y = yy ? yy : 0;
// this.x = xx;
// this.y = yy;
_pointSum++;
};
constructor.prototype.add = function(that){
this.x += that.x;
this.y += that.y;
};
constructor.prototype.subtract = function(that){
this.x -= that.x;
this.y -= that.y;
};
constructor.prototype.toString = function(){
return "(" + this.x + "," + this.y + ")";
}
constructor.prototype.getRadius = function(){
return getR(this.x,this.y);
}
// 类方法
constructor.getPointSum = function(){
return _pointSum;
}
// 私有变量(全局的)
var _pointSum = 0;
// 私有方法
function getR(x,y){
return Math.sqrt(x * x + y * y);
}
return constructor;
}());
var point = new PointC(3,4);
point.add(new PointC(7,9));
point.toString().description();
point.getRadius().description();
/**
* 如果模块API包含多个单元,则它可以返回命名空间对象。
*/
// 创建一个全局变量来存放集合相关的模块
var collections;
if(!collections) collections = {};
// 定义sets模块
collections.sets = (function namespace(){
// 在这里定义多个集合类,使用局部变量和函数
// 通过返回命名空间对象将API导出
return {
AbstractSet:AbstractSet,
NotSet:NotSet,
// .......
};
}());
// 另一种写法是将模块函数当成构造函数,通过new调用,通过将它们赋值给this来将其导出
collections.sets = (new function namespace(){
// 在这里定义多个集合类,使用局部变量和函数
// 将API导出给this对象
this.AbstractSet = AbstractSet;
this.NotSet = NotSet;
}());
// 另一种替代就是将sets作为对象的属性
collections.sets = {};
(function namespace(){
// 在这里定义多个集合类,使用局部变量和函数
collections.sets.AbstractSet = AbstractSet;
collections.sets.NotSet = NotSet;
}());
初学JavaScript之类和模块练习笔记
最新推荐文章于 2024-08-22 00:19:13 发布