01 繼承的概念
繼承:通過某種方式使其中一個類型A獲取另一個類型B的屬性和方法,其中A類型成為子類型,B為父類型
Javascript中的繼承:
Object是所有對象的父級|父類型|超類型:js中所有的對象都直接或間接的繼承自O(shè)bject。
繼承有兩種方式:接口繼承和實現(xiàn)繼承,在js中只支持實現(xiàn)繼承,實現(xiàn)繼承主要依賴原型鏈來完成。
說明:其他語言中繼承通常通過類來實現(xiàn),js中沒有類的概念,js中的繼承是某個對象繼承另外一個對象,是基于對象的。
繼承方式
01 屬性拷貝實現(xiàn)繼承
問題:屬性拷貝如果拷貝的是引用類型,那么會有數(shù)據(jù)共享的問題
直接賦值與屬性拷貝有什么區(qū)別:
直接賦值:所有的數(shù)據(jù)都是共享的
屬性拷貝:僅僅引用類型是共享的
重新設(shè)置引用類型的值:
直接賦值:重新設(shè)置某個對象的引用類型值,那么另外一個對象會受到影響
屬性拷貝:重新設(shè)置某個對象的引用類型值,那么另外一個對象不會受到影響 會切斷他們之間的關(guān)系
代碼示例01
var obj1 = {
name:"張三",
arr:[1,2,3],
showName:function () {
console.log(this.name);
}
};
var obj2 = {};
//要求obj2獲得obj1的屬性和方法
for (var i in obj1)
{
obj2[i] = obj1[i];
}
console.log(obj2);
obj1.arr.push(4);
console.log(obj2.arr); //arr[4]
obj1.name = "李四";
console.log(obj2.name); //張三
obj1.arr = ["demo1","demo2"];
console.log(obj2.arr); //arr[4]
代碼示例02
var o1 = {
name:"張三",
arr:[1,2,3],
showName:function () {
console.log(this.name);
}
};
var o2 = o1;
console.log(o2);
o1.name = "李四";
console.log(o2.name); //李四
o1.arr = ["demo1","demo2"];
console.log(o2.arr); //["demo1","demo2"];
02 原型式繼承
利用動態(tài)特性
直接替換原型對象
-
設(shè)置子對象的原型對象等于父對象的原型對象
01 原型對象成員共享 02 子對象的構(gòu)造器屬性不正確(容易產(chǎn)生誤解) 03 子對象獲取不到父構(gòu)造函數(shù)的實例成員
代碼示例
//利用動態(tài)特性
function Person() {
this.name = "默認(rèn)"
}
Person.prototype.showName = function () {
console.log(this.name);
};
var p1 = new Person();
p1.showName();
//直接替換原型對象
function Person() {
this.name = "默認(rèn)"
}
Person.prototype = {
constructor:Person,
showName:function () {
console.log(this.name);
}
};
var p1 = new Person();
p1.showName();
//設(shè)置子對象的原型對象等于父對象的原型對象
function Person() {
this.name = "默認(rèn)"
}
Person.prototype.showName = function() {
console.log(this.name);
}
function Student(){
this.number = "201701"
}
//設(shè)置原型繼承
Student.prototype = Person.prototype;
var stu = new Student();
console.log(stu);
console.log(stu.showName);
console.log(stu.name);
03 安全擴展內(nèi)置對象
需求3:在所有的數(shù)組上面添加name屬性(name) 添加方法showName,在數(shù)組的原型對象上添加屬性和方法
缺點:
01 原型對象上面的屬性和方法可能會被覆蓋(不安全)
02 原型對象上面的屬性和方法會越來越多,不方便管理和維護,性能會降低(屬性的訪問)
03 可能會出現(xiàn)一些不容易察覺的錯誤(建議:在遍歷數(shù)組的時候使用普通for循環(huán))。for..in 循環(huán)會把原型對象上面的屬性和方法列舉出來。
代碼示例
Array.prototype.name = "name";
Array.prototype.showName = function () {
console.log(this.name);
};
var arr1 = [1,2,3];
var arr2 = ["demo01","demo02"];
console.log(arr1.name);
//arr2.showName();
console.log(arr2.name);
for(var i in arr2)
{
console.log(i, arr2[i]); //會將新添加到原型對象一起遍歷
}
解決方法:設(shè)置自定義構(gòu)造函數(shù)的原型對象為父構(gòu)造函數(shù)的一個實例,既能擁有父構(gòu)造函數(shù)的實例與原型成員,又不會在設(shè)置屬性與方法的時候改變其成員
代碼示例
//01 提供自定義的構(gòu)造函數(shù)
function MyArray() {
}
//02 設(shè)置自定義構(gòu)造函數(shù)的原型對象
MyArray.prototype = new Array(); //擁有數(shù)組的所有屬性和方法
//03 在自定義構(gòu)造函數(shù)上面添加屬性和方法
MyArray.prototype.name = "name";
MyArray.prototype.showName = function () {
console.log(this.name);
}
//04 使用自定義構(gòu)造函數(shù)來創(chuàng)建對象
var arr1 = new MyArray();
arr1.push(1,2,3);
console.log(arr1.length);
console.log(arr1);
04 原型鏈繼承
子構(gòu)造函數(shù).prototype=new 父構(gòu)造函數(shù)();
(子構(gòu)造函數(shù)的實例可以繼承父構(gòu)造函數(shù)的實例屬性與原型方法)
代碼示例
function Animal() {
this.color = "紅色"
}
Animal.prototype.run = function () {
console.log("run");
}
function Person() {
this.name = "人"
}
Person.prototype = new Animal();
Person.prototype.constructor = Person; //注意,動態(tài)添加屬性與方法一定要在原型鏈繼承之后,如果在之前,添加的屬性與方法會被覆蓋
Person.prototype.eat = function () {
console.log("吃飯");
}
function Student() {
this.className = "超神班";
}
Student.prototype = new Person();
// Student.prototype.constructor = Student;
// Student.prototype.read = function () {
// console.log("閱讀");
// }
//錯誤的演示!?。? //Student.prototype = {
constructor : Student,
read:function () {
"讀書"
// } 使用字面量方式添加屬性和方法,會覆蓋原型鏈繼承
}
function Boy() {
this.girlF = "林志玲";
}
Boy.prototype = new Student();
Boy.prototype.constructor = Boy;
Boy.prototype.play = function () {
console.log("打游戲");
}
var boy = new Boy();
console.log(boy);
boy.eat();
注意點:
- 注意設(shè)置原型鏈的位置,先完成原型鏈繼承,再給原型添加成員
- 進行原型鏈繼承時注意構(gòu)造器屬性
- 繼承完成后,不能使用字面量方式給原型添加屬性與方法,會覆蓋。
問題:
01 父對象的實例屬性會成為子對象的原型屬性,如果其實例屬性為引用類型,則存在數(shù)據(jù)共享的問題
02 在創(chuàng)建子類型的實例的時候,不能給父類型的構(gòu)造函數(shù)傳遞參數(shù)
代碼示例
function Person(name) {
this.name = name;
this.friends = ["巴拉巴拉","嘩啦嘩啦","滴答滴答"];
}
Person.prototype.showName = function () {
console.log(this.name);
}
function Boy() {
}
Boy.prototype = new Person("張三");//設(shè)置實現(xiàn)原型鏈繼承
var boy1 = new Boy();
console.log(boy1.friends); //Array(4)
var boy2 = new Boy();
console.log(boy2.friends); //Array(4)
boy1.friends.push("烏拉烏拉");
console.log(boy1.friends); //Array(4)
console.log(boy2.friends); //Array(4)
console.log(boy1.name);
console.log(boy2.name);
05 原型鏈結(jié)構(gòu)
01 每個對象都是由構(gòu)造函數(shù)創(chuàng)建出來的
02 每個構(gòu)造函數(shù)都有一個與之相關(guān)連的原型對象(prototype)
03 構(gòu)造函數(shù)的原型對象本身也是對象,因此構(gòu)造函數(shù)的原型對象也有自己的構(gòu)造函數(shù)
04 構(gòu)造函數(shù)的原型對象的構(gòu)造函數(shù)也有相關(guān)聯(lián)的原型對象,而這個原型對象也是一個對象,因此也有構(gòu)造函數(shù)
05 構(gòu)造函數(shù)的原型對象的構(gòu)造函數(shù)的原型對象也有構(gòu)造函數(shù)..也有原型對象..也是對象...構(gòu)造函數(shù)....
以上 會形成一種鏈?zhǔn)降脑L問結(jié)構(gòu),這種結(jié)構(gòu)稱為原型鏈
原型鏈的終點是Object.prototype ,Object.prototype的原型對象是(Object.prototype.__ proto__ )
所有對象原型鏈的終點都是Object.prototype
代碼示例
function Person() {
}
var p1 = new Person();
//p1 是一個對象,因此有構(gòu)造函數(shù)(Person)
//構(gòu)造函數(shù)都有原型對象因此Person.prototype存在(Object)
//Person.prototype本身是一個對象(Object),因此也有構(gòu)造函數(shù)(Object)
//Object的構(gòu)造函數(shù)也有原型對象,Object.prototype
//Object.prototype本身也是一個對象,這個對象是Object類型
//Object.prototype也有構(gòu)造函數(shù),構(gòu)造函數(shù)是Object
06 原型鏈中屬性搜索規(guī)則
對象在訪問(讀取|寫)屬性的時候,先遍歷當(dāng)前的對象,查找有沒有指定的屬性
01 如果有該屬性,那么就直接使用
02 如果沒有該屬性,那么就遍歷當(dāng)前對象的原型對象,在原型對象身上查詢指定的屬性
01 如果找到那么就直接使用
02 如果沒有找到,那么就繼續(xù)向上查詢
03 重復(fù)這個過程,直到Object.prototype,如果找到那么就使用,如果沒有找到那么就返回undefined或者是報錯。
07 借用構(gòu)造函數(shù)繼承
作用:獲取父構(gòu)造函數(shù)的實例成員(解決傳參的問題)
注意:獲取不到父構(gòu)造函數(shù)的原型成員,可以采用原型式繼承獲取
代碼示例
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.showName = function () {
console.log(this.name);
};
function Boy(name,age) {
//構(gòu)造函數(shù)內(nèi)部會默認(rèn)創(chuàng)建空對象并賦值給this
Person.call(this,name,age); //this.name = name; 借用構(gòu)造函數(shù),也就是將構(gòu)造函數(shù)Person中的實例屬性給了this,this又指向由Boy構(gòu)造函數(shù)創(chuàng)建的實例
}
Boy.prototype = Person.prototype; //原型式繼承C
var boy1 = new Boy("張三",20);
var boy2 = new Boy("李四",30);
console.log(boy1);
console.log(boy2);
boy1.showName();
08 組合繼承
借用構(gòu)造函數(shù)獲取父構(gòu)造函數(shù)的實例成員;
利用原型式繼承,獲取父構(gòu)造函數(shù)的原型對象;
問題:原型對象數(shù)據(jù)的共享問題
09 深拷貝繼承
用法:使用for ...in循環(huán)來遍歷,如果是值類型,直接賦值;如果是引用類型,那么新創(chuàng)建一個對象,再次遍歷,持續(xù)整個過程,使用遞歸函數(shù)。注意:使用for ...in循環(huán)會把原型對象成員也拷貝,使用hasOwnProperty方法進行過濾。
代碼示例1
var person = {
name:"張三",
car:{
type:"飛船",
color:"黑色",
des:{
number:666888
}
},
arr:[1,2,3,4]
};
//參數(shù)1:目標(biāo)對象 ,參數(shù)2:要拷貝屬性的對象
function deepCopy(obj1,obj2) {
obj1 = obj1 || {}; //容錯性處理
for(var i in obj2)
{
//只拷貝實例屬性
if (obj2.hasOwnProperty(i))
{
if (typeof obj2[i] == "object")
{
//引用類型,新創(chuàng)建對象,再次遍歷
//需要判斷是數(shù)組還是對象
obj1[i] = Array.isArray(obj2[i])?[]:{}; //Array.isArray方法判斷是否是數(shù)組,
deepCopy(obj1[i],obj2[i]);
}else
{
obj1[i] = obj2[i];
}
}
}
}
var p = {};
deepCopy(p, person);
console.log(p);
//驗證共享的問題是否解決
person.car.type = "銀河戰(zhàn)艦";
console.log(p.car.type);
console.log(person);
代碼示例2
深拷貝實現(xiàn)繼承,數(shù)據(jù)共享問題解決
function deepCopy(obj1,obj2) {
obj1 = obj1 || {}; //容錯性處理
for(var i in obj2)
{
//只拷貝實例屬性
if (obj2.hasOwnProperty(i))
{
if (typeof obj2[i] == "object")
{
//引用類型,新創(chuàng)建對象,再次遍歷
//需要判斷是數(shù)組還是對象
obj1[i] = Array.isArray(obj2[i])?[]:{};
deepCopy(obj1[i],obj2[i]);
}else
{
obj1[i] = obj2[i];
}
}
}
}
function Person(name,age) {
this.name = name;
this.age = age;
}
Person.prototype.showName = function () {
console.log(this.name);
}
Person.prototype.hi = "hi";
function Boy(name,age) {
//構(gòu)造函數(shù)內(nèi)部會默認(rèn)創(chuàng)建空對象并賦值給this
Person.call(this,name,age); //this.name = name; 借用構(gòu)造函數(shù)
}
deepCopy(Boy.prototype,Person.prototype);
var boy1 = new Boy("張三",20);
var boy2 = new Boy("李四",30);
console.log(boy1);
console.log(boy2);
boy1.showName();
console.log(boy1.hi); //hi
Boy.prototype.hi = "hahahahah";
console.log(boy1.hi); //hahahahah
var p1 = new Person();
console.log(p1.hi); //hi