2.4JavaScript引用类型之类(Class)

1.类声明

类是一种“特殊的函数”,就像函数声明定义方式和函数表达式定义方式一样,定义类的方式也有两种:类声明定义方式和类表达式定义方式。

//类声明定义方式
class Rectangle {
  //类体
}
//类表达式定义方式

//命名类
let Rectangle = class Rectangle2 {
  //类体
};
console.log(Rectangle.name);
//输出类名称:Rectangle2

//未命名/匿名类
let Rectangle = class {
  //类体
};
console.log(Rectangle.name);
//输出类名称:Rectangle

2.继承

一个类只能继承自一个父类,不可以继承自多个父类。

class 子类名 extends 父类名 {
  //子类体
}

3.抽象(Abstract)类

虽然 JavaScript 没有专门支持抽象类的语法,但是我们可以通过 new.target 来实现抽象类。通过在实例化时检测 new.target 是不是抽象父类,可以阻止对抽象父类的实例化。

class Vehicle {
  constructor() {
    console.log(new.target);  
    if (new.target === Vehicle) {
      throw new Error('Vehicle cannot be directly instantiated');
    }
  }
}
class Bus extends Vehicle {}

new Bus();     //class Bus {}

new Vehicle(); //Error: Vehicle cannot be directly instantiated

4.重写(Override)

可以在抽象父类构造函数中进行检查,以要求子类必须定义某个方法。

//父类
class Vehicle {
  constructor() {
    if (new.target === Vehicle) {
      throw new Error('Vehicle cannot be directly instantiated');
    }

    if(!this.foo) {
      throw new Error('Inheriting class must define foo()');
    }

    console.log('success!');
  }
}

//子类
class Bus extends Vehicle {
  foo() {};
}

//子类
class Van extends Vehicle {}

new Bus();  // success!
new Van();  // Error: Inheriting class must define foo()

5.构造函数

一个类非必须有构造函数。如果是父类没有构造函数,相当于父类中定义了一个空的构造函数。如果是子类没有构造函数,相当于使用继承自父类的构造函数。如果有构造函数,则只能有一个构造函数。

class 类名 {
  constructor() {
    //构造函数体
  }
}
//实例属性的值只能由构造函数的参数传递给它。
class Rectangle {
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}
//实例字段不仅仅可以由构造函数的参数传递给它,还可以由访问器属性或方法等方式操作它。
class Rectangle {
  height = 0;
  width;
  constructor(height, width) {
    this.height = height;
    this.width = width;
  }
}

6.静态初始化块(Static Initialization Blocks)

一个类可以有多个静态初始化块。

构造函数是用于实例字段的初始化,而静态初始化块(Static Initialization Blocks)是用于静态字段的初始化。

class 类名 {
  static {
    //静态初始化块体
  }
}
//示例
class ClassWithStaticInitializationBlock {
  static staticProperty1 = 'Property 1';
  static staticProperty2;
  static {
    this.staticProperty2 = 'Property 2';
  }
}

console.log(ClassWithStaticInitializationBlock.staticProperty1);
//输出:Property 1

console.log(ClassWithStaticInitializationBlock.staticProperty2);
//输出:Property 2

7.创建对象

7.1类创建方式

//如果没有参数传递,可省略括号,一般不推荐省略。
let 对象 = new 类名();
let 对象 = new 类名(参数);

7.2对象字面量创建方式

let 对象 = {
  数据属性名: 值,

  方法名: function() {
    //方法体
  },

  //方法名简写
  方法名() {
    //方法体
  }

};

7.3Object()构造函数创建方式

Object()构造函数创建方式目前已很少使用,而对象字面量创建方式和类创建方式非常流行。

//等同于对象字面量创建方式let 对象 = {};
let 对象 = new Object();
对象.属性名 = 值;
对象.方法名 = function() {
  //方法体
};

8.访问成员

//类定义方式使用.访问
类.静态字段名
类.静态访问器属性名
类.静态方法名()
对象.实例字段名
对象.实例访问器属性名
对象.实例方法名()

//对象字面量方式使用.访问
对象.数据属性名
对象.访问器属性名
对象.方法名()
//对象字面量方式还可以使用[]访问属性,但不可以使用[]访问方法。
//[]内可以使用单引号或双引号,没有区别。
对象['数据属性名']
对象['访问器属性名']

