1. 程式人生 > >JavaScript基礎(十三)原型、繼承

JavaScript基礎(十三)原型、繼承

原型、繼承

原型prototype

原型鏈

實現物件和原型物件的連結

function Person(){
// this.number = 10;
}
Person.prototype.number = 20;
var p = new Person();
alert(p.number);
p ==> 建構函式 ==> 原型物件 ==> Object.prototype
在執行“alert(p.number)”時,首先去p的建構函式Person中找是否有number這個屬性
如果找不到就去原型物件Perosn.prototype中找,
還是找不到就去Object的原型物件找

原型的預設方法和屬性

hasOwnProperty

判斷某個屬性是不是物件自己下面的屬性,返回boolean型別
		原型下的屬性不是

例如

function Person(){
	this.number = 10;
}
Person.prototype.number2 = 20;
var p = new Person();
p.num = 5;
alert( p.hasOwnProperty('number') ); // true
alert( p.hasOwnProperty('num') ); // true
alert( p.hasOwnProperty('number2'
) ); // false

constructor

屬性,值就是建構函式,是原型下的構造器
當new一個物件的時候,原型裡面預設帶有很多屬性和函式,但是不能 for...in 遍歷
只能遍歷出我們自己給它的原型繫結的一些屬性和方法
alert( p.constructor ); // function Person(){this.number = 10;}
// constructor的應用:判斷資料型別
var arr = [1, 2, 3];
// alert( arr.constructor.name ); // Array
// alert( arr.constructor == Array ); // true
for(var key in Person.prototype){
	alert(key);
} // number2

instanceof

判斷前面的物件是否是後面類的例項
alert( p instanceof Person ); // true
var o = new Object();
alert( o instanceof Person ); // false

toString

把一個物件轉變成字串
alert( typeof arr.toString() + '=====' + arr.toString() ); // string=====1,2,3
// 可以傳一個引數,進行進位制轉換
var num = 3;
alert(num.toString(2));  // 3轉為二進位制  ==>  11
num = 255;
alert(num.toString(16)); // ff

繼承

繼承:
	兒子繼承爸爸
	兒子可以用爸爸的方法和屬性
	兒子的改變不能影響爸爸

拷貝繼承

function Person(name){
	this.name = name;
}
Person.prototype.showName = function(){
	alert(this.name);
}
var p1 = new Person('aa');
// p1.showName(); // aa

function PersonAge(name, age){
	Person.call(this, name);
	this.age = age;
}
// 拷貝繼承 ==> 拷貝父類原型物件的方法
PersonAge.prototype.showName = function(){
	alert(this.name);
}
// 缺點很明顯,當父類下的原型方法很多的時候,就得一個個拷貝

原型繼承

// 解決上面問題的方法:將子類原型 指向 父類原型,這樣子類就繼承了父類原型物件下的所有方法和屬性
PersonAge.prototype = Person.prototype;
var p2 = new PersonAge('bb', 18);
// p2.showName(); // bb
PersonAge.prototype.showAge = function(){
	alert(this.age);
}
p2.showAge(); // 18
p1.showAge(); // undefined 說明父類也具有了該方法,這樣就有問題啦,影響到父類了,
// 原因是PersonAge.prototype = Person.prototype;

怎麼解決呢?可以將父類原型物件通過一箇中間物件複製一份,然後讓子類原型指向該副本

function extend(obj){
	var newObj = {};
	for(var key in obj){
		newObj[key] = obj[key];
	}
	return newObj;
}
// 原型繼承
PersonAge.prototype = extend(Person.prototype);
var p2 = new PersonAge('bb', 18);

PersonAge.prototype.showAge = function(){
	alert(this.age);
}
// p2.showAge(); // 18
// p1.showAge(); // Uncaught TypeError: p1.showAge is not a function ^_^

但是,新的問題又出現了,那就是子類例項的構造器就變為了Object,這是由於我們是“通過一箇中間物件(Object)複製一份”父類原型給子類的,所以子類原型實際指向的是該Object

alert(p2.constructor); // function Object() { [native code] } 不對啦,原有的構造器的方法沒有了

怎麼解決?可以將子類原型物件作為這個“中間物件”

function extend(obj, newObj){
	for(var key in obj){
		newObj[key] = obj[key];
	}
	return newObj;
}
PersonAge.prototype = extend(Person.prototype, PersonAge.prototype);
PersonAge.prototype.showAge = function(){
	alert(this.age);
}

