一篇JavaScript技術棧帶你瞭解繼承和原型鏈

file

做者 | Jeskson來源 | 達達前端小酒館前端

1算法

在學習JavaScript中,咱們知道它是一種靈活的語言,具備面向對象,函數式風格的編程模式,面向對象具備兩點要記住,三大特性,六大原則。編程

那麼是哪些呢?具體的三大特性指的是什麼?請記住三大特性:封裝(Encapsulation),繼承(Inheritance),多態(Polymorphism)。咱們常說的封裝,繼承,多態,三大特色。六大原則指:單一職責原則(SRP),開放封閉原則(OCP),里氏替換原則(LSP),依賴倒置原則(DIP),接口分離原則(ISP),最少知識原則(LKP)。網絡

繼承的瞭解:數據結構

繼承,若是有不少的類,每一個類中的屬性或者是方法都不同,也有些屬性或者方法都是相同的,因此若是去定義它們,有時候要重複去定義這些相同的屬性或者方法。閉包

這就致使了代碼重複性,這就致使了繼承的出現,繼承就是兒子繼承老子的基因同樣,讓一個類「兒子類」繼承它們的「父親」,這樣就能夠擁有「父親」的全部具備相同的屬性或者是方法了。這樣的類咱們稱爲它叫作「父類」,繼承顧名思義就是兒子繼承老子,具備老子的屬性或者是方法,經過這種的繼承方式,讓全部的子類均可以訪問這些屬性或者是方法,而不用每次都在子類中去定義這些屬性或者是方法咯,多方便,多快捷,多快好省!app

其實JavaScript並非什麼強面嚮對象語言,由於它的靈活性決定了並非全部面向對象的特徵都適合JavaScript的開發。函數

咱們其實講到了類,那麼類又是怎麼理解的呢?工具

類是什麼呢?類是具備屬性或者是方法的集合,能夠經過類的構造函數建立一個實例的對象。說人話就是,如把人類看作一個類,而咱們每個人就是一個實例的對象,類的實例對象包含兩方面:學習

類的全部非靜態(屬性或者是方法)類的全部靜態(屬性或者是方法)

非靜態(屬性或者是方法)就是每個實例的特有的,屬於個性。全部靜態(屬性或者是方法)就是每個實例的共性的,屬於共性。

說人話就是,個性(非靜態)就是每一個人的名字都是不相同的,而名字這個屬性就是非靜態屬性;共性(靜態)就是每一個人都是要吃飯的,而吃飯這個方法就是靜態方法。

2

那麼在JavaScript中的類是如何實現的呢?

類的實現:

利用函數建立類,利用new關鍵字就能夠生成實例對象;利用構造函數實現非靜態(屬性或者是方法),利用prototype實現靜態(屬性或者是方法)。

// 建立函數
function dashucoding() {
 console.log('dashucoding')
}

// 函數賦值
var da = dashucoding() // undefined
// 實例對象
var jeskson = new dashucoding() // {}複製代碼

其中dashucoding是一個普通函數,也是一個類的構造函數,當調用dashucoding()的時候,它做爲一個普通函數會被執行,會輸出dashucoding,因沒有返回值,就會返回undefined;而當調用new dashucoding()時,會輸出dashucoding而且返回一個對象。

咱們把dashucoding這個函數來構造對象,因此咱們把這個dashucoding看做構造函數。構造對象,構造函數。即經過利用函數,定義構造函數,就至關於定義一個類,經過new關鍵字,生成一個實例對象。

// 構造函數
function dashucoding(name) {
 this.name = name
}

var da1 = new dashucoding('jeskson');
var da2 = new dashucoding('jeckson');

console.log(da1.name) // jeskson
console.log(da2.name) // jeckson複製代碼

其中dashucoding構造函數中多個參數,函數體中多this.name=name ,這句中的this指向new關鍵字返回的實例化對象。

根據構造函數中參數不一樣,生成的對象中具備的屬性name值也是不一樣的,這裏的name是什麼呢?看的出來嗎?就是這個類的非靜態(屬性或者方法)。

那麼如何利用prototype來實現靜態呢?(原型鏈的知識點)

3