使用方括号 [] 的主要优势在于支持可计算属性名。

let person = {
  name: '张三',
  age: 28
};

console.log(person.name);     // 张三
console.log(person['name']);  // 张三

let a = "name";
console.log(person[a]);       // 张三

9.重载(Overload)

JavaScript不支持重载,如果定义了两个同名函数,则后定义的会覆盖先定义的。可以通过检查参数的类型和数量,然后执行不同的逻辑来模拟函数重载。

10.访问器属性

访问器属性 getter 和 setter 可以使用相同的名称。

10.1用于类

class ClassWithGetSet {

  //静态getter
  static get staticGetter() {
    //访问器属性体
  }

  //静态setter
  static set staticSetter(参数) {
    //访问器属性体
  }
  
   //实例getter
  get instanceGetter() {
    //访问器属性体
  }

  //实例setter
  set instanceSetter(参数) {
    //访问器属性体
  }

}

10.2用于对象字面量

如果给已存在对象字面量添加访问器属性,可以使用 Object.defineProperty() 方法。

const 对象 = {

  get 访问器属性名() {
    //访问器属性体
  },

  set 访问器属性名() {
    //访问器属性体  
  }
}

11.属性值简写

在对象字面量创建对象方式中,当属性名和代表属性值的变量名一样时,可以只使用变量名简写语法。

//属性名与变量名不一样
let a = '张三';

let person = {
  //前面name是属性名,后面a是变量名。
  name: a
};

console.log(person);  // { name: "张三" }
//属性名与变量名一样
let name = '张三';

let person = {
  //前面name是属性名,后面name是变量名。
  name: name
};

//只使用变量名简写语法
let person = {
  name
};

console.log(person);  // { name: "张三" }

当右侧是返回对象字面量的函数时,也支持属性值简写。

function makePerson(name) {
  return {
    name
  };
}

let person = makePerson('张三');
console.log(person);  // { name: "张三" }

12.可计算名

12.1用于类成员

可计算名还可用于实例字段等类成员。

const PREFIX = "prefix";

class ClassWithField {
  [`${PREFIX}Field`] = "prefixed field";
}

const instance = new ClassWithField();
console.log(instance.prefixField); // prefixed field

12.2用于属性和方法

可计算名可用于数据属性名、访问器属性名、方法名。

12.2.1Object()构造函数创建对象方式

const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';

let person = new Object();
person[nameKey] = '张三';
person[ageKey] = 28;
person[jobKey] = 'Software engineer';

console.log(person);
//输出:{ name: "张三", age: 28, job: "Software engineer" }

12.2.2对象字面量创建对象方式

//数据属性名
const nameKey = 'name';
const ageKey = 'age';
const jobKey = 'job';
let person = {
  [nameKey]: '张三',
  [ageKey]: 28,
  [jobKey]: 'Software engineer'
};

console.log(person);
//输出:{ name: "张三", age: 28, job: "Software engineer" }
//访问器属性名
const expr = "foo";

const obj = {
  get [expr]() {
    return "bar";
  }
};

console.log(obj.foo);  // bar
//方法名
const methodKey = 'sayName';

let person = {
  [methodKey](name) {
    console.log(name);
  }
}

person.sayName('张三');  // 张三

12.3用于对象解构赋值

const key = "z";
const { [key]: foo } = { z: "bar" };

console.log(foo); // bar

13.super关键字

使用场景调用方式
调用父类的构造函数。只支持 super(arguments) 一种方式,必须放在 this 关键字 和 构造函数返回 之前。
调用父类的成员(支持静态字段、静态方法、实例方法,不支持实例方法)。支持 super.prop 和 super[expr]两种方式。
调用父对象字面量的访问器属性和方法。支持 super.prop 和 super[expr]两种方式。
//调用父类的构造函数
class Foo {
  constructor(name) {
    this.name = name;
  }

  getNameSeparator() {
    return '-';
  }
}

class FooBar extends Foo {
  constructor(name, index) {
    super(name);
    this.index = index;
  }

  getFullName() {
    return this.name + super.getNameSeparator() + this.index;
  }
}

const firstFooBar = new FooBar('foo', 1);

console.log(firstFooBar.name);  //输出:foo

