js筆記--面向對象

個人網絡日誌

前言:ECMA-262將對象定義爲:"無序屬性的集合,其屬性可以包含基本值,對象或者函數"。這就相當於說對象是一組沒有特定順序的值。對象的每個屬性或方法都有一個名字,而每個名字都映射到一個值。正因爲這樣,我們可以把ECMAScript的對象看成散列表:一組名值對,值可以是數據或函數。(每個對象都是基於一個引用類型創建的)

對象理解

1.屬性類型

ECMAScript中有兩種屬性:數據屬性和訪問器屬性。
1.數據屬性
數據屬性包含一個數據值的位置,在這個位置可以讀取和寫入值。數據屬性有4個描述其行爲的特性
  • [[Configurable]]:表示能否通過delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改爲訪問器屬性。(直接在對象上定義的屬性,這個特性爲true)
  • [[Enumerable]]:表示能否通過for-in循環返回屬性。(直接在對象上定義的屬性,這個特性爲true)
  • [[Writeable]]:能否修改屬性的值。(直接在對象上定義的屬性,默認true)
  • [[Value]]:包含這個屬性的數據值。讀取屬性值時,從這個位置讀;寫入屬性值時,把新值保存在這個位置。默認值爲undefinde。
	var person ={
	 	name:"zhangsan"
	 };

創建了一個名爲name的屬性,值是zhangsan,[[Value]]特性被設置爲"zhangsan",對這個屬性的修改都會反映在這個位置

使用Object.defineProperty()修改屬性的特性:接收三個參數:屬性所在的對象,屬性名,描述符對象(對象屬性爲:configurable,enumerable,writable,value)

	var person ={
	 	name:"zhangsan"
	 };
	 Object.defineProperty(person,"name",{
	 	writable:false,   //設置name屬性不可修改
	 	value:"lisi"      //設置name屬性值爲 lisi
	 });	
	 alert(person.name);// lisi
	 person.name = "wangwu";
	 alert(person.name); // lisi

注意:一旦將屬性定義爲不可配置,就不能再把它變回可配置了。(再調用Object.defineProperty()方法修改除writable之外的特性,都會報錯)

	var person ={
	 	name:"zhangsan"
	 };
	 Object.defineProperty(person,"name",{
	 	configurable:false,  //設置name屬性不可修改
	 	value:"lisi"      //設置name屬性值爲 lisi
	 });	
	Object.defineProperty(person,"name",{
		configurable:true,   //報錯  Cannot redefine property: name
		value:"wangwu"
	});

2.訪問器屬性

訪問器屬性不包含數據值;它們包含一對getter和setter函數(都不是必需的):4個特徵

  • [[Configurable]]:能否通過delete刪除屬性從而重新定義屬性,能否修改屬性的特性,或者能否把屬性修改爲數據屬性。(直接在對象上定義的屬性,默認true)。
  • [[Enumerable]]:能否通過for-in循環返回屬性。(直接在對象上定義的屬性,默認true)
  • [[Get]]:讀取屬性時調用的函數。默認值爲undefined。
  • [[Set]]:寫入屬性時調用的函數。默認值爲undefined。

訪問器屬性不能直接定義,必須使用Object.defineProperty()來定義

	var book = {
		_year:2016,
		edition:1
	};

	Object.defineProperty(book,"year",{//定義一個訪問器屬性,<strong><span style="color:#ff0000;">沒指定configurable,enumerable特性,默認值都是false</span></strong>
		get:function(){
			return this._year;
		},
		set:function(newValue){
			if(newValue>2016){
				this._year = newValue;
				this.edition += newValue - 2016;
			}
		}
	});
	book.year = 2018;     //通過訪問器屬性設置_year的值
	alert(book.edition); // 3
	alert(book.year);    // 2018,通過訪問器屬性訪問_year屬性
	alert(book._year);   //2018,直接訪問_year屬性

創建了一個book對象,並給它定義了兩個默認的屬性_year和edition。_year前面的下劃線是一種常用記號,用於表示只能通過對象方法訪問的屬性。訪問器屬性是year,包含一個getter函數和setter函數。

注意:調用Object.defineProperty()方法時,如果沒指定configurable,enumerable,writable特性,默認值都是false。

不一定非要同時指定getter和setter。只指定getter意味着屬性是不能寫,嘗試寫入屬性會被忽略。