原型對象鏈,原型鏈,JavaScript內建的繼承方法被稱爲原型對象鏈,又稱爲原型對象繼承。有這樣一句話,對於一個對象,由於它繼承了它的原型對象的屬性,因此它能夠訪問到這些屬性,而原型對象也是一個對象,同理,它也能夠有本身的原型對象,因此也是能夠繼承它的原型對象的屬性。

what?一臉懵逼,是否是沒聽懂,我以爲如小白,鬼聽得懂。原型繼承鏈概念,對象繼承其原型對象,而原型對象繼承它的原型對象。這概念說得鬼聽得懂哦,what?what?what? 賞你一大嘴巴子,你媽媽買菜必漲價,超級加倍。你爺爺下象棋,必備指指點點。

原型鏈:prototype?類的prototype是什麼?對象的proto是什麼?

類中的prototype

被稱做原型:在JavaScript中,每當咱們定義一個構造函數時,JavaScript引擎中就會自動爲這個類添加一個prototype

JavaScript中,當咱們使用new來建立一個對象的時候,JavaScript引擎就會自動爲這個對象添加一個__proto__屬性,並指向其類的prototype

// 構造函數
function dashucoding(name) {
 // 非靜態屬性
 this.name = name;
}
// 每當咱們定義一個構造函數,JavaScript引擎就會自動爲這個
// 類中添加一個prototype

console.log(dashucoding.prototype)

var da1 = new dashucoding('jeskson');
var da2 = new dashucoding('jeckson');

// 對象的proto

console.log(da1.__proto__);
console.log(da2.__proto__);

console.log(dashucoding.prototype === da1.__proto__);
// true
console.log(dashucoding.prototype === da2.__proto__);
// true複製代碼

其中dashucoding.prototype是一個對象,dashucoding類的實例化對象da1,da2都有一個屬性__proto__,也是對象。並dashucoding.prototype等於da1或者da2的__proto__

在JavaScript中引用類型的相等意味着它們所指向的都是同一個對象,任何一個實例化對象的__proto__屬性都指向其類的prototype。

對象中的__proto__屬性:

// 對象賦值
var pro = {
 name: 'jeskson';
}

var person = {
 __proto__: pro
}

console.log(person.name) // jeskson
person.name = 'jeckson'
console.log(person.name) // jeckson複製代碼

看見沒,其中的person並無定義name屬性,而console.log出來的結果是jeskson哦,這是爲啥呢?這就是所謂JavaScript中最厲害牛逼的原型鏈結果。

其實咱們回去看代碼就知道是有關聯的關係的,person中屬性__proto__的值爲pro,其中的pro指向pro這個對象,pro中的屬性具備name:'jeskson'的,也有__proto__屬性,值爲Object,而Object指向Object,Object的屬性也是有__proto__屬性,其值爲null。

來,接下來,讓咱們更加懂的說一下狀況,當咱們訪問person.name是,其中的過程是什麼樣的?

當咱們person.name進行訪問的時候,能夠看到咱們並無寫name這個屬性,首先,person.name會去找對象中是否有這個name屬性,若是沒有,它就會去找__proto__屬性對象。看到沒,在person中是有這個__proto__屬性的,別說沒有?

沒有,你就是沒仔細閱讀文章,沒有,你就是沒看文章內容,沒有,你就是不適合。

person中__proto__屬性對象的值是pro對象,因此person的__proto__指向了pro這個對象,那麼就會發如今pro這個對象中具備name這個屬性,那麼就能夠返回其中的值爲'jeskson'了。

可是假如咱們給person加上了這個name屬性的,先看代碼咱們是否是給它加了name值,這時候咱們console.log中的person.name值就不會找__proto__這個屬性了,會去先找其中的name屬性,值爲'jeckson',因此打印返回的'jeckson'。

這裏重點說一個:pro的__proto__指向是Object,每一個對象中都有__proto__屬性,這個屬性指向建立出來的對象它們默認是Object類的對象,因此記住對象的屬性__proto__天然指向Object.prototype。

好了好了,那麼讀懂了原型鏈,就來講上面沒說的,運用prototype實現靜態(屬性或者是方法)。

// 運用prototype實現靜態(屬性或者方法)
// 構造函數
function dashucoding(name) {
 this.name = name;
}

