ES6的新特性??_ES6是什麼+30分鐘帶你瞭解ES6核心內容(下)

ES6的新特性??_ES6是什麼+30分鐘帶你瞭解ES6核心內容(上)


一.函數參數的擴展

默認參數

基本用法

function fn(name,age=17){
 console.log(name+","+age);
}
fn("Amy",18);  // Amy,18
fn("Amy","");  // Amy,
fn("Amy");     // Amy,17

注意點:使用函數默認參數時,不允許有同名參數。

// 不報錯
function fn(name,name){
 console.log(name);
}
 
// 報錯
//SyntaxError: Duplicate parameter name not allowed in this context
function fn(name,name,age=17){
 console.log(name+","+age);
}

只有在未傳遞參數,或者參數爲 undefined 時,纔會使用默認參數,null 值被認爲是有效的值傳遞。

function fn(name,age=17){
    console.log(name+","+age);
}
fn("Amy",null); // Amy,null

函數參數默認值存在暫時性死區,在函數參數默認值表達式中,還未初始化賦值的參數值無法作爲其他參數的默認值。

function f(x,y=x){
    console.log(x,y);
}
f(1);  // 1 1
 
function f(x=y){
    console.log(x);
}
f();  // ReferenceError: y is not defined

不定參數

不定參數用來表示不確定參數個數,形如,…變量名,由…加上一個具名參數標識符組成。具名參數只能放在參數組的最後,並且有且只有一個不定參數。

基本用法

function f(...values){
    console.log(values.length);
}
f(1,2);      //2
f(1,2,3,4);  //4

箭頭函數

箭頭函數提供了一種更加簡潔的函數書寫方式。基本語法是:
參數 => 函數體

基本用法:

var f = v => v;
//等價於
var f = function(a){
 return a;
}
f(1);  //1

當箭頭函數沒有參數或者有多個參數,要用 () 括起來。

var f = (a,b) => a+b;
f(6,2);  //8

當箭頭函數函數體有多行語句,用 {} 包裹起來,表示代碼塊,當只有一行語句,並且需要返回結果時,可以省略 {} , 結果會自動返回。

var f = (a,b) => {
 let result = a+b;
 return result;
}
f(6,2);  // 8

當箭頭函數要返回對象的時候,爲了區分於代碼塊,要用 () 將對象包裹起來

// 報錯
var f = (id,name) => {id: id, name: name};
f(6,2);  // SyntaxError: Unexpected token :
 
// 不報錯
var f = (id,name) => ({id: id, name: name});
f(6,2);  // {id: 6, name: 2}

注意點:沒有 this、super、arguments 和 new.target 綁定。

var func = () => {


  // 箭頭函數裏面沒有 this 對象,
  // 此時的 this 是外層的 this 對象,即 Window 
  console.log(this)
}
func(55)  // Window 
 
var func = () => {    
  console.log(arguments)
}
func(55);  // ReferenceError: arguments is not defined

箭頭函數體中的 this 對象,是定義函數時的對象,而不是使用函數時的對象。

function fn(){
  setTimeout(()=>{
    // 定義時,this 綁定的是 fn 中的 this 對象
    console.log(this.a);
  },0)
}
var a = 20;
// fn 的 this 對象爲 {a: 19}
fn.call({a: 18});  // 18

不可以作爲構造函數,也就是不能使用 new 命令,否則會報錯

適合使用的場景

ES6之前,使用普通函數把其中每個名字轉換爲大寫形式:

const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(function(name) { 
  return name.toUpperCase();
});

箭頭函數表示:

const upperizedNames = ['Farrin', 'Kagure', 'Asser'].map(
  name => name.toUpperCase()
);

普通函數可以是函數聲明或者函數表達式, 但是箭頭函數始終都是表達式, 全程是箭頭函數表達式, 因此因此僅在表達式有效時才能使用,包括:

  • 存儲在變量中,
  • 當做參數傳遞給函數,
  • 存儲在對象的屬性中。