2.定義多個屬性

通過Object.defineProperties()方法,可通過描述符一次定義多個屬性。這個方法接收兩個對象參數:第一個對象是要添加和修改屬性的對象,第二個對象的屬性就是第一個對象中要添加或修改的屬性,如:

	var book = {_year:null,edition:null};
	Object.defineProperties(book,{
		_year:{
			value:2016
		},
		edition:{
			value:1
		},
		year:{
			get:function(){
				return this._year;
			},
			set:function(newValue){
				if(newValue > 2016){
					this._year = newValue;
					this.edition += newValue -2016;
				}
			}
		}
	});
	book.year = 2018;  //set方法設置_year值
	alert(book.year);  //get方法獲取_year值
	alert(book.edition);

3.讀取屬性的特性

使用Object.getOwnPropertyDescriptor()方法,可以取得給定屬性的描述符。接收兩個參數:屬性所在的對象,要讀取描述符的屬性名稱。返回一個對象,包含訪問器屬性或是數據屬性對應的特性。

	var book = {};
	Object.defineProperties(book,{
		_year:{
			value:2016
		},
		edition:{
			value:1
		},
		year:{
			get:function(){
				return this._year;
			},
			set:function(newValue){
				if(newValue > 2016){
					this._year = newValue;
					this.edition += newValue -2016;
				}
			}
		}
	});
	book.year = 2018;  //set方法設置_year值
	
	var descriptor = Object.getOwnPropertyDescriptor(book,"_year");
	console.log(descriptor.value);  //2016
	console.log(descriptor.configurable); //false  所以輸出的是2016,前面修改值爲2018無效
	
	var descriptor1 = Object.getOwnPropertyDescriptor(book,"year");
	console.log(descriptor1.value);//undefined
	console.log(descriptor1.enumerable);//false
	console.log(typeof descriptor1.get);//function

創建對象

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 person1 = createPerson("zhangsan",22,"Software Engineer");
var person2 = createPerson("lisi",24,"Doctor");
person1.sayName();
person2.sayName();

2.構造函數模式

function Person(name,age,job){ //構造函數Constructor
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = function(){
		alert(this.name);
	}
}
var person1 = new Person("zhangsan",23,"dc");
var person2 = new Person("lisi",23,"se");
person1.sayName();
console.log(typeof(person1)); // object,構造函數Constructor是Person();
console.log(typeof(person1.constructor)); //function
console.log(person1.constructor == Person);  //true
Person()函數取代了createPerson()函數。不同之處:
  • 沒有顯示的創建對象;(沒有 var o = new Object())
  • 直接將屬性和方法賦給了this對象;
  • 沒有return語句
首字母P大寫,與 其他函數區分開,以爲構造函數本身也是函數,只不過可以用來創建對象。
要創建Person的新實例,必須使用new操作符。實際上經歷下面4個步驟:
  1. 創建一個新對象;
  2. 將構造函數的作用域賦給新對象(因此this就指向了這個新的對象);
  3. 執行構造函數中的代碼(爲這個新對象添加屬性);
  4. 返回新對象。
person1和person2分別保存這Person的不同實例,都有一個constructor(構造函數)屬性,該屬性指向Person()函數
對象的constructor屬性最初是用來標識對象類型的。但是檢測對象類型還是用instanceof操作符靠譜。
console.log(person1 instanceof Person); //true
創建自定義的構造函數意味着可以將它的實例標識爲一種特定的類型;這正是構造函數勝過工廠模式的 地方。
這種方式定義的構造函數是定義在Global對象中(window)的
1.將構造函數當作函數
構造函數與其他函數的唯一區別,就在於調用它們的方式不同。任何函數,只要通過new操作符來調用,那它就可以作爲構造函數;
//當構造函數使用
var person  = new Person("zhangsan",24,"teacher");
//當普通函數使用
Person("lisi",25,"driver");
window.sayName(); // lisi
//在另一個對象的作用域中調用
var o = new Object();
Person.call(o,"wangwu",24,"cooker");
o.sayName();  // wangwu
2.構造函數的問題
使用構造函數的問題:每個方法都要在每個實例上創建一遍。
function Person(name,age,job){ //構造函數Constructor
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = new Function("alert(this.name)");//與函數申明方式一樣,函數也是對象
}