// 利用prototype實現靜態(屬性或者是方法)
// 建立了方法
dashucoding.prototype.eat = function() {
 console.log('i eat');
}

// 實例對象
var da1 = new dashucoding('jeskson');
var da2 = new dashucoding('jeckson');

// 給這我的添加方法

da1.eat() // i eat
da2.eat() // i eat

console.log(da1.eat === da2.eat) // true複製代碼

其中一句:dashucoding.prototype.eat = function(){...},經過dashucoding實例化的對象__proto__都會指向dashucoding.prototype。

原型鏈的知識點,只要構造函數中沒有定義同名的非靜態(屬性或者是方法),那麼每一個對象進行訪問的時候都是訪問其內部找到的eat方法,這樣咱們就運用原型鏈,實現了類的靜態(屬性或者是方法)。

// 構造函數
function dashucoding(name) {
 this.name = name;
 eat() {
  console.log('i eat');
 }
}

// 這就是同名複製代碼

4

對象的繼承,使用對象字面量建立對象時,會隱式的指向Object.prototype爲新對象的[[Prototype]],使用Object.create()方法建立對象時,會顯示指定新對象的[[Prototype]]Object.create()方法接受兩個參數,第一個參數爲新對象的[[Prototype]],第二個參數描述了新對象的屬性。

又是懵逼了!!!

// 對象字面量形式
var da = {
 name: 'jeskson'
}
// 原型被隱式地設置爲Object.prototype形式了,這就懂了

// Object.create()建立,顯示指定了Object.prototype
var dada = Object.create(Object.prototype, {
 dashucoding: {
  id: '123',
  code: 'dashucoding',
  value: '前端'
 }
})複製代碼

請把以上代碼記住,緊緊記住。

實現對象的繼承:

var da = {
 // 屬性
 name: 'jeskson',
 // 方法
 write: function() {
  console.log(this.name);
 }
}

var dada = Object.create(da, {
 name: {value: 'jeckson' }
})

da.write(); // 'jeskson'
dada.write(); // 'jeckson'複製代碼

