美文网首页
初步接触ES6中的Class

初步接触ES6中的Class

作者: Ohbug | 来源:发表于2017-11-19 16:32 被阅读0次

    初步接触并认识ES6中的Class

    最近接触到了Class,通过查阮一峰大哥的《ECMAScript 6 入门》以及其它资料总算有了一个初步的认识,在这记录下来。
    有什么写的不对的地方还请指出来,我会及时改正。
    过去生成实例对象都通过构造函数 例如以下
    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);
    
    prototype是个什么鬼?
    查了几篇资料以后总算明白了

    1. 构造函数中的prototype

    要理解prototype 首先要理解构造函数

    所谓构造函数,就是提供了一个生成对象的模板并描述对象的基本结构的函数。一个构造函数,可以生成多个对象,每个对象都有相同的结构。总的来说,构造函数就是对象的模板,对象就是构造函数的实例。

    构造函数的特点有:

    a:构造函数的函数名首字母必须大写。

    b:内部使用this对象,来指向将要生成的对象实例。

    c:使用new操作符来调用构造函数,并返回对象实例。

    例如以下:

    function Person(){
     this.name = 'keith';
    } 
    var boy = new Person();
    console.log(boy.name); //'keith'
    
    ok 构造函数懂了,和prototype有什么关系呢?我们先看下构造函数的一个特点

    所有的实例对象都可以继承构造函数中的属性和方法。但是,同一个对象实例之间,无法共享属性。

    function Person(name,height){
     this.name=name;
     this.height=height;
     this.hobby=function(){
       return 'watching movies';
     }
    } 
    var boy=new Person('keith',180);
    var girl=new Person('rascal',153); 
    console.log(boy.name); //'keith'
    console.log(girl.name); //'rascal'
    console.log(boy.hobby===girl.hobby); //false
    

    上面代码中,一个构造函数Person生成了两个对象实例boy和girl,并且有两个属性和一个方法。但是,它们的hobby方法是不一样的。也就是说,每当你使用new来调用构造函数放回一个对象实例的时候,都会创建一个hobby方法。这既没有必要,又浪费资源,因为所有hobby方法都是童颜的行为,完全可以被两个对象实例共享。

    所以,构造函数的缺点就是:同一个构造函数的对象实例之间无法共享属性或方法。

    为了解决构造函数的对象实例之间无法共享属性的缺点,js提供了prototype属性。

    js中每个数据类型都是对象(除了null和undefined),而每个对象都继承自另外一个对象,后者称为“原型”(prototype)对象,只有null除外,它没有自己的原型对象。

    原型对象上的所有属性和方法,都会被对象实例所共享。

    通过构造函数生成对象实例时,会将对象实例的原型指向构造函数的prototype属性。每一个构造函数都有一个prototype属性,这个属性就是对象实例的原型对象。

    function Person(name,height){
     this.name=name;
     this.height=height;
    } 
    Person.prototype.hobby=function(){
     return 'watching movies';
    }
    var boy=new Person('keith',180);
    var girl=new Person('rascal',153); 
    console.log(boy.name); //'keith'
    console.log(girl.name); //'rascal'
    console.log(boy.hobby===girl.hobby); //true
    

    上面代码中,如果将hobby方法放在原型对象上,那么两个实例对象都共享着同一个方法。我希望大家都能理解的是,对于构造函数来说,prototype是作为构造函数的属性;对于对象实例来说,prototype是对象实例的原型对象。所以prototype即是属性,又是对象。

    原型对象的属性不是对象实例的属性。对象实例的属性是继承自构造函数定义的属性,因为构造函数内部有一个this关键字来指向将要生成的对象实例。对象实例的属性,其实就是构造函数内部定义的属性。只要修改原型对象上的属性和方法,变动就会立刻体现在所有对象实例上。

    有没有感觉看文字头大 但是一看代码秒懂 下面两端代码能让你的理解更清晰
    Person.prototype.hobby=function(){
     return 'swimming';
    }
    console.log(boy.hobby===girl.hobby); //true
    console.log(boy.hobby()); //'swimming'
    console.log(girl.hobby()); //'swimming'
    

    上面代码中,当修改了原型对象的hobby方法之后,两个对象实例都发生了变化。这是因为对象实例其实是没有hobby方法,都是读取原型对象的hobby方法。也就是说,当某个对象实例没有该属性和方法时,就会到原型对象上去查找。如果实例对象自身有某个属性或方法,就不会去原型对象上查找。

    boy.hobby=function(){
     return 'play basketball';
    }
    console.log(boy.hobby()); //'play basketball'
    console.log(girl.hobby()); //'swimming'
    

    上面代码中,boy对象实例的hobby方法修改时,就不会在继承原型对象上的hobby方法了。不过girl仍然会继承原型对象的方法。

    总结一下:

    a:原型对象的作用,就是定义所有对象实例所共享的属性和方法。

    b:prototype,对于构造函数来说,它是一个属性;对于对象实例来说,它是一个原型对象。

    ok 扯了这么多还是回到这段代码
    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);
    

    2. Class初步认识

    现在我们应该理解 Point.prototype.toString 是给Point这个构造函数加入一个toString方法 所以继续向下看

    上面的代码用 ES6 的class改写,就是下面这样。

    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    
      toString() {
        return '(' + this.x + ', ' + this.y + ')';
      }
    }
    

    上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5 的构造函数Point,对应 ES6 的Point类的构造方法。

    Point类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。

    ES6 的类,完全可以看作构造函数的另一种写法。

    class Point {
      // ...
    }
    
    typeof Point // "function"
    Point === Point.prototype.constructor // true
    

    上面代码表明,类的数据类型就是函数,类本身就指向构造函数。

    使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。

    class Bar {
      doStuff() {
        console.log('stuff');
      }
    }
    
    var b = new Bar();
    b.doStuff() // "stuff"
    

    构造函数的prototype属性,在 ES6 的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

    看到这里 我做了个实验 相当于复习prototype了
    class Bar {
        doStuff() {
            console.log('stuff');
        }
    }
    
    var b = new Bar();
    b.doStuff() // "stuff"
    
    Bar.prototype.doStuff = function () {
        console.log('???');
    }
    var a = new Bar();
    b.doStuff(); // "???"
    a.doStuff(); // "???"
    

    在类的实例上面调用方法,其实就是调用原型上的方法。

    class B {}
    let b = new B();
    
    b.constructor === B.prototype.constructor // true
    

    上面代码中,b是B类的实例,它的constructor方法就是B类原型的constructor方法。

    3. Class 的取值函数(getter)和存值函数(setter)

    与 ES5 一样,在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。

    class MyClass {
      constructor() {
        // ...
      }
      get prop() {
        return 'getter';
      }
      set prop(value) {
        console.log('setter: '+value);
      }
    }
    
    let inst = new MyClass();
    
    inst.prop = 123;
    // setter: 123
    
    inst.prop
    // 'getter'
    
    不了解 get 和 set 的可以传送过去了解一下 => link

    4. Class 的静态方法

    类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。

    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    Foo.classMethod() // 'hello'
    
    var foo = new Foo();
    foo.classMethod()
    // TypeError: foo.classMethod is not a function
    

    上面代码中,Foo类的classMethod方法前有static关键字,表明该方法是一个静态方法,可以直接在Foo类上调用(Foo.classMethod()),而不是在Foo类的实例上调用。如果在实例上调用静态方法,会抛出一个错误,表示不存在该方法。

    注意,如果静态方法包含this关键字,这个this指的是类,而不是实例。

    class Foo {
      static bar () {
        this.baz();
      }
      static baz () {
        console.log('hello');
      }
      baz () {
        console.log('world');
      }
    }
    
    Foo.bar() // hello
    

    上面代码中,静态方法bar调用了this.baz,这里的this指的是Foo类,而不是Foo的实例,等同于调用Foo.baz。另外,从这个例子还可以看出,静态方法可以与非静态方法重名。

    父类的静态方法,可以被子类继承。

    class Foo {
      static classMethod() {
        return 'hello';
      }
    }
    
    class Bar extends Foo {
    }
    
    Bar.classMethod() // 'hello'
    
    看完这里附上自己的理解
    class Foo {
        static bar() { //1
            this.baz();
        }
        static baz() { //2
            console.log('hello');
        }
        baz() { //3
            console.log('world');
        }
    }
    
    Foo.bar(); // hello
    
    let f = new Foo();
    f.baz(); // world
    
    看完上面的栗子应该一目了然
    如果直接调用Foo的bar方法 会执行方法1 方法1中执行的baz方法会执行方法2(这里的this指向类 Foo 而不是实例 f)
    下面的实例 f 调用baz的时候只能执行方法3 因为static方法不会被实例继承 所以实例 f 找不到方法1和2呀

    附上参考文章链接

    [1] http://es6.ruanyifeng.com/#docs/class

    [2] https://www.cnblogs.com/qiyebao/p/4028129.html

    [3] http://www.jb51.net/article/91826.htm

    [4] https://www.zhihu.com/question/34183746/answer/58068402

    相关文章

      网友评论

          本文标题:初步接触ES6中的Class

          本文链接:https://www.haomeiwen.com/subject/ckqsvxtx.html