var person1 = new Person("zhangsan",24,"hehe");
var person2 = new Person("lisi",25,"heihei");
alert(person1.sayName == person2.sayName);//  false,創建了兩個函數實例,不同實例上的同名函數是不相等的

解決方法:將函數定義轉移到構造函數外

function Person(name,age,job){ //構造函數Constructor
	this.name = name;
	this.age = age;
	this.job = job;
	this.sayName = sayName;
}
function sayName(){ //函數方法移到構造函數外面
	alert(this.name);
}

這樣一來,構造函數中的sayName屬性指向一個全局的sayName()函數。創建的多個實例sayName都會指向同一個全局函數。但是這樣做的問題在於,這個sayName()全局函數只能被指定的對象調用,另一方面,多個方法就需要定義多個全局函數,對這個自定義的引用類型而言就沒有封裝行了。可通過原型模式來解決

3.原型模式

我們創建的每個函數都有一個prototype(原型)屬性,這個屬性是一個指針,指向一個對象,而這個對象的用途是包含可以由特定類型的所有實例共享的屬性和方法,所以我們不必在構造函數中定義對象實例信息,可以將這些信息直接添加到原型對象中。

	function Person(){
	}
	Person.prototype.name = "zhangsan";
	Person.prototype.age = 25;
	Person.prototype.job = "doctor";
	Person.prototype.sayName = function(){
		alert(this.name);
	}
	var person1 = new Person();
	var person2 = new Person();
	alert(person1.sayName==person2.sayName);// true

1.理解原型對象

無論什麼時候,只要創建了一個新函數,就會根據一組特定的規則爲該函數創建一個prototype屬性,這個屬性指向函數的原型對象。所有原型對象都會自動獲得一個consturctor屬性,這個屬性指向prototype屬性所在函數的指針。Person.prototype.constructor 指向Person。通過這個構造函數可以繼續爲原型對象添加其他屬性和方法。

當創建一個新的實例,該實例內部包含一個指針(  [[Prototype]]   ),指向構造函數的原型對象。這個連接存在於實例與構造函數的原型對象,而不是實例與構造函數之間。


Person的每一個實例person1和person2都包含一個內部屬性[[Prototype]],指向Person.prototype原型對象。實際與構造函數並沒有直接關係

雖然我們在所有實現中都無法訪問到[[Prototype]],但可以通過isPrototypeOf()方法來確定對象之間是否存在這種關係。如果[[Prototype]]指向調用isPrototypeOf()方法的對象(Person.prototype),就返回true。

alert(Person.prototype.isPrototypeOf(person1));  //true
使用原型對象的isPrototypeOf()方法測試person1,因爲person1內部有一個指向Person.prototype的指針,所以返回true。
ECMAScript5增加了一個新方法,叫Object.getPrototypeOf(),返回[[Prototype]]的值。如:

	alert(Object.getPrototypeOf(person1) == Person.prototype); //true
	alert(Object.getPrototypeOf(person2).name);  // zhangsan

注意:每當代碼讀取某個對象的某個屬性時,都會執行一次搜索(目標是具有給定名字的屬性)。搜索首先從對象實例本身開始。

當爲對象實例添加一個屬性時,這個屬性會屏蔽原型對象中的同名屬性。不過添加這個屬性只會阻止我們訪問原型中的屬性,並不會改變原型中的屬性。即使將實例中的這個屬性設置爲null,也不會恢復其指向原型的連接。但是使用delete可完全刪除實例屬性,重新去訪問原型中的屬性,如:

	function Person(){
	}
	Person.prototype.name = "zhangsan";
	Person.prototype.age = 25;
	Person.prototype.job = "doctor";
	Person.prototype.sayName = function(){
		alert(this.name);
	}
	var person1 = new Person();
	var person2 = new Person();

	person1.name = "lisi";
	alert(person1.name); //  lisi 來自實例屬性
	delete person1.name; // 刪除實例屬性
	alert(person1.name); //  zhangsan 訪問的是原型屬性	
hasOwnProperty()方法可檢測一個屬性是存在於實例中還是原型中(從Object中繼承而來):
	person1.name = "lisi";
	alert(person1.name); //  lisi 來自實例屬性
	alert(person1.hasOwnProperty("name"));//true
	delete person1.name; // 刪除實例屬性
	alert(person1.name); //  zhangsan 訪問的是原型屬性	
	alert(person1.hasOwnProperty("name"));//false

