[toc]
### 新增的對象字面量語法
#### 成員速寫
如果對象字面量初始化時,成員的值來自于一個變量,并且該成員的名稱與所獲得數據的變量名稱相同,則可以進行簡寫。例如:
```js
//這是ES6之前的標準寫法,其用途是根據用戶輸入的數據,返回一個用戶數據對象的方法。
function createUser(userId,?userPwd,?userAge){
return?{
????????????userId:?userId,
????????????userPwd:?userPwd,
????????????userAge:?userAge
????}
}
//當返回的對象的每一個key與其對應的變量名稱相同時,在ES6中可以這樣寫
function createUser(userId,?userPwd,?userAge){
const say = function?(){
//代碼塊
}
return?{
userId,
userPwd,
userAge,
say
}
}
//這就是ES6的成員速寫方法,其功能不變,書寫簡化了使閱讀更加清晰。
```
>在ES6中,不僅是屬性和變量同名,屬性與方法同名也可以進行速寫。
#### 方法速寫
對象字面初始化時,方法可以省略冒號和function關鍵字。
```js
//ES6之前定義對象中的方法是這樣書寫的
const obj =?{
????id:?'xxx',
????msg:?'abcd',
print:?function(){
//代碼塊
}
}
//在ES6中可以這樣寫,其效果與上面的代碼完全相同。
const obj =?{
????id:?'xxx',
????msg:?'abcd',
print(){
//代碼塊
????}
}
```
#### 計算屬性名
有的時候,初始化對象時,某些屬性名可能來自某個表達式的值。在ES6中,可以使用中括號來表示該屬性名是通過計算得到的。
```js
//當一個對象在初始化的時候,其屬性名需要通過某個變量或表達式的返回值來確定時,可以使用如下方法
let value1 = 'name';
let value2 = '01'
const obj =?{
????[value1+value2]:?"hahaha"?,??//此時該屬性的屬性名為'name01'
????id:?'xxx',
????msg:?'abcd',
print(){
//代碼塊
}
}
```
### Object構造函數新增的API
ES6中針對Object構造函數新增的API均為靜態API,即集成到Object函數中的API。
#### Object.is方法
用來判斷兩個數據是否相等,基本上與嚴格相等(===)是一致的,除了以下兩點:
1.?用is來判斷兩個數據時,NaN和NaN是相等的。
2.?用is來判斷兩個數據時,+0和-0不相等。
```js
console.log(Object.is(NaN,?NaN));?//返回的是true
console.log(Object.is(+0,?-0));?//返回的是false
```
#### Object.assign方法
用于混合對象,assign方法后面可以填寫無限多的參數。規則就是下一個參數覆蓋上一個參數的值。
```js
let obj1 =?{a:?111,?b:?222,?c:?333};
let obj2 =?{b:?555,?d:?666}
//assign方法的工作原理是,將obj2的數據覆蓋到obj1,并且會對obj1產生改動,然后返回obj1
let newobj = Object.assign(obj1,?obj2)
//此時,newobj的值為{a:?111,?b:555,?c:333,?d:?666},不過obj1的值也變成了{a:111,?b:555,?c:333}
```
>該方法并不完美,會改動原有的對象數據。所以在ES7的展開運算符出現以后。基本上不會使用該方法,而是使用下面的方法:
```js
let obj1 =?{a:?111,?b:?222,?c:?333};
let obj2 =?{b:?555,?d:?666};
let newobj =?{
...obj1,
...obj2
}
//這樣做既可以讓obj2中與obj1相同的數據覆蓋掉obj1的數據,又不會改變obj1的值。
```
>若一定要使用assign方法,又不想改變obj1的值可以使用下面的方法
```js
let newobj = Object.assign ({},?obj1,?obj2);
//在最前面放置一個空對象,這樣就不會改動obj1的值。
```
#### Object.getOwnPropertyNames的枚舉順序
Object.getOwnPropertyNames方法在ES6之前就存在,只不過官方沒有明確要求對屬性的順序如何排序。如何排序,完全由瀏覽器廠商決定。
ES6規定了該方法返回的數組的排序方式為:先排數字并按照升序排序,再排其他并按照書寫順序排序。
#### Object.setPrototypeOf方法
該方法用來設置某個對象的隱式原型
```js
Object.setPrototypeOf(obj1,?obj2);
//該方法相當于obj1.__proto__?=?obj2,將obj2的屬性及方法當做obj1的隱式原型。
```
### 面向對象編程簡介
1.?面向對象是什么?
面向對象是一種編程思想,與具體的語言無關
2.?面向對象與面向過程編程有什么不同?
面向過程編程:思考的切入點是功能實現的步驟。
面向對象編程:思考的切入點是對象的功能劃分。
### 類:構造函數的語法糖
#### ES6之前JS傳統的構造函數存在的問題
1.?屬性和原型方法定義分離,降低了可讀性
2.?原型成員可以被枚舉
3.?默認情況下,構造函數仍然可以被當做普通函數使用
為了解決上述的問題,ES6中引入了類的概念。
#### ES6中如何創建一個類
在ES6中可以使用關鍵字class來創建一個類
```js
class Student?{ //定義類的名稱
//constructor用來創建類的結構,用來定義類的屬性。相當于定義構造函數
constructor(name,?age,?sex,?classNum){
this.name = name;
this.age = age;
this.sex = sex;
this.classNum = classNum;
}
//下面用來定義類的方法,相當于傳統JS在prototype上定義的方法,且定義好的方法會自動被加到原型上。
study(){
//代碼塊
};
run(){
//代碼塊
}
}
```
上面就是在ES6中定義的一個類的寫法。
#### ES6中類的特點
1.?類聲明不會被提升,與let和const一樣,存在暫時性死區
2.?類中的所有代碼均在嚴格模式下執行
3.?類的所有方法都是不可被枚舉的
4.?類的所有方法都無法被當做構造函數使用
5.?類的構造器必須使用new來調用
#### 類的其它書寫方式
1.?可計算的成員名
當類中的某個方法名,在編寫過程中不確定。需要通過某個變量的值或表達式計算才能得到。可以在類中定義方法時使用[變量名/表達式]來定義。
2.?getter(讀取器)和setter(賦值器)
在類的創建使用過程中,某些屬性在賦值時可能會需要加一些判斷來確認用戶輸入的值是否合法。例如:上面代碼中的age屬性,如果用戶輸入'abc'就無法確定該條學生數據的年齡。或者有些時候在我們獲取屬性值的時候需要一些包裝效果,例如:上面代碼中age屬性,如果想讀取年齡值的時候在年齡值后面加上'歲'。為實現上面的效果,在ES6中我們可以使用如下方法:
```js
class Student?{ //定義類的名稱
constructor(name,?age,?sex,?classNum){
this.name = name;
this.age = age;
this.sex = sex;
this.classNum = classNum;
}
//創建一個age屬性,并給其加上setter,當給該屬性賦值時會執行該函數
set age(age){
if( typeof age !== 'number' ){
thrownewTypeError('輸入的年齡數據類型錯誤')
}
if( age < 6 ){
age = 6;
}elseif( age > 13 ){
age = 13;
}
this.\_age\=age;?//各項判斷條件執行完后,將最終的age屬性結果保存至Student類的\_age屬性中。
}
//創建一個age屬性,并給其加上getter,讀取該屬性時會執行該函數
get age(){
return this._age + '歲';?
}
}
```
>getter和setter統稱為訪問器。
>使用getter和setter控制的屬性,不在原型上
>getter不能設置參數,setter只能設置一個參數
3.?靜態成員
構造函數本身的成員(屬性)叫做靜態成員。由new創建的實例中的成員稱為實例成員。
在傳統的JS構造函數定義中,靜態成員的定義只能寫在構造函數外面。影響了代碼的結構和可讀性。
在ES6中引入了一個新的關鍵字'static',可以在類中定義靜態成員。
```js
class Student?{ //定義類的名稱
constructor(name,?age,?sex,?classNum){
this.name = name;
this.age = age;
this.sex = sex;
this.classNum = classNum;
}
static?schoolName?= 'xx小學' //schoolName就是一個靜態屬性。(直接給靜態屬性賦值是ES7中支持的方法)
static sing(){? //也可以定義一個靜態的方法,該方法無需創建實例,可以直接調用。
//代碼塊
}
}
console.log(Student.schoolName);? //返回'xx小學',該屬性無需創建實例。直接可以用類來獲取。
```
>靜態成員在類中可以直接調用,但不會出現在實例的屬性和方法中。
4.?字段初始化器(ES7更新的方法)
在編寫類的過程中,我們需要某些屬性初始化的時候就有值的,在ES7中我們可以用如下方法進行設置。
```js
class Student?{ //定義類的名稱
classNum?= 1;? //用該類創建的實例班級都是1班
age = 8;?//用該類創建的實例年齡都是8歲
//象這樣固定值的屬性,在ES7中可以直接在類中賦值
constructor(name,?age,?sex,?classNum){
this.name = name;
this.sex = sex;
}
}
let std1 = new Student ('小明')
console.log(std1.age); ?//std1的年齡輸出的是默認值8
```
- 使用static的字段初始化器,添加的是靜態成員
- 沒有使用static的字段初始化器,添加的成員位于對象上。(即添加的是實例屬性)
- 箭頭函數在字段初始化器位置上時,this指向當前對象。當類中的方法使用了this,且不想被外部調用時改變this的指向時,可以將該方法使用箭頭函數進行初始化,用來固定方法中this的指向。但如果這樣定義,該方法就不在類的原型上了。該方法與其它實例屬性一樣,是可以被枚舉出的。且每創建一個實例,就會跟隨實例創建一個該方法。
5.?類表達式
類也可以以表達式的形式書寫,例如:
```js
const test = class?{
????a?= 1;
????b?= 2
}
//此時相當于定義了一個匿名類,將類賦值給test
let p = new test();
console.log(p);? //返回{a:1,?b:2}
```
6.?[擴展]裝飾器(ES7)(Decorator)
在開發的過程中,會發生有些類中的方法因無法升級或維護,暫時停用了。但過段時間可能又想使用了。如果因為這種情況來直接刪除或修改類中某個方法的代碼會對整個代碼的安全和穩定性產生影響。
為了解決這種問題,我們采用一種方法叫做橫切關注點。在ES7中為我們提供了一種方法,叫做裝飾器。使用方法如下:
```js
class Student { //定義類的名稱
constructor(name, age, sex, classNum){
this.name = name;
this.age = age;
this.sex = sex;
this.classNum = classNum;
}
study(){
//代碼塊
};
@expire //哪個方法不使用,或提示一個開始者該方法已過期,就在其上面用@加上自定義的一個標識名
run(){
//代碼塊
}
}
//裝飾器的標識名需要在下方對應一個同名的函數,其包含三個參數。
function expire(target, methodName, descriptor){
//target返回的是類名:Student
//methodName返回的是標記的方法名:run
//descriptor返回的是標記方法的內容
//利用該函數可以編寫對應的代碼用來改寫或提示用戶如何調用該方法
}
```
>該方法目前很多瀏覽器對其支持還不是很好,需要做一些兼容性處理。
### 類的繼承
1.?什么樣的關系是類的繼承?
當有兩個類A和B,如果可以描述為B是A,那么就可以說B繼承自A(也可以描述為:A派生B,B是A的子類,A是B的父類)。例如:有兩個類,一個是交通工具,一個是汽車。我們可以描述為汽車是交通工具,那么就是說汽車的類繼承自交通工具類。
當某一個類繼承自另一個類時,繼承自哪個類,那么它就具有該類所具有的所有特性(包括屬性和方法)
如果想讓一個類(構造函數)的原型可以繼承另一個類的原型,可以使用Object.setPrototypeOf()方法來設置。
2.?ES6中繼承類
從ES6開始,提供了新的類的繼承方法,extends方法和super()方法:
```js
class Animal {
constructor(type, name, age, sex){
//定義了Animal的屬性,有種類,名稱,年齡和性別
this.type = type,
this.name = name,
this.age = age,
this.sex = sex
}
print(){
console.log(type);
console.log(name);
console.log(age);
console.log(sex);
}
}
//新定義一個狗的類想繼承動物類,在定義時加上extends 繼承的類名
class Dog extends Animal{
constructor(
super('犬類', name, age, sex);
//super的第一種用法可以當做函數,其用途是表示父類構造函數。因為Dog類繼承自Animal類,所以上面的代碼即表示Dog類的構造函數繼承自Animal的構造函數且type屬性固定為'犬類'。
this.loves = '吃骨頭'
)
//Dog類繼承自Animal類,如果Dog類中也有一個print方法,除了打印Animal方法中的內容外還需要加上Dog方法特有的內容。
print(){
super.print()
//super的第二種用法,用來表示父類。可以在子類中調用父類的方法。這樣就可以不用再重復書寫父類方法的情況下,調用父類的方法并運行自己特有的代碼。
console.log(this.loves)
}
}
//這樣就完成了類的繼承
```
>注意:ES6要求,如果定義了constructor,并且該類是子類,則必須在constructor的第一行手動調用父類的構造函數。
>如果子類不寫constructor,則會有默認的構造器,該構造器需要的參數與父類一致,并且自動調用父類的構造器。
>一個子類只可以繼承自一個父類,使用extends方法后。子類會自動繼承父類的原型
>子類與父類中存在同名的方法時,子類的實例會優先調用子類的方法。