周大俠啊進擊的JavaScript(八)之繼承
原文連結:周大俠啊 進擊的 JavaScript (八) 之 繼承
前面講完原型鏈,現在來講繼承,加深理解下。
首先說一下,物件的相關知識
什麼是物件? 就是一些無序的 key : value 集合, 這個value 可以是 基本值,函式,物件。(注意 key 和 value 之間 是冒號 :
,每個 key : value 之間 是逗號 ,
)
var person = {
name: `zdx`,
age: 18,
get: function(){
return `名字:` + this.name + `年紀:` + this.age;
}
}
這時的 person 就是一個物件
讀取物件的屬性,有兩種方式:點 和 [` `],(使用中括號,裡面是有引號的)
person.name //`zdx`
person[`age`] //18
之前說過,物件的建立 有三種 方式: 字面量,new,Object.create;
但是,這種簡單的建立,並不能滿足,我們實際開發的要求。
比如,每個人的名字,年紀不一樣,我們不可能,每個人都寫一個物件,那不得炸了。
var personA = {
name: `A`,
age: 28
}
var personB = {
name: `B`,
age: 22
}
所以,這時候,我們就需要一些建立模式。
一、工廠模式
工廠模式呢,就是我們寫一個方法,然後通過這個方法複製出我們需要的物件。我們需要多少個,就複製多少個。(注意這裡是用方法(函式) 來生成物件)
var createPerson = function(name,age){
var obj = {},
obj.name = name,
obj.age = age
}
然後我們就可以這樣使用了。
var personA = createPerson(`A`, 28);
var personB = createPerson(`B`, 22);
但是呢,這種模式,建立的物件,無法歸類。也就是說,並不知道 personA,personB 等 是 屬於哪個物件的(哪個建構函式或者類)。
具有相同特性的一類事物,把它總結成一個 類;比如,人類,有男人,女人,小孩。把他們總結成了人類。
比如:
var obj = {};
obj instanceof Object; //true
//instanceof 方法 可以判斷 前者是否是 後者的例項物件
var arr = [];
arr instanceof Array; //true
於是,建構函式模式來了。
二、建構函式模式(類)
要想知道,男人,女人 屬於什麼類, 人類? 獸類? 這時候就需要 建構函式(類)了。
function Person(name, sex){ //注意,我們通常把建構函式(類)的首字母大寫!
this.name = name;
this.sex = sex;
}
var personA = new Person(`張三`, `男`);
var personB = new Person(`李四`, `女`);
console.log( personA instanceof Person ); //true
console.log( personB instanceof Person ); //true
這時候 建立出來的物件, 就知道 它屬於哪個建構函式(類)了。
這裡為啥用了 this 呢?new 一個函式, 為啥會建立出 一個例項物件(我們把 new 出來的物件 稱為 例項物件,簡稱例項)呢?
//這步,之前的原型鏈中說過,如果你懂了,可以簡單過一眼,增強記憶。
這個呢,就要理解new 到底幹了啥呢:
- 新建一個物件;
- 將該物件,即例項指向建構函式的原型;
- 將建構函式的this,指向該新建的物件;
- 返回該物件,即返回例項。下面,手寫一個方法 實現new 的功能。
function New(func){ //func 指傳進來的建構函式
var obj = {}; //這裡就直接新建一個空物件了,不用new Object了,效果一樣的,因為我感覺這裡講實現new的功能 再用 new 就不太好。
if(func.prototype != null){
obj.__proto__ = func.prototype;
}
func.apply(obj,Array.prototype.slice.call(arguments, 1));
//把func建構函式裡的this 指向obj物件(你可以理解成func建構函式的this 替換成 obj物件);
//Array.prototype.slice.call(arguments, 1);這個就是把New 函式,func 之後傳進來的引數,轉成陣列。
//把該引數陣列,傳入func建構函式裡,並執行func建構函式,比如:屬性賦值。
return obj;
}
驗證下:
function Person(name, sex){
this.name = name;
this.sex = sex;
this.getName = function(){
return this.name;
}
}
var p = New(Person,"周大俠啊", "男");
console.log( p.getName() ); //"周大俠啊"
console.log( p instanceof Person ); //true
去掉new 的寫法就是這樣:
function Person(name, sex){
var obj = {};
obj.__proto__ = Person.prototype;
obj.name = name;
obj.sex = sex;
obj.getName = function(){
return obj.name;
}
return obj;
}
var p = Person("周大俠啊", "男");
console.log( p.getName() ); //"周大俠啊"
console.log( p instanceof Person ); //true
使用 new 就簡化了步驟。
這種模式,也有弊端,你看出來了嗎? 有木有發現,每個例項物件 都有 相同 的 getName 方法,這樣,是不是就但佔資源了呢,100個例項物件,就新建了 100 個 getName 方法。
原型模式開始!
3、原型模式
之前的原型鏈說過,屬性、方法的讀取是沿著原型鏈查詢的,也就是說,在建構函式的原型物件上 建立的 屬性、方法,它的例項物件都能夠使用。
function Person(){};
Person.prototype.name = "周大俠啊";
Person.prototype.sex = "男";
Person.prototype.getName = function(){
return Person.prototype.name;
}
var p = new Person();
console.log( p.getName() ); //"周大俠啊"
console.log( p.hasOwnProperty("getName") ); //false getName 不是 p例項的屬性
這樣,建立的例項物件 都能使用 getName ,並且,也不會給每個例項物件,新增該屬性。
不過,你發現了嗎,這樣建立的例項物件,都是固定的屬性值, 一更改,所有的例項物件獲取的值,也都改變了。
那麼說了半天,這都是啥呢,別急,正因為上面做的鋪墊,才有更好的 方法。我覺得,眼尖的同學,可能已經知道了。
建構函式模式 與 原型模式 相結合的模式。
建構函式模式,可以建立 每個 例項物件 自己的屬性, 而原型模式,可以共享,同一個方法。
所以,我們一般,把物件自己的屬性、方法 ,用建構函式模式建立; 公共的方法,用原型模式 建立。
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype.getName = function(){
return this.name;
}
var p = new Person("周大俠啊", 22);
p.getName();
這樣就可以完美的建立物件了。
要是你有餘力,可以想想,這裡的 this.name
中 this
是怎麼回事。
但是,上面這個還可以優化,要是 Person.prototype 上有很多方法, 這種寫法就很不好,一般,我們用這種寫法:
function Person(name, age){
this.name = name;
this.age = age;
}
//新建一個物件,給該物件新增 方法, 然後把該物件 賦值給Person.prototype ,該物件就成為 Person (建構函式)的原型物件了。
Person.prototype = {
constructor : Person, //給新建的物件,新增constructor 屬性,建立與建構函式之間的聯絡。
getName : function(){
return this.name;
},
getAge : function(){
return this.age;
}
}
var p = new Person("周大俠啊", 22);
p.getName();
這裡的 this 又看懂是怎麼回事了嗎? 那我就簡單說一下 this 的知識吧。
關於 this 估計都聽說過 誰呼叫它,this就指向誰,這種說法,不嚴謹,this 的指向,是 在函式呼叫(執行)時確定的!
上面的:
Person.prototype = {
constructor : Person,
getName : function(){
return this.name;
},
}
var p = new Person("周大俠啊", 22);
p.getName();
this
在getName
函式裡,而getName
真正執行 的地方 是 p.getName
,p 是呼叫者,所以, this 就指向 p(換句話說,這時的this 就是 p)。
你要理解 精髓, 函式呼叫(執行)時 確定的呼叫者 之間的關係。
看這個:
var a = 10;
var b = {
a : 20,
say : function(){
console.log(this.a);
}
}
var c = b.say;
c(); //10 非嚴格模式下,獨立呼叫(無呼叫者)this 指向全域性物件
包含 this
的函式 正在呼叫(執行) 的時候 是 c();
此時,無呼叫者,指向全域性物件。
上面做了辣麼多鋪墊,終於要開始繼承的講解啦!什麼是繼承呢,其實呢,就是你 想要使用 別人的屬性,或者方法; 這時候就要利用繼承來實現。
既然是 得到別人的屬性,方法,就是繼承,那麼不就是 除了 Object.prototype 其他都是繼承了? dui ! 你說的沒錯…哈哈
所以,繼承的一種就是基於 原型鏈的了 ,就是屬性的查詢,讀取。
另一種就是 建構函式繼承了。
首先來一個需要被繼承的目標
//父類:
function Person(name) {
this.name = name;
}
Person.prototype = {
constructor: Person,
getName : function() {
return this.name;
}
}
建構函式的繼承
//子類:
function Son(name, age) {
Person.call(this, name); //call 方法,把前面函式的this 指向 給定的物件,第二個引數開始,傳入 引數,並執行函式。
this.age = age;
}
//就相當於
function Son(name, age) {
//Person(name); 此時 Person 裡的 this 等於當前的this
this.age = age;
}
//等同於
function Son(name, age) {
this.name = name,
this.age = age
}
//下面檢驗一下
var s1 = new Son(`周大俠啊`, 22);
console.log(s1.name); //`周大俠啊`
console.log(s1.getName()); //報錯,這是Person原型物件上的方法
原型鏈的繼承
第一種,通過new一個父類例項
這種是,new 一個父類例項,然後把son.prototype 新增到原型鏈上去。
//new 一個 Person 例項 賦給 Son.prototype
Son.prototype = new Person();
//給子類的原型加方法:
Son.prototype.get = function() {
return this.name + this.age;
}
上面的 Son.prototype = new Person() 實際上就是這樣的
//內部就是
var obj = {};
obj.__proto__ = Person.prototype;
Son.prototype = obj
//把 obj 賦給 Son.prototype 。而obj 這個例項 又指向了Person的原型物件,
//這樣,Son 就新增到了 Person 的原型鏈上了。
所以
//給子類的原型加方法:
Son.prototype.get = function() {
return this.name + this.age;
}
//就是:
obj.get = function(){ //通過給obj新增方法 然後賦值給Son的原型物件
return this.name + this.age;
};
驗證:
var s2 = new Son(`周大俠啊`, 22);
console.log(s2.get()); //`周大俠啊22`
console.log(s2.getName()); //`周大俠啊`
於是乎,我們就可以自己封裝一個繼承方法
function create(proto, options){ //proto表示父類的原型物件,options表示給子類新增的屬性
var obj = {};
obj.__proto__ = proto;
return Object.defineProperties(obj,options); //Object.defineProperties方法直接在一個物件上定義新的屬性或修改現有屬性,並返回該物件。所以這裡就直接return了
}
//繼承 就這樣寫了
Son.prototype = create(Person.prototype,{
constructor: { //這種格式的賦值寫法, 是 defineProperties 方法規定的寫法。
value: Son
},
get: {
value: function(){
return this.name + this.age;
}
}
})
//驗證一下
var s3 = new Son("zdx",22);
console.log(s3.getName()); //`zdx`
console.log(s3.get()); //`zdx22`
Object.defineProperties具體用法點這裡
ES5中就有個Object.create 方法 它就實現了剛剛封裝的create 方法
這裡是它具體用法
可以直接使用它來完成繼承:
Son.prototype = Object.create(Person.prototype,{
constructor: {
value: Son
},
get: {
value: function(){
return this.name + this.age;
}
}
})
//驗證一下
var s4 = new Son("zdx",22);
console.log(s4.getName()); //zdx
console.log(s4.get()); //zdx22
相關文章
- 周大俠啊進擊的JavaScript(四)之閉包JavaScript
- JavaScript進階之繼承JavaScript繼承
- JavaScript常用八種繼承方案JavaScript繼承
- javascript之繼承JavaScript繼承
- JavaScript(2)之——繼承JavaScript繼承
- JavaScript之物件繼承JavaScript物件繼承
- JavaScript 學習之繼承JavaScript繼承
- JavaScript之ES5的繼承JavaScript繼承
- JavaScript 複習之 物件的繼承JavaScript物件繼承
- JavaScript的繼承JavaScript繼承
- 講清楚之 javascript 物件繼承JavaScript物件繼承
- Javascript之繼承(原型鏈方式)JavaScript繼承原型
- Javascript繼承4:潔淨的繼承者—-原型式繼承JavaScript繼承原型
- JavaScript繼承JavaScript繼承
- JavaScript 繼承JavaScript繼承
- javascript:繼承JavaScript繼承
- 你不知道的javascript之繼承JavaScript繼承
- JavaScript 七大繼承全解析JavaScript繼承
- JavaScript中的繼承JavaScript繼承
- javascript繼承的方式JavaScript繼承
- javascript的superclass繼承JavaScript繼承
- Javascript繼承2:建立即繼承—-建構函式繼承JavaScript繼承函式
- JavaScript class 繼承JavaScript繼承
- JavaScript extends 繼承JavaScript繼承
- javascript類繼承JavaScript繼承
- 進擊的 JavaScript(六) 之 thisJavaScript
- JavaScript的繼承-轉載JavaScript繼承
- javascript的原型和繼承JavaScript原型繼承
- 《JavaScript物件導向精要》之五:繼承JavaScript物件繼承
- 深入淺出JavaScript之原型鏈&繼承JavaScript原型繼承
- JavaScript之物件導向的繼承淺析2JavaScript物件繼承
- 公有繼承、私有繼承和保護繼承之間的對比繼承
- 征服 JavaScript 面試:類繼承和原型繼承的區別JavaScript面試繼承原型
- Scala學習(八)---Scala繼承繼承
- C++繼承一之公有繼承C++繼承
- 深入JavaScript繼承原理JavaScript繼承
- JavaScript 繼承全解析JavaScript繼承
- JavaScript物件冒充繼承JavaScript物件繼承