Class與構造函數的區別


Class在語法上更貼合面向對象的寫法。

Class實現繼承更加易讀、易理解。

更易於寫java等后端語言的使用。

本質是語法糖,使用prototyp。

一、JS構造函數

JS中的prototype:每一個構造函數都有的一個屬性,能夠用來向對象添加屬性和方法。用來返回對象類型原型的引用。不需要顯式聲明,它是隱式存在的。

object.prototype.name = value

object.prototype.func = function() {...}

object.prototype =object

1、原型法設計模式:現在有1個類A,我想要創建一個類B,這個類是以A為原型的,並且能進行擴展。我們稱B的原型為A。

2、當一個對象被創建時,這個構造函數 將會把它的屬性prototype賦給新對象的內部屬性__proto__。這個__proto__被這個對象用來查找它的屬性。

3、在外部不能通過prototype改變自定義類型的屬性或方法,即當原型方法和對象方法在調用相同的屬性和函數時,會執行對象方法里面的屬性和函數。

二、ES6中的構造語法——Class

三、語法糖
之所以叫做語法糖,不只是因為加糖前后的代碼實現的功能一樣,更重要的是,糖在不改變其所在位置的語法結構的前提下,實現了運行時的等價。也可以理解為,加糖前后的代碼編譯結果是一樣的。加糖只是為了讓代碼的更加簡潔,語義更加自然。

在class語法中:typeof MathHandle ----->function

MathHandle === MathHandle.prototype.constructor     // JS中沒有類,class本質上還是構造函數

function定義的方法(對象方法)有一個prototype屬性,使用new生成的對象就沒有這個prototype屬性。也就是prototype屬性是對象方法或者構造方法的專有屬性。 prototype屬性又指向了一個prototype對象,注意prototype屬性與prototype對象是兩個不同的東西,要注意區別。在prototype對象中又有一個constructor屬性,這個constructor屬性同樣指向一個constructor對象,而這個constructor對象恰恰就是這個function函數本身。

function Person(name)

{

this.name=name;

this.showMe=function()

{

alert(this.name);

}

};

var one=new Person('js');

alert(one.prototype)                              //undefined

alert(typeof Person.prototype);              //object

alert(Person.prototype.constructor);     //function Person(name) {...};

四、JS繼承

1、拓展原型。可以理解為Dog對象將Animal中的屬性和方法全部克隆一遍,Dog能夠使用Animal中的方法和屬性。

2、如果子類和父類中的方法同名,則運行時會先去本體的函數中去找,如果找到則運行,找不到則去prototype中尋找函數,理解為prototype不會克隆同名函數。

五、ES6中的繼承——Class

ES6中的字符串占位符:

JS中:"hello" + str + "world !"

ES6中:hello ${str} world !

JS語言傳統創建對象的方法一般是通過構造函數,來定義生成的,下面是一個使用function生成的例子。(需要了解生成對象的方式,如工廠模式、原型模式等,以及優缺點,請參考文章:JavaScript中創建對象的7種模式)

function Point(x,y){
this.x=x;
this.y = y;
}
Point.prototype.toString = function(){
return '('+this.x+','+this.y+')';
}
var p= new Point(1,2);
上面的例子在ES6中定義如下:

class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
1.類Point中方法之間不用,號隔開,方法不用function進行定義,

構造函數的prototype屬性,在ES6的“類”上面繼續存在。事實上,類的所有方法都定義在類的prototype屬性上面。

class Point {
constructor(){
// ...
}
toString(){
// ...
}
toValue(){
// ...
}
}

// 等同於
Point.prototype = {
toString(){},
toValue(){}
};
2.類的內部所有定義的方法,都是不可枚舉的(但是在es5中prototype的方法是可以進行枚舉的)

3.每一個類中都有一個constructor方法該方法返回實例對象

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

用類進行實例和用普通的構造函數進行實例:

1、用類進行實例的必須使用new否則就會報錯

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

3、與ES5一樣,類的所有實例共享一個原型對象。

4、Class不存在變量提升(hoist),這一點與ES5完全不同

new Foo(); // ReferenceError
class Foo {}
上面代碼中,Foo類使用在前,定義在后,這樣會報錯,因為ES6不會把類的聲明提升到代碼頭部。這種規定的原因與下文要提到的繼承有關,必須保證子類在父類之后定義。

