整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JVM真香系列:.java文件到.class文件

          JVM真香系列:.java文件到.class文件

          識JVM


          什么是JVM

          JVM 全稱 Java Virtual Machine,也就是我們耳熟能詳的 Java 虛擬機。它能識別 .class后綴的文件,并且能夠解析它的指令,最終調用操作系統上的函數,完成我們想要的操作。

          可能有部分小伙伴學習過C++,C++開發出來的程序,編譯成二進制文件后,就可以直接執行了,操作系統是能夠識別的。

          但是咱們開的的Java程序就不一樣了,使用javac命令編譯出來的的.class文件之后,操作系統是不能識別的,需要對應JVM去做一個轉換后,操作系統才能識別。

          我們為什么不能像 C++ 一樣,直接在操作系統上運行編譯后的二進制文件呢?而非要搞一個處于程序與操作系統中間層的虛擬機呢?

          這就是 JVM的過人之處了。大家都知道,Java 是一門抽象程度特別高的語言,提供了自動內存管理等一系列的特性。這些特性直接在操作系統上實現是不太可能的,所以就需要JVM 進行做一系列的轉換。

          大家一開始學Java的時候,就知道有個Write Once, Run Everywhere。就是我們編寫了一個java文件經過編譯成.class文件后,可以在各種系統中進行運行。

          其實這里是有個前提的,我們需要在對應操作系統中安裝對應的JVM,然后我們的.class文件就能運行了。

          比如:Windows操作系統有對應的JDK安裝版本、Linux也有對應的JDK安裝版本等。

          認識JDK

          Java Development Kit (JDK) 是Sun公司(已被Oracle收購)針對Java開發員的軟件開發工具包。自從Java推出以來,JDK已經成為使用最廣泛的Java SDK(Software development kit)。

          經非官方調查,目前JDK8是使用者最多的版本。

          JDK14將在4月和7月收到安全更新,然后由9月到期的非LTS版本的JDK 15取代。JDK14包括16項新功能,例如JDK Flight Recorder事件流,模式匹配和開關表達式等特征。

          從JDK9之后,Oracle采用了新的發布周期:每6個月發布一個版本,每3年發布一個LTS版本。JDK14是繼JDK9之后發布的第四個版本, 該版本為非LTS版本,最新的LTS版本為JDK11。

          下面是JDK版本情況

          這個混個眼熟就行,隨時關注JDK版本更新和新特性。

          官網地址:https://www.oracle.com/java/

          關于JDK安裝這里就省略。

          JDK、JRE、JVM的關系

          上面已經說過JDK和JVM的相關概念,

          JRE全程Java Runtime Environment,是運行基于Java語言編寫的程序所不可缺少的運行環境。也是通過它,Java的開發者才得以將自己開發的程序發布到用戶手中,讓用戶使用。

          三者到底是什么關系呢?

          關于三者關系請看官網

          https://docs.oracle.com/javase/8/docs/index.html

          JDK中包含JRE,也包括JDK,而JRE也包括JDK。范圍關系:JDK>JRE>JVM


          ".java"文件到".class"文件


          `javac`命令

          編寫一個HelloWorld.java文件

          內容就是一個Java入門

          public class HelloWorld {
              public static void main(String[] args) {
                  System.out.println("Hello world");
              }
          }
          

          打開CMD,進入當前目錄,使用命令

          javac HelloWorld.java

          就編譯出HelloWorld.class


          編譯過程

          這個javac命令過程到底干了些什么呢?

          javac背后大致做了這些操作

          這個流程

          1、詞法分析

          讀取源代碼,一個字節一個字節的讀取,找出其中我們定義好的關鍵字(如Java中的if、else、for、while等關鍵詞,識別哪些if是合法的關鍵詞,哪些不是),這就是詞法分析器進行詞法分析的過程,其結果是從源代碼中找出規范化的Token流。

          2、語法分析

          通過語法分析器對詞法分析后Token流進行語法分析,這一步檢查這些關鍵字組合再一次是否符合Java語言規范(如在if后面是不是緊跟著一個布爾判斷表達式),詞法分析的結果是形成一個符合Java語言規范的抽象語法樹。

          3、語義分析

          通過語義分析器進行語義分析。語音分析主要是將一些難懂的、復雜的語法轉化成更加簡單的語法,結果形成最簡單的語法(如將foreach轉換成for循環 ,好有注解等),最后形成一個注解過后的抽象語法樹,這個語法樹更為接近目標語言的語法規則。

          4、生成字節碼

          通過字節碼生產器生成字節碼,根據經過注解的語法抽象樹生成字節碼,也就是將一個數據結構轉化為另一個數據結構。最后生成我們想要的.class文件。

          使用十六進制查看class文件內容

          我只用的是Notepad++,選中文本→插件→Converter→ASCII->HEX

          class文件的開頭就是

          CAFEBABE

          想要學習這里的十六進制的字節碼的含義可以參考

          https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html


          javap查看class文件內容


          javap是 Java class文件分解器,可以反編譯(即對javac編譯的文件進行反編譯),也可以查看java編譯器生成的字節碼。

          新建一個User.java源文件,經過javac編譯后,生成User.classs。

          package com.tian.demo.test;
          
          public class User {
              private int age = 22;
              private String name = "tian";
          
              public int addAge() {
                  return age = age + 1;
              }
          
              public static void main(String[] args) {
          
              }
          }
          


          使用javap命令

          javap -v User.class >log.txt
          

          打開log.txt

          Classfile /D:/workspace/new/demo/src/main/java/com/tian/demo/test/User.class
            Last modified 2020-11-5; size 441 bytes
            MD5 checksum 2fa72d3f53bd9f138e0bfae82aba67e3
            Compiled from "User.java"
          public class com.tian.demo.test.User
            minor version: 0
            major version: 52
            flags: ACC_PUBLIC, ACC_SUPER
          Constant pool:
             #1 = Methodref          #6.#21         // java/lang/Object."<init>":()V
             #2 = Fieldref           #5.#22         // com/tian/demo/test/User.age:I
             #3 = String             #23            // tian
             #4 = Fieldref           #5.#24         // com/tian/demo/test/User.name:Ljava/lang/String;
             #5 = Class              #25            // com/tian/demo/test/User
             #6 = Class              #26            // java/lang/Object
             #7 = Utf8               age
             #8 = Utf8               I
             #9 = Utf8               name
            #10 = Utf8               Ljava/lang/String;
            #11 = Utf8               <init>
            #12 = Utf8               ()V
            #13 = Utf8               Code
            #14 = Utf8               LineNumberTable
            #15 = Utf8               addAge
            #16 = Utf8               ()I
            #17 = Utf8               main
            #18 = Utf8               ([Ljava/lang/String;)V
            #19 = Utf8               SourceFile
            #20 = Utf8               User.java
            #21 = NameAndType        #11:#12        // "<init>":()V
            #22 = NameAndType        #7:#8          // age:I
            #23 = Utf8               tian
            #24 = NameAndType        #9:#10         // name:Ljava/lang/String;
            #25 = Utf8               com/tian/demo/test/User
            #26 = Utf8               java/lang/Object
          {
            public com.tian.demo.test.User();
              descriptor: ()V
              flags: ACC_PUBLIC
              Code:
                stack=2, locals=1, args_size=1
                   0: aload_0
                   1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                   4: aload_0
                   5: bipush        22
                   7: putfield      #2                  // Field age:I
                  10: aload_0
                  11: ldc           #3                  // String tian
                  13: putfield      #4                  // Field name:Ljava/lang/String;
                  16: return
                LineNumberTable:
                  line 3: 0
                  line 4: 4
                  line 5: 10
          
            public int addAge();
              descriptor: ()I
              flags: ACC_PUBLIC
              Code:
                stack=3, locals=1, args_size=1
                   0: aload_0
                   1: aload_0
                   2: getfield      #2                  // Field age:I
                   5: iconst_1
                   6: iadd
                   7: dup_x1
                   8: putfield      #2                  // Field age:I
                  11: ireturn
                LineNumberTable:
                  line 8: 0
          
            public static void main(java.lang.String[]);
              descriptor: ([Ljava/lang/String;)V
              flags: ACC_PUBLIC, ACC_STATIC
              Code:
                stack=0, locals=1, args_size=1
                   0: return
                LineNumberTable:
                  line 13: 0
          }
          SourceFile: "User.java"
          

          魔數與class文件版本
          常量池
          訪問標志
          類索引、父類索引、接口索引
          字段表集合
          方法表集合
          屬性表集合

          然后JVM就可以讀取這個User.class文件進行解析等一系列的操作。

          以上就是我們的Java文件到class文件。

          后續還會更新一系列JVM相關文章,敬請期待~

          CMAScript 6 提供了更接近傳統語言的寫法,新引入的class關鍵字具有正式定義類的能力。類(class)是ECMAScript中新的基礎性語法糖結構,雖然ECMAScript 6類表面上看起來可以支持正式的面向對象編程,但實際上它背后使用的仍然是原型和構造函數的概念,讓對象原型的寫法更加清晰、更像面向對象編程的語法。

          一、類的定義

          定義類也有兩種主要方式:類聲明和類表達式。這兩種方式都使用class關鍵字加大括號:

          kotlin

          復制代碼

          // 類聲明 class Person {} // 類表達式 const TestPerson=class {}

          注意:函數聲明類聲明之間的一個重要區別在于,函數聲明會提升,類聲明不會。需要先聲明類,然后再訪問它,否則就會出現ReferenceError,如:

          arduino

          復制代碼

          const test=new Person(); // ReferenceError: Person is not defined class Person {}

          另一個跟函數聲明不同的地方是,函數受函數作用域限制,而類受塊作用域限制:

          javascript

          復制代碼

          { function FunctionDeclaration () {} class ClassDeclaration {} // 使用var 聲明 var VarClass=class {} // 使用let/const 聲明 let LetClass=class {} } console.log(FunctionDeclaration) // FunctionDeclaration () {} console.log(ClassDeclaration) // ReferenceError: ClassDeclaration is not defined console.log(VarClass) // class {} console.log(LetClass) // ReferenceError: letClass is not defined

          class 類完全可以看成構造函數的另一種寫法,這種寫法可以讓對象的原型屬性和函數更加清晰。

          javascript

          復制代碼

          class Person {} console.log(typeof Person) // function console.log(Person===Person.prototype.constructor) // true

          上面代碼表明,類的數據類型就是函數,類本身就指向構造函數。

          二、類構造函數

          constructor 方法是一個特殊的方法,這種方法用于創建和初始化一個由class創建的對象。通過 new 關鍵字生成對象實例時,自動會調用該方法。一個類只能擁有一個名為constructor構造函數,不能出現多個,如果定義了多個constructor構造函數,則將拋出 一個SyntaxError錯誤。如果沒有定義constructor構造函數,class 會默認添加一個空的constructor構造函數。

          kotlin

          復制代碼

          class Person {} // 等于 class Person { constructor () {} }

          使用new操作符實例化Person的操作等于使用new調用其構造函數。唯一可感知的不同之處就是,JavaScript解釋器知道使用new和類意味著應該使用constructor函數進行實例化。

          類必須使用new調用,否則會報錯。這是它跟普通構造函數的一個主要區別,后者不用new也可以執行。

          scss

          復制代碼

          class Person {} Person() // TypeError: Class constructor Test1 cannot be invoked without 'new'

          使用new調用類的構造函數會執行如下操作。

          1. 在內存中創建一個新對象;
          2. 這個新對象內部的[[Prototype]]指針被賦值為構造函數的prototype屬性;
          3. 構造函數內部的this被賦值為這個新對象(即this指向新對象);
          4. 執行構造函數內部的代碼(給新對象添加屬性);
          5. 如果構造函數返回非空對象,則返回該對象;否則,返回剛創建的新對象;

          一起來看看下面例子:

          javascript

          復制代碼

          class Person {} class Test1 { constructor () { console.log('Test1 初始化') } } class Test2 { constructor () { this.test='通過初始化構造函數設置值' } } // 構造函數返回指定對象 const dataObj={ n: '自定義實例對象' } class Test3 { constructor () { this.test='通過初始化構造函數設置值' return dataObj } } const a=new Person(); const b=new Test1(); // Test1 初始化 const c=new Test2(); console.log(c.test) // 通過初始化構造函數設置值 const d=new Test3(); d instanceof Test3; // false console.log(d) // { n: '自定義實例對象' }

          類實例化時傳入的參數會用作構造函數的參數。如果不需要參數,則類名后面的括號也是可選的:

          javascript

          復制代碼

          class Person { constructor (...args) { console.log(args.length) } } class Test1 { constructor (test) { console.log(arguments.length) this.test=test || '默認值' } } // 不傳值 可以省略() const a=new Person // 0 const b=new Person('1', '2') // 2 const c=new Test1() // 0 console.log(c.test) // 默認值 const d=new Test1('傳入值') // 1 console.log(d.test) // 傳入值 const d=new Test1('1', '2', '3') // 3 console.log(d.test) // 1

          與立即調用函數表達式相似,類也可以立即實例化:

          arduino

          復制代碼

          const a=new class Person { constructor (text) { this.text=text console.log(text) } }('立即實例化類'); // 立即實例化類 console.log(a); // Person

          三、類的實例 、原型及類成員

          類的語法可以非常方便地定義應該存在于實例上的成員、應該存在于原型上的成員,以及應該存在于類本身的成員。

          實例的屬性除非顯式定義在其本身(即定義在this對象上),否則都是定義在原型上。

          每個實例都對應一個唯一的成員對象,這意味著所有成員都不會在原型上共享:

          javascript

          復制代碼

          class Person { constructor (x, y) { this.text=new Number(1); this.x=x this.y=y this.getText=()=> {console.log(this.text)} } toString () { console.log(`${this.x}, ${this.y}`) } } const test1=new Person('x', 'y'), test2=new Person('x2', 'y2'); console.log(test1.getText()) // Number {1} console.log(test2.getText()) // Number {1} console.log(test1.x, test1.y) // x y console.log(test2.x, test2.y) // x2 y2 // console.log(test1.text===test2.text) // false // console.log(test1.getText===test2.getText) // false test1.text='測試' console.log(test1.getText()) // 測試 console.log(test2.getText()) // Number {1} test1.toString() // x, y test2.toString() // x2, y2 test1.hasOwnProperty('x'); // true test1.hasOwnProperty('y'); // true test1.hasOwnProperty('getText'); // true test1.hasOwnProperty('toString'); // false test1.__proto__.hasOwnProperty('toString'); // true // 類的實例共享同一個原型對象 console.log(test1.__proto__===test2.__proto__) // true // 也可以使用ES6提供的 Object.getPrototypeOf 來獲取prototype const test1Prototype=Object.getPrototypeOf(test1) test1Prototype.myName='共享字段' // test2 中也是能獲取到 console.log(test2.myName) // 共享字段

          x、y、text和getText都是實例對象test1自身的屬性,所以hasOwnProperty()方法返回true,而toString()是原型對象的屬性(因為定義在Person類),所以hasOwnProperty()方法返回false,這些都與 ES5 的行為保持一致。

          類的所有實例共享同一個原型對象。這也意味著,可以通過實例的__proto__屬性或Object.getPrototypeOf方法獲取原型為“類”添加方法,這將會出現共享情況,必須相當謹慎,不推薦使用,因為這會改變“類”的原始定義,影響到所有實例。

          類方法等同于對象屬性,因此可以使用字符串、符號或計算的值作為鍵:

          scss

          復制代碼

          const symbolKey=Symbol('test') class Person { stringKey () { console.log('stringKey') } [symbolKey] () { console.log('symbolKey') } ['calculation' + '1'] () { console.log('calculation') } } const a=new Person(); a.stringKey() // stringKey a[symbolKey]() // symbolKey a.calculation1() // calculation

          getter 與 setter

          在 class 內部可以使用 get 與 set 關鍵字,對某個屬性設置存值函數和取值函數,攔截該屬性的存取行為。

          kotlin

          復制代碼

          class Person { constructor (test) { this.test=test || '默認值' } get prop () { return this.test } set prop (value) { console.log(`setter prop value: ${value}`) this.test=value } } const p=new Person('1') p.prop // 1 p.prop='2' // setter prop value: 2 p.prop // 2

          set函數和get函數是設置在屬性的 Descriptor 對象上的,可以通過 Object.getOwnPrototyDescriptor 來獲取指定屬性的指定描述對象。

          javascript

          復制代碼

          const descriptor=Object.getOwnPropertyDescriptor(Person.prototype, 'prop') 'get' in descriptor // true 'set' in descriptor // true

          Generator 方法

          如果某個方法之前加上星號(*),就表示該方法是一個 Generator 函數:

          scss

          復制代碼

          class Person { constructor(...args) { this.args=args; } * generatorFun () { for (let arg of this.args) { yield arg; } } } const a=new Person(1,2,3,4); const generatorNext=a.generatorFun().next generatorNext() // {value: 1, done: false} generatorNext() // {value: 2, done: false} generatorNext() // {value: 3, done: false} generatorNext() // {value: 4, done: false} generatorNext() // {value: undefined, done: true}

          this 指向

          類的方法內部如果含有this,它默認指向類的實例。但是某些情況是指向當前執行環境;

          arduino

          復制代碼

          class Person { constructor () { this.text='1' } getText () { console.log(this.text) } } const a=new Person() a.getText() // 1 const {getText}=a // this 指向為undefined class 默認嚴格模式 getText() // TypeError: Cannot read properties of undefined (reading 'text')

          上面找不到 this 問題,this會指向該方法運行時所在的環境,因為 class 內部是嚴格模式,所以 this 實際指向的是undefined。有兩個方法解決當前問題:

          第一、構造方法中綁定this:

          kotlin

          復制代碼

          class Person { constructor() { this.text='1' this.getText=this.getText.bind(this) } getText () { console.log(this.text) } }

          第二、使用箭頭函數:

          javascript

          復制代碼

          class Person { constructor() { this.text='1' } getText=()=> { console.log(this.text) } }

          箭頭函數內部的 this總是指向定義時所在的對象。

          第三、使用proxy 在獲取方法的時候自動綁定this:

          kotlin

          復制代碼

          function classProxy (target) { const map=new Map() // 讀取攔截配置, 只需要配置 get const hanlder={ get(target, key) { const val=Reflect.get(target, key) // 要獲取的是函數執行, 如果不是函數就直接返回 val if (typeof val !=='function') return val if (!map.has(val)) { // 使用 bind改變運行函數的 this為攔截的實例對象 map.set(val, val.bind(target)) } return map.get(val) } } const proxy=new Proxy(target, hanlder) return proxy } class Person { constructor (text) { this.text=text } getText () { console.log(this.text) return this.text } } const person=classProxy(new Person('test')) const { getText }=person getText() // test

          四、靜態方法、靜態屬性及靜態代碼塊

          靜態方法、靜態屬性及靜態代碼塊(proposal-class-static-block)都是使用 static關鍵字定義的屬性、方法或塊只能 class 自己用,不能通過實例繼承。

          靜態方法中的this 指向的是 當前類,而不是指向實例對象。靜態屬性是當前類自身的屬性。

          javascript

          復制代碼

          class Person { static staticProp='Person靜態屬性' constructor () { // 通過 類名 獲取 console.log(`output: ${Person.staticProp}`) // 也可以通過 構造函數的屬性 this.constructor.staticFun1() } static staticFun1 () { this.staticFun2() console.log(`output: 靜態方法staticFun1,獲取Person靜態屬性==> ${Person.staticProp}`) } static staticFun2 () { console.log(`output: 靜態方法staticFun2,獲取靜態屬性==> ${this.staticProp}`) } } Person.staticProp // 靜態屬性 Person.staticFun1() // output: 靜態方法staticFun2,獲取靜態屬性 Person靜態屬性 // output: 靜態方法staticFun1,獲取Person靜態屬性==> Person靜態屬性 const a=new Person() // output: Person靜態屬性 a.staticProp // undefined a.staticFun1 // undefined a.staticFun2 // undefined // 通過其原型構造函數還是能獲取到 這些靜態屬性及方法 不推薦使用 // a.__proto__.constructor.staticProp // a.__proto__.constructor.staticFun1()

          靜態代碼塊:

          是在 Class 內創建了一個塊狀作用域,這個作用域內擁有訪問 Class 內部私有變量的特權,在這個代碼塊內部,可以通過 this 訪問 Class 所有成員變量,包括 # 私有變量,且這個塊狀作用域僅在引擎調用時初始化執行一次 ,決解以前初始化靜態類屬性需要設置一個靜態變量初始化邏輯。

          注意: static 變量或代碼塊都按順序執行,父類優先執行,一個類中允許多個靜態代碼塊存在。

          kotlin

          復制代碼

          class Person { static staticProp='靜態屬性' static staticPropArr=[] static staticPropObj={} static getStatic (name) { console.log(`獲取:${name}`, name && this[name]) return name && this[name] } static resetData (name, data) { name && (this[name]=data) console.log(`重置:${name}`, name && this[name]) } static { console.log('靜態代碼塊執行'); this.getStatic('staticProp'); this.getStatic('staticPropArr'); this.getStatic('staticPropObj'); this.resetData('staticProp', '重置靜態屬性'); this.resetData('staticPropArr', ['重置靜態數組']); this.resetData('staticPropObj', { text: '重置靜態對象' }); this.staticPropObj.staticBlock1='代碼塊中直接設置' console.log(this.staticPropObj) } } /** * 靜態代碼塊執行 獲取:staticProp 靜態屬性 獲取:staticPropArr [] 獲?。簊taticPropObj {} 重置:staticProp 重置靜態屬性 重置:staticPropArr ['重置靜態數組'] 重置:staticPropObj {text: '重置靜態對象'} {text: '重置靜態對象', staticBlock1: '代碼塊中直接設置'} */

          上面代碼中可以看出,static 關鍵字后面不跟變量,而是直接跟一個代碼塊,就是 class static block 語法的特征,在這個代碼塊內部,可以通過 this 訪問 Class 所有成員變量,包括 # 私有變量。

          在這里提前使用一下私有變量,理論上 class 私有變量外部是訪問不了的,但是有了靜態代碼塊( *class-static-block *)之后,我們可以將私有屬性暴露給外部變量:

          javascript

          復制代碼

          let privateValue export class Person { #value constructor(x) { this.#value=x } static { privateValue=(obj)=> obj.#x; } } export function getPrivateValue (obj) { return privateValue(obj) } // 在另一個文件中 import { Person, getPrivateValue } from 'xxx' const a=new Person('私有變量') getPrivateValue(a) // 私有變量

          其實class-static-block本質上并沒有增加新功能,我們完全可以用普通靜態變量代替,只是寫起來很不自然,所以這個特性可以理解為對缺陷的補充,或者是語法完善,個人認為現在越來越像java。

          五、私有屬性和私有方法

          私有屬性和私有方法,是只能在類的內部訪問的方法和屬性,外部不能訪問,不可以直接通過 Class 實例來引用,其定義方式只需要在方法或屬性前面添加#。

          私有屬性:

          arduino

          復制代碼

          class Person { #privateVar1; #privateVar2='默認值'; constructor (text) { this.#privateVar1=text || '--' console.log(this.#privateVar1) } getPrivateData1 (key) { // 這里是獲取不了的 console.log('傳入key來獲取私有變量:', this[key]) console.log('獲取私有變量', this.#privateVar2, this.#privateVar1) } static staticGetPrivateData (person, key) { console.log('靜態方法獲取私有變量:', person.#privateVar2, person.#privateVar1) // 下面是獲取不到 console.log('靜態方法傳入key來獲取私有變量:', person[key]) } } const a=new Person() // 不傳 默認 -- // output: -- a.getPrivateData1('#privateVar1') // output: 傳入key來獲取私有變量:undefined // output: 獲取私有變量: 默認值 -- // 使用靜態方法 Person.staticGetPrivateData(a, '#privateVar1') // output: 靜態方法獲取私有變量: 默認值 -- // output: 靜態方法傳入key來獲取私有變量:undefined

          從上面代碼中我們可以看到,私有變量是只能內部讀取或寫入,不能通過動態key讀?。ㄍ獠空{用就會報錯)

          注意:在class 中 公共屬性 test 與 #test 是兩個完全不同的值;

          私有方法:

          arduino

          復制代碼

          class Person { #private; constructor () { this.#private='私有變量' this.#methods() // 調用私有方法 } #methods () { console.log('私有方法#methods:', this.#private) } static #staticMethods (person) { if (person) { console.log('靜態私有方法#staticMethods person獲取值', person.#private) person.#methods() } } init1 () { this.#methods() console.log('使用this') Person.#staticMethods(this) } init2 (person) { if (person) { console.log('使用傳入實例') Person.#staticMethods(person) } } } const a=new Person() // output: 私有方法#methods: 私有變量 // a.#methods() SyntaxError // a['#methods']() TypeError: a.#methods is not a function a.init1() // output: 私有方法#methods: 私有變量 // output: 使用this // output: 靜態私有方法#staticMethods person獲取值 私有變量 // output: 私有方法#methods: 私有變量 a.init2(a) // output: 使用傳入實例 // output: 靜態私有方法#staticMethods person獲取值 私有變量 // output: 私有方法#methods: 私有變量

          從上面代碼中我們可以看到,私有方法只能內部調用,在外部調用就會報錯。

          六、繼承 extends

          使用 extends 關鍵字,讓子類繼承父類的屬性和方法。

          javascript

          復制代碼

          class Person { num=1 text='person' getNum=()=> console.log(this.num, this) addNum () { console.log(++this.num, this) } } // 繼承 class Child extends Person { constructor () { super() this.getText() } getText=()=> console.log(this.text, this) } const a=new Child() // output: person Child {} console.log(a instanceof Child) // output: true console.log(a instanceof Person) // output: true a.getText() // output: person Child {} a.getNum() // output: 1 Child {} a.addNum() // output: 2 Child {} a.getNum() // output: 2 Child {} a.text // person a.num // 2

          從上面代碼中,我們可以看出Child 類 繼承了 Person 的屬性及方法,在Child 中也是可以調用Person的方法及屬性,注意 this 的值會反映調用相應方法的實例或者類。子類中(Child)如果設置了 constructor 方法 就必須調用 super() ,否則就會出現新建實例時報錯,如果沒有 constructor 構造函數,在實例化繼承類時會調用 super() ,而且會傳入所有傳給繼承類的參數(后面會詳細講解)。

          arduino

          復制代碼

          class Person { static staticText='staticText'; #private='private' static staticMethods1 (person) { console.log('staticMethods1', this) person.#privateMethods() } #privateMethods () { console.log('#privateMethods', this) } } // 使用表達式格式 也是可以使用 extends 繼承 const Child=class extends Person { methods () { console.log('methods', Child.staticText) } } const a=new Child() a.methods() // output: methods staticText Child.staticMethods1(a) // output: staticMethods1 class Child {} // output: #privateMethods Child {} Person.staticMethods1(a) // output: staticMethods1 class Person {} // output: #privateMethods Child {}

          使用表達式格式 也是可以使用 extends 繼承,類 的靜態方法與屬性是可以繼承的,其私有屬性及方法是不能繼承的,可以從繼承的共有方法與靜態方法 中獲取其私有屬性或調用其私有方法。

          super 關鍵字可以作函數使用,也可以作對象使用,但是其只能在繼承類中使用,且只能在繼承類的constructor 構造函數、實例方法和靜態方法中使用。作為函數時是在 繼承類的constructor 構造函數中使用,根據要求如果繼承類中定義了constructor構造函數就必須要調用super方法(調用父類的constructor),否則就會報錯。

          scala

          復制代碼

          class Person {} class Child extends Person { constructor () { // 如果不調用 super() 就會報錯 // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor super() // 調用父級的constructor console.log(this) // Child {} } }

          注意: constructor() 中必須super() 頂部首段執行代碼,否則也是一樣報錯;

          在使用 super() 時應該注意下面幾個問題:

          1. super只能在繼承類構造函數和靜態方法中使用。
          2. javascript
          3. 復制代碼
          4. class Person { constructor () { // 在非繼承類 的constructor 中使用super 會報錯 super() // SyntaxError: 'super' keyword unexpected here } methods () { console.log(super.text) // undefined } static staticMethods () { console.log(super.text) // undefined } }
          5. 不能單獨引用super關鍵字,要么用它調用構造函數,要么用它引用靜態方法。
          6. scala
          7. 復制代碼
          8. class Person {} class Child extends Person { constructor () { super // SyntaxError: 'super' keyword unexpected here } methods () { console.log(super) // SyntaxError: 'super' keyword unexpected here } static staticMethods () { console.log(super) // SyntaxError: 'super' keyword unexpected here } }
          9. 調用super()會調用父類構造函數,并將返回的實例賦值給this
          10. scala
          11. 復制代碼
          12. class Person {} class Child extends Person { constructor () { super() console.log(this instanceof Person) // output: true } } new Child()
          13. super() 的行為如同調用構造函數,如果需要給父類構造函數傳參,則需要手動傳入。
          14. scala
          15. 復制代碼
          16. class Person { constructor (text) { this.text=text } } class Child extends Person { constructor (text) { super(text) } } // 這里注意 其text 會設置到Child 中 const a=new Child('設置 text') // Child { text: '設置 text' } console.log(a.text) // output: 設置 text
          17. 如果沒有定義類構造函數,在實例化繼承類時會調用super(),而且會傳入所有傳給繼承類的參數。
          18. scala
          19. 復制代碼
          20. class Person { constructor (text) { this.text=text } } class Child extends Person {} const a=new Child('設置 text'); // Child { text: '設置 text' } // 上面提到過 會默認 生成 constructor (...arge) {super(...arge)}
          21. 在類構造函數中,不能在調用super()之前引用this,文章上面已經有案例及說明。
          22. 如果在繼承類中顯式定義了構造函數,則要么必須在其中調用super(),要么必須在其中返回一個對象。
          23. scala
          24. 復制代碼
          25. class Person { methods () {} } class Child1 extends Person {} class Child2 extends Person { constructor () { super() } } class Child3 extends Person { constructor () { return {} } } const a=new Child1() // Child1 {} const b=new Child2() // Child2 {} const c=new Child3() // {} 指向 實例函數 返回的對象
          26. 關于JS Class 相關就介紹到這里,當然還有 Class的 mix-ins 混入及其他class相關知識,這邊就不詳細介紹了,有興趣的同學可以自己去了解一下。


          作者:前端農民晨曦
          鏈接:https://juejin.cn/post/7098891689955164168

          JS中有類?我們都知道JavaScript是基于原型的語言,并沒有內置的類概念。但從ES6開始,JavaScript引入了class關鍵字作為語法糖,它提供了一種更簡潔、更類似于傳統面向對象編程的語法來創建對象。

          class關鍵字在語法上類似于許多其他基于類的語言,如Java或C++,但JavaScript的類仍然是基于原型的。

          正文

          class語法糖有哪些基本用法?

          一,定義類

          在JavaScript中,你可以使用class關鍵字來定義一個類。類是一個抽象的概念,它描述了具有相同屬性和方法的對象的集合。在類中一定有一個構造器函數constructor,我們可以在constructor中接收參數,將我們需要的參數都加進去。

          class Person {
              constructor(name, age) {
                  this.name=name;
                  this.age=age
              }
          
              sayHello() {
                  console.log(`Hello, my name is ${this.name} and my age is ${this.age}`);
              }
          }

          在上面的例子中,我們定義了一個名為MyClass的類,它有一個構造函數(constructor)和一個方法(sayHello)。構造函數在創建類的實例時被調用,它接受一個參數name并將其存儲在實例上。sayHello方法用于打印一條問候信息。

          二, 創建類的實例

          創建類的實例就是將類實例化,我們使用new關鍵字調用類的構造函數來創建一個新的對象,這個對象就是該類的實例。

          這個新創建的對象會繼承類的所有屬性和方法(除非它們是靜態的),并且可以有自己的狀態(即實例屬性和方法)。這里以我自己為例

          class Person {
              constructor(name, age) {
                  this.name=name;
                  this.age=age
              }
          
              sayHello() {
                  console.log(`Hello, my name is ${this.name} and my age is ${this.age}`);
              }
          }
          let p=new Person('綿綿冰', 18);//調用然后傳入參數
          console.log(p);
          
          p.sayHello();

          在上面的例子中,我在class類里面傳入了參數,定義了一個sayHello方法;我將這個類實例化為p,這個p可以訪問我在類中定義的方法

          類的實例對象可以訪問類中的方法,就像原型一樣

          類的原型機制

          為什么類能實現這樣的功能呢?當然和原型離不開關系,因為我們都知道JS是基于原型的語言。js的類也是基于原型的。

          他的運行機制主要有三步:

          1.類定義

          因為當你定義一個類時,實際上你定義了一個構造函數(通過class關鍵字),并且在這個構造函數上掛載了一些屬性和方法。這些屬性和方法實際上是定義在構造函數的prototype對象上的。

          2.創建實例

          當你使用new關鍵字創建類的實例時,JavaScript引擎會執行以下步驟:

          • 創建一個新的空對象。
          • 將這個新對象的__proto__設置為類的prototype對象。
          • 執行構造函數,并將this綁定到新創建的對象上。
          • 如果構造函數返回了一個對象,那么這個對象將成為new表達式的返回值;否則,返回新創建的對象。

          3.訪問屬性和方法

          由于實例的__proto__指向了類的prototype對象,所以當你嘗試訪問實例上的一個屬性或方法時,JavaScript會首先在實例自身上查找。如果找不到,它會繼續在實例的原型鏈上查找,直到找到為止,或者直到原型鏈的末尾(即Object.prototype)

          三,繼承類

          既然JS中的類也是基于原型的,那么就一定和原型一樣有類似的繼承方法。

          在JS中,我們可以使用extends關鍵字來讓一個類繼承另一個類的屬性和方法。這允許你創建更復雜的類結構,并實現代碼的重用。

          class Person {
              constructor(name, age) {
                  this.name=name;
                  this.age=age
              }
          
              sayHello() {
                  console.log(`Hello, my name is ${this.name} and my age is ${this.age}`);
              }
          }
          
          class Person2 extends Person {
              constructor(name, age) {
                  super(name); // 調用父類的構造函數  
                  this.age=age;
              }
          
              introduce() {
                  console.log(`Hi, I'm ${this.name} and I'm ${this.age} years old.`);
              }
          }
          
          const p2=new Person2('綿綿冰2', 25);
          p2.sayHello();
          p2.introduce(); 

          在上面的例子中,p2就繼承了p的class里面的屬性和方法,并且添加了新的方法introduce來介紹自己。

          四, 靜態方法

          在JavaScript中,我們可以使用static關鍵字來定義靜態方法。靜態方法是掛載在類上的方法,而不是類的實例上。這意味著我我們可以直接通過類名來調用靜態方法,而不需要創建類的實例。

          class greet {
              static greet() {
                  console.log('Hello from a static method!');
              }
          }
          
          greet.greet(); // 輸出: Hello from a static method!

          在這個例子中,我們定義了一個名為greet的類,并在其上定義了一個靜態方法greet。我們直接通過類名調用了這個方法,而無需創建類的實例。

          五,類中訪問器

          這是一種特殊類型的方法,用于讀取和寫入對象的屬性。訪問器由getter和setter方法組成。getter方法是一個沒有參數的方法,用于返回與對象屬性相關聯的值。setter方法則接收一個參數(即要寫入屬性的值),并用于修改與對象屬性相關聯的值。

          class Person {
              constructor(name, age) {
                  this._name=name;
                  this._age=age;
              }
          
              // getter方法,用于讀取_name屬性  
              get name() {
                  return this._name;
              }
              get age() {
                  return this._age;
              }
              // setter方法,用于設置_name屬性  
              set age(newAge) {
                  this._age=newAge;
              }
          
              set name(newName) {
                  this._name=newName;
              }
          }
          
          const person=new Person('綿綿冰', 18);
          
          // 使用getter方法讀取屬性  
          console.log(person.name);
          console.log(person.age);
          
          // 使用setter方法設置屬性  
          person.name='綿綿冰2號';
          person.age=20;
          
          // 再次使用getter方法讀取屬性,驗證setter方法是否生效  
          console.log(person.name);
          console.log(person.age);

          在上面這個簡單的例子中,我們通過get方法拿到了name和age并且將他們轉化為了私有屬性(使用下劃線前綴表示私有屬性,盡管在JavaScript中并不是真正的私有),再通過set將這些屬性設置為新的屬性值。但是這個簡單的例子存在一些問題,我們需要對傳入的參數進行一些判斷

          • 使用 typeof判斷輸入參數的類型
          • 使用 trim()移除字符串開頭和結尾的空白符
          • 判斷經過trim處理后的字符是不是一個空字符,不是空字符返回true
          • 使用 &&將兩個判斷連接,只有當這兩個判斷都為true時,整個表達式才會返回true
          • 否則console.error
          class Person {
              constructor(name, age) {
                  this._name=name;
                  this._age=age;
              }
              get name() {
                  return this._name;
              }
              get age() {
                  return this._age;
              }
              set name(newName) {
                  // 在這里添加一些驗證邏輯  
                  if (typeof newName==='string' && newName.trim() !=='') {
                      this._name=newName;
                  } else {
                      console.error('無效');
                  }
              }
              set age(newAge) {
                  if (typeof newAge==='number' && newAge >=0) {
                      this._age=newAge;
                  } else {
                      console.error('無效');
                  }
              }
          }
          const person=new Person('綿綿冰', 18);
          
          console.log(person.name);
          console.log(person.age);
          
          
          person.name='綿綿冰2';
          person.age=20;
          
          console.log(person.name);
          console.log(person.age);
          
          // 嘗試設置一個無效的值,查看setter方法中的驗證邏輯是否生效  
          person.age='十八'; // 這將觸發setter方法中的console.error



          原文轉自:https://juejin.cn/post/7382022102679207955


          主站蜘蛛池模板: 国精品无码一区二区三区在线 | 亚洲欧美一区二区三区日产| 亚洲综合一区二区精品久久| 国产精品被窝福利一区 | 激情内射亚洲一区二区三区爱妻| 无码国产精品一区二区免费16| 国内偷窥一区二区三区视频| 爆乳熟妇一区二区三区霸乳| 一区二区三区伦理高清| 国产伦精品一区二区免费| 中文字幕一区二区视频| 中文字幕精品亚洲无线码一区应用 | 一区二区三区91| 亚洲Aⅴ无码一区二区二三区软件| 午夜肉伦伦影院久久精品免费看国产一区二区三区 | 国产精品视频一区二区三区无码| 99久久精品费精品国产一区二区| 亚洲国产精品一区二区九九| 无码国产亚洲日韩国精品视频一区二区三区| 亚洲欧美成人一区二区三区| 国产福利一区视频| 日韩三级一区二区三区| 一区二区高清视频在线观看| 无码日韩人妻AV一区免费l | 国产精品成人99一区无码| 日本一区二区三区精品国产 | 日本韩国一区二区三区| 538国产精品一区二区在线| 中文字幕AV一区中文字幕天堂| 人妻天天爽夜夜爽一区二区| 黑巨人与欧美精品一区| 精品一区二区三区在线观看l| 国产在线观看一区二区三区四区| 人妻体内射精一区二区| 亚洲AV无码一区二区乱孑伦AS| 无码国产精品一区二区免费式直播 | 日韩一区二区三区精品| 日韩一区二区电影| 精品欧美一区二区在线观看| 国产成人精品a视频一区| 末成年女A∨片一区二区|