年9月份的時候,JDK21發布了,作為又一里程碑的式的JDK,JDK21更新了非常多的內容,如果你在開發過程中依然使用著JDK8或者JDK6,想要升級但卻不知道升級到哪個版本,今天我就來總結一下各個里程碑式JDK的新增功能,分別從JDK8、JDK11、JDK17和JDK21這幾個版本介紹。
目前使用最多最廣泛的JDK,它的升級內容有:
代碼示例:
升級內容有:
代碼示例:
升級內容有:
代碼示例:
升級內容有:
代碼示例:
JDK 8 引入了諸如 Lambda 表達式和 Stream API 等革命性特性,極大地豐富了 Java 的功能性和靈活性。
JDK 11 通過引入 var 關鍵字和新的 HTTP 客戶端 API,進一步簡化了編碼并加強了網絡編程能力。
JDK 17 引入的密封類和模式匹配增強了類型安全和代碼簡潔性。
JDK 21 則通過虛擬線程和對泛型的改進,提升了并發處理能力和類型系統。
大家可以通過上面的總結結合工作需求來選擇適合的JDK版本,畢竟適合的才是最好的!
更多文章,推薦公眾號【程序員老J】
近期似乎總是能在各種場合聽到"程序員中年危機"這種言論,說是程序員到了35歲就會很難跳槽,很容易被優化等等。其實在今年疫情影響下各行各業經濟都不景氣,公司手里沒錢肯定要節省成本,35歲的人一般對工資的要求比較高,所以對這種比較高級的程序員需求就會降低。當然除此之外肯定也有其他大家都在說的原因,例如精力不如畢業生,沒有年輕人能加班能干活,不能走入管理崗的中年人就會被淘汰等等。
其實個人感覺,雖然國內環境比較浮躁,但是渴望高級技術人才的公司還是大有人在,打開招聘軟件還是能找到不少高級前端的招聘信息。到了中年靠的應該是自己的經驗和技術,去做一些更加高級的事情。既然做了開發這一行,我想大家大部分都是想專心做一些技術工作,少一些應酬和虛與委蛇,但是不可否認的是,隨著vue、react等等這種非常強大的框架普及,大部分程序員做的都是一些"最沒技術含量的技術",簡單地復制粘貼,查找官方文檔照著寫,很少再去關心技術細節,時間久了自然會被淘汰。
啰嗦了這么多,其實也是說給自己聽的,要做高級的事情就要離開自己的舒適區,去鉆研一些高級的事情。
今天就一起討論一下JavaScript的"類"和繼承。
如果你想用JavaScript做一些高級的事情,例如打造組件庫,封裝插件,一定離不開"類"和繼承,這是"封裝"里無法繞過的一環。
為什么要給"類"加上引號呢?
因為JavaScript的世界里根本沒有類,所有你見到的類,包括ES6里的新語法class,都不是真正的類,JavaScript是徹頭徹尾的,純粹到極致的面向對象語言,一切皆對象。
我勸你最好暫時忘了之前接觸過的所有面向對象語言。因為他們可能早已深深地誤導了你。
JavaScript的"類"和繼承實際上是利用原型鏈來實現的。例如下面的代碼:
這是js一個最常用的利用構造函數聲明類的形式,里面有我們熟悉的new關鍵詞,表面上看確實是先有了Foo類然后用new實例化了一個對象。
但實際上Foo只是一個函數,它和其它函數沒有什么不同,函數也是對象,直接像普通函數一樣直接調用Foo也不會出錯,加上new之后只是多了幾個操作:
這里我們看到a1并沒有say()方法,但是a1.say()卻正常運行了,這是原型鏈的作用,a1沒有say屬性,就去原型上查找,最終在Foo.prototype里找到。
關于什么是原型鏈這里就不細說了,這屬于js基礎,不在高級討論范圍內。
上面代碼本質上我們是利用一個函數對象(Foo)又創建了另一個對象(a1),根本沒有傳統意義上的類!
嗯?等一下!利用對象生成對象?這不應該是Object.create()該干的事兒嗎?沒錯,上面的代碼完全可以利用Object.create()重構!
這種寫法更符合JavaScript一切皆對象的說法!而且更加清楚易懂,原型鏈從上到下清晰可見。
JavaScript的new真是個千古大忽悠!還有更忽悠的,ES6里的class,讓js的本質更加撲朔迷離:
多么美麗的代碼,多么讓人沉浸無法自拔,當當當!給我清醒點!千萬別讓它美麗的外表迷惑!照妖鏡拿來!給我看清楚了,它的本質跟第一段代碼完全一樣!語法糖而已,實際上這里并沒有真正的類,class 仍然是通過 [[Prototype]]機制實現的。
我們再來看看繼承。
因為JavaScript沒有真正的類,所以所謂的繼承也都是一些掩人耳目的做法,通過各種惡心的手段達到復用和重寫的目的,來看看有多惡心:
代碼里SubFoo繼承了Foo,并且SubFoo重寫了Foo的say方法,里面充滿了大量的xxx.prototype,為了讓SubFoo和Foo扯上關系,必須讓它們的原型鏈接起來:SubFoo.prototype = Object.create(Foo.prototype)。還有許多難以理解的借調(xxx.call()),特別是為了達到繼承Foo的say方法而寫的這一句:Foo.prototype.say.call(this),多么的丑陋。對于當初涉世未深的你能理解這幾句代碼里面的含義嗎?為了理解這些你花了多久?
為了我腦袋上所剩無幾的頭發,呸!
當然有了ES6后情況有所好轉:
還是那句話,語法糖而已,本質上還是要讓Foo和SubFoo兩個小東西互相扯來扯去,如果再深入一點——加上二級、三級繼承——情況會無法想象地復雜。
說到底這些其實都是強行使用類的思想來理解JavaScript的一切皆對象而出現的"變態"代碼。如果你放下屠刀,換一個思路,使用JavaScript語言最初的設計思路就會發現,一切其實非常簡單,我們用一切皆對象的思路再來實現一遍上面的邏輯:
怎么樣?沒有了亂七八糟的prototype和構造函數,不用擔心原型鏈的走向,一切清新自然,而且最重要的,一切都是對象。
這種實現方式的官方叫法(非人話叫法)就是"行為委托"。在行為委托模式中,Foo和 SubFoo只是對象,它們之間是兄弟關系,并不是父類和子類的關系。代碼中 Foo委托了 SubFoo,反向委托也完全沒問題,我們也不需要實例化類,因為它們根本就不是類,它們只是對象。此外,我們擺脫了一大堆的prototype和借調,我們使用了一種極其簡單的方式完成了封裝。
當然往深處里講,上述幾種方式每個方式都有自己的優缺點。不能很武斷地說這個好那個不好,在不同場景里選擇最合適的實現方式是作為一名高級技術人員時刻該考慮的事情。
面向過程編程就是分析出解決問題的步驟,然后使用函數把這些步驟一步步實現,重心放在完成的每個過程上。
面向對象則是以封裝的思想,將問題分析得到的數據封裝成一個個的對象,然后通過對對象的操作來完成相應的功能。
舉個栗子:廚師炒菜
以面向過程的思想來分析應該分為下面幾個步驟:
? 1.檢查食材是否齊全 2.如果不不夠,去菜市場買菜 3.洗菜 4.開火 5.按炒菜(按順序放入相應的食材,調料等) 6.出鍋裝盤
以面向對象的思想分析則是這樣的:
? 1.廚師,檢查食材,炒菜 2.采購員,去菜市場買菜 3.墩子,洗菜,切菜,備菜
? 通過調用上面對象中的行為方法即可完成炒菜的整個過程
從上面的例子可以看出,面向對象和面向過程最大的不同在于,面向對象關心的是由哪些對象,每個對象應該有哪些功能,而面向過程關心的是實現過程中的每個步驟。
那么這兩種思想到底孰優孰劣呢?從表面上看,貌似面向對象更好,為什么呢?因為它完全符合我們的正常思維方式,所以在接受度方面,面向對象的思想肯定是更好。但是面向過程也有他的優勢,就是靈活便捷,而面向對象相對來說會更耗資源,更慢一點。
所以,至于以后使用哪一種,這就需要看我們的具體需求,根據不同的需求做不同的選擇。
通過上面的分析,我們知道面向對象的重點在于功能分析和對象的封裝上,那么最終我們得到的對象的結構是怎樣的,我們繼續往下學習。
比如,我通過對人的分析得到,每個人都有姓名,年齡,性別等屬性,同時也有吃飯睡覺等行為,那么用JS可以做如下的封裝:
var p = {
name : "xiao song",
age : 10,
sex : 1,
eat : function () {
console.log("吃飯");
},
sleep : function () {
console.log("睡覺");
}
}
console.log(p.name);//訪問對象的屬性
p.eat();//訪問對象的方法
上面的p則表示一個對象,其中的name / age / sex稱之為對象的屬性,eat / sleep 稱之為對象的方法,我們通過訪問該對象的屬性或者方法達到相應的目的即可。
在學習了html之后我們發現,html文檔中的內容其實就是由一堆的標簽組成,由于在后面的課程中需要使用到html,所以我們先大致的回顧一下它的結構。
<div id="div1" class="clz1">
<h3>H5-JS面向對象</h3>
</div>
div h3:元素節點
id class:屬性節點
H5-JS面向對象:文本節點
一個html文檔主要由這三部分組成,DOM(文檔對象模型)是對操作這些元素的屬性或者方法進行了封裝,從而達到方便快捷的操作html的目的。
獲取元素對象:document.getElementById(“div1”)
訪問元素的屬性:div1.className
訪問元素的文本內容:div1.innerText
增刪改元素:div1.appendChild(newNode)
下面,我們就通過這些API來講解說明面向對象相關的內容。
需求1:創建三個div元素,并設置邊框,背景色,文本及字體顏色
for (var i = 0; i < 3; i++) {
var div = document.createElement("div");
div.innerText="div"+i;
div.style.backgroundColor="green";
div.style.border="1px solid #000";
div.style.color="white";
document.body.appendChild(div);
}
需求2:為頁面中存在的三個P元素設置邊框,背景色,文本及字體顏色
<p>我是P1</p>
<p>我是P2</p>
<p>我是P3</p>
<script>
var ps = document.getElementsByTagName("p");
for (var i = 0; i < ps.length; i++) {
ps[i].style.backgroundColor="red";
ps[i].style.border="1px solid #000";
ps[i].style.color="white";
}
</script>
需求3:獲取頁面上三個class=“test”的元素,設置邊框,背景色,文本及字體顏色
<h3 class="test">我是標題1</h3>
<h3 class="test">我是標題2</h3>
<h3 class="test">我是標題3</h3>
<script>
var tests = document.getElementsByClassName("test");
for (var i = 0; i < tests.length; i++) {
tests[i].style.backgroundColor="yellow";
tests[i].style.border="1px solid #000";
tests[i].style.color="red";
}
</script>
上面的代碼是以面向過程的思想完成的,可以看到,兩個需求中的每個步驟都是我們一步一步完成的,問題很明顯,代碼大量的冗余,這種代碼后期不好維護。
對于上面重復的代碼,我們可以使用函數對其進行封裝
<script>
function setStype(eles,bgcolor) {
for (var i = 0; i < eles.length; i++) {
eles[i].style.backgroundColor=bgcolor;
eles[i].style.border="1px solid #000";
eles[i].style.color="white";
}
}
function getElementsByTagName(tagName) {
return document.getElementsByTagName(tagName);
}
function getElementsByClassName(className) {
return document.getElementsByClassName(className);
}
var ps = getElementsByTagName("p")
setStype(ps,"green");
var tests=getElementsByClassName("test");
setStype(tests,"red");
</script>
封裝了三個函數:
接下來就是調用三個方法完成了上面的需求,解決了第一種方式中大量的重復代碼的問題。
但是,這種方式仍然存在問題。在前面JS基礎中說過,我們應該盡量避免大量使用全局變量,這會降低程序的執行效率,在上面的程序中,我們就出現了5個(包括函數)。所以需要繼續優化。
使用面向對象的思想來解決上面的問題,我們可以將上面的三個函數都裝到一個對象中
var $ = {
setStype:function (eles,bgcolor) {
for (var i = 0; i < eles.length; i++) {
eles[i].style.backgroundColor=bgcolor;
eles[i].style.border="1px solid #000";
eles[i].style.color="white";
}
},
getElementsByTagName: function (tagName) {
return document.getElementsByTagName(tagName);
},
getElementsByClassName:function (className) {
return document.getElementsByClassName(className);
}
}
var ps = $.getElementsByTagName("p")
$.setStype(ps,"green");
var tests=$.getElementsByClassName("test");
$.setStype(tests,"red");
后面如果我們還都需要封裝其他功能,可以直接在$這個對象中添加即可
如,根據元素的id屬性獲取元素,并為其設置樣式
getElementById:function (eleId) {
return [document.getElementById(eleId)];
}
需要注意的是,在設置樣式方法中,我們默認是將傳遞進來的元素當做數組進行處理的,所以,在這里,我們在getElementById方法中,手動將獲取到的元素添加到數組中返回。
通過觀察,在$對象中,存在三個獲取元素的方法,這里我們最好將其按照下面的方式來歸類
var $ = {
getElements:{
byTagName: function (tagName) {
return document.getElementsByTagName(tagName);
},
byClassName:function (className) {
return document.getElementsByClassName(className);
},
byId:function (eleId) {
return [document.getElementById(eleId)];
}
},
setStype:function (eles,bgcolor) {
for (var i = 0; i < eles.length; i++) {
eles[i].style.backgroundColor=bgcolor;
eles[i].style.border="1px solid #000";
eles[i].style.color="white";
}
}
}
將獲取元素的方法封裝到$對象的getElements屬性中,今后如果還有其他獲取元素的方法,都應該是添加到getElements屬性中,其他類型的方法也應該按照這種思想進行封裝。
面向對象的特性:
直接使用字面量方式創建對象比較方便,以鍵值對的格式來定義數據
var book1 = {
name:"JavaScript權威指南",
price:100,
author:"tim",
showInfo:function () {
console.log(this.name,this.price,this.author);
}
}
console.log(book1);
上面定義了一個書對象,并為其添加了屬性和方法,我們也可以直接訪問其中的屬性和方法。
這種方式的弊端是,如果需要創建多個類似的對象,就顯得不太方便了,會出現大量的重復代碼。
也就是說,這種方式不適合創建大量的相同或相似的對象。
使用new關鍵字+內置的構造函數創建對象
var book2 = new Object();
book2.name="JS";
book2.price=10;
book2.author="作者";
book2.showInfo=function () {
console.log(this.name,this.price,this.author);
}
book2.showInfo();
這種方式和字面量方式創建對象存在的問題差不多,在大量創建對象的時候都會存在大量重復的代碼。
那么,利用前面的封裝的思想,我們應該可以想到,當有重復代碼的時候,我們可以將這些重復代碼抽取到函數中來解決。
function createBook(name, price, author) {
var book = new Object();
book.name=name;
book.price=price;
book.author=author;
book.showInfo=function () {
console.log(this.name,this.price,this.author);
}
return book;
}
var book3 = createBook("bookName1",10,"author1");
var book4 = createBook("bookName2",10,"author2");
console.log(book3);
console.log(book4);
我們將創建book對象的代碼封裝到createBook函數中,當需要創建一個book對象的時候,直接調用該函數,將函數需要的參數傳遞過去即可。
那么,相同的思想,如果我們需要創建其他的對象,一樣可以使用封裝函數的方法來解決,這是沒問題的。
function createPerson(name, age) {
var p = new Object();
p.name = name;
p.age = age;
return p;
}
console.log(createPerson("Neld", 10))
利用上面的函數,我們可以創建一個Person對象出來,但是通過打印對比,我們無法通過創建出來的對象判斷該對象的類型,而在實際開發中,判斷對象的類型是我們經常需要執行的,所以我們繼續看下面的自定義構造函數創建對象。
構造函數和普通的函數的定義方式完全一樣,如下,我們定義一個創建Person的構造函數
function createPerson(name, age, sex) {
this.name=name;
this.age=age;
this.sex=sex;
}
var p = new createPerson("Neld", 10, 1);
var p2 = new createPerson("Song", 12, 0);
console.log(p);
console.log(p2);
自定義函數和工廠函數非常相似,但是還是存在很大的區別
到這里,大家肯定會有疑問,自定義構造函數到底是如何創建并封裝對象呢?
通過上面的分析,相信大家已經能夠理解自定義構造函數的基本使用以及基本的原理了。
默認情況下,構造函數內部會返回新創建好的對象(this)
主動返回:
在JS世界里,函數屬于一等公民,擁有最高特權,在使用過程中可以作為變量賦值,可以作為參數傳遞,也可以作為函數的返回值,下面我們具體來看看他的使用。
函數作為參數使用
function f1(name,age,fn) {
console.log("name:",name,"age:",age);
fn();
}
function fn(){
console.log("Hello H5");
}
f1("Neld", 10, fn);
輸出結果:
? name: Neld age: 10? Hello H5
在上面的代碼中,我們將函數fn作為參數傳遞給了函數f1,并且在函數f1中調用,得到的相應的打印輸出。
函數作為返回值使用
function f1(name,age,fn) {
console.log("name:",name,"age:",age);
return fn;
}
function fn(){
console.log("Hello H5");
}
var retFun = f1("Neld", 10, fn);
retFun();
在函數f1中將傳遞進來的fn作為返回值返回,接收到調用f1之后的返回值得到的是返回的函數,然后再調用retFun得到打印結果。
此時的f1為高階函數,即參數中有一個或多個函數,并且把函數作為返回值。
此時的fn為回調函數,fn作為參數傳遞給函數f1,在f1內部調用。
函數作為構造函數的參數使用
function createPerson(name, age, sex, say) {
this.name=name;
this.age=age;
this.sex=sex;
this.say=say;
}
var p = new createPerson("Neld", 10, 1, function () {
console.log("say hello");
});
var p2 = new createPerson("Song", 12, 0,function () {
console.log("say bye");
});
p.say();
p2.say();
在構造函數中也可以對方法進行封裝,如果方法的實現是由調用者決定的,那么可以在構造函數中接收一個函數對象,然后在構造函數中進行封裝。
如上面的函數say,在創建p和p2對象的時候傳遞并賦值給形參say,然后在構造函數中賦值給當前對象。
前面說到工廠函數創建對象是比較方便的,但是存在一個問題就是無法得知創建出來的對象的類型,所以我們選擇使用自定義的構造函數來創建,構造函數創建對象我們已經會使用了,那么如何通過他得知創建對象的類型呢?這里我們提供兩種方式。
使用constructor屬性可以獲取到創建對象使用的構造器函數對象,所以我們可以通過判斷構造器的類型來得知創建的對象的類型
2.instanceof關鍵字
instanceof關鍵字可以直接用來判斷對象的類型,如果是指定的類型,返回true,反之返回false。
在學習了構造函數之后,有的同學對于它和普通函數的區別還是不太清楚,這里我們就再對構造函數做一個說明。
在JS編程的過程中發現,我們大量使用到this關鍵字,用好了this,能讓我們的代碼更加優雅。
this總是執行一個對象(引用類型),但是具體執行誰,需要根據我們在哪里使用this有關。這里主要分為下面幾種情況:
在開發中,我們也可以使用call或者apply函數修改this的執行,這一點我們在后面繼續說明。
自定義構造函數可以解決工廠函數帶來的對象類型不確定的問題,在開發中用得非常多,那么目前我們的自定義構造函數又是否存在問題呢?先來看看下面的對象內存結構分析。
function Person(name, age, say) {
this.name = name;
this.age = age;
this.say = function(){
console.log("say hello");
}
}
var p = new Person("zs", 10, say);
console.log(p);
上面創建的p對象的內存結構圖:
可以看出,我們每創建一個Person對象,都會在內存中分配如0x22和0x33這樣的內存來存儲數據,但是通過觀察發現,0x33中存儲的是一個函數,而這個函數在每個對象中都是相同
所以從內存資源分配考慮,我們無需為每個對象創建并分配一份新的函數對象(完全相同),這種函數大家最好共享同一份。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。