2.原型與in操作符

在使用in操作符時,如果對象能夠訪問給定屬性就返回true(無論是實例屬性還是原型屬性)。
	alert("name" in person1); //訪問的原型中的name屬性   true
	person1.name = "lisi";
	alert("name" in person1); //訪問的實例中的name屬性    true
for-in,返回的是所有能夠通過對象訪問的,可枚舉(enumerated)屬性(包括實例中的屬性和原型中的屬性)。屏蔽了原型中不可枚舉屬性([[Enumerable]]標記的屬性])的實例屬性也會在for-in循環中返回。因爲根據 規定,所有開發人員定義的屬性都是可枚舉的。

要取得對象上所有可枚舉的實例屬性,可以使用Object.keys()方法,接收一個對象作爲參數,返回包含所有可枚舉屬性的字符串數組。

	var keys = Object.keys(Person.prototype);
	alert(keys); //name,age,job,sayName
	var person1 = new Person();
	person1.name="zhangsan";
	person1.age= 25;
	var keys1 = Object.keys(person1);
	alert(keys1); // name,age
獲取所有實例屬性(無論是否可枚舉)Object.getOwnPropertyNames()
	var keys2 = Object.getOwnPropertyNames(Person.prototype);
	alert(keys2); // constructor,name,age,job,sayName   不可枚舉的constructor屬性也顯示出來了

3.更簡單的原型語法

避免每次添加屬性都寫一遍Person.prototype,使用包含所有屬性和方法的字面量來重寫整個原型對象。

	function Person(){
	}
	Person.prototype = {
		constructor:Person,  //如果不顯示指定構造函數指向Person,constructor屬性默認會指向Object構造函數(對原來構造函數的引用就因爲重寫斷開了)
		name:"zhangsan",
		age:25,
		job:"Software Engineer",
		sayName:function(){
			alert(this.name);
		}
	}
	//像上面給constructor屬性重新設置值會導致它的[[Enumerable]]特性被設置爲true(默認情況下是false,不可枚舉的)。
	//可使用Object.defineProperty()。來設置constructor屬性。
	Object.defineProperty(Perosn.prototype,"constructor",{
		enumerable:false,
		value:Person
	});

4.原型的動態性

由於在原型中查找值的過程是一次搜索,因此對原型對象做任何修改都能立刻從實例中體現出來。(即使是先創建實例後修改原型)
	function Person(){
	}
	var person = new Person();//先創建實例
	Person.prototype.sayHi = function(){//後修改原型也可以訪問
		alert("hi");
	};
	person.sayHi();

注意:調用構造函數創建對象時,會給實例添加一個屬性[[Prototype]],指向最初的原型對象。如果將原型修改成另一個對象後,就切斷了構造函數和最初的原型間的聯繫。(原來創建的實例還是指向原來的原型,在新的原型對象中添加屬性,老的實例並不會有變化)

	function Person(){
	}
	var person = new Person();//先創建實例
	Person.prototype = { //構造函數prototype屬性指向新原型對象
		constructor:Person,
		name:"張三",
		sayHi:function(){
			alert(this.name);
		}
	};
	person.sayHi();//實例還是指向的最初的原型對象,所以會報錯,提示沒有sayHi這個函數

5.原生對象的原型

原生引用類型(Object,Array,String,等等)都在其構造函數的原型上定義了方法。如:Array.prototype中可以找到sort()
與自定以的引用類型一樣,也可以給原生的引用類型添加方法(通過原生對象的prototype屬性)。
但是:不推薦這麼做,最好不要修改或添加原生對象的方法,可能命名衝突,或意外重寫原生方法。

6.原型對象的問題

由於原型本身的共享特性,對於包含引用類型值的屬性,會出現下面的問題:
	function Person(){
	}
	Person.prototype = { //構造函數prototype屬性指向新原型對象
		constructor:Person,
		name:"張三",
		friends:["wangwu","zhaoliu"],//原型中定義引用類型
		sayHi:function(){
			alert(this.name);
		}
	};
	var person1 = new Person();
	var person2 = new Person();
	person1.friends.push("sunqi");  //給person1實例的friends屬性添加值後,person2實例也跟着改變了
	alert(person1.friends);  // wangwu,zhaoliu,sunqi
	alert(person2.friends);  // wangwu,zhaoliu,sunqi
	alert(person1.friends == person2.friends); //ture