所以,當我們需要維護一個 this 上下文的時候,就可以使用箭頭函數。

不適合使用的場景

定義函數的方法,且該方法中包含 this

var Person = {
    'age': 18,
    'sayHello': ()=>{
        console.log(this.age);
      }
};
var age = 20;
Person.sayHello();  // 20
// 此時 this 指向的是全局對象
 
var Person1 = {
    'age': 18,
    'sayHello': function () {
        console.log(this.age);
    }
};
var age = 20;
Person1.sayHello();   // 18
// 此時的 this 指向 Person1 對象

需要動態 this 的時候
var button = document.getElementById('userClick');
button.addEventListener('click', () => {
     this.classList.toggle('on');
});

button 的監聽函數是箭頭函數,所以監聽函數裏面的 this 指向的是定義的時候外層的 this 對象,即 Window,導致無法操作到被點擊的按鈕對象。

二.javascript標準函數this

new 對象

const mySundae = new Sundae('Chocolate', ['Sprinkles', 'Hot Fudge']);

sundae這個構造函數內的this的值是實例對象, 因爲他使用new被調用.

指定的對象

const result = obj1.printName.call(obj2);

函數使用call/apply被調用,this的值指向指定的obj2,因爲call()第一個參數明確設置this的指向

上下文對象

data.teleport();

函數是對象的方法, this指向就是那個對象,此處this就是指向data.

全局對象或 undefined

teleport();

此處是this指向全局對象,在嚴格模式下,指向undefined.

javascript中this是很複雜的概念, 要詳細判斷this,請參考this你不懂JS: this 與對象原型

三.箭頭函數和this

對於普通函數, this的值基於函數如何被調用, 對於箭頭函數,this的值基於函數週圍的上下文, 換句話說,this的值和函數外面的this的值是一樣的.

function IceCream() {
    this.scoops = 0;
}

// 爲 IceCream 添加 addScoop 方法
IceCream.prototype.addScoop = function() {
    setTimeout(function() {
        this.scoops++;
        console.log('scoop added!');
        console.log(this.scoops); // undefined+1=NaN
        console.log(dessert.scoops); //0
    }, 500);
};


const dessert = new IceCream();
dessert.addScoop();

傳遞給 setTimeout() 的函數被調用時沒用到 new、call() 或 apply(),也沒用到上下文對象。意味着函數內的 this 的值是全局對象,不是 dessert 對象。實際上發生的情況是,創建了新的 scoops 變量(默認值爲 undefined),然後遞增(undefined + 1 結果爲 NaN);

解決此問題的方式之一是使用閉包(closure):

// 構造函數
function IceCream() {
  this.scoops = 0;
}

// 爲 IceCream 添加 addScoop 方法
IceCream.prototype.addScoop = function() {
  const cone = this; // 設置 `this` 給 `cone`變量
  setTimeout(function() {
    cone.scoops++; // 引用`cone`變量
    console.log('scoop added!'); 
    console.log(dessert.scoops);//1
  }, 0.5);
};

const dessert = new IceCream();
dessert.addScoop();

箭頭函數的作用正是如此, 將setTimeOut()的函數改爲剪頭函數:

// 構造函數
function IceCream() {
  this.scoops = 0;
}

// 爲 IceCream 添加 addScoop 方法
IceCream.prototype.addScoop = function() {
  setTimeout(() => { // 一個箭頭函數被傳遞給setTimeout
    this.scoops++;
    console.log('scoop added!');
    console.log(dessert.scoops);//1
  }, 0.5);
};

const dessert = new IceCream();
dessert.addScoop();

四.Class類

在ES6中,class (類)作爲對象的模板被引入,可以通過 class 關鍵字定義類。
class 的本質是 function。

它可以看作一個語法糖,讓對象原型的寫法更加清晰、更像面向對象編程的語法。

基礎用法

類定義

類表達式可以爲匿名或命名。

// 匿名類
let Example = class {
    constructor(a) {
        this.a = a;
    }
}
// 命名類
let Example = class Example {
    constructor(a) {
        this.a = a;
    }
}

