下述內容主要講述了《JavaScript高級程序設計(第3版)》第6章關於“面向對象的程序設計”。
ECMA-262把對象定義為:”無序屬性的集合,其屬性可以包含基本值、對象或者函數。”
一、理解對象
1. 屬性類型
ECMAScript中有兩種屬性:數據屬性和訪問器屬性。
數據屬性包含一個值;訪問器屬性不包含值而定義了一個當屬性被讀取時調用的函數(getter)和一個當屬性被寫入時調用的函數(setter)。
(1)數據屬性
(2)訪問器屬性
<table><thead>修改屬性默認的特性:<code>Object.defineProperty(屬性所在的對象, 屬性, 描述符對象)/<code>
示例:數據屬性
<code>var person = {};/<code><code>Object.defineProperty(person, "name", {/<code><code> configurable: true,/<code><code> enumerable: false,/<code><code> writable: false,/<code><code> value: "lg"/<code><code>});/<code><code>
/<code><code>console.log(person.name); // "lg"/<code><code>for(var prop in person){/<code><code> console.log(prop); // 未執行/<code><code>}/<code><code>person.name = "li";/<code><code>console.log(person.name); // "lg"/<code>
示例:訪問器屬性
<code>var person = {/<code><code> _name: "ligang"/<code><code>};/<code><code>Object.defineProperty(person, "name", {/<code><code> configurable: false,/<code><code> enumerable: true,/<code><code> set: function(name){/<code><code> /* 此處可以做其他操作 *//<code><code> this._name = name;/<code><code> },/<code><code> get: function(){/<code><code> /* 此處可以做其他操作 *//<code><code> return this._name;/<code><code> }/<code><code>});/<code><code>console.log(person.name); // "lg"/<code><code>for(var prop in person){/<code><code> console.log(prop); // "_name"/<code><code>}/<code><code>person.name = "li";/<code><code>console.log(person.name); // "li"/<code>
可以通過<code>Object.getOwnPropertyDescriptor(屬性所在的對象, 屬性)/<code>取得給定屬性的描述符;通過Object.defineProperties(屬性所在的對象, {屬性1:描述符對象1, 屬性2:描述符對象2})一次性定義多個屬性。
二、創建對象
1. 工廠模式
工廠模式抽象了創建具體對象的過程。
<code>function createPerson(name, age){/<code><code> var obj = new Object();/<code><code> obj.name = name;/<code><code> obj.age = age;/<code><code> obj.sayName = function(){/<code><code> console.log(this.name);/<code><code> };/<code><code> return obj;/<code><code>}/<code><code>var p1 = createPerson("z3", 26);/<code><code>var p2 = createPerson("l4", 27);/<code>
工廠模式可以解決創建多個相似對象的問題,但是會出現識別問題(即怎麼知道一個對象的類型)。
2. 構造函數模式
<code>function Person(name, age){/<code><code> this.name = name;/<code><code> this.age = age;/<code><code> this.sayName = function(){/<code><code> console.log(this.name);/<code><code> };/<code><code>}/<code><code>var p1 = new Person("z3", 26);/<code><code>var p2 = new Person("l4", 27);/<code><code>console.log(p1 instanceof Person); // true/<code>
可以標識類型(<code>p1.constructor ==> Person/<code>),其方法都要在每個實例上重新創建一遍。
3. 原型模式
每個函數都有一個prototype屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型創建的所有實例共享的屬性和方法。
<code>function Person(){}/<code><code>Person.prototype.name = "lg";/<code><code>Person.prototype.age = 26;/<code><code>Person.prototype.sayName = function(){/<code><code> console.log(this.name);/<code><code>};/<code><code>var p1 = new Person();/<code><code>p1.name = "z3";/<code><code>console.log(p1.name); // "z3" 實例/<code><code>console.log("name" in p1); // true/<code><code>console.log(p1.hasOwnProperty("name")); // true/<code><code>delete p1.name;/<code><code>console.log(p1.name); // "lg" 原型/<code><code>console.log("name" in p1); // true/<code><code>console.log(p1.hasOwnProperty("name")); // false/<code>
當為對象添加一個屬性時,這個屬性就會屏蔽原型對象中保存的同名屬性。hasOwnProperty()方法可以檢測一個屬性是否存在於實例中,還是存在於原型中;in操作符無論該屬性存在於實例中還是原型中。
示例:判斷屬性存在於原型中還是對象中
<code>/* 方式一:函數封裝 *//<code><code>function hasPrototypeProperty(object, name){/<code><code> return !object.hasOwnProperty(name) && (name in object);/<code><code>}/<code><code>hasPrototypeProperty(p1, "name"); // true 原型/<code><code>/* 方式二:原型擴展 *//<code><code>Object.prototype.hasPrototypeProperty = function(prop){/<code><code> return !this.hasOwnProperty(prop) && (prop in this);/<code><code>};/<code><code>p1.hasPrototypeProperty("name"); // true 原型/<code>更簡單的原型語法:
<code>function Person(){}/<code><code>Person.prototype = {/<code><code> name: "lg",/<code><code> age: 26,/<code><code> friends: ["camile"],/<code><code> sayName: function(){/<code><code> console.log(this.name);/<code><code> }/<code><code>};/<code><code>// 修正構造函數指向,不可枚舉/<code><code>Object.defineProperty(Person.prototype, "constructor", {/<code><code> enumerable: false,/<code><code> value: Person/<code><code>});/<code><code>var p1 = new Person();/<code><code>var p2 = new Person();/<code><code>
/<code><code>p1.friends.push("Gavin");/<code><code>console.log(p1.friends); // ["camile", "Gavin"]/<code><code>console.log(p2.friends); // ["camile", "Gavin"]/<code>
如果我們的初衷就是所有實例共享一個數組,那麼其符合預期;若想每個實例都有屬於自己的全部屬性,會存在上述問題。
4. 組合使用構造函數模式和原型模式
構造函數模式用於定義實例屬性,而原型模式用於定義方法和共享的屬性。每個實例都會有自己的一份實例屬性的副本,但同時又共享著方法的引用,最大限度地節省內存。
<code>function Person(name, age, friends){/<code><code> this.name = name;/<code><code> this.age = age;/<code><code> this.friends = friends || [];/<code><code>}/<code><code>Person.prototype.sayName = function(){/<code><code> console.log(this.name);/<code><code>};/<code><code>var p1 = new Person("Gavin", 26);/<code><code>var p2 = new Person("Camile", 26);/<code><code>p1.friends.push(["James"]);/<code><code>console.log(p1.friends); // ["James"]/<code><code>p2.friends.push(["Tom"]);/<code><code>console.log(p2.friends); // ["Tom"]/<code>
是目前ECMAScript中使用最廣泛、認同度最高的一種創建自定義類型的方法!
5. 動態原型模式
將構造函數和原型結合,不再獨立。
<code>function Person(name, age){/<code><code> this.name = name;/<code><code> this.age = age;/<code><code> // 不存在情況下,才會添加/<code><code> if(typeof this.sayName !== "function"){/<code><code> Person.prototype.sayName = function(){/<code><code> console.log(this.name);/<code><code> }/<code><code> }/<code><code>}/<code><code>var p1 = new Person("Gavin", 26);/<code><code>p1.sayName(); // "Gavin"/<code><code>// Person.prototype.sayName不會被執行/<code><code>var p2 = new Person("Camile", 26);/<code>
注意:不能使用對象字面量重寫原型,其會切斷現有實例與新原型之間的聯繫。
6. 寄生構造函數模式
其和典型的構造函數有略微的區別。
<code>function Person(name, age){/<code><code> var obj = new Object();/<code><code> obj.name = name;/<code><code> obj.age = age;/<code><code> obj.sayName = function(){/<code><code> console.log(this.name);/<code><code> };/<code><code> return obj;/<code><code>}/<code><code>var p1 = new Person("ligang", 26);/<code><code>console.log(p1 instanceof Person); // false/<code><code>console.log(p1.constructor); // Object/<code>
如果我們想創建一個具有額外方法的特殊屬性,使用上述模式會達到很好的效果!!
<code>function SpecialArray(){/<code><code> var ary = new Array();/<code><code> ary.push.apply(ary, arguments);/<code><code> ary.toPipedString = function(){/<code><code> return this.join("|");/<code><code> };/<code><code> return ary;/<code><code>}/<code><code>var colors = new SpecialArray("red", "yellow", "blue");/<code><code>colors.toPipedString();/<code>
三、繼承
JavaScript主要通過原型鏈實現繼承。
1. 原型鏈
每個構造函數都有一個原型對象(<code>prototype/<code>),原型對象都包含一個指向構造函數的指針(<code>constructor/<code>),而實例都包含一個指向原型對象的內部指針(<code>__proto__/<code>)。
<code>function Super(){/<code><code> this.property = true;/<code><code>}/<code><code>Super.prototype.getSuperValue = function(){/<code><code> return this.property;/<code><code>};/<code><code>
/<code><code>function Sub(){/<code><code> this.subProperty = false;/<code><code>}/<code><code>Sub.prototype = new Super(); // 將一個類型的實例賦給另一個構造函數的原型/<code><code>Sub.prototype.getSubValue = function(){/<code><code> return this.subProperty;/<code><code>};/<code><code>
/<code><code>var instance = new Sub();/<code><code>console.log(instance.getSubValue()); // false/<code><code>console.log(instance.getSuperValue()); // true/<code><code>console.log(instance.constructor); // Super/<code><code>console.log(instance instanceof Sub); // true/<code><code>console.log(instance instanceof Super); // true/<code>
問題:
(1)包含引用類型值的原型,會被所有實例共享;
(2)創建子類型的實例時,不能向父類型的構造函數中傳遞參數。
2. 借用構造函數
在子類構造函數的內部調用父類的構造函數。
<code>function Super(name){/<code><code> this.name = name;/<code><code> this.colors = ["red"];/<code><code>}/<code><code>
/<code><code>function Sub(name, age){/<code><code> // 繼承Super,可傳遞參數/<code><code> Super.call(this, name);/<code><code> this.age = age;/<code><code>}/<code><code>
/<code><code>var instance = new Sub("Gavin", 26);/<code><code>instance.colors.push("blue");/<code><code>console.log(instance.name, instance.age); // Gavin 26/<code><code>console.log(instance.colors); // ["red", "blue"] 獨立的colors副本/<code><code>console.log(new Sub().colors); // ["red"] 獨立的colors副本/<code>
問題:方法都在構造函數中定義,函數複用無從談起。
3. 組合繼承
將原型鏈和借用構造函數組合一起。使用原型鏈實現對原型屬性和方法的繼承,而通過借用構造函數來實現對實例屬性的繼承。
<code>function Super(name){/<code><code> this.name = name;/<code><code> this.color = ["red"];/<code><code>}/<code><code>Super.prototype.sayName = function(){/<code><code> console.log(this.name);/<code><code>};/<code><code>function Sub(name, age){/<code><code> Super.call(this, name);/<code><code> this.age = age;/<code><code>}/<code><code>Sub.prototype = new Super();/<code><code>Sub.prototype.sayAge = function(){/<code><code> console.log(this.age);/<code><code>};/<code><code>
/<code><code>var instance1 = new Sub("Gavin", 26);/<code><code>instance1.color.push("blue");/<code><code>console.log(instance1.color); // ["red", "blue"]/<code><code>instance1.sayName(); // "Gavin"/<code><code>instance1.sayAge(); // 26/<code><code>
/<code><code>var instance2 = new Sub("Camile", 26);/<code><code>instance2.color.push("yellow");/<code><code>console.log(instance2.color); // ["red", "yellow"]/<code><code>instance2.sayName(); // "Camile"/<code><code>instance2.sayAge(); // 26/<code>
JavaScript中最常用的繼承模式!!!
4. 原型式繼承
<code>function createObj(o){/<code><code> function F(){}/<code><code> F.prototype = o; // 對o的一種淺複製/<code><code> return new F();/<code><code>}/<code>
該方法等價於ECMAScript5中<code>Object.create()/<code>方法只傳入第一個參數。引用類型值的屬性會共享相應的值。
5. 寄生式繼承
在原型式繼承基礎上,繼續改造。
<code>function createAnother(original){/<code><code> var clone = createObj(original);/<code><code> clone.sayHi = function(){/<code><code> console.log("Hi");/<code><code> };/<code><code> return clone;/<code><code>}/<code><code>var person = {/<code><code> name: "LIGANG",/<code><code> age: 26/<code><code>};/<code><code>var anotherPerson = createAnother(person);/<code><code>anotherPerson.sayHi();/<code>
對象方法不能被複用!
【更多內容,可關注微信公眾號「Super 前端」】
閱讀更多 Super前端 的文章
關鍵字: JavaScript 對象 程序設計