目錄
1 原型鏈
1.1 原型
1.1.1 什么是原型?
1.1.2 原型的作用:資料共享,節省記憶體空間
1.1.3 原型的寫法:
1.1.4 通過原型為內置物件添加原型的屬性或者方法
1.2 原型鏈
1.3 原型的指向
1.3.1 原型的指向是可以改變的
1.3.2 原型的最終指向
1.3.3 在原型改變指向之后添加原型方法
1.4 實體物件屬性和原型物件屬性重名
1.5 一條神奇的原型鏈
2 繼承
2.1 什么是繼承
2.2 通過原型實作繼承
2.3 繼承實體
2.4 借用建構式實作繼承
2.5 組合繼承
2.6 另一種繼承方式:拷貝繼承(for-in)
?
3 總結
1 原型鏈
1.1 原型
關于原型在JS面向物件編程這篇文章已經講過了,今天簡單來復習一下,
1.1.1 什么是原型?
在JS建構式中有一個屬性prototype,叫做原型,這是給程式員使用的,在JS實體物件中有一個屬性__proto__,它也是原型,這是供瀏覽器使用的,它不是標準的屬性, 實體物件中的__proto__指向的是該實體物件中的建構式中的prototype,建構式中的prototype里面的屬性或者方法,可以直接通過實體物件呼叫,

一般情況下,實體物件.__proto__才能訪問到建構式中的prototype的屬性或者方法,即 per._proto_.eat(), 因為__proto__不是標準的屬性,所以直接寫成 per.eat()即可,原型是一個屬性,而這個屬性也是一個物件,
1.1.2 原型的作用:資料共享,節省記憶體空間
在建構式中定義的屬性和方法,當實體化物件的時候,實體物件中的屬性和方法都是在自己的空間中存在的,如果是多個物件,這些屬性和方法都會在單獨的空間中存在,浪費記憶體空間,所以,為了資料共享,把想要節省空間的屬性或者方法寫在原型物件中,達到了資料共享,節省了記憶體空間,
1.1.3 原型的寫法:
建構式.prototype.屬性=值
建構式.prototype.方法=值---->函式.prototype,函式也是物件,所以,里面也有__proto__
實體物件.prototype-------->實體物件中沒有這個屬性,只有__proto__
原型的簡單寫法
缺陷:--->原型直接指向{}---->就是一個物件,沒有構造器
建構式.prototype={
切記:如果這這種寫法,要把構造器加上
};
1.1.4 通過原型為內置物件添加原型的屬性或者方法
系統的內置物件的屬性和方法可能不滿足現在需求,所以可以通過原型的方式加入屬性或者方法,為內置物件的原型添加屬性和方法,這個內置物件的實體物件可以直接使用添加的屬性或方法,
//為內置物件添加原型方法
//我們在系統的物件的原型中添加方法,相當于在改變原始碼
//我希望字串中有一個倒序字串的方法
String.prototype.myReverse = function() {
for (var i = this.length - 1; i >= 0; i--) {
console.log(this[i]);
}
};
var str = "abcdefg";
str.myReverse();
//為Array內置物件的原型物件中添加方法
Array.prototype.mySort = function() {
for (var i = 0; i < this.length - 1; i++) {
for (var j = 0; j < this.length - 1 - i; j++) {
if (this[j] < this[j + 1]) {
var temp = this[j];
this[j] = this[j + 1];
this[j + 1] = temp;
} //end if
} // end for
} //end for
};
var arr = [100, 3, 56, 78, 23, 10];
arr.mySort();
console.log(arr);
String.prototype.sayHi = function() {
console.log(this + "哈哈,我又變帥了");
};
//字串就有了打招呼的方法
var str2 = "小楊";
str2.sayHi();
1.2 原型鏈
原型鏈是一種關系,是實體物件和原型物件之間的關系,這種關系是通過原型(proto)來聯系的,JavaScript 物件有一個指向一個原型物件的鏈,當試圖訪問一個物件的屬性時,它不僅僅在該物件上搜尋,還會搜尋該物件的原型,以及該物件的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾,
請看下邊的代碼:
// 人的建構式
function Person(name,age) {
// 屬性
this.name=name;
this.age=age;
// 在建構式中的方法
this.eat=function () {
console.log("吃吃吃");
};
}
// 添加共享的屬性
Person.prototype.sex="男";
// 添加共享的方法
Person.prototype.sayHi=function () {
console.log("哈哈哈");
};
// 實體化物件,并初始化
var per=new Person("張三",18);
per.sayHi();
上邊的代碼中實體物件的原型__proto__和建構式的原型prototype指向是相同的,實體物件中的__proto__原型指向的是建構式中的原型prototype,
console.log(per.__proto__==Person.prototype);// true
它們的關系如下:
上圖中紅線鏈接部分即Person實體物件和Person原型物件之間的關系,可以看成一個原型鏈(不完整,目前可以先這樣理解),
1.3 原型的指向
1.3.1 原型的指向是可以改變的
請看下面的代碼:
//人的建構式
function Person(age) {
this.age=10;
}
//人的原型物件方法
Person.prototype.eat=function () {
console.log("吃吃吃!!!");
};
//學生的建構式
function Student() {
}
Student.prototype.sayHi=function () {
console.log("學學學,為中華崛起而讀書!!!");
};
//學生的原型,指向了一個人的實體物件
Student.prototype=new Person(18);
var stu=new Student();
console.dir(stu);
stu.eat(); // 吃吃吃!!!
stu.sayHi();//錯誤stu.sayHi is not a function,原型指向發生改變,找不到sayHi()方法
輸出結果:
上邊的代碼中,實體物件stu呼叫原型方法sayHi()的時候發生錯誤,這是因為Student.prototype=new Person(18);使原型指向發生了改變,stu物件中已經找不到sayHi()方法,原型指向改變分析: 圖中的紅線構成原型鏈,
1.3.2 原型的最終指向
通過上邊的學習我們可以知道,實體物件中有__proto__原型,建構式中有prototype原型,prototype也是是一個物件,那么,prototype這個物件中應該也有__proto__,那么它又指向了哪里呢?
即然實體物件中的__proto__指向的是建構式的prototype,所以prototype這個物件中__proto__指向的應該是某個建構式的原型prototype,
function Person() {
}
Person.prototype.eat=function () {
console.log("吃吃吃");
};
var per=new Person();
console.dir(per);
console.dir(Person);

