JavaScript中的类

简介: JavaScript中的类

js中的类

基础知识

class在js中是构造函数的语法糖,其底层设计模式依然为原型和继承

声明定义

可以使用类声明和赋值表达式定义类,推荐使用类声明来定义类

//类声明
class User {
}
console.log(new Article());
let Article = class {
};
console.log(new User());

类方法间不需要逗号

class User {
  show() {}
  get() {
    console.log("get method");
  }
}
const hd = new User();
hd.get();

类方法是直接写到原型上的,如在User类中写类方法show()相当于User.prototype.show = function(){}

构造函数

使用 constructor 构造函数传递参数,下例中show为构造函数方法,getName为原型方法

  • constructor 会在 new 时自动执行

下面的User类,实例化的user对象的属性是name和age,在他的原型上,也就是User.prototype中有getName方法

class User{
  constructor(name, age){
    this.name = name;
    this.age = age;
  }
  getName(){
    console.log(`名字是${this.name},年龄是${this.age}`);
  }
}
let user = new User('kevin', 20);
user.getName();//名字是kevin,年龄是20
console.log(user);

构造函数用于传递对象的初始参数,但不是必须定义的。

  • 子构造器中调用完super 后才可以使用 this
  • 如果需要调用父类构造方法,必须写 constructor,因为 super 方法只能写在 constructor 里面。
  • 如果你不写 constructor,那么代码执行上会给你增加一个默认的 constructor

关于最后一条,默认构造函数,基类默认的 constructor 是:

constructor() {} // 就是啥都不干

派生类默认的 constructor 是:

constructor(...args) {
  super(...args) // 用派生类实例的参数调用下基类构造函数
}

可以说,如果你的类不需要特殊处理参数(与默认的构造函数一致),那么可以省略 constructor

原理分析

类其实是函数,class实际上就是构造函数的语法糖。

class User {
}
console.log(typeof User); //function

属性定义

每次new对象都是独立的属性。

class User{
  constructor(name, age){
    this.name = name;
    this.age = age;
  }
  getName(){
    console.log(`名字是${this.name},年龄是${this.age}`);
  }
}
new User('kk', 22).getName();//名字是kk,年龄是22
new User('jj', 20).getName();//名字是jj,年龄是20

函数差异

class 是使用函数声明类的语法糖,但也有些区别

class 中定义的方法(放在类的实例对象的原型上),不能被遍历。

可以看到定义的方法的特征中的enumerable属性为false

class User{
  constructor(name, age){
    this.name = name;
    this.age = age;
  }
  getName(){
    console.log(`名字是${this.name},年龄是${this.age}`);
  }
}
console.log(
  JSON.stringify(
    Object.getOwnPropertyDescriptor(User.prototype, 'getName'),
    null,
    2
  )
);
// {
// "writable": true,
// "enumerable": false,
// "configurable": true
// }
let user = new User('kevin', 20);
for (const key in user) {
  console.log(key);//换行输出name age 没有getName方法,因为特征为不能遍历
}

严格模式

class 默认使用strict 严格模式执行

class User {
  constructor(name) {
    this.name = name;
  }
  show() {
    function test() {
      //严格模式下输出 undefined
      console.log(this);
    }
    test();
  }
}
let xj = new User("kk");
xj.show();//undefined

静态访问

静态属性

静态属性即为类设置属性,而不是为生成的对象设置,下面是原理实现

function User() {}
User.site = "dd";
console.dir(User);

const hd = new User();
console.log(hd.site); //undefiend
console.log(User.site); //dd
console.log(hd);

class 中为属性添加 static 关键字即声明为静态属性

  • 可以把为所有对象使用的值定义为静态属性
class Request{
  static Host = 'localhost';
  requestApi(api){
    return `${api}-${Request.Host}`
  }
}
let request  = new Request();
console.log(request.requestApi('www.baidu.com/'));//www.baidu.com/-localhost

静态方法

指通过类访问不能使用对象访问的方法,比如系统的Math.round()就是静态方法

  • 一般来讲方法不需要对象属性参与计算就可以定义为静态方法

下面是静态方法实现原理

function User() {
  this.show = function() {
    return "this is a object function";
  };
}
User.show = function() {
  return "welcome to houdunren";
};
const xj = new User();
console.dir(xj.show()); //this is a object function
console.dir(User.show()); //welcome to houdunren

class 内声明的方法前使用 static 定义的方法即是静态方法

class Request{
  static Host = 'localhost';
  static requestApi(api){
    return `${api}-${Request.Host}`
  }
}
console.log(Request.requestApi('www.baidu.com/'));//www.baidu.com/-localhost

下面使用静态方法在课程类中的使用

