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());
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());
r instanceof Range // 若是r继承自Range.prototype, 则返回true
这里, instanceof并不会检查r是否由Range()构造函数初始化而来, 而会检查r是否继承自Range.prototype. 因此更原始检测原型的方法是:javascript
Range.prototype.isPrototypeOf(r) // 检查r的原型是否为Range.prototype
但上例中存在必定的错误, 当咱们执行如下代码时候:java
r.constructor.prototype == Range.prototype ==> false
这里是false, 是由于咱们并无给Range的constructor增长Range.浏览器
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:app
Range.prototype.includes = function(x) { ... }
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添加方法, 也是基于这种考虑的.this
构造函数是类的公共标识, 但原型是惟一的标识. 尽管instanceof运算符的右操做数是构造函数, 但计算过程其实是检测了对象的继承关系, 而不是检测建立对象的构造函数.spa
range.methods.isPrototypeOf(r); // range.method 是原型对象
instanceof/isPrototypeOf的不足之处在于两点: 1是咱们没法确切知道(o instanceof c)中o的具体类名; 2是在多窗口多框架的子页面中, Array()并不相等.prototype
备注: 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));
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());
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); }
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);