通過分析我們發現Person的prototype中的__proto__的指向為Object;Object.prototype的__proto__是null,
console.log(Object.prototype.__proto__);// null
所以per實體物件的原型的指向為:
per實體物件的__proto__------->Person.prototype,Person.prototype的__proto__---->Object.prototype,Object.prototype的__proto__是null,這構成了一個完整的原型鏈,

上圖中的紅線鏈接構成了一個完整的原型鏈,
1.3.3 在原型改變指向之后添加原型方法
在1.3.1的代碼中,因為原型指向發生改變,找不到sayHi()方法而發生錯誤,我們可以在原型改變指向之后添加原型方法,改變代碼如下:
//人的建構式
function Person(age) {
this.age=10;
}
//人的原型物件方法
Person.prototype.eat=function () {
console.log("吃吃吃!!!");
};
//學生的建構式
function Student() {
}
//學生的原型,指向了一個人的實體物件
Student.prototype=new Person(18);
Student.prototype.sayHi=function () {
console.log("學學學,為中華崛起而讀書!!!");
};
var stu=new Student();
console.dir(stu);
stu.eat(); // 吃吃吃!!!
stu.sayHi();// 學學學,為中華崛起而讀書!!!

注意:簡單的原型寫法,會將 Person.prototype 重置到了一個新的物件,即改變原型的指向,我們也應該在原型改變指向之后添加原型方法,
function Person(age) {
this.age = age;
}
//指向改變了
Person.prototype = {
eat: function () {
console.log("吃");
}
};
//添加原型方法
Person.prototype.sayHi = function () {
console.log("你好帥呀!!!");
};
var per = new Person(10);
per.sayHi();// 你好帥呀!!!
1.4 實體物件屬性和原型物件屬性重名
實體物件訪問某個屬性,應該先從實體物件中找,找到了就直接用,找不到就去指向的原型物件中找,找到了就使用,找不到回傳undefined,
function Person(age,sex) {
this.age=age;
this.sex=sex;
}
Person.prototype.sex="女";
var per=new Person(18,"男");
console.log(per.sex); // 男
// 因為JS是一門動態型別的語言,如果物件沒有這個屬性,只要物件.屬性名字,物件就有了這個屬性了
// 但是該屬性沒有賦值, 所以per.albert結果是:undefined
console.log(per.albert); // undefined
console.log(albert); // 報錯
通過實體物件不能改變原型物件中的屬性值,要想改變原型物件中的屬性值,應該直接通過原型物件.屬性=值;進行改變,
Person.prototype.sex="我的天吶!!!我為什么這么帥?";
per.sex="不知道";
console.log(per.sex);
console.dir(per);

