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);