const data = [
  {name: 'js',click: 55, price: 77},
  {name: 'ts',click: 35, price: 127},
  {name: 'node',click: 45, price: 88},
];
class Lesson{
  constructor(data){
    this.model = data;
  }
  get price(){
    return this.model.price;
  }
  static totalPrice(data){//获取总价
    return data.reduce((t,c) => {
      return t + c.price
    }, 0);
  }
  static maxPrice(data){//获取最大价格
    return Math.max(...data.map(item => item.price));
  }
}
console.log(Lesson.totalPrice(data));//292
console.log(Lesson.maxPrice(data));//127

访问器

使用访问器可以对对象的属性进行访问控制,下面是使用访问器对私有属性进行管理。

语法介绍

  • 使用访问器可以管控属性,有效的防止属性随意修改
  • 访问器就是在函数前加上 get/set修饰,操作属性时不需要加函数的扩号,直接用函数名
class User{
  constructor(name){
    this.data = {name};
  }
  get name(){
    return this.data.name;
  }
  set name(value){
    if(value.trim === '') throw new Error('名字错误');
    this.data.name = value;
  }
}
let user = new User('kevin');
console.log(user.name);//kevin
user.name = 'tom'
console.log(user.name);//tom

访问控制

设置对象的私有属性有多种方式,包括后面章节介绍的模块封装。

public

public 指不受保护的属性,在类的内部与外部都可以访问到

protected

protected是受保护的属性修释,不允许外部直接操作,但可以继承后在类内部访问,有以下几种方式定义

命名保护

将属性定义为以 _ 开始,来告诉使用者这是一个私有属性,请不要在外部使用。

  • 外部修改私有属性时可以使用访问器 setter 操作
  • 但这只是提示,就像吸烟时烟盒上的吸烟有害健康,但还是可以抽的

Symbol

下面使用 Symbol定义私有访问属性,即在外部通过查看对象结构无法获取的属性

const protecteds = Symbol();
class Common {
  constructor() {
    this[protecteds] = {};
    this[protecteds].host = "https://baidu.com";
  }
  set host(url) {
    if (!/^https?:/i.test(url)) {
      throw new Error("错误");
    }
    this[protecteds].host = url;
  }
  get host() {
    return this[protecteds].host;
  }
}
class User extends Common {
  constructor(name) {
    super();
    this[protecteds].name = name;
  }
  get name() {
    return this[protecteds].name;
  }
}
let hd = new User("dd");
hd.host = "https://www.gg.com";
console.log(hd.name);//dd

WeakMap

WeakMap 是一组键/值对的集,也可以利用WeakMap类型特性定义私有属性

private

private 指私有属性,只在当前类可以访问到(只能在声明私有属性的类的函数体中使用),并且不允许继承使用

  • 为属性或方法名前加 # 为声明为私有属性
  • 私有属性只能在声明的类中使用

下面声明私有属性 #host 与私有方法 check 用于检测用户名

class User{
  //private
  #host = 'www.baidu.com';
  constructor(name){
    this.name = name;
  }
  #check = ()=>{
    return `private func`;
  }
  func(){
    return `${this.#host} && ${this.#check()}`;
  }
}
class Admin extends User{
  constructor(age){
    super();
    this.age = age;
  }
}
let user = new User('kevin');
console.log(user);//User {name: 'kevin', #host: 'www.baidu.com', #check: ƒ}
console.log(user.func());//www.baidu.com && private func

详解继承

在构造函数中实现继承

属性继承

属性继承的原型如下

function User(name) {
  this.name = name;
}
function Admin(name) {
  User.call(this, name); 
}
let hd = new Admin("dd");
console.log(hd);//Admin {name: 'dd'}

方法继承

调用父类方法时传递this

let user = {
  name: "user",
  show() {
    return this.name;
  }
};
let admin = {
  __proto__: user,
  name: "admin",
  show() {
    return this.__proto__.show.call(this);
  }
};
console.log(admin.show());

但如果是多层继承时,会出现新的问题

  • 因为始终传递的是当前对象this ,造成从 this 原型循环调用
let common = {
  show() {
    console.log("common.init");
  }
};
let user = {
  __proto__: common,
  name: "user",
  show() {
    return this.__proto__.show.call(this);
  }
};
let admin = {
  __proto__: user,
  name: "admin",
  get() {
    return this.__proto__.show.call(this);
  }
};
console.log(admin.get());

为了解决以上问题 js 提供了 super 关键字

  • 使用 super 调用时,在所有继承中 this 始终为调用对象
  • super 是用来查找当前对象的原型,而不像上面使用 this 查找原型造成死循环
  • 也就是说把查询原型方法的事情交给了 superthis 只是单纯的调用对象在各个继承中使用

super的使用

为什么super要在this前调用:因为当前类属性的优先级是要大于父类的,如果有重名属性,super放在后面就会把当前类的属性覆盖掉,所以编译器就直接规定要把super写在前面

在使用extends关键字继承后,在constructor中使用this关键字之前必须先使用super来继承属性,不然会报错。括号里面的值为想要继承的属性(当然也可以为空),在构造函数中也需要设置相应的形参,不然继承的类在new对象时不能传递相应的参数。