1.5 一條神奇的原型鏈
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>微信公眾號:AlbertYang</title>
</head>
<body>
<div id="dv"></div>
<script>
var divObj = document.getElementById("dv");
console.dir(divObj);
</script>
</body>
</html>
通過在控制臺分析divObj,得出divObj原型鏈指向為:
divObj.proto---->HTMLDivElement.prototype的__proto__--->HTMLElement.prototype的__proto__---->Element.prototype的__proto__---->Node.prototype的__proto__---->EventTarget.prototype的__proto__---->Object.prototype沒有__proto__,所以,Object.prototype中的__proto__是null
2 繼承
面向物件編程思想是根據需求分析物件,找到物件有什么特征和行為,然后通過代碼的方式來實作需求,要想實作這個需求,就要創建物件,要想創建物件,就應該有建構式,然后通過建構式來創建物件,通過物件呼叫屬性和方法來實作相應的功能及需求,
因為面向物件的思想適合于人的想法,編程起來會更加的方便,后期維護的時候也要會更加容易,所以我們才要學習面向物件編程,但JS不是一門面向物件的語言,而是一門基于物件的語言,JS不像JAVA,C#等面向物件的編程語言中有類(class)的概念(也是一種特殊的資料型別),JS中沒有類(class),但是JS可以模擬面向物件的思想編程,在JS可以通過建構式來模擬類的概念(class),在ES6中,class (類)作為物件的模板被引入,可以通過 class 關鍵字定義類,但是它的的本質還是 function,
2.1 什么是繼承
繼承是一種類(class)與類之間的關系,JS中沒有類,但是可以通過建構式模擬類,然后通過原型來實作繼承,繼承是為了實作資料共享,js中的繼承當然也是為了實作資料共享,
繼承是子類繼承父類的特征和行為,使得子類物件(實體)具有父類的屬性和方法,或子類從父類繼承方法,使得子類具有父類相同的行為,繼承可以使得子類具有父類的各種屬性和方法,而不需要再次撰寫相同的代碼,
例如:人有 姓名, 性別, 年齡 ,吃飯, 睡覺等屬性和行為,
學生有: 姓名, 性別, 年齡 ,吃飯, 睡覺 學習等屬性和行為,
老師有: 姓名, 性別, 年齡 ,吃飯, 睡覺 ,教學等屬性和行為,
先定義一個人類,人有姓名, 性別, 年齡等屬性,有,吃飯, 睡覺等行為,由人這個類派生出學生和老師兩個類,為學生添加學習行為,為老師添加教學行為,
2.2 通過原型實作繼承
// 人
function Person(name, age, sex) {
this.name = name;
this.sex = sex;
this.age = age;
}
Person.prototype.eat = function() {
console.log("吃吃吃!!!");
};
Person.prototype.sleep = function() {
console.log("睡睡睡!!!");
};
//學生
function Student(score) {
this.score = score;
}
//改變學生的原型的指向即可讓學生繼承人
Student.prototype = new Person("張三", 18, "男");
Student.prototype.study = function() {
console.log("學習真的太累了!!!");
};
var stu = new Student(99);
console.log("學生從人中繼承的屬性和行為:"); // >學生從人中繼承的屬性和行為:
console.log(stu.name); // >張三
console.log(stu.age); // >18
console.log(stu.sex); // >男
stu.eat(); // >吃吃吃!!!
stu.sleep(); // >睡睡睡!!!
console.log("學生中自己有的屬性和行為:"); // >學生中自己有的屬性和行為:
console.log(stu.score); // >99
stu.study(); // >學習真的太累了!!!
2.3 繼承實體
動物有名字,體重等屬性,還有吃東西的行為,
狗有名字,體重,毛色等屬性, 還有吃東西和咬人的行為,
哈士奇有名字,體重,毛色,年齡等屬性, 還有吃東西,咬人, 逗人玩等行為,
它們之間的繼承關系代碼實作如下:
// 動物的建構式
function Animal(name, weight) {
this.name = name;
this.weight = weight;
}
//動物的原型的方法
Animal.prototype.eat = function() {
console.log("弟兄們沖啊,趕快吃吃吃!!!");
};
//狗的建構式
function Dog(color) {
this.color = color;
}
Dog.prototype = new Animal("小三", "30kg");
Dog.prototype.bitePerson = function() {
console.log("~汪汪汪~,快讓開,我要咬人了!!!");
};
//哈士奇建構式
function Husky(age) {
this.age = age;
}
Husky.prototype = new Dog("黑白色");
Husky.prototype.playYou = function() {
console.log("咬壞充電器,咬壞耳機,拆家...哈哈,好玩不!!!");
};
var husky = new Husky(3);
console.log(husky.name, husky.weight, husky.color);
husky.eat();
husky.bitePerson();
husky.playYou();
它們的原型鏈關系為:
2.4 借用建構式實作繼承
在上邊的講解中,我們為了資料共享,改變了原型指向,做到了繼承,即通過改變原型指向實作了繼承,這導致了一個問題,因為我們改變原型指向的同時,直接初始化了屬性,這樣繼承過來的屬性的值都是一樣的了,這是個問題,如果我們想要改變繼承過來的值,只能重新呼叫物件的屬性進行重新賦值,這又導致我們上邊的初始化失去了意義,
function Person(name, age, sex, weight) {
this.name = name;
this.age = age;
this.sex = sex;
this.weight = weight;
}
Person.prototype.sayHi = function() {
console.log("你好帥呀!!!");
};
function Student(score) {
this.score = score;
}
//希望人的類別中的資料可以共享給學生---繼承
Student.prototype = new Person("小三", 18, "男", "58kg");
var stu1 = new Student("99");
console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.score);
stu1.sayHi();
var stu2 = new Student("89");
console.log(stu2.name, stu2.age, stu2.sex, stu2.weight, stu2.score);
stu2.sayHi();
var stu3 = new Student("66");
console.log(stu3.name, stu3.age, stu3.sex, stu3.weight, stu3.score);
stu3.sayHi();