console.log(da.hasOwnProperty('write')); // true
console.log(da.isPrototypeOf(dada)) // true
console.log(dada.hasOwnProperty('write') // false
console.log('write' in dada); // true

console.log(dada.__proto__ === da); // true
console.log(dada.__proto__ === Object.prototype) // true複製代碼

原型鏈繼承:

原型鏈是JavaScript實現繼承的主要方法,其基本思想是利用原型讓一個引用類型繼承另外一個引用類型的屬性和方法。實現原型鏈的基本模式是,讓當前構造函數的原型對象等於另外一個構造函數的實例。

function A(name, age) {
 this.name = name;
 this.age = age;
 this.family = ["爸爸","媽媽"];
 if(typeof(this.getName) != "function") {
  A.prototype.getName = function() {
   return this.name;
  }
 }
}

function B() {
 this.job = 'IT';
 if(typeof(this.getJob) != "function") {
  B.prototype.getJob = function() {
   return this.job;
  }
 }
}

B.prototype = new A("jeskson", 12);

var da = new B();

// 實例da 的屬性name,age是繼承自原型B。prototype
console.log(da.name); // jeskson
console.log(da.age); // 12
console.log(da.hasOwnProperty("name")); // false
console.log(da.hasOwnProperty("age")); // false複製代碼

// 實例da的原型B.prototype被重寫,因此da的構造函數指向A
console.log(da.constructor == B);
console.log(da.constructor == A);
// 輸出 false, true複製代碼

// 一個完整的原型鏈
// da.__proto__ > B.prototype.__proto__ > A.prototype.__proto__ > Object.prototype
console.log(B.prototype.isPrototype(da));
// true
cosole.log(A.prototype.isPrototype(B.prototype));
// true
console.log(Object.protoype.isProtypeOf(A.prototype));
// true複製代碼

原型鏈繼承:JavaScript中的對象繼承是構造函數基礎的基礎,幾乎全部的函數都有prototype屬性,除了經過Function.prototype.bind方法構造出來的函數是個例外,它是能夠被替換和修改的。

原型鏈實現繼承,讓子類繼承父類的靜態(屬性或者是方法)

// 父類
function Father() {}
Father.prototype.say = function() {
 console.log('father')
}

function Son() {}
var son1 = new Son();
console.log(son1.say); // undefined

// 原型鏈實現繼承的關鍵代碼
Son.prototype = new Father();
var son2 = new Son();
console.log(son2.say) // function(){...}複製代碼

當咱們使用Son.prototype = new Father()後,經過new Sow()生成的對象都會有__proto__屬性,這個屬性指向Son.prototype。實現了子類繼承了父類的靜態(屬性或者是方法)。

JavaScript中的原型和原型鏈:

prototype,當咱們建立的每個函數都有一個prototype原型屬性,這個屬性就是一個指針,指向了一個對象,而這個對象的用途就是能夠由特定類型的全部實例共享的屬性和方法。使用原型的好處就是可讓全部的對象實例共享原型對象所包含的屬性和方法。

function da(){}

da.prototype.name = 'jeskson';
da.prototype.age = 12;
da.prototype.job = 'it';

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

var person1 = new da();
person1.sayName(); // jeskson

var person2 = new da();
person2.sayName(); // jeskson

alert(person1.sayName() === person2.sayName());
// true複製代碼

da.prototype指向原型對象,da.prototype.constructor指向da,默認建立一個新函數,它的原型對象只包含constructor屬性,da對象的實例的內部屬性僅僅指向da.prototype

5

__proto__,全部的對象都具備__proto__屬性,隱式原型,指向構造該對象的構造函數的原型對象。

function da() {}
da.prototype.name = 'jeskson';
da.prototype.age = 12;
da.prototype.job = 'it';
da.prototype.sayName = function() {
 alert(this.name);
}

// 實例化對象
var person1 = new da();
console.log(da);
// da(){}

console.log(da.prototype);
console.log(da.prototype.__proto__);
console.log(da.prototype.constructor);
console.log(person1);
console.log(person1.__proto__);複製代碼

原型鏈:當爲對象實例添加一個屬性的時候,這個屬性會屏蔽掉原型對象中的同名對象。

function da() {}
da.prototype.name = 'jeskson';
da.prototype.age = 12;
da.prototype.job = 'it';
da.prototype.sayName=function() {
 alert(this.name);
}

var person1 = new da();
console.log(person1.name); // jeskon

person1.name="jeckson"
console.log(person1.name); // jeckson

person1.name = null
console.log(person1.name); // null

delete person1.name // 刪除,實例屬性,注意實例屬性
console.log(person1.name); // jeskson複製代碼

構造函數有一個prototype屬性,指向的是實例對象的原型對象,原型對象有一個constructor屬性,指向的是原型對象對應的構造函數,實例對象有一個__proto__屬性,指向的是該實例對象對應的原型對象。

原型方法:

isPrototypeOf()方法用來判斷,某個prototype對象和某個實例之間的關係:

alert(Cat.prototype.isPrototypeOf(cat1)); //true
alert(Cat.prototype.isPrototypeOf(cat2)); //true複製代碼

hasOwnProperty()方法,每一個實例對象都有一個hasOwnProperty()方法,用來判斷某一個屬性究竟是本地屬性,仍是繼承prototype對象的屬性。

alert(cat1.hasOwnProperty("name")); // true
alert(cat1.hasOwnProperty("type")); // false複製代碼

in運算符,用來判斷某個實例是否含有某個屬性,不論是不是本地屬性。

alert("name" in cat1); // true
alert("type" in cat1); // true複製代碼

構造函數,原型,實例之間的關係:

構造函數,是建立對象的一種經常使用的方式,其餘建立對象的方式還包括工廠模式,原型模式,對象字面量等,咱們來看一個簡單的構造函數。

// 構造函數
function Da(name, age) {
 // 構造函數的命名約定第一個字母使用大寫的形式
 this.name = name;
 this.age = age;
}複製代碼

每個構造函數都有一個prototype屬性,Da.prototype屬性實際上是一個指針,指向一個對象,該對象擁有一個constructor屬性,因此構造函數中的prototype屬性指向一個對象。

原型:

不管何時,只要建立一個新函數,就會根據一組特定的規則爲該函數建立一個prototype屬性,這個屬性指向函數的原型對象,在默認狀況下,全部原型對象都會自動得到一個constructor()構造函數,這個屬性包含一個指向prototype屬性所在函數的指針。

一臉懵逼中!!!

構造函數與原型的關係

構造函數中有prototype屬性,指向原型對象中constructor,原型對象中有constructor()構造函數,在原型對象中這個constructor指向構造函數中所在指針。

原型:構造函數的prototype屬性所指向的對象。(原型對象)

原型這個對象中,有一個constructor屬性又指回構造函數自己。

function Da(name,age) {
 this.name = name;
 this.age = age;
}
 
var da = new Da('jeskson', '12');複製代碼

經過構造函數建立對象的過程叫作實例化,建立出來的對象叫作實例

爲原型添加一個方法:

Da.prototype.eat = function() {
 console.log('i eat');
}複製代碼

在實例中調用該方法:

var da = new Da('jeskson', '12');
da.eat(); // i eat複製代碼

原型中的屬性和方法,在鏈接到其對應的構造函數的實例上,是可使用的。

構造函數,實例,原型 的關係

構造函數裏有什麼?

構造函數裏有prototype

原型裏有什麼?

原型裏有constructor,eat()方法

實例裏有什麼?

實例有屬性或者是方法

最好的實例代碼:

// 建立 Da1 構造函數
function Da1 () {
 this.name = 'jeskson';
}

// 給Da1構造函數的原型添加方法
Da1.prototype.eat1 = function() {
 console.log('i eat da1');
}

// 建立Da2構造函數
function Da2 () {
 this.name = 'jeckson';
}

// 將Da1的實例對象直接賦值給D2的原型
Da2.prototype = new Da1();

// 給Da2的原型添加一個方法
Da2.prototype.eat2 = function() {
 console.log('i eat da2');
}

// 實例化Da2
var da2 = new Da2();
da2.eat1(); // jeskson複製代碼

6

原型鏈繼承,函數聲明建立函數時,函數的prototype屬性被自動設置爲一個繼承自Object.prototype的對象,該對象有個本身的屬性constructor,其值就是函數自己。

// 構造函數
function Da() {}

// JavaScript引擎
Da.prototype = Object.create(Object.prototype, {
 constructor: {
  name: true,
  age: true
 }
});

console.log(Da.prototype.__proto__ === Object.prototype);
// true複製代碼

建立出來的構造函數都繼承自Object.prototype,JavaScript引擎幫你把構造函數的prototype屬性設置爲一個繼承自Object.prototype的對象。

構造函數實現繼承,讓子類繼承了父類的非靜態(屬性或者是方法)

// 構造函數
function Da(name) {
 this.name = name
}

function Son() {
 Da.apply(this, agruments)
 this.sing = function() {
  console.log(this.name);
 }
}

var obj1 = new Son('jeskson');
var obj2 = new Son('jeckson');

obj1.sing(); // jeskson
obj2.sing(); // jeckson複製代碼

組合方式實現繼承,原型鏈繼承和構造函數基礎,實現對父類的靜態及其非靜態的(屬性或者是方法)的繼承。

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

function Son() {
 Father.apply(this, arguments)
}
Son.prototype = new Father();
var son1 = new Son('jeskson');
var son2 = new Son('jeckson');

son1.sayName(); // jeskson
son2.sayName(); // jeckson複製代碼

寄生組合方式實現繼承:Super函數,讓Father的原型寄生在Super的原型上,讓Son去繼承Super,而後把這個過程放到一個閉包內。

// 構造函數
function Father(name) {
 this.name = name
}

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

function Son() {
 Father.apply(this,arguments)
}

(function() {
 function Super(){}
 Super.prototype = Father.prototype
 Son.prototype = new Super()
}())
var son1 = new Son('jeskson');複製代碼

子類型構造函數的內部調用父類構造函數

function getArea() {
    return this.length * this.width
}

/* 四邊形 */
function Rectangle(length, width) {
    this.length = length
    this.width = width
}

/* 獲取面積 */
Rectangle.prototype.getArea = getArea

/* 獲取尺寸信息 */
Rectangle.prototype.getSize = function() {
    console.log(`Rectangle: ${ this.length }x${ this.width },面積: ${ this.getArea() }`)
}

/* 正方形 */
function Square(size) {
    Rectangle.call(this, size, size)
    
    this.getArea = getArea
    
    this.getSize = function() {
        console.log(`Square: ${ this.length }x${ this.width },面積: ${ this.getArea() }`)
    }
}

var rect = new Rectangle(5, 10)
var squa = new Square(6)

rect.getSize()       // Rectangle: 5x10,面積: 50
squa.getSize()       // Square: 6x6,面積: 36複製代碼

7

面向對象,建立對象,使用構造函數建立

var obj = new Object();複製代碼

字面量建立:

var obj = {};複製代碼

工廠模式:

var p1 = new Object();
p1.name = 'da';
p1.age = '12'
p1.showName = function() {
 return this.name
}

var p2 = new Object();
p2.name = 'da2';
p2.age = '23',
p2.showName = function() {
 return this.name
}複製代碼

採用工廠模式,抽象建立對象的過程,封裝相同的屬性或者是方法

function createF(name, age) {
 var obj = new Object();
 obj.name = name;
 obj.age = age;
 obj.showName = function() {
  return this.name;
 };
 return obj;
}

var p1 = createF('jeskson',12);
var p2 = createF('jeckson',23);複製代碼

構造模式:

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.showName = function() {
        console.log(this.name);
    }
}