4.組合使用構造函數和原型模式(最常用的方式,默認模式)

構造函數用來定義實例屬性,原型用來定義方法和共享的屬性。這樣,每個實例都會有自己的實例屬性,同時也有共享的方法
	function Person(name,age){
		this.name = name;
		this.age = age;
		this.friends = ["sunliu"];
	}
	Person.prototype = { //構造函數prototype屬性指向新原型對象
		constructor:Person,
		sayHi:function(){
			alert(this.name);
		}
	};
	var person1 = new Person("zhaosi",34);
	var person2 = new Person("zhangsan",24);
	person1.friends.push("sunqi");  //給person1實例的friends屬性添加值
	alert(person1.friends);  // sunliu,sunqi
	alert(person2.friends);  // sunliu
	alert(person1.friends == person2.friends); //false

5.動態原型模式

在構造函數中動態去構造並初始化原型。
	function Person(name,age){
		this.name = name;
		this.age = age;
		if(typeof this.sayHi != 'function'){  //只在初次調用構造函數sayHi沒有的時候才執行
			Person.prototype.sayHi = function(){
				alert(this.name);
			}
		}
	}
	var person  = new Person("zhangsan",24);
	person.sayHi()

6.寄生構造函數模式

創建一個函數(作用只是封裝創建對象的代碼),返回新創建的對象。乍一看很像構造函數
	function Person(name,age){
		var o = new Object();//新建一個對象
		o.name = name;
		o.age = age;
		o.sayHi = function(){
			alert(this.name);
		};
		return o;
	}
	var person = new Person("zhangsan",23);
	alert(typeof person);  // Object
	alert(person instanceof Person);// false
	person.sayHi(); // zhangsan

返回的對象與構造函數或者與構造函數的原型屬性沒有關係。所以不能依賴instanceof來確定對象類型。

如果構造函數不返回值,默認會返回新的對象實例。通過在構造函數末尾添加return語句,可以重寫調用構造函數時返回的值。

	function SpecialArray(){
		var values = new Array(); //創建數組

		//添加值
		values.push.apply(values,arguments);// push方法初始化數組值

		//添加方法
		values.toPipedString = function(){  //給數組實例添加方法
			return this.join(" | ");
		}

		//返回數組
		return values;
	}
	var colors = new SpecialArray("red","blue","black");
	alert(colors.toPipedString()); // red | blue | black

7.穩妥構造函數模式

穩妥對象即沒有公共屬性,而且其方法也不引用this的對象。適合在安全環境中(禁止使用this和new),或者在防止數據被其他應用程序改動時使用。與寄生構造函數不同點:1.新創建對象的實例方法不引用this;2.不使用new操作符調用構造函數。
	function Person(name,age){
		//創建要返回的對象
		var o = new Object();

		o.sayHi = function(){
			alert(name); // 不使用this
		}
		return o;
	}
	var person = Person("zhangsan",23);
	person.sayHi(); // zhangsan
像上面的方式,除了sayHi()函數外,沒有其他辦法訪問name值。即使有其他代碼給這個對象添加方法或數據,但也不可能有別的辦法訪問傳入構造函數中的原始數據。穩妥構造函數模式提供的這種安全性,使得它非常適合在安全執行環境下使用。

繼承

1.原型鏈

利用原型讓一個引用類型繼承另一個引用類型的屬性和方法。

基本模式

	function Father(){
		this.fatherName = "father";
	}
	Father.prototype.getFatherName = function(){
		return this.fatherName;
	}

	function Child(){
		this.childName = "child";
	}
	//子繼承父(通過重寫原型對象,指向父實例)
	Child.prototype = new Father(); //注意:先繼承,後給子原型添加方法(原型動態性)

	Child.prototype.getChildName = function(){
		return this.childName;
	}
	var child = new Child();
	alert(child.getChildName()); //child
	alert(child.getFatherName()); //father
	alert(child.fatherName);  //father


如上圖所示:

通過創建Father父的實例,然後讓子構造函數的Child.prototype指向該實例。因此,father實例中的屬性和方法就存在與Child.prototype原型對象中了。並且在新的原型對象(father instance)中還有一個內部指針([[Prototype]])指向 Father.prototype。

