JavaScript權威指南(9)——類和模組

夕陽下的奔跑發表於2019-08-19

類和模組

類和原型

  1. 類的所有例項物件都從同一個原型物件上繼承屬性——以下定義一個工廠方法,用來建立物件
   function range(from, to) {
       // Use the inherit() function to create an object that inherits from the
       // prototype object defined below. The prototype object is stored as
       // a property of this function, and defines the shared methods (behavior)
       // for all range objects.
       var r = inherit(range.methods);
       // Store the start and end points (state) of this new range object.
       // These are noninherited properties that are unique to this object.
       r.from = from;
       r.to = to;
       // Finally return the new object
       return r;
   }
   
   range.methods = {
   	// 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 = 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)
複製程式碼

類和建構函式

  1. 使用建構函式代替工廠方法
   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 = {
       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); // 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)
複製程式碼
  1. 建構函式型別首字母大寫,建構函式通過new關鍵字呼叫的
  2. 當且僅當兩個物件繼承自同一個原型物件時,它們才是屬於同一類的例項。建構函式不能作為類的標識。
  3. 每個JavaScript函式都擁有一個prototype屬性。這個屬性的值是一個物件,這個物件有包含唯一一個不可列舉屬性constructor
   var F = function() {}; // This is a function object.
   var p = F.prototype; // This is the prototype object associated with it.
   var c = p.constructor; // This is the function associated with the prototype.
   c === F // => true: F.prototype.constructor==F for any function
   
   var o = new F(); // Create an object o of class F
   o.constructor === F // => true: the constructor property specifies the class
複製程式碼
  1. 重寫預定義的Range.prototype,不含有constructor,可以顯式地給原型新增一個建構函式
   Range.prototype = {
       constructor: Range, // Explicitly set the constructor back-reference
       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 + ")"; }
   };
複製程式碼
  1. 或者使用預定義的原型物件,給它新增方法
   Range.prototype.includes = function(x) { return this.from<=x && x<=this.to; };
   Range.prototype.foreach = function(f) {
   	for(var x = Math.ceil(this.from); x <= this.to; x++) f(x);
   };
   Range.prototype.toString = function() {
   	return "(" + this.from + "..." + this.to + ")";
   };
複製程式碼

javascript中java式的類繼承

  1. 建構函式物件——任何新增到建構函式物件中的屬性都是類欄位和類方法
  2. 原型物件——原型物件的屬性被類的所有例項所繼承,如果屬性是函式的話,這個函式作為類的例項的方法
  3. 例項物件——定義在例項上的非函式屬性,是例項的欄位
  4. 定義類
   function defineClass(constructor,//用以設定例項的屬性的函式
       methods, // 例項的方法,複製到原型中
       statics) // 類屬性,複製到建構函式中
   {
       if (methods) extend(constructor.prototype, methods);
       if (statics) extend(constructor, statics);
       return constructor;
   }
   // This is a simple variant of our Range class
   var SimpleRange =
   	defineClass(function(f,t) { this.f = f; this.t = t; },
       {
           includes: function(x) { return this.f <= x && x <= this.t;},
           toString: function() { return this.f + "..." + this.t; }
       },
   	{ upto: function(t) { return new SimpleRange(0, t); } });
