摘要:原型模式就是通過調用構造函數而創建的那個對象實例的原型對象。我們來看下面一個例子來理解這句話與構造函數模式不同的是,新對象的這些屬性和方法是由所有實例共享的。原型對象的問題首先,它省略了為構造函數傳遞初始化參數這一環節。
最近因為在給一個小同學做學習計劃,所以也記錄一些知識點,便于后面的同學的學習交流。這篇文章是關于Javascript的面向對象的程序設計,主要從三個方面來介紹,1. 理解對象屬性; 2. 理解并創建對象; 3. 理解繼承
一、理解對象屬性
首先我們來理解Javascript對象是什么?在Javascript中,萬物皆對象。其中創建自定義對象的最簡單的方式就是創建一個Object的實例,如下:
</>復制代碼
var person = new Object();
person.age = 29;
// 對象字面量的形式:
var person = {
age: 29
};
ECMAScript中有兩種屬性:數據屬性和訪問器屬性。
數據屬性:
其中數據屬性有四個描述其行為的特性:
Configurable: 表示能都通過delete刪除屬性從而重新定義屬性。
Enumerable: 表示能否通過for in 循環返回屬性。
Writable: 表示能否修改屬性的值。
Value: 包含這個屬性的數據值。
要修改屬性默認的配置,必須使用Object.defineProperty(), 這個方法接收三個參數:屬性所在的對象,屬性的名字和一個描述性對象。
比如:
</>復制代碼
var person = {};
Object.defineProperty(person, ’name’, {
writable: false,
value: ’Nicholas"
});
alert(person.name); //Nicholas
person.name = ‘Greg’;
alert(person.name); //Nicholas
訪問器屬性:
訪問器屬性包含一對setter和getter函數。包含如下4個特性:
Configurable:能否被delete刪除屬性重新定義。默認值:true
Enumerable:能否被for-in枚舉。默認值:true
Get:讀取屬性值。默認值:undefined
Set:寫入屬性值。默認值:undefined
</>復制代碼
var dog = {
_age: 2,
weight: 10
}
Object.defineProperty(dog, "age", {
get: function () {
return this._age
},
set: function (newVal) {
this._age = newVal
this.weight += 1
}
})
知道了對象的屬性,那么我們創建對象的方式是什么呢?
二、創建對象的方式
創建對象的方式通常有下面幾種方式:
1、工廠模式
我們舉個例子:
</>復制代碼
function createPerson(name, age, job) {
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function () {
alert(this.name);
};
Return o;
}
var person = createPerson(‘Greg’, 27, ‘Doctor’);
工廠模式雖然解決了創建多個相似對象的問題,但卻沒有解決問題識別的問題(即怎樣知道一個對象的類型)
2、構造函數模式
我們舉個例子:
**function Person(name, age, job) {
</>復制代碼
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
alert(this.name);
};
}
var person = new Person(‘Greg’, 27, ‘Doctor’);**
構造函數始終都應該以一個大寫字母開頭,而非構造函數則應該以一個小寫字母開頭。這里要提的一個屬性是Constructor, 每個new 出來的實例都有一個Constructor(構造函數)屬性,該屬性指向構造函數。
對象的Constructor屬性最初是用來標識對象類型的。但是,提到檢測對象類型,還是instanceof操作符更好可靠一些。
alert(person1 instanceof Object); //true
構造函數的問題問題就是,每個方法都要在每個實例上重新創建一遍,當然,可以把函數定義轉移到構造函數外部來解決這個問題,如下實例:
**function Person(name, age, job) {
</>復制代碼
this.name = name;
this.age = age;
this.job = job;
this.sayName = sayName;
}
function sayName() {
</>復制代碼
alert(this.name);
}
var person = new Person(‘Greg’, 27, ‘Doctor’);**
那么這里的問題就是:在全局作用域中定義的函數實際上被只能被某個對象調用,這讓全局作用域有點名不副實。而更讓人無法接受的是:如果獨享需要定義很多方法,那么就要定義很多個全局函數。下面我們來看看原型模式是不能解決這個問題。
3、原型模式
prototype就是通過調用構造函數而創建的那個對象實例的原型對象。我們來看下面一個例子來理解這句話:
</>復制代碼
function Person() {
}
Person.prototype.name = “Nicholas”;
Person.prototype.age = 29;
Person.prototype.job = “Software Engineer”;
Person.prototype.sayName = function() {
alert(this.name);
};
var person1 = new Person();
person1.sayName(); // “Nicholas”
與構造函數模式不同的是,新對象的這些屬性和方法是由所有實例共享的。
針對這個特性,我們要注意的幾個點就是。
第一,如果實例的屬性與方法與原型的屬性和方法同名,那誰的優先級高呢?
當然是創建出來的實例的屬性和方法的優先級高。
第二,實例的屬性與方法的修改會影響原型同名的屬性和方法嗎?
不會
這里提一下hasOwnProperty(), 使用hasOwnProperty()方法可以檢測一個屬性是存在于實例中,還是存在原型中。
第三,如何判斷某個屬性是存在原型中?
</>復制代碼
function hasPrototypeProperty(object, name) {
return !Object.hasOwnProperty(name) && (name in object);
}
hasOwnProperty()只在屬性存在于實例中才返回true, 因此只要in操作符返回true而hasOwnProperty()返回false, 就可以確定屬性是原型中的屬性、這里說下Constructor, 每創建一個函數,就會同時創建它的Prototype對象,這個對象也會自動獲得Constructor屬性。
我們來看一個例子:
</>復制代碼
function Person() {
}
Person.prototype = {
constructor: Person,
name: “Nichloas”,
age: 29,
job: “Software Engineer”,
sayName: function () {
alert(this.name);
}
};
以上代碼特意包含了一個Constructor屬性,并將它的值設置為Person, 從而確保了通過該屬性能夠訪問到適當的值。但是,以這種方式重設Constructor屬性會導致它的Enumerable特性被設置為true, 默認情況下原生的Constructor屬性是不可枚舉的。
</>復制代碼
Object.defineProperty(Person.prototype, “Constructor”, {
enumerable: false,
value: Person
});
原型對象的問題:
首先,它省略了為構造函數傳遞初始化參數這一環節。
然后,原型中所有屬性是被很多實例共享的,對于包含引用類型值的屬性來說,問題比較突出。所以,使用最多的方式使用構造函數和原型模式
4、組合使用構造函數模式和原型模式
</>復制代碼
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = [“Shelby”, “Court”];
}
Person.prototype = {
constructor: Person,
sayName: function () {
alert(this.name);
}
};
var person = new Person(‘Greg’, 27, ‘Doctor’);
知道了創建對象的方式,那么在Javascript中我們如何來繼承對象呢?
三、繼承
1、原型鏈
構造函數,原型和實例的關系:每一個構造函數都也有一個原型對象,原型對象都包含一個指向構造函數的指針,而實例都包含一個指向原型對象的內部指針。那么,假如我們讓原型對象等于另一個類型的實例,結果會怎么樣呢?顯然,此時的原型對象將包含一個指向另一個原型的指針,相應的,另一個原型中也包含也包含著一個指向一個構造函數的指針,假如另一個原型又是另一個類型的實例,那么上訴關系依然成立,我們來看下面一個例子.
</>復制代碼
function SuperType() {
this.property = true;
}
SuerType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
This.subProperty = false;
}
// 繼承了SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function () {
return this.subProperty();
};
var instance = new SubType();
alert(instance.getSuperValue); // true
這個例子中,SubType繼承了SuperType,而SuperType繼承了Object。如果要確定原型和實例的關系,可以用instanceOf操作符與isPropertyOF()方法。
如 instance instanceOf object; // true
Object.prototype isPropertype(instance); // true
但是通過原型鏈實現繼承時,不能使用對象字面量創建原型方法。
</>復制代碼
function SuperType() {
this.property = true;
}
SuerType.prototype.getSuperValue = function () {
return this.property;
};
function SubType() {
This.subProperty = false;
}
// 繼承了SuperType
SubType.prototype = new SuperType();
// 使用字面量添加新方法,會導致上一行代碼無效
SubType.prototype = {
getSubValue: function () {
return this.subproperty;
},
someOtherMethod: function () {
return false;
}
};
var instance = new SubType();
alert(instance.getSuperValue); // error
接下來,我們來說下原型鏈繼承的問題:
第一,最主要的問題來自包含引用類型值的原型。包含引用類型值的原型屬性會被所有實例共享,而這也正是為什么要在構造函數中,而不是在原型對象中定義屬性的原因。
第二,在創建子類型的實例時候,不能向超類型的構造函數中傳遞參數。
2、借用構造函數
</>復制代碼
function SuperType(name) {
this.name = name;
}
function SubType() {
// 繼承了SuperType, 同時還傳遞了參數
SuperType.call(this, “Nicholas”);
this.age = 29;
}
var instance = new SubType();
alert(instance.name); // “Nicholas”
借用構造函數的問題,方法都在構造函數中定義,因此函數復用就無從談起了。而且,在超類型的原型中定義的方法,對于子類型而言也是不可見的,結果所有類型都只能使用構造函數模式,考慮到這些問題,借用構造函數的技術也是很少多帶帶使用的。
3、組合繼承
組合繼承,指的是將原型鏈和借用構造函數的技術組合到一塊,從而發揮二者之長的一種繼承模式。其背后的思路是使用原型鏈實現對原型屬性和放大的績效,而通過借用構造函數來實現對實例屬性的繼承。
</>復制代碼
function SuperType(name) {
this.name = name;
this.colors = [“red”, “blue”, “green"]
}
SuperType.prototype.sayName = function () {
alert[this.name];
};
function SubType(name, age) {
// 繼承屬性
SuperType.call(this, name);
this.age = age;
}
// 繼承方法
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function () {
alert(this.age);
};
var instance = new SubType(“Nicholas”, 29);
instance.colors.push(“black”);
alert(instance.colors); // red, blue, green, black
組合繼承避免了原型鏈和借用構造函數的缺陷,融合了它們的優點,成為Javascript中最常見的繼承模式。
4、原型式繼承
</>復制代碼
function object(o) {
function F() {}
F.prototype = o;
return new F();
]
var person = {
Name: “Nicholas”,
friends: [“Shelby”, “Court”, “Van"]
};
var anotherPerson = object(person);
anotherPerson.name = “Greg”;
ECMAScipt5通過新增Object.create()方法規范化了原型式繼承。
接著上面的例子:
</>復制代碼
var anotherPerson = Object.create(person);
anotherPerson.name = “Greg”;
5、寄生式繼承
寄生式繼承是與原型式繼承緊密相關的一種思路,它與工廠模式類似,即創建一個僅用于封裝過程的函數,該函數在內部以某種方法來增強對象,最后再像真地是它做了所有工作一樣返回對象。
例如:
</>復制代碼
function createAnother(original) {
var clone = object(original);
clone.sayHi = function () {
alert(‘hi’);
};
return clone;
}
var person = {
name: ’Nochplas’,
friends: [’Shelby’, ‘Court’, ‘Van"]
};
var anotherPerson = createAnother(person);
6、組合寄生式繼承
組合繼承是Javascript最常用的繼承模式,不過,組合繼承最大的問題就是無論什么情況下,都會調用兩次超類型構建函數:一次是在創建子類型原型的時候,另一次是在子類型構造函數內部。
</>復制代碼
function SuperType(name) {
this.name = name;
this.colors = [“red”, “blue”, “green"]
}
SuperType.prototype.sayName = function () {
alert[this.name];
};
function SubType(name, age) {.
// 繼承屬性
SuperType.call(this, name); // 第二次調用 SuperType()
this.age = age;
}
// 繼承方法
SubType.prototype = new SuperType(); // 第一次調用 SuperType()
SubType.prototype.sayAge = function () {
alert(this.age);
};
所謂寄生組合式繼承,即通過借用構造函數來繼承屬性,通過原型鏈的混成形式來繼承方法。不必為了制定子類型的原型而調用超類型的構造函數,我們所需要的無非就是超類型原型的一個副本而已。本質上,就是使用寄生式繼承來繼承超類型的原型,然后再將結果制定給子類型的原型。如下:
</>復制代碼
function inheritPrototype (subType, superType) {
var prototype = object (superType.prototype); // 前面的原型式繼承object方法
prototype.constructor = subtype;
subtype.prototype = prototype;
}
function SuperType(name) {
this.name = name;
this.colors = [“red”, “blue”, “green"]
}
SuperType.prototype.sayName = function () {
alert[this.name];
};
function SubType(name, age) {.
// 繼承屬性
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function () {
alert(this.age);
};
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/109243.html
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:很多情況下,通常一個人類,即創建了一個具體的對象。對象就是數據,對象本身不包含方法。類是相似對象的描述,稱為類的定義,是該類對象的藍圖或原型。在中,對象通過對類的實體化形成的對象。一類的對象抽取出來。注意中,對象一定是通過類的實例化來的。 showImg(https://segmentfault.com/img/bVTJ3H?w=900&h=385); 馬上就要到七夕了,離年底老媽老爸...
摘要:是完全的面向對象語言,它們通過類的形式組織函數和變量,使之不能脫離對象存在。而在基于原型的面向對象方式中,對象則是依靠構造器利用原型構造出來的。 JavaScript 函數式腳本語言特性以及其看似隨意的編寫風格,導致長期以來人們對這一門語言的誤解,即認為 JavaScript 不是一門面向對象的語言,或者只是部分具備一些面向對象的特征。本文將回歸面向對象本意,從對語言感悟的角度闡述為什...
摘要:用代碼可以這樣描述安全到達國外面向過程既然說了面向對象,那么與之對應的就是面向過程。小結在這篇文章中,介紹了什么是面向對象和面向過程,以及中對象的含義。 這是 javascript 面向對象版塊的第一篇文章,主要講解對面向對象思想的一個理解。先說說什么是對象,其實這個還真的不好說。我們可以把自己當成一個對象,或者過年的時候相親,找對象,那么你未來的老婆也是一個對象。我們就要一些屬性,比...
摘要:對象重新認識面向對象面向對象從設計模式上看,對象是計算機抽象現實世界的一種方式。除了字面式聲明方式之外,允許通過構造器創建對象。每個構造器實際上是一個函數對象該函數對象含有一個屬性用于實現基于原型的繼承和共享屬性。 title: JS對象(1)重新認識面向對象 date: 2016-10-05 tags: JavaScript 0x00 面向對象 從設計模式上看,對象是...
閱讀 3696·2021-11-12 10:36
閱讀 3836·2021-09-22 15:48
閱讀 3548·2019-08-30 15:54
閱讀 2601·2019-08-29 16:44
閱讀 2368·2019-08-29 16:08
閱讀 2416·2019-08-29 16:06
閱讀 1290·2019-08-29 15:21
閱讀 3176·2019-08-29 12:39