var p2 = new PersonAge('bb', 18);
alert(p2.constructor);
p2.showAge(); // 18
p1.showAge(); // Uncaught TypeError: p1.showAge is not a function ^_^

類式繼承

function Person(name){ // 父類
	this.name = name;
}
Person.prototype.showName = function(){ // 父類原型方法
	alert(this.name);
}
var p1 = new Person('aa');
// 先讓子類繼承父類的建構函式中的方法和屬性
function PersonAge(name, age){ // 子類
	Person.call(this, name);
	this.age = age;
}
// 再通過一箇中間空函式的原型指向父類的原型,然後將子類原型指向新建立(new)出的“中間空函式”,這樣子類就間接繼承了父類原型下面的所有方法和屬性
var Fn = function(){}
Fn.prototype = Person.prototype;
PersonAge.prototype = new Fn();

var p2 = new PersonAge('bb', 18);
// p2.showName(); // bb
// 給子類原型新增方法
PersonAge.prototype.showAge = function(){
	alert(this.age);
}
// alert( p1.showAge ); // undefined,說明子類沒有影響到父類
// 但是子類構造器也會間接指向父類的構造器
// alert( p2.constructor ); // function Person(name){this.name=name} 就是和父類的構造
// 怎麼解決 ==> 直接將子類建構函式賦給子類的構造器即可
PersonAge.prototype.constructor = PersonAge;
alert( p2.constructor ); // function PersonAge(name, age){...}

instance之安全模式

var Person = function(name, age){
	this.name = name;
	this.age = age;
}
var p = Person('aa', 18); // 這裡其實是方法的呼叫,預設當然就是返回undefined,傳遞的引數實際是window下的
console.log(p + ' <==> ' + name + ' <==> ' + age);  // undefined <==> aa <==> 18
// 改寫:安全模式
var Person = function(name, age){
	if(this instanceof Person){
		this.name = name;
		this.age = age;
	}else{
		return new Person(name, age);				
	}
}
var p = Person('aa', 18);
// console.log(p + ' <==> ' + name + ' <==> ' + age); // age is not defined  ^_^
console.log(p + ' <==> ' + p.name + ' <==> ' + p.age); // [object Object] <==> aa <==> 18

繼承總結

類式繼承

類式繼承的原理:
新建立的物件複製了父類建構函式內的屬性與方法,並且將原型_proto_指向了父類的原型物件,這樣就擁有了父類原型物件上的屬性與方法
子類的原型同樣可以訪問父類原型上的屬性和方法,以及從父類建構函式中複製的屬性和方法
function SuperClass(){
	this.books = ['javascript', 'html', 'css'];
}
function SubClass(){
}
SubClass.prototype = new SuperClass();
/*
instanceof 可以用來判斷物件的prototype鏈中,某個物件是否是某個類的例項
*/
var instance = new SubClass();
/*
console.log(instance instanceof SubClass);		// true
console.log(instance instanceof SuperClass);	// true
console.log(SubClass instanceof SuperClass);	// false
console.log(SubClass.prototype instanceof SuperClass);	// true
console.log(instance instanceof Object);	// true
*/
// 缺陷:會影響到其他例項物件的引用變數
var instance1 = new SubClass();
var instance2 = new SubClass();
console.log(instance2.books); // ["javascript", "html", "css"]
instance1.books.push('設計模式');
console.log(instance2.books); // ["javascript", "html", "css", "設計模式"]

建構函式繼承

// 宣告父類
function SuperClass(id){
	this.books = ['javascript', 'html', 'css'];
	this.id = id;
}
// 父類宣告原型方法
SuperClass.prototype.showBooks = function(){
	console.log(this.books);
}
// 宣告子類
function SubClass(id){
	// 繼承父類
	SuperClass.call(this, id);
}
// 建立第一個子類的例項
var instance1 = new SubClass(0);
// 建立第二個子類的例項
var instance2 = new SubClass(1);
instance1.books.push('設計模式');
console.log(instance1.books, instance1.id);	// ["javascript", "html", "css", "設計模式"] 0
console.log(instance2.books, instance2.id);	// ["javascript", "html", "css"] 1
// 缺陷
// 建構函式沒有涉及prototype,故父類的原型方法不會被子類繼承
console.log(instance2.showBooks()); // Uncaught TypeError: instance2.showBooks is not a function