結果就是: 子實例(child  instance)的 原型([[Prototype]])指向  父實例(father instance),父實例的原型([[Prototype]])指向Father.prototype。getFatherName()是原型方法,還是存在與Father.prototype中。fatherName是實例屬性,在(father instance)中,因此就在Child.prototype中(子構造函數繼承了父構造函數的實例屬性)

注意:child instance的 constructor指向的是  Father構造函數

child.getFatherName() 搜索過程:1.搜索child實例。2.搜索Child.prototype子原型。3.搜索Father.prototype父原型

  • 確定原型和實例的關係
	console.log(child instanceof Child);  //true
	console.log(child instanceof Father);  //true
	console.log(child instanceof Object);  //true

	console.log(Child.prototype.isPrototypeOf(child));  //true
	console.log(Father.prototype.isPrototypeOf(child));  //true
	console.log(Object.prototype.isPrototypeOf(child));  //true
  • 謹慎地定義方法
給原型添加方法的語句一定要在替換原型語句的後面。
在通過原型鏈實現繼承時,不能使用對象字面量創建原型方法,這樣會重寫原型鏈,如:
	function Father(){
		this.fatherName = "father";
	}
	Father.prototype.getFatherName = function(){
		return this.fatherName;
	}

	function Child(){
		this.childName = "child";
	}
	//子繼承父(通過重寫原型對象,指向父實例)
	Child.prototype = new Father(); //注意:先繼承,後給子原型添加方法(原型動態性)

	Child.prototype = {  //原來的繼承的原型鏈就斷了
		getChildName:function(){
			return this.childName;
		}
	}
	var child = new Child();
	child.getFatherName();// 報錯child.getFatherName is not a function  找不好這個函數
  • 原型鏈的問題

1.Father中定義的實例屬性,被Child繼承後成爲了  Child.prototype中的原型屬性。引用類型的原型屬性會被所有實例共享

2.在創建子類型的實例時,不能向父類構造函數傳遞參數

所以實踐中很少單獨使用原型鏈。

2.借用構造函數

通過借用構造函數(constructor stealing)的技術(僞造對象或經典繼承)。即在子類型構造函數的內部調用父類型構造函數。

	function Father(){
		this.colors = ["red","blue"];
	}
	function Child(){
		//子對象調用父構造函數
		Father.call(this);
	}
	var child = new Child();
	child.colors.push("black");
	var child2 = new Child();
	child2.colors.push("yellow");
	console.log(child);
	console.log(child2);
上面代碼通過使用call()方法(或apply()方法),在(未來將要)新創建的Child實例的環境下調用了Father構造函數。如此就可以在新的Child實例上執行Father()函數中定義的所有對象初始化代碼
1.傳遞參數

可以在子類型構造函數中向父類型構造函數傳遞參數

	function Father(name){
		this.name = name;
	}
	function Child(name,age){
		Father.call(this,name); //防止Father構造函數不會重寫Child的屬性,先調用父類構造函數,後給子類添加屬性
		this.age = age;
	}
	var child = new Child("zhangsan",23);
	console.log(child.name);

問題:方法都在構造函數中定義,函數無法複用。很少單獨使用。

3.組合繼承(常用的繼承方式)

使用原型鏈實現對原型屬性和方法的繼承,通過借用構造函數類實現對實例屬性的繼承。
	function Father(name){
		this.name = name;
		this.colors = ["red","blue"];
	}
	Father.prototype.sayName  = function(){
		alert(this.name);
	}
	function Child(name,age){
		Father.call(this,name);//繼承父類的實例屬性
		this.age = age;
	}
	Child.prototype = new Father();//繼承父類的原型方法
	Child.prototype.sayAge = function(){
		alert(this.age);
	}

	var child = new Child("zhangsan",23);
	child.colors.push("black");
	child.sayName();    //zhangsan
	child.sayAge();     //23
	alert(child.colors);  //red,blue,blace

	var child1 = new Child("lisi",24);
	child1.sayName();   //lisi
	child1.sayAge();    //24
	alert(child1.colors);  //red,blue

4.原型式繼承

function object(o){ //藉助原型基於已有的對象創建新對象
	function F(){};
	F.prototype = o;
	return new F();
}