var p1 = new Person('張三', '1');
var p2 = new Person('李四', '2');複製代碼

什麼是原型鏈:

在JavaScript中繼承的主要方法就是經過原型鏈,主要是一個原型對象等於另外一個類型的實例,因爲實例內部含有一個指向構造函數的指針,至關於重寫了該原型對象,此時該原型對象包含了一個指向另外一個原型的指針。原型鏈的底層是:Object.prototype.__proto__,值爲null。

JavaScript只有一種結構就是對象,每一個實例對象都有一個私有的屬性爲__proto__,它的指向它的構造函數的原型對象(prototype)。該原型對象也有一個本身的原型對象__proto__,層層向上直到一個對象的原型對象爲null。

基於原型鏈的繼承,JavaScript對象有一個指向一個原型對象的鏈,Object.prototype屬性表示Object的原型對象。

let f = function() {
 this.a = 1;
 this.b = 2;
}

// 
function f() {
 this.a = 1;
 this.b = 2;
}
//
let o = new f(); // {a:1,b:2}
f.prototype.b=3;
f.prototype.c=4;複製代碼

當繼承的函數被調用時,this指向的是當前繼承的對象,而不是繼承的函數所在的原型對象。

var o = {
 a: 2,
 m: function() {
  return this.a+1;
 }
};