類聲明

class Example {
    constructor(a) {
        this.a = a;
    }
}

注意要點:不可重複聲明。

class Example{}
class Example{}
// Uncaught SyntaxError: Identifier 'Example' has already been 
// declared
 
let Example1 = class{}
class Example{}
// Uncaught SyntaxError: Identifier 'Example' has already been 
// declared

注意要點
類定義不會被提升,這意味着,必須在訪問前對類進行定義,否則就會報錯

類中方法不需要 function 關鍵字。
方法間不能加分號。

new Example(); 
class Example {}

類的主體

屬性

prototype

ES6 中,prototype 仍舊存在,雖然可以直接自類中定義方法,但是其實方法還是定義在 prototype 上的。 覆蓋方法 / 初始化時添加方法

Example.prototype={
    //methods
}

添加方法

Object.assign(Example.prototype,{
    //methods
})

靜態屬性

靜態屬性:class 本身的屬性,即直接定義在類內部的屬性( Class.propname ),不需要實例化。 ES6 中規定,Class 內部只有靜態方法,沒有靜態屬性。
class Example {
// 新提案
    static a = 2;
}
// 目前可行寫法
Example.b = 2;

公共屬性
class Example{}
Example.prototype.a = 2;

實例屬性

實例屬性:定義在實例對象( this )上的屬性。
class Example {
a = 2;
constructor () {
console.log(this.a);
}
}

name 屬性

返回跟在 class 後的類名(存在時)。

let Example=class Exam {
    constructor(a) {
        this.a = a;
    }
}
console.log(Example.name); // Exam
 
let Example=class {
    constructor(a) {
        this.a = a;
    }
}
console.log(Example.name); // Example

方法

constructor 方法

constructor 方法是類的默認方法,創建類的實例化對象時被調用。

class Example{
    constructor(){
      console.log('我是constructor');
    }
}
new Example(); // 我是constructor

返回對象

class Test {
    constructor(){
        // 默認返回實例對象 this
    }
}
console.log(new Test() instanceof Test); // true
 
class Example {
    constructor(){
        // 指定返回對象
        return new Test();
    }
}
console.log(new Example() instanceof Example); // false

靜態方法

class Example{
    static sum(a, b) {
        console.log(a+b);
    }
}
Example.sum(1, 2); // 3

原型方法

class Example {
    sum(a, b) {
        console.log(a + b);
    }
}
let exam = new Example();
exam.sum(1, 2); // 3

實例方法

class Example {
    constructor() {
        this.sum = (a, b) => {
            console.log(a + b);
        }
    }
}

類的實例化

new

class 的實例化必須通過 new 關鍵字。

class Example {}
 
let exam1 = Example(); 
// Class constructor Example cannot be invoked without 'new'

實例化對象共享原型對象

class Example {
    constructor(a, b) {
        this.a = a;
        this.b = b;
        console.log('Example');
    }
    sum() {
        return this.a + this.b;
    }
}
let exam1 = new Example(2, 1);
let exam2 = new Example(3, 1);
console.log(exam1._proto_ == exam2._proto_); // true
 
exam1._proto_.sub = function() {
    return this.a - this.b;
}
console.log(exam1.sub()); // 1
console.log(exam2.sub()); // 2

decorator

decorator 是一個函數,用來修改類的行爲,在代碼編譯時產生作用。
類修飾

一個參數

第一個參數 target,指向類本身。

function testable(target) {
    target.isTestable = true;
}
@testable
class Example {}
Example.isTestable; // true

多個參數——嵌套實現

function testable(isTestable) {
    return function(target) {
        target.isTestable=isTestable;
    }
}
@testable(true)
class Example {}
Example.isTestable; // true

實例屬性

上面兩個例子添加的是靜態屬性,若要添加實例屬性,在類的 prototype 上操作即可。
方法修飾

3個參數:target(類的原型對象)、name(修飾的屬性名)、descriptor(該屬性的描述對象)。