複製程式碼
  1. 一個表述複數的類
   // 建構函式定義了例項欄位
   function Complex(real, imaginary) {
       if (isNaN(real) || isNaN(imaginary)) // Ensure that both args are numbers.
       	throw new TypeError(); // Throw an error if they are not.
       this.r = real; // The real part of the complex number.
       this.i = imaginary; // The imaginary part of the number.
   }
   // 類的例項方法定義為原型物件的函式值屬性
   // Add a complex number to this one and return the sum in a new object.
   Complex.prototype.add = function(that) {
   	return new Complex(this.r + that.r, this.i + that.i);
   };
   // Multiply this complex number by another and return the product.
   Complex.prototype.mul = function(that) {
   	return new Complex(this.r * that.r - this.i * that.i,
   						this.r * that.i + this.i * that.r);
   };
   // Return the real magnitude of a complex number. This is defined
   // as its distance from the origin (0,0) of the complex plane.
   Complex.prototype.mag = function() {
   	return Math.sqrt(this.r*this.r + this.i*this.i);
   };
   // Return a complex number that is the negative of this one.
   Complex.prototype.neg = function() { return new Complex(-this.r, -this.i); };
   // Convert a Complex object to a string in a useful way.
   Complex.prototype.toString = function() {
   	return "{" + this.r + "," + this.i + "}";
   };
   // Test whether this Complex object has the same value as another.
   Complex.prototype.equals = function(that) {
   	return that != null && // must be defined and non-null
   			that.constructor === Complex && // and an instance of Complex
   			this.r === that.r && this.i === that.i; // and have the same values.
   };
   // 類欄位和類方法直接定義為建構函式的屬性
   // Here are some class fields that hold useful predefined complex numbers.
   // Their names are uppercase to indicate that they are constants.
   // (In ECMAScript 5, we could actually make these properties read-only.)
   Complex.ZERO = new Complex(0,0);
   Complex.ONE = new Complex(1,0);
   Complex.I = new Complex(0,1);
   // This class method parses a string in the format returned by the toString
   // instance method and returns a Complex object or throws a TypeError.
   Complex.parse = function(s) {
       try { // Assume that the parsing will succeed
           var m = Complex._format.exec(s); // Regular expression magic
           return new Complex(parseFloat(m[1]), parseFloat(m[2]));
       } catch (x) { // And throw an exception if it fails
           throw new TypeError("Can't parse '" + s + "' as a complex number.");
       }
   };
   //類的“私有”欄位,字首表示是類內部使用
   Complex._format = /^\{([^,]+),([^}]+)\}$/
複製程式碼

以上定義用到了建構函式、例項欄位、例項方法、類欄位和類方法

類的擴充

  1. 可以給原型物件新增新方法來擴充JavaScript類
  2. 給Object.prototype新增新方法,在ES 5之前,這些新增的方法不能設定為不可列舉的。

類和型別

  1. instanceof——如果o繼承自c.prototype,則o instanceof c為true
  2. 建構函式時公共標識,原型是唯一的標識
  3. 檢測物件的原型鏈上是否存在某個特定的原型物件——isPrototypeOf
   range.methods.isPrototypeOf(r);  //range.method是原型物件
複製程式碼
  1. instanceof和isPrototypeOf無法通過物件來獲得類名
  2. constructor屬性
   function typeAndValue(x) {
       if (x == null) return ""; // Null and undefined don't have constructors
           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; // 自定義型別
       }
   }
複製程式碼