console.log(firstFooBar.getFullName());  //输出:foo-1
//调用父类的成员
//静态方法
class Rectangle {
  static logNbSides() {
    return "I have 4 sides";
  }
}

class Square extends Rectangle {
  static logDescription() {
    return `${super.logNbSides()} which are all equal`;
  }
}
Square.logDescription(); 
//输出:I have 4 sides which are all equal

//静态字段和实例方法
class Base {
  static baseStaticField = 90;
  baseMethod() {
    return 10;
  }
}

class Extended extends Base {
  extendedField = super.baseMethod(); // 10
  static extendedStaticField = super.baseStaticField; // 90
}
//调用父对象字面量的属性
//访问器属性
const parent = { prop: 1 };

const child = {
  myParent() {
    console.log(super.prop);
  }
};

Object.setPrototypeOf(child, parent);
child.myParent();  //输出:1

//方法
const obj1 = {
  method1() {
    console.log("method 1");
  },
};

const obj2 = {
  method2() {
    super.method1();
  },
};

Object.setPrototypeOf(obj2, obj1);
obj2.method2();  //输出:method 1

14.this关键字

函数体可以包含 this 关键字,但具体 this 关键字指向谁,大多数情况下,this 指向此函数被真正调用的上下文,极个别情况下,this 指向此函数被真正声明的上下文。

//指向函数被真正调用的上下文
class MyClass {
  name = "MyClass";
  getName() {
    return this.name;
  }
}
const c = new MyClass();
const obj = {
  name: "obj",
  getName: c.getName,
};
 
console.log(obj.getName());  //输出:obj
//指向函数真正被声明的上下文
class MyClass {
  name = "MyClass";
  getName = () => {
    return this.name;
  };
}
const c = new MyClass();
const g = c.getName;
console.log(g());  //输出:MyClass

14.1全局上下文

全局上下文this指向
全局指向函数被真正调用的上下文,所以指向 window

14.2类上下文

类上下文this指向
静态上下文(静态初始化块、静态方法)指向函数被真正调用的上下文,所以指向类。
实例上下文(构造函数、实例方法)指向函数被真正调用的上下文,所以指向对象。

14.3函数上下文

函数上下文this指向
函数(普通函数、回调函数)指向函数被真正调用的上下文。
箭头函数指向函数被真正声明的上下文。

14.4bind()方法

调用 f.bind(someObject) 会创建一个与 f 函数具有相同函数体和作用域的新函数,但是 this 的值将永久绑定到 bind() 方法的第一个参数,无论如何调用该函数。

function f() {
  return this.a;
}

const g = f.bind({ a: "azerty" });
console.log(g());  //输出:azerty

//bind()方法只可以运行一次
const h = g.bind({ a: "yoo" });
console.log(h());  //输出:azerty

const o = { a: 37, f, g, h };
console.log(o.a, o.f(), o.g(), o.h());  //输出:37, 37, azerty, azerty

原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/javascript/javascriptlang/4729.html

(0)
上一篇 2020年9月3日
下一篇 2020年9月5日

相关推荐

  • 1.4JavaScript语言的类型(Type)

    JavaScript 语言有两种类型:原始类型(Primitive Type) 和 引用类型(Reference Type)。 原始类型包括:大整数(BigInt)、数值(Number)、布尔(Boolean)、字符串(String)、符号(Symbol)、未初始化(Undefined)、空(Null)。 引用类型包括…

  • 2.4JavaScript引用类型之类(Class)

    1.类声明 类是一种“特殊的函数”,就像函数声明定义方式和函数表达式定义方式一样,定义类的方式也有两种:类声明定义方式和类表达式定义方式。 2.继承 一个类只能继承自一个父类,不可以继承自多个父类。 3.抽象(Abstract)类 虽然 JavaScript 没有专门支持抽象类的语法,但是我们可以通过 new.targ…

    JavaScript语言教程 2020年9月4日 02:13
    01.1K0
  • 2.9JavaScript引用类型之生成器(Generator)

    生成器对象也实现了 Iterable 接口。 1.生成器函数声明 定义生成器函数只需要在普通函数名称前面加一个星号(*),只要是可以定义函数的地方(不支持箭头函数),就可以定义生成器。 标识生成器函数的星号不受两侧空格的影响。 2.yield yield 关键字用来多次暂停和恢复一个生成器函数,它可以被认为是…

发表回复

登录后才能评论