第9章Class

14638 ワード

クラスメイト
私たちはjavascriptがクラスの概念がないことを知っています.ES 6は新しくクラスのキーワードを追加しました.書き込みのためにとても便利です.次にES 5とES 6を通して、ES 6という新しい文法飴を比べてみます.
  • classとfunction;
  • クラスと対象字面量.
  • 静的方法「static」キーワード.
  • 「extens」「super」を継承する.
  • 抽象類new.targt
  • 一.クラスと関数の関係
    1.ES 5類の表現とES 6クラス
    ES 5は関数コンストラクタとプロトタイプの方法によってクラスの概念をシミュレートします.
    function Person(name, age) {
        this.name = name;
        this.age = age;
    }
    //              :     
    //                      ,         
    Person.prototype.sayName = function() {
        console.log(this.name);
    }
    //        
    var person = new Person("james", 26);
    person.sayName(); // "james"
    console.log(person instanceof Person); // true 
    
    ES 6はclassのキーワードを通して、constructor()の方法もあります.実例の属性をコンストラクタ関数に書いて、原型の属性は簡単な文法を使っています.
    class Person {
        //    
        constructor(name, age) {
            this.name = name;
            this.age = age;
        }
        //     ,       
        sayName() {
            console.log(this.name);
        }
    }
    //     new   
    var person = new Person("kobe", 37);
    person.sayName(); // "kobe"
    //     Person     
    console.log(typeof Person); // "function"
    console.log(typeof Person.prototype.sayName); // "function"
    
    2.クラス文法とカスタム関数表示類の違い
  • class declarationsはアップグレードしません.let声明を使うように、関数宣言は自動的にアップグレードされます.
  • は、class declarationsにおいて、すべてのコードが自動的に厳格なモードにある.
  • classの中のすべての方法は列挙できないものです.関数宣言は方法を列挙することができなくて、Object.defineProperty方法を使う必要があります.
  • classにはメソッド内部に「Contstruct」属性が存在しないので、newは使用できません.
  • classコンストラクタを呼び出すにはnewを使わなければならない.
  • classの中の方法でclass nameを書き換えることを試みて誤りを投げることができて、外部で書き直して、let声明を使った後に再び価値を賦与するように、間違いを投げることはできません;
  • クラス文法は以下のコードに相当します.
    //   let  ,      
    let PersonType = (function() {
        "use strict";
            
        //   const,       class   
         const PersonType = function(name) {
             //        new
            if (typeof new.target === "undefined") {
                throw new Error("Constructor must be called with new");
            }
            this.name = name;
        }
        //       
        Object.defineProperty(PersonType.prototype, "sayName", {
            value: function() { 
                //        new   
                if (typeof new.target !== "undefined") {
                    console.log(this.name)
                }     
            },
            enumerable: false,
            writable: true,
            configurable: true
        });
    
        return PersonType;    
    }());
    
    3.Class Expressions
    classとfunctionの似ているところは二つの形式があります.宣言式と表現式、class式は変数宣言として一般的に使われます.あるいはパラメータ入力関数として使われます.式の形式:
    let Person = class {
        constructor(name) {
            this.name = name;
        }
        sayName() {
            console.log(this.name);
        }
    };
    
    機能的にはclass宣言と同じで、「name」の属性が違います.上の匿名class表現Person.nameは空の文字列です.
    4.Named Class expressions
    これはNamed Function expressitionsと同じで、classの後に識別子を追加します.この識別子はclass定義の中でのみ使用できます.
    let Person = class PersonType {
        constructor(name) {
            this.name = name;
        }
        sayName() {
            console.log(this.name);
        }
    }
    
    console.log(typeof Person); // "function"
    console.log(typeof PersonType); // undefined PersonType   class    
    
    相当于:
       let Person = (function() {
        "use strict";
            
        //   const,       class        Person   
         const PersonType = function(name) {
             //        new
            if (typeof new.target === "undefined") {
                throw new Error("Constructor must be called with new");
            }
            this.name = name;
        }
        //       
        Object.defineProperty(PersonType.prototype, "sayName", {
            value: function() { 
                //        new   
                if (typeof new.target !== "undefined") {
                    console.log(this.name)
                }     
            },
            enumerable: false,
            writable: true,
            configurable: true
        });
    
        return PersonType;    
    }());
    
    二.クラスと対象の字面量関係
    1.アクセス機器get、set属性
    classはコンストラクタにインスタンス属性を定義することができ、プロトタイプにアクセス属性を定義することもでき、定義方法とオブジェクトの字面量における方法は一致しています.
    class CustomHTMLElement {
        constructor(element) {
            this.element = element;
        }
        get html() {
            return this.element.innerHTML;
        }
        set html(value) {
            this.element.innerHTML = value;
        }
    }
    
    var descriptor = Object.getOwnPropertyDescriptor(CustomHTMLElement.prototype, "html");
    console.log("get" in descriptor); // true
    console.log("set" in descriptor); // true
    console.log(descriptor.enumerable); // false
    
    jqueryを熟知しているのはhtml方法を知っているべきです.原理はこれと同じです.
    let CustomHTMLElement = (function() {
        function CustomHTMLElement(element) {
            //     new     
            if (typeof new.target === "undefined") {
                throw new Error("Constructor must be called with new");
            }
            this.element = element;
        }
        Object.defineProperty(CustomHTMLElement.prototype, "html", {
            enumerable: false,
            configurable: true,
            get: function() {
                return this.element.innerHTML;
            },
            set: function(value) {
                this.element.innerHTML = value;
            }
        });
        return CustomHTMLElement;
    }());
    
    2.Computed Member Names
    オブジェクトの字面の量に変数の属性を計算して、ES 6に新しく追加された文法があります.同様に、class方法とアクセス器も同様の計算命名ができます.
    let methodName = "sayName";
    class Person {
        constructor(name) {
            this.name = name;
        }
        [methodName]() {
            console.log(this.name);
        }
    }
    
    アクセス:
    let propertyName = "html";
    class CustomHTMLElement {
        constructor(element) {
            this.element = element;
        }
        get [propertyName]() {
            return this.element.innerHTML;
        }
        set [propertyName](value) {
            this.element.innerHTML = value;
        }    
    }
    
    3.生産方法(generatomethod)
    方法名の前に「*」を追加して、任意の方法を一つの生産器に変えてもいいです.Generator methodは対象に対してセットタイプの値であり、反復するのがとても便利です.デフォルトのディテールを定義することはSymbol.iteratorによってデフォルトのジェネレータ方法を定義できます.
    class Collection {
        constructor() {
            //        
            this.items = [];
        }
        //             ,   computed name
        *[Symbol.iterator]() {
            //           values()
            yield *this.items.values();
        }
    }
    
    var c = new Collection();
    c.items.push(1);
    c.items.push(2);
    c.items.push(3);
    for (let n of c) {
        console.log(n);
    }
    // 1
    // 2
    // 3
    
    //   spread   
    var arr = [...c];
    arr; // [1, 2, 3]
    
    4.first-citizen
    クラスの本質はfunctionであり、オブジェクトとして同じパラメータまたは戻り値として扱うことができることを意味する.
    1.パラメータとして
    function createObj(classDef) {
        return new classDef();
    }
    let obj = createObj(class {
        sayHi() {
            console.log("hi");
        }
    });
    obj.sayHi(); // "hi"
    
    2.シングルを作成し、IIEFを利用する.
    let person = new class {
        constructor(name) {
            this.name = name;
        }
    
        sayName() {
            console.log(this.name);
        }
    }("Nicholas");
    
    person.sayName(); // "Nicholas"
    
    三.静的方法static
    ES 6の前に、構造体に直接添加することによって、静的方法をシミュレートすることができます.
    function PersonType(name) {
        this.name = name;
    }
    
    //        static method
    PersonType.create = function(name) {
        //          
        return new PersonType(name);
    }
    
    // instance method
    PersonType.prototype.sayName = function() {
        console.log(this.name);
    }
    
    //     PersonType    
    var person = PersonType.create("Mike"); 
    
    ES 6 staticのキーワードを導入する
    class PersonType {
        constructor(name) {
            this.name = name;
        }
        // instance method
        sayName() {
            console.log(this.name);
        }
    
        // static method
        static create(name) {
            return new PersonType(name);
        }
    }
    
    私達はいかなる方法に対してstaticキーワードを追加して静的な方法に変えられます.また、静的なメンバーは直接にクラスを通して、インスタンスを使ってアクセスすることができません.
    四.継承
    ES 6の前に、継承を実現するのは比較的に複雑なステップであり、構造体を使って盗む必要があります.サブクラスを父親クラスの実例に向ける必要があります.(またはObject.creat()方法を見てみます.
    1.ES 6の前にアナログクラスの継承
    function Rectangle(width, height) {
        this.width = width;
        this.height = height;
    }
    Rectangle.prototype.getArea = function() {
        return this.width * this.height;
    }
    
    function Square(width) {
        //      
        Rectangle.call(this, width, width);
    }
    //     
    Square.prototype = Object.create(Rectangle.prototype, {
        constructor: {
            //          
            value: Square,
            enumerable: true,
            wriable: true,
            configurable: true
        }
    });
    var square = new Square(5);
    square.getArea(); // 25
    square instanceof Square; // true
    square instanceof Rectangle; // true
    
    2.ES 6はsuper()とextendsによって継承されます.
    class Rectangle {
        constructor(width, height) {
            this.width = width;
            this.height = height;
        }
    
        getArea() {
            return this.width * this.height;
        }
    }
    
    class Square extends Rectangle {
        constructor(width) {
            //   super()
            super(width, width);
        }
    }
    
    var square = new Square(5);
    square.getArea(); // 25
    square instanceof Square; // true
    square instanceof Rectangle; // true
    
    このような書き方は非常に簡潔であると同時にsuper()の注意事項を明確に使うことができます.
  • は派生クラス(すなわちサブクラス)コンストラクタにsuper()を使用することができ、非派生クラスまたは一つのfunctionにsuperを使用すると、例外を投げます.
  • は、サースにアクセスする前にsuper()を呼び出す必要があります.
  • super()の呼び出しを唯一回避することができる方法は、オブジェクトコンストラクタからオブジェクトを返す
  • である.
    3.子類overrideの親の方法
    他のプログラミング言語を学んだことがある人は、確かにoverrideを知っています.父の種類の属性を書き換えることができます.ES 6もこのような選択を提供しています.直接書き換えることができます.super.property方式も使えます.
    class Square extends Rectangle {
        constructor(width) {
            super(width, width);
        }
    
        //     shadow        getArea
        getArea() {
            return this.width * this.width;
        }
    } 
    
    またはsuperを使用する
    class Square extends Rectangle {
        constructor(width) {
            super(width, width);
        }
        // override or shadow            
        getArea() {
            return super.getArea();
        }
    }
    
    4.親の静的な属性を継承する
    父のクラスの静的属性は同様に継承されることができます.これはES 6の新しい概念です.
     class Rectangle {
        constructor(width, height) {
            this.width = width;
            this.height = height;
        }
    
        getArea() {
            return this.width * this.height;
        }
        //     
        static create(width, height) {
            return new Rectangle(width, height);
        } 
    }
    
    class Square extends Rectangle {
        constructor(width) {
            super(width, width);
        }
    }
    
    //           
    var rect = Square.create(3, 4);
    console.log(rect instanceof Rectangle); // true
    rect.getArea(); // 12
    console.log(rect instanceof Square); // false   !
    
    5.派生クラス継承自己表現
    1.表現が[[Costruct]]属性と原型を持つと他の種類に継承されます.この方式はダイナミックな継承に大きな便宜を提供します.
    //    ,   [[Construct]]  
    function Rectangel(width, height) {
        this.width = width;
        this.height = height;
    }
    //     
    Rectangle.prototype.getArea = function() {
        return this.widht * this,height;
    }
    
    //     
    class Square extends Rectangle {
        constructor(width) {
            super(width, width);
        }
    }
    var x = new Square(4);
    x.getArea(); // 16
    x instanceof Rectangle; // true
    
    2.ダイナミックな決定は誰から継承されますか?
    //    ,   [[Construct]]  
    function Rectangel(width, height) {
        this.width = width;
        this.height = height;
    }
    //     
    Rectangle.prototype.getArea = function() {
        return this.widht * this,height;
    }
    
    //       ,      Rectangle    
    function getBase() {
        return Rectangle;
    }
    //       ,   getBase()
    class Square extends getBase() {
        constructor(width) {
            super(width, width);
        }
    }
    var x = new Square(4);
    x.getArea(); // 16
    x instanceof Rectangle; // true
    
    3.上記の方法はmixinsを作成し、複数の属性を同じクラスに追加し、サブクラスがこのmixinsを継承するために使用できます.
    let serializableMixin = {
        serialize() {
            return JSON.stringify(this);
        }
    };
    
    let AreaMixin = {
        getArea() {
            return this.width * this.height;
        }
    };
    
    //   mixins,  Object.assign()
    function Mixins(...mixins) {
        //       base  
        var base = function() {};
        Object.assign(base.prototype, ...mixins);
        return base;
    }
    //   Mixins
    class Square extends Mixins(serializableMixin, AreaMixin) {
        constructor(width) {
            super(width, width);
            //       
            this.width = width;
            this.height = width;
        }
    }    
    var s = new Square(3);
    s.getArea(); // 9
    s.serilize(); // {"width": 3, "height: 3}
    
    4.内蔵クラス(built-in)ES 6を継承する前に内蔵クラスを継承すると問題が発生しますが、ES 6は内蔵クラス、例えばArayクラスを継承することができます.
    class MyArray extends Array {
        // empty
    }
    var myArray = new MyArray();
    myArray[0] = "red";
    myArray.length; // 1
    
    5.Symbol.species
    Symbol.species:一つの静的アクセス器の属性は、一つのfunctionを返すために使用され、このfunctionは一つのコンストラクタであり、例が内部法を使用して作成しなければならない場合、下記の内蔵タイプはSymbol.species属性を持つ:
  • Aray
  • ArayBuffer
  • Map
  • Promise
  • RegExp
  • セット
  • Type Aray
  • eg:
    class MyClass {
        static get [Symbol.species]() {
            return this;
        }
    
        constructor(value) {
            this.value = value;
        }
    
        //           ,           
        clone() {
            return new this.constructor[Symbol.species](this.value);
        }
    }
    
    //    
    class DerivedClass1 extends MyClass {
        // empty
    }
    class DerivedClass2 extends MyClass {
        //   
        static get [Symbol.species]() {
            return MyClass;
        }
    }
    let instance1 = new DerivedClass1("foo"),
        clone1 = instance1.clone();
    let instance2 = new DerivedClass2("bar"),
        clone2 = instance2.clone();
    
    instance1 intanceof MyClass; // true
    clone1 instanceof DerivedClass1; // true
    instance2 instanceof MyClass; // true
    clone2 instanceof DerivedClass2; // false
    
    例えば上記の例:
    class MyArray extends Array {
          static get [Symbol.species]() {
            return Array;
          }
    }
    var arr = new MyArray(1,2,3,4);
    var sub = arr.slice(1, 3);
    sub instanceof Array; // true
    sub instanceof MyArray; // false
    
    五.抽象クラスの作成
    抽象類の本質は、親がnewオペレータを使っている時に例外を出すことです.これはnew.targetを使ってコントロールできます.
    class Sharp {
        contructor(width, height) {
            //   new                
            if (new.target === Sharp) {
                throw new Error("Abstract class cannot be instantiated");
            }
    
        }
    }
    
    class Rectangle extends Sharp {
        contructor(width, height) {
            super();
            this.width = width;
            this.height = height;
        }
    }
    
    var sharp = new Sharp(); // ERROR
    var rec = new Rectangel(10, 5);
    rec instanceof Sharp; // true
    
    締め括りをつける
    クラスの出現はクラスの作成に極めて便利であり、このような構文糖は本質的にはまだfunctionであり、関数としてあるべき特性を持っています.例えば、いくつかの表現方法がfirst-citizenとしてパラメータまたは戻り値として使用できます.また、書く側と対象の字面量は十分に分かりますので、いろいろな略字を提供します.また、最も重要なのは継承の実現に非常に友好的であると同時に、mixinsは、拡張機能に大きな便宜を提供し、抽象的な出現もあります.これはjavascriptをさらにOOPにしました.