console.log(o.m()); // 3
當調用o.m時,'this'指向了o

var p = Object.create(o);
// p是一個繼承自o的對象

p.a = 4;
console.log(p.m());複製代碼

function doSomething(){}
doSomething.prototype.foo = "bar";
console.log( doSomething.prototype );複製代碼

使用Object.create建立對象

var a = {a: 1}; 
// a ---> Object.prototype ---> null

var b = Object.create(a);
// b ---> a ---> Object.prototype ---> null
console.log(b.a); // 1 (繼承而來)

var c = Object.create(b);
// c ---> b ---> a ---> Object.prototype ---> null

var d = Object.create(null);
// d ---> null
console.log(d.hasOwnProperty); 
// undefined, 由於d沒有繼承Object.prototype複製代碼

關於目前文章內容即涉及前端,PHP知識點,若是有興趣便可關注,很榮幸,能被您發現,真是慧眼識英!也感謝您的關注,在將來的日子裏,但願可以一直默默的支持我,我也會努力寫出更多優秀的做品。咱們一塊兒成長,從零基礎學編程,將 Web前端領域、數據結構與算法、網絡原理等通俗易懂的呈現給小夥伴。分享 Web 前端相關的技術文章、工具資源、精選課程、熱點資訊。

意見反饋:若本號內容有作得不到位的地方(好比:涉及版權或其餘問題),請及時聯繫咱們進行整改便可,會在第一時間進行處理。

感謝閱讀,原創不易,喜歡就點個贊吧,這是我寫做最大的動力。

歡迎關注達達的簡書!

這是一個有質量,有態度的博客

博客

相關文章
相關標籤/搜索