class Example {
    @writable
    sum(a, b) {
        return a + b;
    }
}
function writable(target, name, descriptor) {
    descriptor.writable = false;
    return descriptor; // 必須返回
}

修飾器執行順序

由外向內進入,由內向外執行。

class Example {
    @logMethod(1)
    @logMthod(2)
    sum(a, b){
        return a + b;
    }
}
function logMethod(id) {
    console.log('evaluated logMethod'+id);
    return (target, name, desctiptor) => console.log('excuted         logMethod '+id);
}
// evaluated logMethod 1
// evaluated logMethod 2
// excuted logMethod 2
// excuted logMethod 1

封裝與繼承

getter / setter

class Example{
    constructor(a, b) {
        this.a = a; // 實例化時調用 set 方法
        this.b = b;
    }
    get a(){
        console.log('getter');
        return this.a;
    }
    set a(a){
        console.log('setter');
        this.a = a; // 自身遞歸調用
    }
}
let exam = new Example(1,2); // 不斷輸出 setter ,最終導致 RangeError
class Example1{
    constructor(a, b) {
        this.a = a;
        this.b = b;
    }
    get a(){
        console.log('getter');
        return this._a;
    }
    set a(a){
        console.log('setter');
        this._a = a;
    }
}
let exam1 = new Example1(1,2); // 只輸出 setter , 不會調用 

getter 方法

console.log(exam._a); // 1, 可以直接訪問

getter 不可單獨出現

class Example {
    constructor(a) {
        this.a = a; 
    }
    get a() {
        return this.a;
    }
}
let exam = new Example(1); // Uncaught TypeError: Cannot set property // a of #<Example> which has only a getter

getter 與 setter 必須同級出現

class Father {
    constructor(){}
    get a() {
        return this._a;
    }
}
class Child extends Father {
    constructor(){
        super();
    }
    set a(a) {
        this._a = a;
    }
}
let test = new Child();
test.a = 2;
console.log(test.a); // undefined
 
class Father1 {
    constructor(){}
    // 或者都放在子類中
    get a() {
        return this._a;
    }
    set a(a) {
        this._a = a;
    }
}
class Child1 extends Father1 {
    constructor(){
        super();
    }
}
let test1 = new Child1();
test1.a = 2;
console.log(test1.a); // 2

extends

通過 extends 實現類的繼承。

class Child extends Father { ... }

super

子類 constructor 方法中必須有 super ,且必須出現在 this 之前。

class Father {
    constructor() {}
}
class Child extends Father {
    constructor() {}
    // or 
    // constructor(a) {
        // this.a = a;
        // super();
    // }
}
let test = new Child(); // Uncaught ReferenceError: Must call super 
// constructor in derived class before accessing 'this' or returning 
// from derived constructor

調用父類構造函數,只能出現在子類的構造函數。

class Father {
    test(){
        return 0;
    }
    static test1(){
        return 1;
    }
}
class Child extends Father {
    constructor(){
        super();
    }
}
class Child1 extends Father {
    test2() {
        super(); // Uncaught SyntaxError: 'super' keyword unexpected     
        // here
    }
}

調用父類方法, super 作爲對象,在普通方法中,指向父類的原型對象,在靜態方法中,指向父類

class Child2 extends Father {
    constructor(){
        super();
        // 調用父類普通方法
        console.log(super.test()); // 0
    }
    static test3(){
        // 調用父類靜態方法
        return super.test1+2;
    }
}
Child2.test3(); // 3

注意要點

不可繼承常規對象。

var Father = {
    // ...
}
class Child extends Father {
     // ...
}
// Uncaught TypeError: Class extends value #<Object> is not a constructor or null
 
// 解決方案
Object.setPrototypeOf(Child.prototype, Father);

本文參考:

https://www.jianshu.com/p/87008f4f8513
https://www.runoob.com/w3cnote/es6-tutorial.html

本次介紹就到這,學習愉快! 😃

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章