📢 大家好,我是 【戰神劉玉棟】,有10多年的研發經驗,致力于前后端技術棧的知識沉淀和傳播。 💗
🌻 近期剛轉戰 CSDN,會嚴格把控文章質量,絕不濫竽充數,歡迎多多交流。👍
文章目錄
- 寫在前面的話
- 快速了解
- 知識介紹
- prototype 與原型鏈
- 關于 prototype 繼承
- 使用 prototype 添加屬性
- 原型鏈的性能和優化
- Java 和 JavaScript 面向對象
- 用法拓展
- 創建對象的N種方式
- prototype 實現繼承
- 擴展原有對象的方法 Demo
- prototype 基礎測試 Demo
- 總結陳詞
寫在前面的話
又回到前端系列了,之前寫后端和其他內容去了,進度落下,趕緊補一下。
本篇文章介紹一下 JavaScript 里面的一些經典知識點,先以prototype
原型鏈開篇。
讓我們開始!
快速了解
【概念說明】
在JavaScript
中,每個對象都有一個原型prototype
屬性,它指向另一個對象。這個被指向的對象也有自己的原型,以此類推,最終形成了一個原型鏈。原型鏈的頂端是Object.prototype,它是所有對象的根原型。
當我們訪問一個對象的屬性時,如果該對象自身沒有這個屬性,JavaScript會沿著原型鏈向上查找,直到找到匹配的屬性或者到達原型鏈的末端。
【用代碼說話】
直接上一段示例代碼:
function Person(name, age) {this.name = name;this.age = age;
}Person.prototype.sayHello = function() {console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};const alice = new Person('Alice', 20);
alice.sayHello(); // 輸出:Hello, my name is Alice and I am 20 years old.
瀏覽器運行該代碼,F12控制臺查看alice
對象,如下圖所示:
分析一下上圖的這些內容:
- alice 對象包含 age 和 name 兩個屬性和一個原型對象
- 原型對象可以通過 alice.proto 訪問,或 Person.prototype,兩者是相等的
- 原型對象的再上一次層指向 Object.prototype,再往上一層就是 null
- 對,沒有錯,你看到了一個類似鏈式調用的東西,這是原型鏈
Tips:學習前端,特別是JavaScript,直接運行Demo,F12查看效果是一個快速學習方式。
知識介紹
prototype 與原型鏈
在 JavaScript 中,prototype 是用于實現繼承和共享屬性與方法的一種機制。每一個 JavaScript 對象(除了 null)在創建時都與另一個對象(稱為其 “prototype”)相關聯。這個 “prototype” 對象本身也有一個 “prototype”,這樣一直到一個對象的 prototype 為 null 為止,這形成了一個鏈條,被稱為 “原型鏈”(prototype chain)。
關于 prototype 繼承
所有的 JavaScript 對象都會從一個 prototype(原型對象)中繼承屬性和方法:
Date 對象從 Date.prototype 繼承。
Array 對象從 Array.prototype 繼承。
Person 對象從 Person.prototype 繼承。
所有 JavaScript 中的對象都是位于原型鏈頂端的 Object 的實例。
JavaScript 對象有一個指向一個原型對象的鏈。當試圖訪問一個對象的屬性時,它不僅僅在該對象上搜尋,還會搜尋該對象的原型,以及該對象的原型的原型,依次層層向上搜索,直到找到一個名字匹配的屬性或到達原型鏈的末尾。
Date 對象、Array 對象、以及 Person 對象從 Object.prototype 繼承。
console.log(alice.__proto__ === Person.prototype); // 輸出:true
console.log(Person.prototype.__proto__ === Object.prototype); // 輸出:true
console.log(Object.prototype.__proto__ === null); // 輸出:true
使用 prototype 添加屬性
JavaScript 對象要添加新的屬性或方法,很簡單。
有點像 Java 的Map那邊隨意,直接追加即可,類似 alice.xxx = ‘1111’
但如果想追加的屬性是和其他對象,繼承和共享的,那就可以用到 prototype 方式。
Person.prototype.nationality = "English";
console.log(alice.__proto__.nationality);
使用 prototype 屬性就可以給對象的構造函數添加新的屬性和方法。
關鍵詞 constructor
前面示例一定看到了原型對象上有一個 constructor 屬性,這是何物?
其實就是和 Java 中的構造函數類似的概念,用下面代碼可以看出來,原型的 constructor就是指向類本身。
alice.__proto__.constructor === Person // true
結合前面說的內容, 整個概念是否繞來繞去,找了下面這張圖,感覺能比較直觀了看明白之間的調用關系。
幾個概念復述一下:
- prototype 是 JavaScript 中實現繼承和共享屬性與方法的基礎。
- 構造函數的 prototype 屬性用于定義所有實例共享的方法和屬性。
- 每個對象都有一個 proto 屬性,對象通過該屬性引用其原型,形成原型鏈。
- 每個函數在定義時會自動獲得一個 prototype 屬性,這個屬性是一個對象,包含一個 constructor 屬性指向函數本身。
- 當訪問對象的一個屬性或方法時,JavaScript 會首先在對象本身查找。如果沒有找到,它會沿著原型鏈向上查找,直到找到或到達鏈的末端(即 null)。
Tips:理解 prototype 和原型鏈是掌握 JavaScript 面向對象編程的關鍵。通過這種機制,可以實現代碼的重用和更高效的內存使用。
原型鏈的性能和優化
原型鏈在JavaScript中的運作會帶來一定的性能開銷。在訪問屬性時,查找過程需要沿著原型鏈逐級查找,直到找到屬性或者到達原型鏈末端。因此,過深的原型鏈結構可能導致性能下降。為了優化性能,可以合理設計對象的原型鏈,避免過于龐大和復雜的結構。
Tips:簡單來說,就是不要太復雜,但一般前端設計也不應該很重,最多父子類的結構了,損耗可以忽略不計。
Java 和 JavaScript 面向對象
JavaScript 和 Java 都是面向對象編程語言,但它們在類和對象的實現和使用上有一些顯著的差異。以下是對 JavaScript 中類和對象的介紹,并通過 Java 的概念進行對比說明。
類:
JavaScript ES6 引入類,使得類定義更加簡潔和直觀,但其底層依然基于原型鏈。
Java 中類是核心概念,定義了對象的屬性和行為。
對象:
JavaScript 對象是鍵值對的集合,可以動態修改。
Java 對象是類的實例,屬性和方法在類中定義。
繼承:
JavaScript 通過 extends 關鍵字實現類的繼承,使用 super 調用父類的構造函數和方法。
Java 通過 extends 關鍵字實現類的繼承,使用 super 關鍵字調用父類的構造函數和方法。
總結:
盡管 JavaScript 和 Java 在語法和特性上有很多不同,但它們都支持面向對象編程的基本概念,如類、對象和繼承。JavaScript 的類和對象模型更加靈活和動態,而 Java 的類和對象模型更為靜態和嚴格。
用法拓展
創建對象的N種方式
對于 Java 后端程序猿而言,類與對象不是 Java 面向對象的概念嗎,前端怎么也有?
其實 JavaScript 已經不是后端眼中隨意簡單的腳本語言了,Java 有的概念它也有,Java 能做的很多事情,它也能做。
我們有多種方式可以創建對象,根據不同的創建方式,JavaScript對象的原型鏈也會有所不同。
構造函數方式
使用構造函數方式創建對象的原型是構造函數的原型對象(prototype)。
這個前面章節都在介紹這種方式,示例代碼再貼一下?
function Person(name, age) {this.name = name;this.age = age;
}Person.prototype.sayHello = function() {console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};const alice = new Person('Alice', 20);
alice.sayHello(); // 輸出:Hello, my name is Alice and I am 20 years old.console.log(alice.__proto__ === Person.prototype); // 輸出:true
字面量方式創建對象
使用字面量方式創建對象的原型是 Object.prototype,即所有字面量創建的對象都是 Object 的實例。
const lwObj = {name: '戰神',age: 32,sayHello() {console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);}
};console.log(lwObj.__proto__ === Object.prototype);// 輸出:true
Class 方式創建對象
在 ES6 之前,JavaScript 沒有類的概念,開發者主要通過構造函數和原型鏈來實現面向對象編程。ES6 引入了類的語法,使得定義和繼承類更加直觀和類似于其他面向對象語言(如 Java)。
Class 方式創建的對象和構造函數方式創建的對象一樣,其原型是Class構造函數的prototype屬性所指向的對象。
Tips:這也是推薦方式,不過一般復雜的前端邏輯才會去專門設計和定義類。
class PersonClass {constructor(name, age) {this.name = name;this.age = age;}sayHello() {console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);}
}const hanMei = new PersonClass('HanMei', 25);
hanMei.sayHello(); // 輸出:Hello, my name is HanMei and I am 25 years old. console.log(hanMei.__proto__ === PersonClass.prototype); // 輸出:true
prototype 實現繼承
直接看下面的代碼,就是基于原型鏈的特性。
// 定義父類函數
function Company(){ this.boss = 'lw'; this.name = 'cjwmy'; this.show = function(){ return this.boss + ',' + this.name; }
} // 定義子類函數
function Zysoft(){ this.soft = ['zyhip','zycomm'];
} // 綁定原型鏈
Zysoft.prototype = new Company();
let one = new Zysoft();
console.log(one.show()); //lw,cjwmy
擴展原有對象的方法 Demo
首先,我們要先了解一下類的概念,JavaScript 本身是一種面向對象的語言,它所涉及的元素根據其屬性的不同都依附于某一個特定的類。我們所常見的類包括:數組變量(Array)、邏輯變量(Boolean)、日期變量(Date)、結構變量(Function)、數值變量(Number)、對象變量(Object)、字符串變量(String) 等,而相關的類的方法,也是程序員經常用到的(在這里要區分一下類的注意和屬性發方法),例如數組的push方法、日期的get系列方法、字符串的split方法等等,但是在實際的編程過程中不知道有沒有感覺到現有方法的不足?prototype 方法應運而生!
【最簡單的例子,自己寫add等方法,了解 prototype】
是不是很簡單?這一節僅僅是告訴讀者又這么一種方法,這種方法是這樣運用的。
(1) Number.add(num):作用,數字相加
實現方法:Number.prototype.add = function(num){return(this+num);}
試驗:alert((3).add(15)) -> 顯示 18(2) Boolean.rev(): 作用,布爾變量取反
實現方法:Boolean.prototype.rev = function(){return(!this);}
試驗:alert((true).rev()) -> 顯示 false(3)測試demo
String.prototype.test = function(value) {alert(1)
};
【已有方法的實現和增強,初識 prototype】
(1) Array.push(new_element)
//作用:在數組末尾加入一個新的元素
Array.prototype.push = function(new_element){this[this.length]=new_element;return this.length;
}
//讓我們進一步來增強他,讓他可以一次增加多個元素!
Array.prototype.pushPro = function() {var currentLength = this.length;for (var i = 0; i < arguments.length; i++) {this[currentLength + i] = arguments[i];}return this.length;
}(2)String.length
//作用:這實際上是 String 類的一個屬性,但是由于 JavaScript 將全角、半角均視為是一個字符,
//在一些實際運用中可能會造成一定的問題,現在我們通過 prototype 來彌補這部不足。
String.prototype.cnLength = function(){var arr=this.match(/[^\x00-\xff]/ig);return this.length+(arr==null?0:arr.length);
}
alert("EaseWe空間Spaces".cnLength()) -> 顯示 16
【新功能的實現,深入 prototype】
在實際編程中所用到的肯定不只是已有方法的增強,更多的實行的功能的要求。
(1) String.left()
//問題:用過 vb 的應該都知道left函數,從字符串左邊取 n 個字符,但是不足是將全角、半角均視為是一個字符,造成在中英文混排的版面中不能截取等長的字符串
//作用:從字符串左邊截取 n 個字符,并支持全角半角字符的區分
String.prototype.left = function(num,mode){if(!/\d+/.test(num))return(this);var str = this.substr(0,num);if(!mode) return str;var n = str.Tlength() - str.length;num = num - parseInt(n/2);return this.substr(0,num);
}
alert("EaseWe空間Spaces".left(8)) -> 顯示 EaseWe空間
alert("EaseWe空間Spaces".left(8,true)) -> 顯示 EaseWe空
//本方法用到了上面所提到的String.Tlength()方法,自定義方法之間也能組合出一些不錯的新方法呀!(2) Date.DayDiff()
作用:計算出兩個日期型變量的間隔時間(年、月、日、周)
Date.prototype.DayDiff = function(cDate,mode){try{cDate.getYear();}catch(e){return(0);}var base =60*60*24*1000;var result = Math.abs(this - cDate);switch(mode){case "y":result/=base*365;break;case "m":result/=base*365/12;break;case "w":result/=base*7;break;default:result/=base;break;}return(Math.floor(result));}
alert((new Date()).DayDiff((new Date(2002,0,1)))) -> 顯示 329
alert((new Date()).DayDiff((new Date(2002,0,1)),"m")) -> 顯示 10
//當然,也可以進一步擴充,得出響應的小時、分鐘,甚至是秒。(3) Number.fact()
//作用:某一數字的階乘
Number.prototype.fact=function(){var num = Math.floor(this);if(num<0)return NaN;if(num==0 || num==1)return 1;elsereturn (num*(num-1).fact());
}
alert((4).fact()) -> 顯示 24
//這個方法主要是說明了遞歸的方法在 prototype 方法中也是可行的!(4) Array.indexof()
//下面是給js中的Array加的一個方法(本來js中Array是沒有返回下標的方法的)
Array.prototype.indexof = function(value) {var a = this;for (var i = 0; i < a.length; i++) {if (a[i] == value)return i;}
}
var arr = ['1', '2', '3', '4', '5', '6', '7'];
alert(arr.indexof('3'));
prototype 基礎測試 Demo
本章節,是博主早期針對 prototype 做的一些測試代碼,可以跳過。
===== 第一例,prototype初使用--為類型增加額外的方法和屬性
Object.prototype.Property = 1;
Object.prototype.Method = function (){ alert(1);
}
var obj = new Object();
alert(obj.Property);
obj.Method();
結論:可以在類型上使用proptotype來為類型添加行為,這些行為只能在類型的實例上體現,并且在增加之前實例化的對象也享有這些變化。
規則1:JS中允許的內置類型有Array, Boolean, Date, Enumerator, Error, Function, Number, Object, RegExp, String;
規則2:直接用Object.Method()也可以調用(但一般不這么使用),因為Object是所有對象(包括函數對象)的基礎,其他內置類型是不可以。===== 第二例,在實例對象上不能使用prototype,否則發生編譯錯誤
var obj = new Object();
obj.prototype.Property = 1; //Error
obj.prototype.Method = function() //Error
{alert(1);
}===== 第三例,直接給類型定義"靜態"屬性和方法,只能使用類型本身調用,對象無法調用,同Java的static關鍵字聲明的類變量
Object.Property = 1;
Object.Method = function()
{alert(1);
}
alert(Object.Property);
Object.Method(); //正常調用
var obj = new Object();
alert(obj.Property); //Error實例不能調用類型的靜態屬性或方法,否則發生對象未定義的錯誤。===== 第四例,演示通常在JavaScript中定義一個自定義類型的方法
function Aclass()
{
this.Property = 1;
this.Method = function()
{alert(1);
}
}
var obj = new Aclass();
alert(obj.Property);
obj.Method();===== 第五例,接第四例,可以在外部使用prototype為自定義的類型添加屬性和方法。
Aclass.prototype.Property2 = 2;
Aclass.prototype.Method2 = function
{alert(2);
}
var obj = new Aclass();
alert(obj.Property2);
obj.Method2();===== 第六例,接第四例,在外部不能通過prototype改變自定義類型的屬性或方法,不會報錯但改變無效;
但如果屬性本身就是通過prototype擴展的,則可以繼續用prototype修改。
Aclass.prototype.Property = 2;
Aclass.prototype.Method = function
{alert(2);
}
var obj = new Aclass();
alert(obj.Property); //仍然彈出1
obj.Method();===== 第七例,接第四例,可以在對象上改變屬性。(這個是肯定的)
var obj = new Aclass();
obj.Property = 2;
obj.Method = function()
{alert(2);
}
alert(obj.Property);
obj.Method();
也可以在對象上改變方法。(和普遍的面向對象的概念不同)===== 第八例,接第四例,可以在對象上增加屬性或方法
var obj = new Aclass();
obj.Property2 = 2;
obj.Method2 = function()
{alert(2);
}
alert(obj.Property2);
obj.Method2();===== 第九例,接第四例,這個例子說明了一個類型如何從另一個類型繼承。
function AClass2()
{this.Property2 = 2;this.Method2 = function(){alert(2);}
}
AClass2.prototype = new AClass();var obj = new AClass2();
alert(obj.Property);
obj.Method();
alert(obj.Property2);
obj.Method2();===== 第十例,接上例, 這個例子說明了子類如何重寫父類的屬性或方法。但不能改變自身的屬性和方法。
AClass2.prototype = new AClass();()
AClass2.prototype.Property = 3;
AClass2.prototype.Method = function()
{alert(4);
}
var obj = new AClass2();
alert(obj.Property);
obj.Method();
可見JavaScript能夠實現的面向對象的特征有:·公有屬性(public field)·公有方法(public Method)·私有屬性(private field)·私有方法(private field)·方法重載(method overload)·構造函數(constructor)·事件(event)·單一繼承(single inherit)·子類重寫父類的屬性或方法(override)·靜態屬性或方法(static member)
總結陳詞
本篇文章分享一下 JS 世界中prototype
的用法,希望能幫助到大家。
后端程序猿如果想往全棧工程獅發展,那JavaScript
必須和Java
玩得一樣明白,接下來系列文章,帶你一起探索,無所不能的JavaScript
。
💗 后續會逐步分享企業實際開發中的實戰經驗,有需要交流的可以聯系博主。