多個執行上下文環境中無法正常工作;並非所有的物件都包含constructor屬性 6. 建構函式的名稱——通過建構函式的名字進行判斷

   /** 以字串形式返回o的型別:
    *  -如果o是null,返回“null”,如果是NaN,返回“nan”
    *	-如果typeof所返回的值不是“object”,則返回這個值
    *	(有些JavaScript的實現將正規表示式識別為函式)
    *	-如果o的類不是“Object”,則返回這個值
    *	-如果o包含建構函式並且這個建構函式具有名稱,則返回這個名稱
    *	—否則,一律返回“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];
   };
複製程式碼

並不是所有物件都具有constructor屬性;並不是所有的函式都有名字。 7. 鴨式辯型——不要關注“物件的類是什麼”,而是關注“物件能做什麼” 8. 定義quacks()函式以檢查一個物件是否實現了剩下的引數所表示的方法

   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;
   }
複製程式碼

JavaScript中的物件導向技術

  1. 一個例子:集合類
   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; };
   // 遍歷集合中的所有元素,在指定的上下文中呼叫f
   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
   };
   // 將任意JavaScript值和唯一的字串對應起來
   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;
複製程式碼
  1. 一個例子:列舉型別
   //建立一個新的列舉型別,實參物件標識類的每個勢力的名字和值
   //返回的建構函式包含名/值對的對映表
   //包括由值組成的陣列,以及一個foreach()迭代器函式
   function enumeration(namesToValues) {
       // 虛擬的建構函式時返回值,不能例項化
       var enumeration = function() { throw "Can't Instantiate Enumerations"; };
       // 列舉值繼承自這個物件
       var proto = enumeration.prototype = {
           constructor: enumeration, // Identify type
           toString: function() { return this.name; }, // Return name
           valueOf: function() { return this.value; }, // Return value
           toJSON: function() { return this.name; } // For serialization
       };
       enumeration.values = []; // An array of the enumerated value objects
       // 建立新型別的例項
       for(name in namesToValues) { // For each value
           var e = inherit(proto); // Create an object to represent it
           e.name = name; // Give it a name
           e.value = namesToValues[name]; // And a value
           enumeration[name] = e; // Make it a property of constructor
           enumeration.values.push(e); // And store in the values array
       }
       // A class method for iterating the instances of the class
       enumeration.foreach = function(f,c) {
       	for(var i = 0; i < this.values.length; i++) f.call(c,this.values[i]);
       };
       // Return the constructor that identifies the new type
       return enumeration;
   }
複製程式碼
   // Create a new Coin class with four values: Coin.Penny, Coin.Nickel, etc.
   var Coin = enumeration({Penny: 1, Nickel:5, Dime:10, Quarter:25});
   var c = Coin.Dime; // 新的例項
   c instanceof Coin // => true: instanceof works
   c.constructor == Coin // => true: constructor property works
   Coin.Quarter + 3*Coin.Nickel // => 40: values convert to numbers
   Coin.Dime == 10 // => true: more conversion to numbers
   Coin.Dime > Coin.Nickel // => true: relational operators work
   String(Coin.Dime) + ":" + Coin.Dime // => "Dime:10": coerce to string
複製程式碼

撲克牌

   // Define a class to represent a playing card
   function Card(suit, rank) {
       this.suit = suit; // Each card has a suit
   	this.rank = rank; // and a rank
   }
   // 使用列舉型別定義花色和點數
   Card.Suit = enumeration({Clubs: 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});
   // Define a textual representation for a card
   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 (a.suit < b.suit) return -1;
       if (a.suit > b.suit) return 1;
       if (a.rank < b.rank) return -1;
       if (a.rank > b.rank) return 1;
       return 0;
   };
   // 定義標準撲克牌
   function Deck() {
       var cards = this.cards = []; // A deck is just an array of cards
       Card.Suit.foreach(function(s) { // Initialize the array
           Card.Rank.foreach(function(r) {
               cards.push(new Card(s,r));
           });
       });
   }
   // 洗牌
   Deck.prototype.shuffle = function() {
       // For each element in the array, swap with a randomly chosen lower element
       var deck = this.cards, len = deck.length;
       for(var i = len-1; i > 0; i--) {
           var r = Math.floor(Math.random()*(i+1)), temp; // Random number
           temp = deck[i], deck[i] = deck[r], deck[r] = temp; // Swap
       }
   	return this;
   };
   // 發牌
   Deck.prototype.deal = function(n) {
       if (this.cards.length < n) throw "Out of cards";
   	return this.cards.splice(this.cards.length-n, n);
   };
   // Create a new deck of cards, shuffle it, and deal a bridge hand
   var deck = (new Deck()).shuffle();
   var hand = deck.deal(13).sort(Card.orderBySuit);
複製程式碼
  1. 標準轉換方法
   extend(Set.prototype, {
   	// Convert a set to a string
       toString: function() {
           var s = "{", i = 0;
           this.foreach(function(v) { s += ((i++ > 0)?", ":"") + v;})
           return s + "}";
       },
       // Like toString, but call toLocaleString on all values
       toLocaleString : function() {
           var s = "{", i = 0;
           this.foreach(function(v) {
           if (i++ > 0) s += ", ";
           if (v == null) s += v; // null & undef
           else s += v.toLocaleString(); // all o
           });
           return s + "}";
       },
       // Convert a set to an array of values
       toArray: function() {
           var a = [];
           this.foreach(function(v) { a.push(v); });
           return a;
       }
   });
   // Treat sets like arrays for the purposes of JSON stringification.
   Set.prototype.toJSON = Set.prototype.toArray;
複製程式碼
  1. 比較方法 equals()方法
   Range.prototype.equals = function(that) {
       if (that == null) return false; // Reject null and undefined
       if (that.constructor !== Range) return false; // Reject non-ranges
       // Now return true if and only if the two endpoints are equal.
       return this.from == that.from && this.to == that.to;
   }
複製程式碼
Set.prototype.equals = function(that) {
    // Shortcut for trivial case
    if (this === that) return true;
    // If the that object is not a set, it is not equal to this one.
    // We use instanceof to allow any subclass of Set.
    // We could relax this test if we wanted true duck-typing.
    // Or we could strengthen it to check this.constructor == that.constructor
    // Note that instanceof properly rejects null and undefined values
    if (!(that instanceof Set)) return false;
    // If two sets don't have the same size, they're not equal
    if (this.size() != that.size()) return false;
    // Now check whether every element in this is also in that.
    // Use an exception to break out of the foreach if the sets are not equal.
    try {
        this.foreach(function(v) { if (!that.contains(v)) throw false; });
        return true; // All elements matched: sets are equal.
    } catch (x) {
        if (x === false) return false; // An element in this is not in that.
        throw x; // Some other exception: rethrow it.
    }
};
複製程式碼

compareTo()方法

   Range.prototype.compareTo = function(that) {
       if (!(that instanceof Range))
       	throw new Error("Can't compare a Range with " + that);
       var diff = this.from - that.from; // Compare lower bounds
       if (diff == 0) diff = this.to - that.to; // If equal, compare upper bounds
       return diff;
   };
複製程式碼

定義了compareTo()方法可以進行排序

   ranges.sort(function(a,b) { return a.compareTo(b); });
   Range.byLowerBound = function(a,b) { return a.compareTo(b); };
   ranges.sort(Range.byLowerBound);
複製程式碼
  1. 方法借用——泛型實現
   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; // skip methods
               if (n++) s += ", ";
               s += name + '=' + value;
           }
           return s + ']';
       },
       // 通過比較this和that的建構函式和例項屬性來判斷它們是否相等
       // 原始值可以通過“===”來比較
       // 忽略由Set類新增的特殊屬性
       equals: function(that) {
           if (that == null) return false;
           if (this.constructor !== that.constructor) return false;
           for(var name in this) {
           if (name === "|**objectid**|") continue; // skip special prop.
           if (!this.hasOwnProperty(name)) continue; // skip inherited
           if (this[name] !== that[name]) return false; // compare values
           }
           return true; // If all properties matched, objects are equal.
       }
   };
複製程式碼
  1. 私有狀態——通過將變數(或引數)閉包在一個建構函式內來模擬實現私有例項欄位
   function Range(from, to) {
       // Don't store the endpoints as properties of this object. Instead
       // define accessor functions that return the endpoint values.
       // These values are stored in the closure.
       this.from = function() { return from; };
       this.to = function() { return to; };
   }
   // The methods on the prototype can't see the endpoints directly: they have
   // to invoke the accessor methods just like everyone else.
   Range.prototype = {
       constructor: Range,
       includes: function(x) { return this.from() <= x && x <= this.to(); },
       foreach: function(f) {
       	for(var x=Math.ceil(this.from()), max=this.to(); x <= max; x++) f(x);
       },
       toString: function() { return "(" + this.from() + "..." + this.to() + ")"; }
   };
複製程式碼
  1. 使用閉包來封裝類的狀態的類會比不用封裝的狀態變數的等價類執行速度更慢,並佔用更多記憶體
  2. 建構函式的過載和工廠方法——物件初始化有多種方式

過載建構函式:

   function Set() {
       this.values = {}; // The properties of this object hold the set
       this.n = 0; // How many values are in the set
       // 如果傳入一個類陣列物件,將元素新增至集合中,否則,將所有引數新增至集合中
       if (arguments.length == 1 && isArrayLike(arguments[0]))
       	this.add.apply(this, arguments[0]);
       else if (arguments.length > 0)
       	this.add.apply(this, arguments);
   }
複製程式碼

使用工廠方法:

   Complex.polar = function(r, theta) {
   	return new Complex(r*Math.cos(theta), r*Math.sin(theta));
   };
   
   // 通過陣列初始化Set物件
   Set.fromArray = function(a) {
       s = new Set(); // Create a new empty set
       s.add.apply(s, a); // Pass elements of array a to the add method
       return s; // Return the new set
   };
複製程式碼
  1. 定義多個建構函式繼承自同一個原型物件,它們所建立的物件都屬於同一型別
   // Set類的一個輔助建構函式
   function SetFromArray(a) {
   // 通過以函式的形式呼叫Set()來初始化這個新物件
   Set.apply(this, a);
   }
   // Set the prototype so that SetFromArray creates instances of Set
   SetFromArray.prototype = Set.prototype;
   var s = new SetFromArray([1,2,3]);
   s instanceof Set // => true
複製程式碼

子類

  1. 定義子類——O是類B的例項,B是A的子類,那麼O也一定從A中繼承了屬性
   B.prototype = inherit(A.prototype); // Subclass inherits from superclass
   B.prototype.constructor = B; 
複製程式碼
   function defineSubclass(superclass, // Constructor of the superclass
           constructor, // The constructor for the new subclass
           methods, // 例項方法,複製至原型中
           statics) // 類屬性:複製至建構函式中
   {
       // 建立子類的原型物件
       constructor.prototype = inherit(superclass.prototype);
       constructor.prototype.constructor = constructor;
       // Copy the methods and statics as we would for a regular class
       if (methods) extend(constructor.prototype, methods);
       if (statics) extend(constructor, statics);
       // Return the class
       return constructor;
   }
複製程式碼
   // 通過父類建構函式的方法來做到這一點
   Function.prototype.extend = function(constructor, methods, statics) {
   	return defineSubclass(this, constructor, methods, statics);
   };
複製程式碼

實現一個SingletonSet:

   // The constructor function
   function SingletonSet(member) {
   	this.member = member; // Remember the single member of the set
   }
   // Create a prototype object that inherits from the prototype of Set.
   SingletonSet.prototype = inherit(Set.prototype);
   // Now add properties to the prototype.
   // These properties override the properties of the same name from Set.prototype.
   extend(SingletonSet.prototype, {
               // Set the constructor property appropriately
               constructor: SingletonSet,
               // This set is read-only: add() and remove() throw errors
               add: function() { throw "read-only set"; },
               remove: function() { throw "read-only set"; },
               // A SingletonSet always has size 1
               size: function() { return 1; },
               // Just invoke the function once, passing the single member.
               foreach: function(f, context) { f.call(context, this.member); },
               // The contains() method is simple: true only for one value
               contains: function(x) { return x === this.member; }
           });
複製程式碼
  1. 建構函式和方法鏈 定義一個NonNullSet
   function NonNullSet() {
       // 連結到父類,呼叫父類的建構函式來初始化通過該建構函式呼叫建立的物件
       Set.apply(this, arguments);
   }
   // 設定NonNullSet為Set的子類
   NonNullSet.prototype = inherit(Set.prototype);
   NonNullSet.prototype.constructor = NonNullSet;
   // 重寫add方法
   NonNullSet.prototype.add = function() {
       // Check for null or undefined arguments
       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);
   };
複製程式碼
  1. 類工廠和方法鏈
   // Define a set class that holds strings only
   var StringSet = filteredSetSubclass(Set,
   					function(x) {return typeof x==="string";});
   // Define a set class that does not allow null, undefined or functions
   var MySet = filteredSetSubclass(NonNullSet,
   					function(x) {return typeof x !== "function";});
複製程式碼
   function filteredSetSubclass(superclass, filter) {
       var constructor = function() { // The subclass constructor
           superclass.apply(this, arguments); // 呼叫父類建構函式
       };
       var proto = constructor.prototype = inherit(superclass.prototype);
       proto.constructor = constructor;
       proto.add = function() {
           // Apply the filter to all arguments before adding any
           for(var i = 0; i < arguments.length; i++) {
               var v = arguments[i];
               if (!filter(v)) throw("value " + v + " rejected by filter");
      	 	}
           // Chain to our superclass add implementation
           superclass.prototype.add.apply(this, arguments);
       };
       return constructor;
   }
複製程式碼
  1. 使用包裝函式和extend重寫
   var NonNullSet = (function() { // Define and invoke function
       var superclass = Set; // Only specify the superclass once.
       return superclass.extend(
           function() { superclass.apply(this, arguments); }, // the constructor
               { // the methods
               add: function() {
                   // Check for null or undefined arguments
                   for(var i = 0; i < arguments.length; i++)
                   if (arguments[i] == null)
                   throw new Error("Can't add null or undefined");
                   // Chain to the superclass to perform the actual insertion
                   return superclass.prototype.add.apply(this, arguments);
               }
           });
       }());
複製程式碼
  1. 組合VS子類——組合優於繼承
   var FilteredSet = Set.extend(
       function FilteredSet(set, filter) { // The constructor
           this.set = set;
           this.filter = filter;
       },
       { // The instance methods
           add: function() {
               // If we have a filter, apply it
               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");
                   }
               }
               // Now forward the add() method to this.set.add()
               this.set.add.apply(this.set, arguments);
               return this;
           },
           // The rest of the methods just forward to this.set and do nothing else.
           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 s = new FilteredSet(new Set(), function(x) { return x !== null; })
   var t = new FilteredSet(s, { function(x} { return !(x instanceof Set); });
複製程式碼
  1. 類的層次結構和抽象類
   //抽象方法
   function abstractmethod() { throw new Error("abstract method"); }
複製程式碼
   //抽象類,定義了一個抽象方法
   function AbstractSet() { throw new Error("Can't instantiate abstract classes");}
   AbstractSet.prototype.contains = abstractmethod;
複製程式碼
   //非抽象子類
   var NotSet = AbstractSet.extend(
       function NotSet(set) { this.set = set; },
       {
           contains: function(x) { return !this.set.contains(x); },
           toString: function(x) { return "~" + this.set.toString(); },
           equals: function(that) {
           	return that instanceof NotSet && this.set.equals(that.set);
       	}
       }
   );
複製程式碼
   //定義了抽象方法size(),foreach(),實現了幾個非抽象方法
   var AbstractEnumerableSet = AbstractSet.extend(
       function() { throw new Error("Can't instantiate abstract classes"); },
       {
           size: abstractmethod,
           foreach: abstractmethod,
           isEmpty: function() { return this.size() == 0; },
           toString: function() {
               var s = "{", i = 0;
               this.foreach(function(v) {
                   if (i++ > 0) s += ", ";
                       s += v;
                   });
               return s + "}";
           },
           toLocaleString : function() {
               var s = "{", i = 0;
               this.foreach(function(v) {
                       if (i++ > 0) s += ", ";
                       if (v == null) s += v; // null & undefined
                       else s += v.toLocaleString(); // all others
                   });
               return s + "}";
           },
           toArray: function() {
               var a = [];
               this.foreach(function(v) { a.push(v); });
               return a;
           },
           equals: function(that) {
               if (!(that instanceof AbstractEnumerableSet)) return false;
               // If they don't have the same size, they're not equal
               if (this.size() != that.size()) return false;
               // Now check whether every element in this is also in that.
               try {
                   this.foreach(function(v) {if (!that.contains(v)) throw false;});
                   return true; // All elements matched: sets are equal.
               } catch (x) {
               	if (x === false) return false; // Sets are not equal
               	throw x; // Some other exception occurred: rethrow it.
           	}
       	}
   });
複製程式碼
   //AbstractEnumerableSet的非抽象子類
   var SingletonSet = AbstractEnumerableSet.extend(
   	function SingletonSet(member) { this.member = member; },
       {
           contains: function(x) { return x === this.member; },
           size: function() { return 1; },
           foreach: function(f,ctx) { f.call(ctx, this.member); }
       }
   );
複製程式碼
   //AbstractEnumerableSet的抽象子類,定義了抽象方法add(),remove()
   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;
               this.foreach(function(v) { if (!that.contains(v)) self.remove(v);});
               return this;
           },
           difference: function(that) {
               var self = this;
               that.foreach(function(v) { self.remove(v); });
               return this;
           }
   	});
複製程式碼
   //AbstractWritableSet的非抽象子類
   var ArraySet = AbstractWritableSet.extend(
       function ArraySet() {
           this.values = [];
           this.add.apply(this, arguments);
       },
       {
           contains: function(v) { return this.values.indexOf(v) != -1; },
           size: function() { return this.values.length; },
           foreach: function(f,c) { this.values.forEach(f, c); },
           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 p = this.values.indexOf(arguments[i]);
                   if (p == -1) continue;
                       this.values.splice(p, 1);
                   }
               return this;
           }
       }
   );
複製程式碼

ES 5中的類

  1. ES 5給屬性特性增加了方法支援(getter,setter,可列舉性,可寫性和可配置性),而且增加了可擴充套件性的限制
  2. 讓屬性不可列舉
   // 將程式碼包裝在一個匿名函式中,這樣定義的變數就在這個函式作用域內
   (function() {
       // Define objectId as a nonenumerable property inherited by all objects.
       // When this property is read, the getter function is invoked.
       // 沒有setter,只讀
       // 不可配置,無法刪除
       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 #
   }()); // 立即執行這個包裝函式
複製程式碼
  1. 定義不可變的類——使用Object.defineProperties() 和Object.create()
   function Range(from,to) {
       // These are descriptors for the read-only from and to properties.
       var props = {
           from: {value:from, enumerable:true, writable:false, configurable:false},
           to: {value:to, enumerable:true, writable:false, configurable:false}
       };
       if (this instanceof Range) // 如果是建構函式
       	Object.defineProperties(this, props); // Define the properties
       else // Otherwise, as a factory
       	return Object.create(Range.prototype, // Create and return a new
       			props); // Range object with props
   }
   
   //設定方法的可沒舉行,可寫性和可配置性預設為false
   Object.defineProperties(Range.prototype, {
       includes: {
       	value: function(x) { return this.from <= x && x <= this.to; }
       },
       foreach: {
           value: function(f) {
           	for(var x = Math.ceil(this.from); x <= this.to; x++) f(x);
           }
       },
       toString: {
       	value: function() { return "(" + this.from + "..." + this.to + ")"; }
       }
   });
複製程式碼
  1. 定義屬性描述符工具函式
   //設定為不可寫的和不可配置的
   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
   }
   //設定為不可列舉的和可配置的
   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;
   }
   
   function Range(from, to) { // 不可變的類的建構函式
       this.from = from;
       this.to = to;
       freezeProps(this); // Make the properties immutable
   }
   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 + ")"; }
   });
複製程式碼
  1. 封裝物件狀態
   //定義getter和setter方法將裝填變數更健壯地封裝起來
   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 + ")"; }
   });
複製程式碼
  1. 防止類的擴充套件
   Object.preventExtensions()將物件設定為不可擴充套件的,不能新增任何新屬性
複製程式碼
   Object.seal(Object.prototype)不能給物件新增新屬性,還能將當前已有的屬性設定為不可配置的,不能刪除這些屬性(不可配置的屬性可以是可寫的,頁可以轉換為只讀屬性)
複製程式碼
   //物件方法可以隨時替換——monkey-patch
   var original_sort_method = Array.prototype.sort;
   Array.prototype.sort = function() {
       var start = new Date();
       original_sort_method.apply(this, arguments);
       var end = new Date();
       console.log("Array sort took " + (end - start) + " milliseconds.");
   };
複製程式碼

可以通過將例項方法設定為只讀來防止這類修改,使用freezeProps()工具函式,或者使用Object.freeze(),將所有屬性設定為只讀和不可配置的。 7. 子類和ES 5

   function StringSet() {
       this.set = Object.create(null); // 無原型
       this.n = 0;
       this.add.apply(this, arguments);
   }
   
   StringSet.prototype = Object.create(AbstractWritableSet.prototype, {
       constructor: { value: StringSet },
       contains: { value: function(x) { return x in this.set; } },
       size: { value: function(x) { 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;
       }
       }
   })
複製程式碼
  1. 屬性描述符
   (function namespace() { // 將所有邏輯閉包在一個私有函式作用域中
       // This is the function that becomes a method of all object
       function properties() {
           var names; // An array of property 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 a new Properties object representing the named properties
           return new Properties(this, names);
       }
       // Make it a new nonenumerable property of Object.prototype.
       // 從私有函式作用域匯出的唯一一個值
       Object.defineProperty(Object.prototype, "properties", {
           value: properties,
           enumerable: false, writable: true, configurable: true
       });
       // This constructor function is invoked by the properties() function above.
       // The Properties class represents a set of properties of an object.
       function Properties(o, names) {
           this.o = o; // The object that the properties belong to
           this.names = names; // The names of the properties
       }
       // Make the properties represented by this object nonenumerable
       Properties.prototype.hide = function() {
           var o = this.o, hidden = { enumerable: false };
           this.names.forEach(function(n) {
           if (o.hasOwnProperty(n))
           	Object.defineProperty(o, n, hidden);
           });
           return this;
       };
       // Make these properties read-only and nonconfigurable
       Properties.prototype.freeze = function() {
           var o = this.o, frozen = { writable: false, configurable: false };
           this.names.forEach(function(n) {
           if (o.hasOwnProperty(n))
           	Object.defineProperty(o, n, frozen);
           });
           return this;
       };
       // 返回名字到屬性描述符的對映表
       Properties.prototype.descriptors = function() {
           var o = this.o, desc = {};
           this.names.forEach(function(n) {
           if (!o.hasOwnProperty(n)) return;
           	desc[n] = Object.getOwnPropertyDescriptor(o,n);
           });
           return desc;
       };
       // Return a nicely formatted list of properties, listing the
       // name, value and attributes. Uses the term "permanent" to mean
       // nonconfigurable, "readonly" to mean nonwritable, and "hidden"
       // to mean nonenumerable. Regular enumerable, writable, configurable
       // properties have no attributes listed.
       Properties.prototype.toString = function() {
           var o = this.o; // Used in the nested function below
           var lines = this.names.map(nameToString);
           return "{\n " + lines.join(",\n ") + "\n}";
           function nameToString(n) {
               var s = "", 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;
           }
       };
       // Finally, make the instance methods of the prototype object above
       // nonenumerable, using the methods we've defined here.
       Properties.prototype.properties().hide();
   }()); // Invoke the enclosing function as soon as we're done defining it.
複製程式碼

模組

  1. 模組化的目標是支援大規模的程式開發,處理分散源中程式碼的組裝
  2. 用做名稱空間的物件
   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.
複製程式碼
  1. 作為私有名稱空間的函式
   //宣告全域性變數Set,使用一個函式的返回值給它複製
   //立即執行
   //它是一個函式表示式
   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;
       //這個模組的共有API是Set()建構函式
       //將這個函式從私有名稱空間中匯出來,使外部可以使用
       return Set;
   }()); // Invoke the function immediately after defining it.
複製程式碼
   // Create a single global variable to hold all collection-related modules
   var collections;
   if (!collections) collections = {};
   // 定義sets模組
   collections.sets = (function namespace() {
       // 定義多種“集合”類,使用區域性變數和函式
       // 通過名稱空間物件將API匯出
       return {
           // Exported property name : local variable name
           AbstractSet: AbstractSet,
           NotSet: NotSet,
           AbstractEnumerableSet: AbstractEnumerableSet,
           SingletonSet: SingletonSet,
           AbstractWritableSet: AbstractWritableSet,
           ArraySet: ArraySet
       };
   }());
複製程式碼

將模組函式當做建構函式,通過new呼叫,將它們賦值給this來匯出

   var collections;
   if (!collections) collections = {};
   collections.sets = (new function namespace() {
       // ... Lots of code omitted...
       // Now export our API to the this object
       this.AbstractSet = AbstractSet;
       this.NotSet = NotSet; // And so on....
       // Note no return value.
   }());
複製程式碼

如果已經定義了全域性名稱空間,可以直接設定物件的屬性

   var collections;
   if (!collections) collections = {};
   collections.sets = {};
   (function namespace() {
       // ... Lots of code omitted...
       // Now export our public API to the namespace object created above
       collections.sets.AbstractSet = AbstractSet;
       collections.sets.NotSet = NotSet; // And so on...
       // No return statement is needed since exports were done above.
   }());
   
複製程式碼

相關文章