ES6之那些年咱們都迷茫的原型和類(下)

ES6之類(二)

這是下篇,講述了類的一些常見用法和基本概念。對比了 ES5 與 ES6 的差別。es6

其實是特殊的函數,就像你可以定義的函數表達式和函數聲明,類語法有兩個組成部分: 類表達式和類聲明。微信

構造函數 、靜態方法、原型方法、getter、setter 一個構造函數方法是一個特殊方法, 其用於建立和初始化使用一個類建立的一個對象。app

一個構造函數可使用 super 關鍵字來調用一個父類的構造函數。函數

靜態方法: static 關鍵字來定義。調用靜態類方法不須要實例話該類,但不能經過一個類實例調用靜態方法。經常使用語爲一個應用程序回建立工具函數。工具

⚠️: 只有靜態方法 沒有靜態屬性性能

表達式和聲明

類聲明

定義一個類的一種方法是使用一個類聲明。 要聲明一個類, 你可使用帶有 class 關鍵字的類名ui

class Retctange {
    constructor(hight, width) {
        this.height = height;
        this.width = width;
    }
}
複製代碼

函數聲明和類聲明之間的一個重要區別就是函數聲明會提高, 類聲明不會。例以下面例子this

let p = new Retctange();
class Retctange {}
//Uncaught ReferenceError: Cannot access 'Retctange' before initialization
複製代碼

類表達式

一個類表達式是定義一個類的另外一種方式。類表達式能夠是被命名的或者匿名的。 賦予一個命名類表達式的名稱是類的主體的本地名稱。spa

let person = new class {
    constructor(name) {
        this.name = name;
    }
    sayName() {
        console.log(this.name);
    }
}('張三');
person.sayName();
複製代碼

ES6的繼承

class 內定義的全部函數都會置於該類的原型當中。prototype

class Animal {
    constructor(name) {
        this.name = name
    }
}
Animal.prototype.species = 'animal'
class Leo extends Animal {
    constructor(name) {
        super(name)
    }
}
複製代碼

constructor(){} 充當了以前的構造函數,   super() 做爲函數調用扮演着 Animal.call(this, name) 的角色(還能夠表示父類). 最重要的是 Leo 的 proto 也指向了 Animal.

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

    toString() {
        return '(' + this.x + ', ' + this.y + ')';
    }
}
var point = new Point(2, 3);
point.toString() // (2, 3)
point.hasOwnProperty('x') // true
point.hasOwnProperty('y') // true
point.hasOwnProperty('toString') // false
point.__proto__.hasOwnProperty('toString')
複製代碼

x 和 y 都是實例對象point 自身的屬性(由於定義在 this 變量上), 因此 hasOwnPropery 返回 true, 而toString 是原型對象的屬性(定義在 Point類上), 因此 hasOwnProperty 方法返回 false。

class 實現原理

ES5的繼承,實質是先創造子類的實例對象 this, 而後再將父類的方法添加到 this 上面 Parent.apply(this), es6 的繼承則是,將父類實例對象的屬性和犯法,駕到this上面(因此必須先調用super方法), 而後在用子類的構造函數修改this. super在調用以後, 內部的 this 指向的是 child,

class parent {}
  class Child extends Parent {}
  Child.__proto__ === Parent // 繼承屬性
  Child.prototype.__proto__ === Parent.prototype // 繼承方法
複製代碼
class Point {}
var point = new Point()
console.log(point.constructor === Point.prototype.constructor) // true
複製代碼
class B {}
let b = new B();
b.constructor === B.prototype.constructor // true
複製代碼

b 是 B 類的實例。它的constructor方法就 是B 類原型的 constructor 方法。

class Foo {
    constructor() {
        return Object.create(null);
    }
}
// console.log('new Foo() instanceof Foo', new Foo() instanceof Foo);
//實例再也不指向 Foo
複製代碼

類必須使用 new 調用,不然會報錯。 這是它跟普通構造函數的一個主要區別,後面不用 new 也能夠執行。

class Foo {
    constructor() {
        return Object.create(null);
    }
}
Foo()
複製代碼
class A {
    p() {
        return 2;
    }
}
class B extends A {
    constructor() {
        super();
        console.log(super.p()); // 2
    }
}
let b = new B();
複製代碼