var person  = {
	name:"zhangsan",
	friends:["lisi","wangwu"]   //引用數據類型,被多個實例共享
};

var person1 = object(person);
person1.name = "ethan";
person1.friends.push("zhaoliu");

var person2 = object(person);
person2.name = "king";
person2.friends.push("sunqi");

console.log(person1.name);     //ethan
console.log(person1.friends);  //lisi,wangwu,zhaoliu,sunqi 
console.log(person2.name);     //king
console.log(person2.friends);  //lisi,wangwu,zhaoliu,sunqi
ECMAScript5通過新增Object.create()方法規範化了原型式繼承。接收兩個參數:一,當作新對象原型的對象和(可選)爲新對象定義額外屬性的對象。

第二個參數與Object.defineProperties()方法的第二個參數格式相同:每個屬性都是通過自己的描述定義的。

var person  = {
	name:"zhangsan",
	friends:["lisi","wangwu"]   //引用數據類型,被多個實例共享
};

var person1 = Object.create(person,{
	name:{
		value:"ethan"  //覆蓋原來對象中的name屬性
	}
});
console.log(person1.name);  // ethan
在沒有必要創建構造函數,只想讓一個對象與另一個對象保持類似的情況下,原型式繼承是完全可以勝任的。


5.寄生式繼承

創建一個用於封裝集成過程的函數,在函數內部增強對象,然後返回對象
function object(o){
	function F(){};
	F.prototype = o;
	return new F();
}
function createObject(original){  //寄生式繼承
	var clone = object(original);
	clone.sayHi = function(){ //每個實例對象都會創建一個sayHi函數,無法複用
		alert(this.name);
	};
	return clone;  //返回這個對象
}
var person  = {
	name:"zhangsan",
	friends:["lisi","wangwu"]   //引用數據類型,被多個實例共享
};

var person1 = createObject(person);
person1.sayHi();
使用寄生式繼承來爲對象添加函數,與構造函數模式有點類似,都由於不能做到函數複用而效率地下。

6.寄生組合式繼承

組合繼承無論在什麼情況下都會調用兩次父類的構造函數:一是在創建子類原型的時候。二是在子類型構造函數內部。子類型最終會包含超類型對象的全部實例屬性,但我們不得不在調用子類型構造函數時重寫這些屬性。
function Father(name){
	this.name = name;
	this.colors = ["red","blue","green"];
}

Father.prototype.sayName = function(){
	alert(this.name);
}

function Child(name,age){  
	Father.call(this,name);//第二次調用Father父類構造函數,在新的子類實例對象上添加name,和colors屬性,覆蓋第一次(子原型中的屬性)
	this.age = age;
}
Child.prototype = new Father();//第一次調用Father父類構造函數,繼承父的實例屬性,name,colors。原型鏈指向父類
Child.prototype.constructor = Child;//原型構造函數指向Child
Child.prototype.sayAge = function(){
	alert(this.age);
}
寄生組合式繼承:通過借用構造函數來繼承屬性,通過原型鏈混成形式來繼承方法。
思路:不必爲了指定子類型的原型而調用父類的構造函數,只需獲得父類型的一個副本而已。使用寄生式繼承來繼承父類的原型,然後將結果指給之類的原型。

function object(o){
	function F(){};
	F.prototype = o;
	return new F();
}
//繼承父類的原型方法,保持原型鏈不變
function inheritPrototype(child,father){
	var prototype = object(father.prototype);//創建父類原型的副本
	prototype.constructor = child;//防止重寫原型失去默認的constructor屬性
	child.prototype = prototype;//重寫子類原型
}
function Father(name){
	this.name = name;
	this.colors = ["red","blue"];
}

Father.prototype.sayName = function(){
	alert(this.name);
}
function Child(name,age){
	Father.call(this,name);
	this.age = age;
}
inheritPrototype(Child,Father);
//會把父類的原型直接暴露給子類,那麼子類可以任意的修改父類的屬性和方法
//Child.prototype = Father.prototype;//在子類原型上添加方法實際上添加到父類上了
Child.prototype.sayAge  = function(){
	alert(this.age);
}

var child = new Child("zhangsan",23);
console.log(child);
child.sayName(); //zhangsan
console.log(child.colors); //red,blue
個人網絡日誌
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章