重新呼叫物件的屬性進行重新賦值,非常的麻煩,而且使我們上邊的new Person("小三", 18, "男", "58kg");初始化失去了意義,
var stu3 = new Student("66");
stu3.name = "小紅";
stu3.age = 16;
stu3.sex = "女";
stu3.weight = "45kg";
console.log(stu3.name, stu3.age, stu3.sex, stu3.weight, stu3.score);
stu3.sayHi();
如何解決上邊的問題呢?答案是借用建構式實作繼承,
繼承的時候,不改變原型的指向,直接呼叫父級的建構式來為屬性賦值,即把要繼承的父級的建構式拿過來,借用一下為屬性賦值,這叫做借用建構式,借用建構式需要使用call ()這個方法,我會在后邊的文章中進行講解,大家在這里先記住用法就行了,
function Person(name, age, sex, weight) {
this.name = name;
this.age = age;
this.sex = sex;
this.weight = weight;
}
Person.prototype.sayHi = function() {
console.log("你好帥呀!!!");
};
function Student(name, age, sex, weight, score) {
//借用建構式
Person.call(this, name, age, sex, weight);
this.score = score;
}
var stu1 = new Student("小三", 16, "男", "50kg", "110");
console.log(stu1.name, stu1.age, stu1.sex, stu1.weight, stu1.score);
var stu2 = new Student("小紅", 22, "女", "45kg", "88");
console.log(stu2.name, stu2.age, stu2.sex, stu2.weight, stu2.score);
var stu3 = new Student("小舞", 16, "女", "40kg", "100");
console.log(stu3.name, stu3.age, stu3.sex, stu3.weight, stu3.score);