在继承方法时,也要使用super关键字。具体方式为super.func()func为想要继承的方法名。

class User{
  constructor(name, age){
    this.name = name;
    this.age = age;
  }
  show(){
    return `show User`;
  }
}
class Admin extends User{
  constructor(title,...args){
    super(...args);
    this.title = title;
  }
  gg(){
    return super.show();
  }
}
let admin = new Admin('js','kevin',20);
console.log(admin);
console.log(admin.gg());//show User

super 只能在类或对象的方法中使用,而不能在函数中使用

方法的重写

在子类中声明与父类同名的方法,这样使用子类的实例对象调用方法时就会用子类的方法,而不是父类(覆盖掉了)

在父类的基础上进行调整或扩展其功能,可以使用super关键字

class User{
  constructor(name, age){
    this.name = name;
    this.age = age;
  }
  show(){
    return `show User`;
  }
}
class Admin extends User{
  constructor(title,...args){
    super(...args);
    this.title = title;
  }
  show(){
    return `${super.show()}变成Admin`;
  }
}
let admin = new Admin('js','kevin',20);
console.log(admin);
console.log(admin.show());//show User变成Admin

instanceOf 和 isPrototypeOf

instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。

object instanceof constructor

参数

  • object

    某个实例对象

  • constructor

    某个构造函数

isPrototypeOf() 方法用于测试一个对象是否存在于另一个对象的原型链上。

prototypeObj.isPrototypeOf(object)

参数

  • object

    在该对象的原型链上搜寻

class User{}
class Admin extends User{}
let admin = new Admin();
console.log(admin instanceof User);//true
console.log(User.prototype.isPrototypeOf(admin));//true

继承内置类

我们可以声明一个继承于内置类的类,可以在这个类上添加功能

class Arr extends Array{
  add(value){
    this.push(value);
  }
  remove(value){
    let removeIndex = this.findIndex(item => item !== value);
    this.splice(removeIndex,1);
  }
}
let arr = new Arr(1,2,3,4,5);
arr.add(6);
console.log(arr);//Arr(6) [1, 2, 3, 4, 5, 6]
arr.remove(5)
console.log(arr);//Arr(5) [2, 3, 4, 5, 6]

mixin

JS不能实现多继承,如果要使用多个类的方法时可以使用mixin混合模式来完成。

  • mixin 类是一个包含许多供其它类使用的方法的类
  • mixin 类不用来继承做为其它类的父类

我们可以通过Object.assign()方法来将需要的对象的方法复制到需要的类中,进而模仿多继承

let ss = {
  show(){
    console.log('showshow');
  }
};
let gg = {
  gege(){
    console.log('gg');
  }
}
class User{}
Object.assign(User.prototype, ss);
Object.assign(User.prototype, gg);
let user = new User();
user.show();//showshow
user.gege();//gg
相关文章
|
4月前
|
JavaScript 前端开发
js开发:请解释原型继承和类继承的区别。
JavaScript中的原型继承和类继承用于共享对象属性和方法。原型继承通过原型链实现共享,节省内存,但不支持私有属性。
41 0
|
3月前
|
设计模式 JavaScript 前端开发
在JavaScript中,继承是一个重要的概念,它允许我们基于现有的类(或构造函数)创建新的类
【6月更文挑战第15天】JavaScript继承促进代码复用与扩展,创建类层次结构,但过深的继承链导致复杂性增加,紧密耦合增加维护成本,单继承限制灵活性,方法覆盖可能隐藏父类功能,且可能影响性能。设计时需谨慎权衡并考虑使用组合等替代方案。
43 7
|
17天前
|
JavaScript 前端开发
JavaScript基础知识-构造函数(也称为"类")定义
本文介绍了JavaScript中构造函数(也称为“类”)的定义和使用方法。
24 1
JavaScript基础知识-构造函数(也称为"类")定义
|
1天前
|
JavaScript 前端开发 索引
|
9天前
|
JavaScript 前端开发
JS中Promise的类式实现写法
JS中Promise的类式实现写法
|
1月前
|
JavaScript 前端开发
记录Javascript数组类练习
记录Javascript数组类练习
|
3月前
|
JavaScript 前端开发
记录Javascript数组类练习
记录Javascript数组类练习
19 1
|
3月前
|
JavaScript 前端开发 Java
【JavaScript】ECMAS6(ES6)新特性概览(二):解构赋值、扩展与收集、class类全面解析
【JavaScript】ECMAS6(ES6)新特性概览(二):解构赋值、扩展与收集、class类全面解析
43 2
|
3月前
|
存储 JavaScript 前端开发
【JavaScript】JavaScript 中的 Class 类:全面解析
【JavaScript】JavaScript 中的 Class 类:全面解析
62 1
|
2月前
|
JavaScript 索引
js 类数组 转 数组
js 类数组 转 数组
32 0