首頁  >  文章  >  web前端  >  淺析JavaScript的繼承與原型鏈

淺析JavaScript的繼承與原型鏈

WBOY
WBOY轉載
2022-02-15 17:44:332063瀏覽

這篇文章為大家帶來了javascript中繼承和原型鏈的相關知識,其中包括建構函式、原型以及class語法糖的相關問題,希望對大家有幫助。

淺析JavaScript的繼承與原型鏈

淺析JavaScript的繼承與原型鏈

#一、前言

##JavaScript的繼承與原型鏈是我在學習前端過程中遇到的較為少有的難以理解的部分,這裡便將我所有知道和了解到的東西記錄了下來,希望能夠給還在這裡面苦苦掙扎的兄弟萌一點點小的幫助,也歡迎各位大佬批評指正。

二、建構子

2.1 建構子的實例成員和靜態成員

建構子由實例成員和靜態成員二者組成,其中實例成員是在函數內部透過

this關鍵字添加的成員;只能透過實例化物件以後透過實例化物件進行存取;而靜態成員是函數本身上新增的成員,只能透過建構函數來訪問。

//创造一个构造函数let Father = function(name,age){
    //实例成员
    this.name = name;
    this.age = age;
    this.method = "我是一个实例成员";}
    //静态成员Father.like = "mother";
    //检验实例对象是否能够被构造函数直接访问console.log(Father.method);
    //undefinedconsole.log(Father.like);
    //mother
    //实例化一个对象let father = new Father("小王",27);
    //检验静态对象是否能够被实例化对象访问console.log(father.name);
    //小王console.log(father.age);
    //27console.log(father.like);
    //undefined
2.2 實例化物件的過程

透過

new#關鍵字可以透過建構函式實現一個實例化對象,那麼在具體實例化的過程中發生了什麼呢?大致可以劃分為以下步驟:

(1) 建立一個空物件son {}

(2) 為son 準備原型鏈連接

son.__proto__ = Father.prototype

(3) 重新綁定this,使建構子的this指向新物件

Father.call(this)

(4) 為新物件屬性賦值

son.name

(5) 傳回this

return this,此時的新物件就擁有了建構函式的方法和屬性了

一個小問題:所有實例化物件的方法都是共享的嗎?
建構函數的方法分為兩種,第一種為在函數內部直接定義的方法,第二種為透過原型添加的方法;

//函数内部直接定义的方法let Father = function(){
    this.read = function(){
        console.log("我是内部定义的read方法!");
    }}//通过淺析JavaScript的繼承與原型鏈添加的方法Father.prototype.look = function(){
    console.log("我是通过淺析JavaScript的繼承與原型鏈定义的look方法!");}
    //实例化对象进行检验let father1 = new Father();let father2 = new Father();
    father1.read();
    //我是内部定义的read方法!father2.read();
    //我是内部定义的read方法!console.log(father1.read === father2.read);
    //falsefather1.look();
    //我是通过淺析JavaScript的繼承與原型鏈定义的look方法!father2.look();
    //我是通过淺析JavaScript的繼承與原型鏈定义的look方法!console.log(father1.look === father2.look);
    /true
可以發現,函數內部直接定義的方法在每實例化一個新的物件以後,都會為這個方法分配一個新的記憶體空間,而透過原型添加的方法便會共享一個空間。

一個小問題:所有實例化物件的屬性都是共享的嗎?
不存在記憶體空間的問題,判斷時看其值是否相同;

let Father = function(name){
    this.name = name;}let father1 = new Father("小王");
    let father2 = new Father("小红");
    console.log(father1.name === father2.name);
    //falselet father1 = new Father("小王");
    let father2 = new Father("小王");
    console.log(father1.name === father2.name);
    //true
因此我們可以總結定義建構子的基本規則,即

公共屬性定義到建構函數裡面,公共方法我們放到原型物件身上。

三、原型

3.1 什麼是原型

Father.prototype 就是原型,它是一個對象,也可以稱為原型對象。

3.2 原型的作用是什麼

原型的作用,就是共享方法。

我們透過

Father.prototype.method 可以共用方法,不會反應開啟空間儲存方法。

3.3 原型中的this指向哪裡

原型中this的指向是實例。

四、原型鏈

原型鏈本人感覺是一個對於初學者或者說是部分前端菜雞(例如本人)來說特別難以理解的東西,為了讓下面的部分更容易理解,這裡強行先記住以下幾點:

  1. __proto__是每個物件都有的屬性,prototype是每個函數特有的方法;
  2. 每個物件的
  3. __proto__屬性都會指向自身建構函式的prototype;
  4. constructor屬性總是指向建立目前物件的建構子;
  5. #Function.__proto__ === Function.prototype;
  6. #Object.prototype.__proto__ === null 也就是原型鏈的終點;
