ES6中的class创建类

来源:互联网 发布:phantomjs java 编辑:程序博客网 时间:2024/06/09 17:25

转载请注明预见才能遇见的博客:http://my.csdn.net/

原文地址:http://blog.csdn.net/pcaxb/article/details/53759637

ES6 类(Class)基本用法和静态属性+方法详解

JavaScript语言的传统方法是通过构造函数,定义并生成新对象,prototype 属性使您有能力向对象添加属性和方法。下面是通过传统的方式创建和使用对象的案例:

[html] view plain copy
print?
  1. <span style="font-size:18px;">//Person.js  
  2. function Person(x,y){  
  3.     this.x = x;  
  4.     this.y = y;  
  5. }  
  6.   
  7. Person.prototype.toString = function (){  
  8.     return (this.x + "的年龄是" +this.y+"岁");  
  9. }  
  10. export {Person};  
  11. //index.js  
  12. import {Person} from './Person';  
  13. let person = new Person('张三',12);  
  14. console.log(person.toString());</span>  

1.Class的基本用法

ES6引入了Class(类)这个概念,作为对象的模板,通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。上面的代码用ES6的“类”改写,就是下面这样。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//Person.js  
  2. class Person{  
  3.     // 构造  
  4.     constructor(x,y){  
  5.         this.x = x;  
  6.         this.y = y;  
  7.     }  
  8.   
  9.   
  10.     toString(){  
  11.         return (this.x + "的年龄是" +this.y+"岁");  
  12.     }  
  13. }  
  14. export {Person};  
  15. //index.js  
  16. import {Person} from './Person';  
  17. let person = new Person('张三',12);  
  18. console.log(person.toString());</span>  
上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5的构造函数Person,对应ES6的Person类的构造方法。Person类除了构造方法,还定义了一个toString方法。注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。另外,方法之间不需要逗号分隔,加了会报错。
ES6的类,完全可以看作构造函数的另一种写法。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//Person.js  
  2. console.log(typeof Person);//function  
  3. console.log(Person === Person.prototype.constructor);//true</span>  
上面代码表明,类的数据类型就是函数,类本身就指向构造函数。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//Person.js  
  2. console.log(Person.prototype);//输出的是一个对象</span>  
构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面,通过以下方式可是覆盖类中的方法,当然定义类的时候也可以通过这个方式添加方法。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//index.js  
  2. Person.prototype = {  
  3.     getName(){  
  4.         return '张三';  
  5.     },  
  6.     getAge(){  
  7.         return '12';  
  8.     }  
  9. };</span>  
在类的实例上面调用方法,其实就是调用原型上的方法
[html] view plain copy
print?
  1. <span style="font-size:18px;">//index.js  
  2. console.log(person.constructor === Person.prototype.constructor);//true</span>  
Object.assign方法可以给对象Person动态的增加方法,而Person.prototype = {}是覆盖对象的方法,或者在初始化的时候添加方法。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//index.js  
  2. Object.assign(Person.prototype,{  
  3.     getWidth(){  
  4.         console.log('12');  
  5.     },  
  6.     getHeight(){  
  7.         console.log('24');  
  8.     }  
  9. });  
  10. console.log(Person.prototype);</span>  
toString方法是Person类内部定义的方法,ES6中它是不可枚举的,这一点与ES5的行为不一致,ES5是可以枚举的。

[html] view plain copy
print?
  1. <span style="font-size:18px;">//index.js  
  2. //ES5  
  3. console.log(Object.keys(Person.prototype));//["toString", "getWidth", "getHeight"]  
  4. console.log(Object.getOwnPropertyNames(Person.prototype));//["constructor", "toString", "getWidth", "getHeight"]  
  5.   
  6. //ES6  
  7. console.log(Object.keys(Person.prototype));//["getWidth", "getHeight"]  
  8. console.log(Object.getOwnPropertyNames(Person.prototype));//["constructor", "toString", "getWidth", "getHeight"]</span>  
Object.keys(obj),返回一个数组,数组里是该obj可被枚举的所有属性。Object.getOwnPropertyNames(obj),返回一个数组,数组里是该obj上所有的实例属性。

在ES6中,类的属性名可以使用表达式,具体实现方式如下

[html] view plain copy
print?
  1. <span style="font-size:18px;">//Article.js  
  2. let methodName = "getTitle";  
  3. export default class Article{  
  4.     [methodName](){  
  5.         console.log('输出文章的标题1');  
  6.     }  
  7. }  
  8. //index.js  
  9. import Article from './Article';  
  10. //console.log(Article.prototype);  
  11. let article = new Article();  
  12. article.getTitle()</span>  
constructor方法是类的构造函数是默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个默认的constructor方法会被添加。所以即使你没有添加构造函数,也是有默认的构造函数的。一般constructor方法默认返回实例对象this,但是也可以指定constructor方法返回一个全新的对象,让返回的实例对象不是该类的实例。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//ConstructorStu.js  
  2. import Article from './Article';  
  3. export default class ConstructorStu{  
  4.     // 构造  
  5.     constructor() {  
  6.         console.log('constructor');  
  7.         return new Article();  
  8.     }  
  9. }  
  10. //index.js  
  11. import ConstructorStu from './ConstructorStu';  
  12. console.log('==111==');  
  13. console.log(new ConstructorStu() instanceof ConstructorStu);//false  
  14. console.log('==222==');  
  15. let cons =  new ConstructorStu();  
  16. console.log('==333==');  
  17. cons.constructor();  
  18. console.log('==444==');  
  19. 运行结果  
  20. ==111==  
  21. constructor  
  22. false  
  23. ==222==  
  24. constructor  
  25. ==333==  
  26. ==444==</span>  
说明:类的构造函数,不使用new是没法调用的,即使你使用实例对象去调用也是不行的,这是它跟普通构造函数的一个主要区别。
实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上(即定义在class上)。hasOwnProperty()函数用于指示一个对象自身(不包括原型链)是否具有指定名称的属性。如果有,返回true,否则返回false。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//Person.js  
  2. class Person{  
  3.     // 构造  
  4.     constructor(x,y){  
  5.         this.x = x;  
  6.         this.y = y;  
  7.     }  
  8.   
  9.     toString(){  
  10.         return (this.x + "的年龄是" +this.y+"岁");  
  11.     }  
  12. }  
  13.   
  14. let person = new Person('lis',8);  
  15. console.log(person.toString());  
  16. console.log(person.hasOwnProperty('x'));//true  
  17. console.log(person.hasOwnProperty('y'));//true  
  18. console.log(person.hasOwnProperty('toString'));//false  
  19. console.log(person.__proto__.hasOwnProperty('toString'));//true</span>  
说明:上面结果说明对象上有x,y属性,但是没有toString属性。也就是说x,y是定义在this对象上,toString定义在类上。
[html] view plain copy
print?
  1. <span style="font-size:18px;">let person1 = new Person('张三',12);  
  2. let person2 = new Person('李四',13);  
  3. console.log(person1.__proto__ === person2.__proto__);//true</span>  
类的所有实例共享一个原型对象,person1和person2都是Person的实例,它们的原型都是Person.prototype,所以__proto__属性是相等的。这也意味着,可以通过实例的__proto__属性为Class添加方法。
[html] view plain copy
print?
  1. <span style="font-size:18px;">let person1 = new Person('张三',12);  
  2. let person2 = new Person('李四',13);  
  3. person1.__proto__.getH = function (){  
  4.     return "Height";  
  5. };  
  6. console.log(person1.getH());  
  7. console.log(person2.getH());</span>  
上面代码在person1的原型上添加了一个getH方法,由于person1的原型就是person2的原型,因此person2也可以调用这个方法。而且,此后新建的实例person3也可以调用这个方法。这意味着,使用实例的__proto__属性改写原型,必须相当谨慎,不推荐使用,因为这会改变Class的原始定义,影响到所有实例。
__proto__参考资料:点击打开链接

class不存在变量提升,需要先定义再使用,因为ES6不会把类的声明提升到代码头部,但是ES5就不一样,ES5存在变量提升,可以先使用,然后再定义。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//正确  
  2. new A();  
  3. function A(){  
  4.   
  5. }//ES5可以先使用再定义,存在变量提升  
  6. //错误  
  7. new B();  
  8. class B{  
  9.   
  10. }//B is not a constructor  
  11. //ES6不能先使用再定义,不存在变量提升</span>  
这个类的名字是Expression而不是Expre,Expre只在Class的内部代码可用,指代当前类。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//Expression.js  
  2. const Expression = class Expre{  
  3.   
  4.     static getAge(){  
  5.         return '12';  
  6.     }  
  7.   
  8.     getClassName(){  
  9.         return " ClassName1" +Expre.name + " ClassName2= " +Expression.name;  
  10.     }  
  11.   
  12. };  
  13.   
  14. let exp = new Expression();  
  15. //let exp = new Expre();错误  
  16. //bundle.js:7935 Uncaught ReferenceError: Expre is not defined  
  17. console.log(exp.getClassName());//ClassName1Expre ClassName2Expre  
  18. //console.log(Expre.getAge());错误  
  19. //bundle.js:7935 Uncaught ReferenceError: Expre is not defined  
  20. console.log(Expression.getAge());</span>  
说明:Expre.name和Expression.name返回的都是Expre,返回的都是当前。
如果类的内部没用到的话,可以省略Expre,也就是可以写成下面的形式
[html] view plain copy
print?
  1. <span style="font-size:18px;">//Expression.js  
  2. const MyExpre = class{  
  3.   
  4.    getClassName(){  
  5.         return MyExpre.name;  
  6.     }  
  7.   
  8. };  
  9.   
  10. let myExpre = new MyExpre();  
  11. console.log(myExpre.getClassName());//MyExpre</span>  
说明:如果省略了class后面的那个名字Expre,MyExpre.name返回的就是MyExpre,如果没有省略MyExpre.name返回就是class后面的那个名字Expre。
采用Class表达式,可以写出立即执行的Class
[html] view plain copy
print?
  1. <span style="font-size:18px;">//Expression.js  
  2. let person = new class{  
  3.   
  4.     // 构造  
  5.     constructor(props) {  
  6.         this.props = props;  
  7.     }  
  8.   
  9.     getProps(){  
  10.         return this.props;  
  11.     }  
  12.   
  13. }('构造函数的参数');  
  14.   
  15. console.log(person.getProps());//构造函数的参数</span>  

私有方法是常见需求,但ES6不提供,只能通过变通方法模拟实现。一种做法是在命名上加以区别,在方法前面加上_(下划线),表示这是一个只限于内部使用的私有方法。但是,这种命名是不保险的,在类的外部,还是可以调用到这个方法。另一种方法就是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//PrivateMethod.js  
  2. export default class PrivateMethod{  
  3.   
  4.     // 构造  
  5.     constructor() {  
  6.     }  
  7.   
  8.     getName(){  
  9.         priName();  
  10.     }  
  11. }  
  12.   
  13. function priName(){  
  14.     console.log('私有方法测试');  
  15. }  
  16. //index.js  
  17. import PriMe from './PrivateMethod';  
  18. let prime = new PriMe();  
  19. prime.getName();</span>  
说明:通过这种方式还可以定义私有属性,同理。还有一种方法是利用Symbol值的唯一性,将私有方法的名字命名为一个Symbol值,不过这种方式稍微麻烦点。
类的方法内部如果含有this,它默认指向类的实例。getName方法中的this,默认指向ThisStu类的实例。但是,如果将这个方法提取出来单独使用,this会指向该方法运行时所在的环境,因为找不到name方法而导致报错。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//ThisStu.js  
  2. class ThisStu{  
  3.   
  4.     getName(){  
  5.         return this.name();  
  6.     }  
  7.   
  8.     name(){  
  9.         return '王五';  
  10.     }  
  11.   
  12. }  
  13. export {ThisStu};  
  14.   
  15. //index.js  
  16. import {ThisStu} from './ThisStu';  
  17. let thisStu = new ThisStu();  
  18. console.log(thisStu.getName());  
  19. const {getName} = thisStu;  
  20. getName();  
  21. //Cannot read property 'name' of undefined</span>  
一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到name方法了。修改ThisStu.js文件如下:
[html] view plain copy
print?
  1. <span style="font-size:18px;">//ThisStu.js  
  2. class ThisStu{  
  3.     // 构造  
  4.     constructor() {  
  5.         this.getName = this.getName.bind(this);  
  6.     }  
  7.   
  8.     getName(){  
  9.         return this.name();  
  10.     }  
  11.   
  12.     name(){  
  13.         return '王五';  
  14.     }  
  15.   
  16. }  
  17. export {ThisStu};</span>  
使用构造函数,直接给当前实例的getName赋值,修改修改ThisStu.js文件的构造函数如下:
[html] view plain copy
print?
  1. <span style="font-size:18px;">// 构造  
  2. constructor() {  
  3.     //this.getName = this.getName.bind(this);  
  4.     this.getName = ()=>{  
  5.         return this.name();  
  6.     }  
  7. }  
  8. </span>  

2.Class的静态方法
类相当于实例的原型,所有在类中定义的方法,都会被实例继承。如果在一个方法前,加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//StaticMethod.js  
  2. //定义静态方法  
  3. static getAge(){  
  4.  return '获取Age的静态方法';  
  5. }  
  6. //通过类名直接调用  
  7. console.log(StaticMethod.getAge());</span>  
如果StaticMethod继承StaticMethodParent,StaticMethodParent的静态方法,可以被StaticMethod继承。
[html] view plain copy
print?
  1. <span style="font-size:18px;">//StaticMethodParent.js  
  2. export default class StaticMethodParent{  
  3.     static getCommon(){  
  4.         return '父类的静态方法';  
  5.     }  
  6. }  
  7. //通过子类直接调用  
  8. console.log(StaticMethod.getCommon());  
  9. 如果StaticMethod继承StaticMethodParent,StaticMethodParent的静态方法,可以在StaticMethod中用super调用。  
  10. //定义静态方法  
  11. static getAge(){  
  12.     //子类可以调用父类的静态方法  
  13.     console.log(super.getCommon());  
  14.     return '获取Age的静态方法';  
  15. }</span>  
说明:静态方法只能在静态方法中调用,不能再实例方法中调用。

3.Class静态属性和实例属性
静态属性指的是Class本身的属性,即Class.propname,而不是定义在实例对象(this)上的属性。ES6使用静态属性和实例属性:
[html] view plain copy
print?
  1. <span style="font-size:18px;">//StaticMethod.js  
  2. //定义静态属性  
  3. StaticMethod.firstName = 'pca';  
  4. console.log(StaticMethod.firstName);  
  5. //定义实例属性  
  6. //ES6实例属性只能在constructor构造函数中定义  
  7. constructor() {  
  8.     super();  
  9.     this.width = '40cm';  
  10. }  
  11. getWidth(){  
  12.     return this.width;//使用的时候需要加上this  
  13. }  
  14. //为了可读性的目的,对于那些在constructor里面已经定义的实例属性,新写法允许直接列出。  
  15. width;</span>  
说明:目前ES6,只有这种写法可行,因为ES6明确规定,Class内部只有静态方法,没有静态属性。

ES7有一个静态属性的提案,目前Babel转码器支持。安装babel-preset-stage-0 包含了0-3的stage,可根据需要添加,不同的stage封装了不同的插件,官方推荐是使用stage-1
安装命令(根据自己的需求调整):
[html] view plain copy
print?
  1. <span style="font-size:18px;">npm install --save babel-preset-stage-0</span>  
ES7使用静态属性和实例属性:
[html] view plain copy
print?
  1. <span style="font-size:18px;">//StaticMethod.js  
  2. //ES7提案 定义静态属性  
  3. static lastName = 'pcaca';  
  4. //ES7定义实例属性  
  5. height = '150cm';</span>  
说明:ES7和ES6的静态属性和实例属性只是定义不一样,调用的方式是一样的

Class的静态方法/Class静态属性和实例属性的整个案例:
[html] view plain copy
print?
  1. <span style="font-size:18px;">//StaticMethodParent.js  
  2. export default class StaticMethodParent{  
  3.     static getCommon(){  
  4.         return '父类的静态方法';  
  5.     }  
  6. }  
  7.   
  8. //StaticMethod.js  
  9. import StaticMethodParent from './StaticMethodParent'  
  10.   
  11. //定义静态属性和静态方法  
  12. class StaticMethod extends StaticMethodParent{  
  13.     //因为ES6明确规定,Class内部只有静态方法,没有静态属性,所以ES6在类中定义静态属性都是错误的。  
  14.     //static lastName = 'pcaca';ES6错误  
  15.   
  16.     //ES7提案 定义静态属性  
  17.     //安装babel-preset-stage-0 包含了0-3的stage,可根据需要添加,  
  18.     //不同的stage封装了不同的插件,官方推荐是使用stage-1  
  19.     static lastName = 'pcaca';  
  20.   
  21.     //ES7定义实例属性  
  22.     height = '150cm';  
  23.   
  24.     getHeight(){  
  25.         return this.height;//ES7的使用也要加上this  
  26.     }  
  27.   
  28.     //ES6实例属性只能在constructor构造函数中定义  
  29.     constructor() {  
  30.         super();  
  31.         this.width = '40cm';  
  32.     }  
  33.   
  34.     //为了可读性的目的,对于那些在constructor里面已经定义的实例属性,新写法允许直接列出。  
  35.     width;  
  36.   
  37.     getWidth(){  
  38.         return this.width;//使用的时候需要加上this  
  39.     }  
  40.   
  41.     //定义静态方法  
  42.     static getAge(){  
  43.         //子类可以调用父类的静态方法  
  44.         console.log(super.getCommon());  
  45.         return '获取Age的静态方法';  
  46.     }  
  47. };  
  48.   
  49. //定义静态属性  
  50. StaticMethod.firstName = 'pca';  
  51.   
  52. export {StaticMethod};  
  53.   
  54. //index.js  
  55. import {StaticMethod} from './StaticMethod';  
  56. console.log(StaticMethod.getAge());  
  57. console.log(StaticMethod.getCommon());  
  58. console.log(StaticMethod.firstName);  
  59. console.log(StaticMethod.lastName);  
  60. let staticMethod = new StaticMethod();  
  61. console.log(staticMethod.height);  
  62. console.log(staticMethod.getHeight());  
  63. console.log(staticMethod.width);  
  64. console.log(staticMethod.getWidth());  
  65. //staticMethod.getAge();//bundle.js:7906 Uncaught TypeError: staticMethod.getAge is not a function</span>  
参考资料:点击打开链接

ES6 类(Class)基本用法和静态属性+方法详解

博客地址:http://blog.csdn.net/pcaxb/article/details/53759637

下载地址:http://download.csdn.net/detail/pcaxb/9716137