借用建構式繼承,解決了繼承的時候屬性重復的問題,但是這又導致一個問題即父類中的原型方法不能被繼承,
function Person(name, age, sex, weight) {
this.name = name;
this.age = age;
this.sex = sex;
this.weight = weight;
this.eat = function() {
console.log('吃吃吃!!!');
}
}
Person.prototype.sayHi = function() {
console.log("你好帥呀!!!");
};
function Student(name, age, sex, weight, score) {
//借用建構式
Person.call(this, name, age, sex, weight);
this.score = score;
}
var stu = new Student("小舞", 16, "女", "40kg", "100");
console.log(stu.name, stu.age, stu.sex, stu.weight, stu.score);
stu.eat();
stu.sayHi(); // >報錯 stu.sayHi is not a function

無論是單獨使用原型鏈繼承,還是單獨使用借用建構式繼承,都有很大的缺點,最好的辦法是,將兩者結合一起使用,發揮各自的優勢,這就是我們下面要講的組合繼承,
2.5 組合繼承
原型繼承和借用建構式繼承都存在各自的缺點,我們可以將這二者結合到一起,從而發揮二者之長,即在繼承程序中,既可以保證每個實體都有它自己的屬性,又能做到對一些屬性和方法的復用,這時組合繼承應運而生,組合繼承=原型繼承+借用建構式繼承,
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.sayHi = function() {
console.log("你好帥呀!!!");
};
function Student(name, age, sex, score) {
//借用建構式:解決屬性值重復的問題
Person.call(this, name, age, sex);
this.score = score;
}
//改變原型指向---原型繼承解決原型方法不能被繼承問題
Student.prototype = new Person(); //不傳值
Student.prototype.eat = function() {
console.log("吃吃吃!!!");
};
var stu = new Student("小三", 16, "男", "111分");
console.log(stu.name, stu.age, stu.sex, stu.score);
stu.sayHi();
stu.eat();
var stu2 = new Student("小舞", 15, "女", "1111分");
console.log(stu2.name, stu2.age, stu2.sex, stu2.score);
stu2.sayHi();
stu2.eat();

2.6 另一種繼承方式:拷貝繼承(for-in)
拷貝繼承:把一個物件中的屬性或者方法直接復制到另一個物件中,
function Person() {}
Person.prototype.name = "小三";
Person.prototype.age = 18;
Person.prototype.sex = "男";
Person.prototype.height = 100;
Person.prototype.play = function() {
console.log("玩的好開心呀!!!??");
};
var obj = {};
// Person中有原型prototype,prototype就是一個物件,那么里面,name,age,sex,height,play都是該物件中的屬性或者方法
// 新物件obj通過拷貝Person中原型prototype物件中的屬性和方法繼承Person中原型prototype物件的屬性和方法
for (var key in Person.prototype) {
obj[key] = Person.prototype[key];
}
console.dir(obj);
obj.play();
3 總結
原型鏈是一種關系,是實體物件和原型物件之間的關系,這種關系是通過原型(proto)來聯系的,繼承是類與類之間的關系,js不是面向物件的語言,沒有類但可以通過函式模擬類,模擬面向物件中的繼承,模擬繼承是為了實作資料共享,節省記憶體空間,
JS中的繼承方式:
1 原型繼承:通過改變原型的指向實作繼承,
2 借用建構式繼承:主要解決屬性重復的問題,會導致父類中的原型方法不能繼承,
3 組合繼承:原型繼承+借用建構式繼承,既能解決屬性重復問題,又能解決方法不能被繼承的問題,
4 拷貝繼承:把物件中需要共享的屬性或方法,直接通過遍歷的方式復制到另一個物件中,
小伙伴們今天的學習就到這里了,你可以使用今天學習的技巧來改善一下你曾經的代碼,如果想繼續提高,歡迎關注我,每天學習進步一點點,就是領先的開始,如果覺得本文對你有幫助的話,歡迎轉發,評論,收藏,點贊!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/4827.html
標籤:其他
上一篇:[記錄十二] Vue(全家桶)+node+koa2+mysql+nginx+redis,博客全堆疊專案之管理員界面的撰寫
下一篇:CSS基礎知識第三篇