4.1 什麼是原型鏈

原型與原型層層連結的過程即為原型鏈。

4.2 原型鏈的應用

物件可以使用建構子prototype原型物件的屬性與方法,就是因為物件有

__proto__原型的存在每個物件都有__proto__原型的存在

let Father = function(name){
    this.name = name;}let father = new Father("老王");console.log(father.__proto__ === Father.prototype);
    //true
    //验证上述说法中的第二条
4.3 原型鏈圖

淺析JavaScript的繼承與原型鏈

結合寫在最前面的幾點,理解上圖應該問題不大了,圖中圈起來的部分就是駭人聽聞的原型鏈。

4.4 原型鏈的尋找方式

function Star(name) {
	this.name = name;
	//(1)首先看obj对象身上是否有dance方法,如果有,则执行对象身上的方法
	this.dance = function () {
		console.log(this.name + '1');
	}}//(2)如果没有dance方法,就去构造函数原型对象prototype身上去查找dance这个方法。Star.prototype.dance = function () {
	console.log(this.name + '2');};
	//(3)如果再没有dance方法,就去Object原型对象prototype身上去查找dance这个方法。Object.prototype.dance = function () {
	console.log(this.name + '3');};
	//(4)如果再没有,则会报错。let obj = new Star('小红');obj.dance();

(1)先看obj物件身上是否有dance方法,如果有,則執行物件身上的方法。

(2)如果沒有dance方法,就去建構函式原型物件prototype身上去找dance這個方法。

(3)如果再沒有dance方法,就去Object原型物件prototype身上去找dance這個方法。

(4)如果再沒有,則會報錯。

一个小问题:在原型上添加方法需要注意的地方

有两种添加方法,第一种为上面的写法,直接通过 构造函数.prototype.方法名 进行添加;第二种为重定义构造函数的prototype,但是此种情况会丢失掉原有的constructor构造器,所以一定要再连接回去,例子如下:

function Star(name) {
	this.name = name;}Star.prototype = {
    dance:function(){
    	console.log("重定义prototype");
	}}Star.prototype.constructor = Star;

另外,类似于Array、String这些内置的类是不能这么处理的。

五、继承

这里就长话短说,首先我们要明确继承需要继承哪些东西,在前文中我们提到了定义构造函数的基本规则,即**公共属性定义到构造函数里面,公共方法我们放到原型对象身上。**我们所需要继承的东西也不外乎就这二者,公共属性的继承可以通过call()或者apply()进行this的指向定义,而公共方法可以通过原型对象的赋值进行处理,因此我们很容易想到如下的方法:

//定义一个父类function Father(name) {
	this.name = name;}Father.prototype.dance = function () {
	console.log('I am dancing');};//定义一个子类function Son(name, age) {
	Father.call(this, name);
	this.age = age;}//通过赋值的方法连接Son.prototype = Father.prototype;//为子类添加方法Son.prototype.sing = function () {
	console.log('I am singing');};
	let son = new Son('小红', 100);
	//此时父类也被影响了console.log(Father.prototype) 
	//{dance: ƒ, sing: ƒ, constructor: ƒ}

很显然,当我们只想修改子类里面的方法时,显然上述方法不太合适;因此 我们可以尝试new一个新的父类出来,代码如下:

function Father(name) {
	this.name = name;}Father.prototype.dance = function () {
	console.log('I am dancing');};function Son(name, age) {
	Father.call(this, name);
	this.age = age;}Son.prototype = new Father();Son.prototype.sing = function () {
	console.log('I am singing');};let son = new Son('小红', 100);console.log(Father.prototype) 
	//{dance: ƒ, constructor: ƒ}

六、class语法糖

对于以前了解过面向对象编程的程序员来讲,上述关于继承的写法属实让人有些难以接受,因此在es6里面新增了一个语法糖来更方便更便捷地书写继承,这里就直接上代码了;

class Father {
	constructor(name) {
		this.name = name;
	}
	dance() {
		console.log("我是" + this.name + ",我今年" + this.age + "岁," + "我在跳舞");
	}}class Son extends Father {
	constructor(name, age) {
		super(name);
		this.age = age;
	}
	sing() {
		console.log("我是" + this.name + ",我今年" + this.age + "岁," + "我在唱歌");
	}}let obj = new Son('小红', 19);
	obj.sing();obj.dance();

分析一下上面代码,首先一个类(构造函数)里面依旧为两部分,即公共属性和公共方法,constructor() 里面存放了该构造函数的公共属性,后面接着的便是公共方法,extends 关键字表示继承的是哪个类,super() 便是将里面父类里面相应的公共属性拿出来,这样看下来便可以将代码规整许多。

相关推荐:javascript学习教程

以上是淺析JavaScript的繼承與原型鏈的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除