子類的 super.p(),就是將 super 看成是一個對象使用,super在普通方法中, 指向 A.prototype, 因此super.p()就至關於A.prototype.p(); super 指向父類的原型對象, 因此定義在父類實例上的方法或屬性,是沒法經過 super 調用的。

var o1 = {
    foo() {
        console.log('o1:foo')
    }
}
var o2 = {
    foo() {
        super.foo();
        console.log("o2: foo")
    }
}
// Object.setPrototypeOf(o2, o1);
o2.foo();
複製代碼

o2.foo()方法中的 super 引用靜態鎖定到 o2, 具體說是鎖定到 o2 的[[Prototype]]。 基本上這裏的 super 就是 Object.getPrototypeOf(o2) 實例再也不指向 Foo

new target

這個屬性可以用來確認構造函數是怎麼調用

function Person(name) {
    if (new.target === Person) {
        this.name = name;
    } else {
        throw new Error('必須使用 new 命令生成實例');
    }
}
var person = new Person('張三'); // 正確
var notAPerson = Person.call(person, '張三'); // 報錯
複製代碼

ES5 和 ES6

共同點

  1. name
class Point {}
Point.name // "Point"
複製代碼
const obj = function Person() {
    console.log('obj')

}
console.log('obj', obj.name) //Person
複製代碼

ES6 的類只是 ES5 的構造函數的一層包裝,因此函數的許多特性都被Class繼承,包括name屬性。

  1. 繼承
class Point {
      constructor() {
          // ...
      }
      toString() {
          // ...
      }
      toValue() {
          // ...
      }
  }
  // 等同於下邊的代碼
  Point.prototype.constructor = function() {}
  Point.prototype.toString = function() {}
  Point.prototype.toValue = function() {}
複製代碼

在類的實例上面調用方法,其實就是調用原型上的方法。

class Point {}
  var point = new Point()
  console.log(point.constructor === Point.prototype.constructor) // true
複製代碼

不一樣點

  1. 不存在提高

    上面例子有講,不在重複敘述。

  2. this 的指向

    函數的this 是指向區分多種,在這裏不作展開,咱們只看最簡單的例子。

    類的方法內部若是含有 this, 它默認指向類的實例。

function A(){
   this.sayThis = function(){
    console.log('sayThis', this)
  }
}

const a = new A();
console.log('a', a.sayThis() )
console.log('a', sayThis() )

複製代碼

sayThis // A

sayThis() // window

class Logger {
  printName(name = 'there') {
      this.print( `Hello ${name}` );
  }

  print(text) {
      console.log(text);
  }
}

const logger = new Logger();
// const {
// printName
// } = logger;

console.log('printName', logger.printName())
console.log('printName', printName())
複製代碼

printName //Hello there

printName(); // TypeError: Cannot read property 'print' of undefined

咱們能夠用 bind 或者箭頭函數解決 this 指向問題

class Logger {
  constructor() {
    this.printName = this.printName.bind(this);
  }

  // ...
}
//或者
class Obj {
  constructor() {
    this.getThis = () => this;
  }
}
複製代碼
  1. 私有變量和私有方法

咱們能夠將私有方法移出模塊,模塊內部的全部方法都是對外可見的。

class Widget {
    constructor(snaf) {
        this.snaf = snaf;
    }
    foo(baz) {
        console.log(this.snaf)
        bar.call(this, baz);
    }
    // ...
}

function bar(baz) {
    console.log('baz', baz)
    return this.snaf = baz;
}

const widget = new Widget('obj');
const obj = widget.foo('1');
console.log('widget', widget.snaf)
console.log('obj', obj.snaf)
複製代碼

私有方法帶#的提案

class Foo {
    #
    privateValue = 42;
    static getPrivateValue(foo) {
        return foo.#privateValue;
    }
}
console.log('Foo.getPrivateValue(new Foo());', Foo.getPrivateValue(new Foo())) //42
複製代碼

後記

歡迎關注微信公衆號!,進行更好的交流。
複製代碼

微信
相關文章
相關標籤/搜索