組合式繼承

組合式繼承 = 類式繼承 + 建構函式繼承
// 宣告父類
function SuperClass(name){
	this.name = name;
	this.books = ['javascript', 'html', 'css'];
}
// 父類宣告原型方法
SuperClass.prototype.getName = function(){
	console.log(this.name);
}
// 宣告子類
function SubClass(name, time){
	SuperClass.call(this, name); // 第一次利用SuperClass建構函式
	this.time = time;
}
// 類式繼承 子類的原型繼承父類
SubClass.prototype = new SuperClass(); // 第二次利用SuperClass建構函式
SubClass.prototype.getTime = function(){
	console.log(this.time);
}

var instance1 = new SubClass('js', 2014);
instance1.books.push('設計模式');
console.log(instance1.books); // ["javascript", "html", "css", "設計模式"]
instance1.getTime(); // 2014
instance1.getName(); // js

var instance2 = new SubClass('php', 2013);
console.log(instance2.books); // ["javascript", "html", "css"]
instance2.getTime(); // 2013
instance2.getName(); // php

var instance3 = new SuperClass('java');
console.log(instance3.books); // ["javascript", "html", "css"]
instance3.getName(); // java
// instance3.getTime(); // Uncaught TypeError: instance3.getTime is not a function

console.log(instance1.constructor.name); // SuperClass
console.log(instance1.constructor.name); // SuperClass
// 解決辦法
SubClass.prototype.constructor = SubClass;
console.log(instance1.constructor.name); // SubClass
console.log(instance1.constructor.name); // SubClass
缺點:建構函式開銷大
	有沒有更好的方法?
		==> 寄生組合式繼承方式

寄生組合式繼承

寄生組合式繼承
	通過在一個函式內的過渡物件實現繼承並且返回新物件的方式稱為寄生式繼承,此時再結合建構函式式繼承,就稱為寄生組合式繼承

	1. 寄生式繼承依託於原型繼承
	2. 建構函式式繼承

回顧一下

// 原型繼承
// 值型別屬性被複制,引用型別的屬性被公用
function extend(obj){
	// 宣告一個過渡函式物件
	var F = function(){};
	// 過渡物件的原型繼承父物件
	F.prototype = obj;
	// 返回過渡物件的例項,並且該例項的原型繼承了父物件
	return new F();
}

var book = {
	name: 'js', // 值型別
	books: ['css', 'java'] // 引用型別
};
var instance1 = extend(book);
console.log(instance1.name);		// js
console.log(instance1.books);		// ['css', 'java']
console.log(instance1.constructor); // ƒ Object() { [native code] }
instance1.name = 'jsp';
instance1.books.push('ajax');
console.log(instance1.name, instance1.books); // jsp (3) ["css", "java", "ajax"]
console.log(book.name, book.books); // js (3) ["css", "java", "ajax"]
function extend(obj){
	// 宣告一個過渡函式物件
	var F = function(){};
	// 過渡物件的原型繼承父物件
	F.prototype = obj;
	// 返回過渡物件的例項,並且該例項的原型繼承了父物件
	return new F();
}
function extendPrototype(subClass, superClass){
	// 複製一份父類的原型副本儲存在變數中
	var p = extend(superClass.prototype);
	// 將構造器重新指向子類
	p.constructor = subClass;
	subClass.prototype = p;
}
// 定義父類
function SuperClass(name){
	this.name = name;
	this.colors = ['red', 'blue', 'yellow'];
}
// 定義父類原型方法
SuperClass.prototype.getName = function(){
	console.log(this.name);
}
// 定義子類
function SubClass(name, time){
	// 建構函式繼承
	SuperClass.call(this, name);
	// 子類新增屬性
	this.time = time;
}
// 寄生式繼承父類原型
extendPrototype(SubClass, SuperClass);
SubClass.prototype.getTime = function(){
	console.log(this.time);
}

// 舉例
var instance1 = new SuperClass('js');
var instance2 = new SubClass('css', 2016);
instance1.colors.push('pink');
console.log(instance1.colors); // ["red", "blue", "yellow", "pink"]
console.log(instance2.colors); // ["red", "blue", "yellow"]
instance2.getName(); // css
instance2.getTime();