{
let Foo = class {};
class Bar extends Foo {
}
}
上面的代碼不會報錯,因為Bar繼承Foo的時候,Foo已經有定義了。但是,如果存在class的提升,上面代碼就會報錯,因為class會被提升到代碼頭部,而let命令是不提升的,所以導致Bar繼承Foo的時候,Foo還沒有定義。

Class的繼承
class中的繼承使用extend
1、子類必須在constructor方法中調用super方法,否則新建實例時會報錯。這是因為子類沒有自己的this對象,而是繼承父類的this對象,然后對其進行加工。如果不調用super方法,子類就得不到this對象。
2、ES5的繼承,實質是先創造子類的實例對象this,然后再將父類的方法添加到this上面(Parent.apply(this))。ES6的繼承機制完全不同,實質是先創造父類的實例對象this(所以必須先調用super方法),然后再用子類的構造函數修改this。
3、
另一個需要注意的地方是,在子類的構造函數中,只有調用super之后,才可以使用this關鍵字,否則會報錯。這是因為子類實例的構建,是基於對父類實例加工,只有super方法才能返回父類實例。

class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}

class ColorPoint extends Point {
constructor(x, y, color) {
this.color = color; // ReferenceError
super(x, y);
this.color = color; // 正確
}
}
上面代碼中,子類的constructor方法沒有調用super之前,就使用this關鍵字,結果報錯,而放在super方法之后就是正確的。

super.print.call(this)

類的prototype屬性和__proto__屬性
大多數瀏覽器的ES5實現之中,每一個對象都有__proto__屬性,指向對應的構造函數的prototype屬性。Class作為構造函數的語法糖,同時有prototype屬性和__proto__屬性,因此同時存在兩條繼承鏈。

(1)子類的__proto__屬性,表示構造函數的繼承,總是指向父類。

(2)子類prototype屬性的__proto__屬性,表示方法的繼承,總是指向父類的prototype屬性。

class A {
}

class B extends A {
}

B.proto === A // true
B.prototype.proto === A.prototype // true
上面代碼中,子類B的__proto__屬性指向父類A,子類B的prototype屬性的__proto__屬性指向父類A的prototype屬性。

這樣的結果是因為,類的繼承是按照下面的模式實現的。

實例的__proto__屬性
子類實例的__proto__屬性的__proto__屬性,指向父類實例的__proto__屬性。也就是說,子類的原型的原型,是父類的原型。

原生構造函數的繼承
原生構造函數是指語言內置的構造函數,通常用來生成數據結構。ECMAScript的原生構造函數大致有下面這些。

Boolean()
Number()
String()
Array()
Date()
Function()
RegExp()
Error()
Object()
以前,這些原生構造函數是無法繼承的,比如,不能自己定義一個Array的子類。

function MyArray() {
Array.apply(this, arguments);
}

MyArray.prototype = Object.create(Array.prototype, {
constructor: {
value: MyArray,
writable: true,
configurable: true,
enumerable: true
}
});
上面代碼定義了一個繼承Array的MyArray類。但是,這個類的行為與Array完全不一致。

var colors = new MyArray();
colors[0] = "red";
colors.length // 0

colors.length = 0;
colors[0] // "red"
之所以會發生這種情況,是因為子類無法獲得原生構造函數的內部屬性,通過Array.apply()或者分配給原型對象都不行。原生構造函數會忽略apply方法傳入的this,也就是說,原生構造函數的this無法綁定,導致拿不到內部屬性。

ES5是先新建子類的實例對象this,再將父類的屬性添加到子類上,由於父類的內部屬性無法獲取,導致無法繼承原生的構造函數。比如,Array構造函數有一個內部屬性[[DefineOwnProperty]],用來定義新屬性時,更新length屬性,這個內部屬性無法在子類獲取,導致子類的length屬性行為不正常。
ES6允許繼承原生構造函數定義子類,因為ES6是先新建父類的實例對象this,然后再用子類的構造函數修飾this,使得父類的所有行為都可以繼承。下面是一個繼承Array的例子。

class MyArray extends Array {
constructor(...args) {
super(...args);
}
}

var arr = new MyArray();
arr[0] = 12;
arr.length // 1

arr.length = 0;
arr[0] // undefined
上面代碼定義了一個MyArray類,繼承了Array構造函數,因此就可以從MyArray生成數組的實例。這意味着,ES6可以自定義原生數據結構(比如Array、String等)的子類,這是ES5無法做到的。

上面這個例子也說明,extends關鍵字不僅可以用來繼承類,還可以用來繼承原生的構造函數。因此可以在原生數據結構的基礎上,定義自己的數據結構。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM