es6引入了类的概念,让对象原型的写法更加清晰、更像面向对象编程的语法,它可以看作是一个语法糖,它的绝大部分功能,ES5都可以做到。
class类的声明方法
第一种声明式,例如:
class Res{
constructor(x,y){
this.x=x;
this.y=y;
}
a(){
return "method:a==>"+this.x;
}
b(){
return "method:b==>"+this.y;
}
}
第二种命名式,例如:
let Res=class Res{
constructor(x,y){
this.x=x;
this.y=y;
}
a(){
return "method:a==>"+this.x;
}
b(){
return "method:b==>"+this.y;
}
}
第三种匿名式,例如:
let Res=class {
constructor(x,y){
this.x=x;
this.y=y;
}
a() {
return "method:a==>" + this.x;
}
b() {
return "method:b==>" + this.y;
}
}
注意:采用命名式或者匿名式的写法是可以写出立即执行的类的,例如
let res=new class{
constructor(x,y){
this.x=x;
this.y=y;
}
a(){
return "method:a==>"+this.x;
}
b(){
return "method:b==>"+this.y;
}
}("测试a方法","测试b方法")
console.log(res.a())//method:a==>测试a方法
生成类的实例:在类名前加上new关键字
类的函数必须是类的实例来调用,类直接调用会报错,但是如果函数前加上static关键字,则是类可以直接调用该方法,而实例不能调用
//类的实例调用内部的方法
let test=new Res(1,2);
console.log(test.a());//method:a==>1
console.log(test.b());//method:b==>2
//类直接调用会报错
Res.a()//TypeError: Class constructor Res cannot be invoked without 'new'
注意 :类里写多个方法时,方法之间不要用逗号分隔,会报错,另外类的内部所有定义的方法都是不可枚举的,通俗点说就是不能遍历,然而通过prototype添加到类里的方法则是可以枚举的
class Res{
constructor(x,y){
this.x=x;
this.y=y;
}
a(){
return "method:a==>"+this.x;
}
b(){
return "method:b==>"+this.y;
}
}
//Object.keys() 方法会返回一个由一个给定对象的自身可枚举属性组成的数组
console.log(Object.keys(Res.prototype));//[]
Object.assign(Res.prototype,{
c(){},
d(){}
})
console.log(Object.keys(Res.prototype));//["c","d"]
//Object.getOwnPropertyNames获取对象的所有属性,包括不可枚举的
console.log(Object.getOwnPropertyNames(Res.prototype));//["constructor","a","b","c","d"]
类的方法名可以采用表达式,例如:
let methodA=a;
class Res{
constructor(x,y){
this.x=x;
this.y=y;
}
[methodA](){
return "method:a==>"+this.x;
}
b(){
return "method:b==>"+this.y;
}
}
class类的constructor
构造函数constructor是类的默认方法,一个类如果没有定义constructor方法,一个空的constructor方法则会被默认添加,而通过new生成类的实例时,constructor会被自动调用,constructor默认返回实例对象this,但是也可以指定返回另外一个全新的对象,例如:
class Res{
constructor(){
return Object.create(null)
}
}
class类的prototype和__proto__
什么是原型和原型链?
原型概念:在javascript中,每一个对象(除null)外都有__proto__
属性,指向对应的构造函数的prototype属性。
原型链概念:
在javascript中,每个对象都有一个指向它的原型(prototype)对象的内部链接。每个原型对象又有自己的原型,直到某个对象的原型为null为止,组成这条链的最后一环。
要理解这两个概念,请看下面的例子:
//用new创建个字符串类型的对象
let bol=new Boolean(true);
console.log(bol);
每当我们使用new
语法的时候,对象就会拥有__proto__
这个属性,该属性会指向new之后对应函数(也就是Boolean)的prototype属性,这个属性是Number共有属性,该共有属性会拥有诸多共有方法,也就是说:
bol.__proto__ === Boolean.prototype// true
如果bol的共有方法在Boolean里找不到,就会向上一层去找,也就是向Boolean.prototype.__proto__中去找,该对象对应的就是Object.prototype,也就是说:
bol.__proto__.__proto__===Object.prototype//true
如果依旧找不到呢?那就再去上一层找,也就是Object.prototype.__proto__,找出来的值为null,在javascript的数据结构中,这就是顶层了,也就是说:
bol.__proto__.__proto__.__proto__====Object.prototype.__proto__//true
Class作为构造函数的语法糖,同时有prototype属性和__proto__属性,因此同时存在两条继承链。
(1)子类的__proto__
属性,表示构造函数的继承,总是指向父类。
(2)子类prototype
属性的__proto__
属性,表示方法的继承,总是指向父类的prototype
属性。
class A {
}
class B extends A {
}
B.__proto__ === A // true
B.prototype.__proto__ === A.prototype // true
class类的继承(extends)以及super关键字
class之间可以通过extends
关键字实现继承,例如:
class ParentClass {
constructor(x) {
this.x = x;
}
a() {
console.log("method-a");
}
}
class ChildClass extends ParentClass {
constructor(x, y) {
super(x);
this.y = y
}
childA() {
console.log("method-childA");
}
}
上面的代码中类ChildClassl的constructor里调用了super方法,它在这里表示父类的构造函数,用来新建父类的this
对象。
子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象,所以要注意子类要先在constructor函数里调用super方法,才能在super方法下面使用this关键字,代码顺序不能写错
。
判断一个类是否继承了另一个类,可以用Object.getPrototypeOf(),例如:
Object.getPrototypeOf(ChildClass)===ParentClass//true
注意,如果一个类混入了多个类就不能用这个方法判断了。
super
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的constructor函数必须执行一次super函数,并且也只能在constructor函数里调用,在类的地方调用会报错;
第二种情况,super作为对象调用时,指向的是父类的原型对象,但是需要注意的是由于指向是父类的原型对象,所以实例是无法调用super的。
class类的实现多个类的继承(mixin)
mixin指的是,将多个类的接口混入另一个类,实现mixin代码如下:
function mix(...mixins) {
class Mix {}
for (let mixin of mixins) {
copyProperties(Mix, mixin);
copyProperties(Mix.prototype, mixin.prototype);
}
return Mix;
}
function copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if (key !== "constructor" &&
key !== "prototype" &&
key !== "name"
) {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
//想要一次性继承多个类,可以将上面的mixin封装代码复制到自己的项目里
class A{
a(){
console.log("class A");
}
}
class B{
b(){
console.log("class B");
}
}
class C extends mix(A,B){
c(){
console.log("class C");
}
}
let testMix=new C();
console.log(testMix.a());//class A
console.log(testMix.b());//class B
console.log(Object.getOwnPropertyNames(C.prototype.__proto__))//["constructor","a","b"],从打印结果就可以看到A类和B类的方法都被C类继承
class类的静态方法(static)
在类中定义的所有方法,都会被实例继承,但是如果在一个方法前,加上static
关键字,就表示该方法不会被实例继承,而是直接通过类来调用,这就称为“静态方法”。例如:
class Res {
constructor() {}
static testStatic(a) {
return "method:testStatic==>" + a;
}
}
let R = Res.testStatic("类调用静态方法测试");
console.log(R) //method:a==>类调用静态方法测试
let res = new Res().testStatic("实例调用静态方法测试");
console.log(res) // TypeError: (intermediate value).testStatic is not a function
class类的私有方法
私有方法就是仅供class类的内部使用;
第一种是方法前面的下划线,表示这是一个只限于内部使用的私有方法。但是,在类的外部,是可以调用到这个方法,所以不建议使用,例如:
class Res {
constructor() {}
_test(a) {
return "method:test==>" + a;
}
}
let res=new Res();
console.log(res.a("私有方法测试"))//method:test==>私有方法测试
第二种是索性将私有方法移出模块,因为模块内部的所有方法都是对外可见的。
class Res {
constructor() {}
//公有方法
testPublic(b) {
wayPrivate.call(this,b)//使用call方法将wayPrivate变成了Res类的私有方法
return "method:test==>" + b;
}
}
function wayPrivate(b){
console.log(b);
}
let res=new Res();
console.log(res.wayPrivate("测试私有方法"))//TypeError: res.wayPrivate is not a function
第三种是利用Symbol
值的唯一性,将私有方法的名字命名为一个Symbol
值。
let way1=Symbol("wayPrivate");
let str=Symbol("str");
class Res{
wayPublic(){
console.log("公有方法");
}
[way1](param){
console.log("私有方法");
this[str]=param;//用Symbol定义的str是私有属性
}
}
class类的getter和setter
在class内部可以使用get
和set
关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。例如:
let _test="测试
class Res{
constructor(x){
}
get val(){
return _test;
}
set val(upd){
_test=upd;
}
}
let res=new Res();
console.log(res.val);//测试
res.val="重新setter val的值"
console.log(res.val)//重新setter val的值
class类的生成器(generator)
generator是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
函数在执行过程中,如果遇到return
语句,return后面的代码就不会执行,函数末尾如果没有return
,就是隐含的return undefined;
generator跟函数很像,generator和函数不同的是,generator由function*
定义(注意多出的*
号),并且,除了return
语句,还可以用yield
返回多次。在class类里将*
放在函数名前面,例如:
let res=new class{
//虽然这个类里没有定义constructor,但是类会默认添加一个空的constructor
*a(){
yield "yield返回值1";
yield "yield返回值2";
return "return返回值3";
}
}()
可以使用for.....of方法循环遍历generator对象,打印出来的结果就是yield返回值,例如:
for(let x of res.a()){
console.log(x)
//yield返回值1;
//yield返回值2;
}
也可以使用next()的方法,每次遇到yield xxx;
就返回一个对象{value: x, done: true/false}
,然后“暂停”。返回的value
就是yield
的返回值,done
表示这个generator是否已经执行结束了。如果done
为false
,则value
是yield
的返回值,如果done
为true
,则value
是return
的返回值。当执行到done
为true
时,这个generator对象就已经全部执行完毕,不要再继续调用next()
了,不建议使用这种方法,因为这方法需要我们自己去通过done
来判断循环是否结束,例如:
let r=res.a();
console.log(r.next());//{ value: 'yield返回值1', done: false }
console.log(r.next());//{ value: 'yield返回值2', done: false }
console.log(r.next());//{ value: 'return返回值3', done: true };
注意:使用next()方法执行generator时,要先将函数赋值给一个变量,然后通过这个变量来调用next()方法,如果直接通过函数来调用next()方法时,得到值永远都是第一个yield返回值,例如:
console.log(res.a().next());//{ value: 'yield返回值1', done: false }
console.log(res.a().next());//{ value: 'yield返回值1', done: false }
console.log(res.a().next());//{ value: 'yield返回值1', done: false }
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。